mirror of
https://github.com/ancsemi/Haven.git
synced 2026-10-29 01:55:29 +00:00
539 lines
37 KiB
HTML
539 lines
37 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" data-haven-theme-api="1">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<link rel="manifest" href="/manifest.webmanifest">
|
||
<title data-i18n="auth.page_title">Haven | Login</title>
|
||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cG9seWdvbiBwb2ludHM9IjUwLDMgOTMsMjggOTMsNzIgNTAsOTcgNyw3MiA3LDI4IiBmaWxsPSJub25lIiBzdHJva2U9IiM2YjRmZGIiIHN0cm9rZS13aWR0aD0iOCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjx0ZXh0IHg9IjUwIiB5PSI2MyIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmIiBmb250LXdlaWdodD0iYm9sZCIgZm9udC1zaXplPSIzOCIgZmlsbD0iIzZiNGZkYiIgb3BhY2l0eT0iMC45NSI+SDwvdGV4dD48L3N2Zz4=">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
<script src="/js/theme-compat.js"></script>
|
||
<script src="/js/theme-init.js"></script>
|
||
</head>
|
||
<body class="auth-page" data-haven-page="auth">
|
||
|
||
<div class="auth-container" data-haven-region="auth-shell">
|
||
<div class="auth-card" data-haven-region="auth-card">
|
||
<div class="auth-header" data-haven-region="auth-header">
|
||
<div class="logo">⬡</div>
|
||
<h1>HAVEN</h1>
|
||
<p class="tagline" data-i18n="auth.tagline">Private chat, your rules</p>
|
||
<p class="server-title" id="server-title"></p>
|
||
</div>
|
||
|
||
<div class="auth-tabs">
|
||
<button class="auth-tab active" data-tab="login" data-i18n="auth.tabs.login">Login</button>
|
||
<button class="auth-tab" data-tab="register" data-i18n="auth.tabs.register">Register</button>
|
||
<button class="auth-tab" data-tab="sso" data-i18n="auth.tabs.sso">Link Server</button>
|
||
</div>
|
||
|
||
<form id="login-form" class="auth-form">
|
||
<div class="form-group">
|
||
<label for="login-username" data-i18n="auth.login.username_label">Username</label>
|
||
<input type="text" id="login-username" autocomplete="username" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="login-password" data-i18n="auth.login.password_label">Password</label>
|
||
<input type="password" id="login-password" autocomplete="current-password" required>
|
||
</div>
|
||
<button type="submit" class="btn-primary" data-i18n="auth.login.submit">Login</button>
|
||
<p class="auth-consent-note" data-i18n-html="auth.login.consent_note" style="text-align:center;color:var(--text-muted);font-size:0.72rem;line-height:1.4;margin-top:10px">By clicking "Login", you formally consent to our <a href="#" id="login-tos-link">current ToS</a>, which may be different from the ToS that was effective when you first registered.</p>
|
||
<div class="auth-recovery-links">
|
||
<a href="#" id="forgot-password-show" data-i18n="auth.login.forgot_password">Forgot password?</a>
|
||
<a href="#" id="admin-recover-show" data-i18n="auth.login.locked_out">Locked out? Admin recovery</a>
|
||
</div>
|
||
<div id="admin-recover-section" style="display:none;text-align:center;margin-top:6px">
|
||
<small style="color:var(--text-muted)" data-i18n="auth.login.admin_recover_hint">Enter your admin credentials above and click Recover</small><br>
|
||
<input type="text" id="admin-recover-code" inputmode="numeric" autocomplete="one-time-code" maxlength="10" data-i18n-placeholder="auth.login.admin_recover_code" placeholder="Two-factor code" style="display:none;margin-top:6px;max-width:180px;text-align:center">
|
||
<button type="button" class="btn-primary" id="admin-recover-btn" data-i18n="auth.login.admin_recover_btn" style="margin-top:6px;font-size:0.85rem;padding:6px 16px">Recover Admin Access</button>
|
||
</div>
|
||
<!-- (#12) SSO: only shown when the server has OIDC configured -->
|
||
<div id="oidc-login-section" style="display:none;text-align:center;margin-top:14px;padding-top:12px;border-top:1px solid var(--border-color,#333)">
|
||
<button type="button" class="btn-secondary" id="oidc-login-btn" style="font-size:0.9rem;padding:8px 18px" data-i18n="auth.oidc.submit">Sign in with SSO</button>
|
||
</div>
|
||
<!-- (#5381) Guest login: only shown when the server has guests enabled -->
|
||
<div id="guest-login-section" style="display:none;text-align:center;margin-top:14px;padding-top:12px;border-top:1px solid var(--border-color,#333)">
|
||
<small style="color:var(--text-muted);display:block;margin-bottom:6px" data-i18n="auth.guest.prompt">No account? Just want to look around?</small>
|
||
<button type="button" class="btn-secondary" id="guest-login-show-btn" style="font-size:0.9rem;padding:8px 18px" data-i18n="auth.guest.submit">Join as Guest</button>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- (#12) Encryption passphrase for SSO accounts.
|
||
Signing in through a provider means no password is ever typed into
|
||
Haven, and the private key that decrypts DMs is wrapped with a key
|
||
derived from exactly that. So SSO users pick a separate passphrase
|
||
here. It never leaves this page: it is stretched with PBKDF2 and only
|
||
the derived key is kept, for this session. -->
|
||
<form id="e2e-pass-form" class="auth-form" style="display:none">
|
||
<h3 id="e2e-pass-title" style="text-align:center;margin-bottom:6px;font-size:1.05rem" data-i18n="auth.e2e_pass.title">Set an encryption passphrase</h3>
|
||
<p id="e2e-pass-blurb" style="text-align:center;color:var(--text-muted);font-size:0.85rem;margin-bottom:14px" data-i18n="auth.e2e_pass.desc">
|
||
Your private messages are encrypted with a key only you hold. Signing in with SSO means Haven never sees a
|
||
password to protect it with, so pick a passphrase. You will need it again on any other device.
|
||
</p>
|
||
<div class="form-group">
|
||
<label for="e2e-pass-input" data-i18n="auth.e2e_pass.label">Passphrase</label>
|
||
<input type="password" id="e2e-pass-input" autocomplete="new-password" minlength="8" maxlength="128" required>
|
||
<small id="e2e-pass-hint" data-i18n="auth.e2e_pass.hint">At least 8 characters. Nobody can reset this for you, including the server admin.</small>
|
||
</div>
|
||
<div class="form-group" id="e2e-pass-confirm-group">
|
||
<label for="e2e-pass-confirm" data-i18n="auth.e2e_pass.confirm_label">Confirm passphrase</label>
|
||
<input type="password" id="e2e-pass-confirm" autocomplete="new-password" minlength="8" maxlength="128">
|
||
</div>
|
||
<button type="submit" class="btn-primary" id="e2e-pass-submit" data-i18n="auth.e2e_pass.continue">Continue</button>
|
||
<p style="text-align:center;margin-top:10px">
|
||
<a href="#" id="e2e-pass-skip" style="font-size:0.85rem;color:var(--text-muted)" data-i18n="auth.e2e_pass.skip">Skip for now</a>
|
||
</p>
|
||
</form>
|
||
|
||
<!-- (#5381) Guest username prompt: appears after clicking Join as Guest -->
|
||
<form id="guest-form" class="auth-form" style="display:none">
|
||
<p style="text-align:center;color:var(--text-muted);font-size:0.9rem;margin-bottom:12px" data-i18n="auth.guest.desc">
|
||
Pick a temporary username. No password, no recovery. Your account is wiped when you disconnect.
|
||
</p>
|
||
<div class="form-group">
|
||
<label for="guest-username" data-i18n="auth.guest.username_label">Username</label>
|
||
<input type="text" id="guest-username" autocomplete="off" minlength="3" maxlength="20" pattern="[a-zA-Z0-9_]+" required>
|
||
<small data-i18n="auth.guest.username_hint">3-20 characters, letters/numbers/underscores</small>
|
||
</div>
|
||
<button type="submit" class="btn-primary" data-i18n="auth.guest.submit">Join as Guest</button>
|
||
<p style="text-align:center;margin-top:8px">
|
||
<a href="#" id="guest-back-btn" style="color:var(--text-muted);font-size:0.8rem;text-decoration:none" data-i18n="auth.guest.back">← Back to login</a>
|
||
</p>
|
||
</form>
|
||
|
||
<form id="register-form" class="auth-form" style="display:none">
|
||
<div class="form-group">
|
||
<label for="reg-username" data-i18n="auth.register.username_label">Username</label>
|
||
<input type="text" id="reg-username" autocomplete="username"
|
||
minlength="3" maxlength="20" pattern="[a-zA-Z0-9_]+" required>
|
||
<small data-i18n="auth.register.username_hint">3-20 characters, letters/numbers/underscores</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="reg-password" data-i18n="auth.register.password_label">Password</label>
|
||
<input type="password" id="reg-password" autocomplete="new-password"
|
||
minlength="8" required>
|
||
<small data-i18n="auth.register.password_hint">Minimum 8 characters</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="reg-confirm" data-i18n="auth.register.confirm_label">Confirm Password</label>
|
||
<input type="password" id="reg-confirm" autocomplete="new-password" required>
|
||
</div>
|
||
<!-- (#5344) Registration token field: shown by auth.js only when the server requires it -->
|
||
<div class="form-group" id="reg-token-group" style="display:none">
|
||
<label for="reg-token" data-i18n="auth.register.token_label">Invite Token</label>
|
||
<input type="text" id="reg-token" autocomplete="off" maxlength="64"
|
||
placeholder="Paste the token your admin shared" data-i18n-placeholder="auth.register.token_placeholder">
|
||
<small data-i18n="auth.register.token_hint">Required by this server. Ask the admin if you don't have one.</small>
|
||
</div>
|
||
<!-- Opt-in Cloudflare Turnstile CAPTCHA: shown by auth.js only when the server enables it -->
|
||
<div class="form-group" id="reg-captcha-group" style="display:none">
|
||
<div id="reg-captcha"></div>
|
||
</div>
|
||
<button type="submit" class="btn-primary" data-i18n="auth.register.submit">Create Account</button>
|
||
</form>
|
||
|
||
<!-- SSO form: register using identity from another Haven server -->
|
||
<div id="sso-form" class="auth-form" style="display:none">
|
||
<p style="text-align:center;color:var(--text-muted);font-size:0.9rem;margin-bottom:16px;">
|
||
<span data-i18n="auth.sso.instruction">Register using your account from another Haven server. Your username and profile picture will be imported.</span>
|
||
</p>
|
||
|
||
<!-- Step 1: Enter home server URL -->
|
||
<div id="sso-step-server">
|
||
<div class="form-group">
|
||
<label for="sso-server-url" data-i18n="auth.sso.server_label">Your Haven server</label>
|
||
<input type="text" id="sso-server-url" list="sso-recent-servers" placeholder="haven.example.com" required>
|
||
<datalist id="sso-recent-servers"></datalist>
|
||
<small data-i18n="auth.sso.server_hint">Enter the address of the Haven server you already have an account on</small>
|
||
</div>
|
||
<button type="button" class="btn-primary" id="sso-connect-btn" data-i18n="auth.sso.connect">Connect</button>
|
||
</div>
|
||
|
||
<!-- Step 2: SSO result + set local password -->
|
||
<div id="sso-step-register" style="display:none">
|
||
<div class="sso-profile-preview" style="text-align:center;margin-bottom:16px;padding:12px;background:var(--bg-tertiary,#12122a);border-radius:8px;border:1px solid var(--border,#333)">
|
||
<div id="sso-preview-avatar" style="width:64px;height:64px;border-radius:50%;margin:0 auto 8px;background:var(--bg-secondary,#1a1a2e);overflow:hidden;display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--text-muted)">?</div>
|
||
<div id="sso-preview-username" style="font-weight:600;font-size:15px;color:var(--text-primary)">-</div>
|
||
<small style="color:var(--text-muted);font-size:11px" data-i18n="auth.sso.imported_from">Imported from your home server</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="sso-username" data-i18n="auth.sso.username_label">Username on this server</label>
|
||
<input type="text" id="sso-username" autocomplete="username" maxlength="20" required>
|
||
<small data-i18n="auth.sso.username_hint">Taken from your home server. If it is already in use here, pick another. 3-20 characters, letters/numbers/underscores.</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="sso-password" data-i18n="auth.sso.password_label">Set a password for this server</label>
|
||
<input type="password" id="sso-password" autocomplete="new-password" minlength="8" required>
|
||
<small data-i18n="auth.sso.password_hint">Required for encryption. Minimum 8 characters.</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="sso-confirm" data-i18n="auth.sso.confirm_label">Confirm Password</label>
|
||
<input type="password" id="sso-confirm" autocomplete="new-password" required>
|
||
</div>
|
||
<!-- Opt-in Turnstile CAPTCHA: shown by auth.js only when the server enables it -->
|
||
<div class="form-group" id="sso-captcha-group" style="display:none">
|
||
<div id="sso-captcha"></div>
|
||
</div>
|
||
<button type="button" class="btn-primary" id="sso-register-btn" data-i18n="auth.sso.register">Create Account</button>
|
||
<p style="text-align:center;margin-top:8px">
|
||
<a href="#" id="sso-back-btn" style="color:var(--text-muted);font-size:0.8rem;text-decoration:none" data-i18n="auth.sso.back">← Start over</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="auth-error" class="auth-error" style="display:none"></div>
|
||
|
||
<!-- TOTP verification form (shown after login when 2FA is enabled) -->
|
||
<form id="totp-form" class="auth-form" style="display:none">
|
||
<p style="text-align:center;color:var(--text-muted);font-size:0.9rem;margin-bottom:12px;">
|
||
<span data-i18n="auth.totp.instruction">🔐 Enter the 6-digit code from your authenticator app</span>
|
||
</p>
|
||
<div class="form-group">
|
||
<label for="totp-code" data-i18n="auth.totp.code_label">Authentication Code</label>
|
||
<input type="text" id="totp-code" autocomplete="one-time-code" inputmode="numeric"
|
||
pattern="[0-9]*" maxlength="10" placeholder="000000" required
|
||
style="text-align:center;font-size:1.4rem;letter-spacing:0.3em">
|
||
</div>
|
||
<button type="submit" class="btn-primary" data-i18n="auth.totp.submit">Verify</button>
|
||
<p style="text-align:center;margin-top:8px">
|
||
<a href="#" id="totp-use-backup" data-i18n="auth.totp.use_backup" style="color:var(--text-muted);font-size:0.8rem;text-decoration:none">Use a backup code instead</a>
|
||
</p>
|
||
<p style="text-align:center;margin-top:4px">
|
||
<a href="#" id="totp-back-btn" data-i18n="auth.totp.back" style="color:var(--text-muted);font-size:0.8rem;text-decoration:none">← Back to login</a>
|
||
</p>
|
||
</form>
|
||
|
||
<!-- Forced change-password form, shown when an admin reset the account (#5300) -->
|
||
<form id="forced-change-form" class="auth-form" style="display:none">
|
||
<p style="text-align:center;color:var(--text-muted);font-size:0.9rem;margin-bottom:12px;">
|
||
<span data-i18n="auth.forced_change.instruction">🔒 An admin reset your password. Set a new one to continue.</span>
|
||
</p>
|
||
<div style="background:rgba(231,76,60,0.12);border:1px solid rgba(231,76,60,0.4);border-radius:8px;padding:8px 12px;margin-bottom:12px;font-size:0.8rem;color:var(--text-secondary)"
|
||
data-i18n-html="auth.forced_change.dm_warning">
|
||
⚠️ Setting a new password here will <strong>permanently erase your encrypted DM history</strong>. The wrap key is derived from your password, so old DMs become unreadable once the password rotates. If you still remember your <em>original</em> password, expand the section below to cancel the reset and keep your DMs intact.
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="forced-new-password" data-i18n="auth.forced_change.new_password_label">New Password</label>
|
||
<input type="password" id="forced-new-password" autocomplete="new-password" minlength="8" maxlength="128">
|
||
<small data-i18n="auth.forced_change.new_password_hint">Minimum 8 characters</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="forced-confirm-password" data-i18n="auth.forced_change.confirm_password_label">Confirm New Password</label>
|
||
<input type="password" id="forced-confirm-password" autocomplete="new-password" minlength="8" maxlength="128">
|
||
</div>
|
||
<details id="forced-change-recall" style="margin:8px 0 12px 0;border:1px solid var(--border-color,#444);border-radius:8px;padding:6px 10px;">
|
||
<summary style="cursor:pointer;font-size:0.85rem;color:var(--text-secondary)" data-i18n="auth.forced_change.recall_summary">I actually remember my original password (preserve my DMs)</summary>
|
||
<p style="font-size:0.78rem;color:var(--text-muted);margin:8px 0;" data-i18n="auth.forced_change.recall_hint">Enter your original password below. If it matches, the admin reset is cancelled and you sign in normally with no password change. Your encrypted DM history stays intact.</p>
|
||
<div class="form-group">
|
||
<label for="forced-old-password" data-i18n="auth.forced_change.old_password_label">Original Password</label>
|
||
<input type="password" id="forced-old-password" autocomplete="current-password" maxlength="128">
|
||
</div>
|
||
</details>
|
||
<button type="submit" class="btn-primary" data-i18n="auth.forced_change.submit">Continue</button>
|
||
</form>
|
||
|
||
<!-- Account Recovery / Forgot Password form -->
|
||
<form id="recover-form" class="auth-form" style="display:none">
|
||
<p style="text-align:center;color:var(--text-muted);font-size:0.9rem;margin-bottom:12px;">
|
||
<span data-i18n="auth.recover.instruction">🔑 Reset password with a recovery code</span>
|
||
</p>
|
||
<div style="background:rgba(231,76,60,0.12);border:1px solid rgba(231,76,60,0.4);border-radius:8px;padding:8px 12px;margin-bottom:12px;font-size:0.8rem;color:var(--text-secondary)"
|
||
data-i18n-html="auth.recover.warning">
|
||
⚠️ This will <strong>permanently erase your encryption keys</strong>. All encrypted DM history will be unreadable.
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="recover-username" data-i18n="auth.recover.username_label">Username</label>
|
||
<input type="text" id="recover-username" autocomplete="username" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="recover-code" data-i18n="auth.recover.code_label">Recovery Code</label>
|
||
<input type="text" id="recover-code" placeholder="XXXX-XXXX" maxlength="9"
|
||
style="font-family:monospace;letter-spacing:0.1em" autocomplete="off" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="recover-new-password" data-i18n="auth.recover.new_password_label">New Password</label>
|
||
<input type="password" id="recover-new-password" autocomplete="new-password" minlength="8" required>
|
||
<small data-i18n="auth.recover.new_password_hint">Minimum 8 characters</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="recover-confirm-password" data-i18n="auth.recover.confirm_label">Confirm New Password</label>
|
||
<input type="password" id="recover-confirm-password" autocomplete="new-password" required>
|
||
</div>
|
||
<button type="submit" class="btn-primary" data-i18n="auth.recover.submit">Reset Password</button>
|
||
<p style="text-align:center;margin-top:8px">
|
||
<a href="#" id="recover-back-btn" data-i18n="auth.recover.back" style="color:var(--text-muted);font-size:0.8rem;text-decoration:none">← Back to login</a>
|
||
</p>
|
||
</form>
|
||
|
||
<!-- Ban appeal form (#5457), shown when a login is rejected with a ban -->
|
||
<form id="ban-appeal-form" class="auth-form" style="display:none">
|
||
<p style="text-align:center;color:var(--text-muted);font-size:0.9rem;margin-bottom:12px;">
|
||
<span data-i18n="auth.ban_appeal.instruction">🚫 Your account is banned from this server.</span>
|
||
</p>
|
||
<div id="ban-appeal-reason" style="background:rgba(231,76,60,0.12);border:1px solid rgba(231,76,60,0.4);border-radius:8px;padding:8px 12px;margin-bottom:12px;font-size:0.85rem;color:var(--text-secondary)"></div>
|
||
<div class="form-group">
|
||
<label for="ban-appeal-text" data-i18n="auth.ban_appeal.text_label">Appeal to the admins</label>
|
||
<textarea id="ban-appeal-text" maxlength="2000" rows="5" required style="resize:vertical"
|
||
data-i18n-placeholder="auth.ban_appeal.text_placeholder"
|
||
placeholder="Explain why you think the ban should be lifted..."></textarea>
|
||
<small data-i18n="auth.ban_appeal.text_hint">The server admins will see this next to your ban. Max 2000 characters.</small>
|
||
</div>
|
||
<button type="submit" class="btn-primary" data-i18n="auth.ban_appeal.submit">Submit Appeal</button>
|
||
<p style="text-align:center;margin-top:8px">
|
||
<a href="#" id="ban-appeal-back-btn" data-i18n="auth.ban_appeal.back" style="color:var(--text-muted);font-size:0.8rem;text-decoration:none">← Back to login</a>
|
||
</p>
|
||
</form>
|
||
|
||
<!-- EULA acceptance. Shown on the account-creation views (register / SSO /
|
||
guest) only; hidden on the login tab, where consent is instead
|
||
re-affirmed by the note under the Login button. Toggled in auth.js. -->
|
||
<div class="eula-notice" id="eula-notice" style="display:none">
|
||
<label class="eula-check-row">
|
||
<input type="checkbox" id="age-checkbox">
|
||
<span data-i18n-html="auth.eula.age_confirm">I confirm that I am <strong>18 years of age or older</strong> (or the age of legal majority in my jurisdiction)</span>
|
||
</label>
|
||
<label class="eula-check-row">
|
||
<input type="checkbox" id="eula-checkbox">
|
||
<span data-i18n-html="auth.eula.tos_confirm">I have read and agree to the <a href="#" id="eula-link">Terms of Service & Release of Liability Agreement</a></span>
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Theme selector on login page -->
|
||
<div class="auth-theme-bar" id="auth-theme-bar" data-haven-region="theme-picker">
|
||
<button class="theme-btn active" data-theme="haven" title="Haven"><span class="theme-icon">⬡</span></button>
|
||
<button class="theme-btn" data-theme="discord" title="Discord"><span class="theme-icon">🎮</span></button>
|
||
<button class="theme-btn" data-theme="matrix" title="Matrix"><span class="theme-icon">Ⅿ</span></button>
|
||
<button class="theme-btn" data-theme="tron" title="Tron"><span class="theme-icon">◈</span></button>
|
||
<button class="theme-btn" data-theme="halo" title="HALO"><span class="theme-icon">⌁</span></button>
|
||
<button class="theme-btn" data-theme="lotr" title="LoTR"><span class="theme-icon">⚜</span></button>
|
||
<button class="theme-btn" data-theme="cyberpunk" title="Cyberpunk"><span class="theme-icon">⚡</span></button>
|
||
<button class="theme-btn" data-theme="nord" title="Nord"><span class="theme-icon">❄</span></button>
|
||
<button class="theme-btn" data-theme="dracula" title="Dracula"><span class="theme-icon">🧛</span></button>
|
||
<button class="theme-btn" data-theme="bloodborne" title="Bloodborne"><span class="theme-icon">🩸</span></button>
|
||
<button class="theme-btn" data-theme="darksouls" title="Dark Souls"><span class="theme-icon">🔥</span></button>
|
||
<button class="theme-btn" data-theme="eldenring" title="Elden Ring"><span class="theme-icon">💍</span></button>
|
||
<button class="theme-btn" data-theme="ice" title="Ice"><span class="theme-icon">🧊</span></button>
|
||
<button class="theme-btn" data-theme="abyss" title="Abyss"><span class="theme-icon">🌊</span></button>
|
||
<button class="theme-btn" data-theme="minecraft" title="Minecraft"><span class="theme-icon">⛏️</span></button>
|
||
<button class="theme-btn" data-theme="ffx" title="Final Fantasy X"><span class="theme-icon">⚔️</span></button>
|
||
<button class="theme-btn" data-theme="zelda" title="Zelda"><span class="theme-icon">🗡️</span></button>
|
||
<button class="theme-btn" data-theme="fallout" title="Fallout (PIP Boy)"><span class="theme-icon">☢️</span></button>
|
||
<button class="theme-btn" data-theme="scripture" title="Scripture"><span class="theme-icon">✝️</span></button>
|
||
<button class="theme-btn" data-theme="chapel" title="Chapel"><span class="theme-icon">⛪</span></button>
|
||
<button class="theme-btn" data-theme="gospel" title="Gospel"><span class="theme-icon">🕊️</span></button>
|
||
<button class="theme-btn" data-theme="midnightpurple" title="Midnight Purple"><span class="theme-icon">🔮</span></button>
|
||
<button class="theme-btn" data-theme="crt" title="CRT"><span class="theme-icon">📺</span></button>
|
||
<button class="theme-btn" data-theme="win95" title="Windows 95"><span class="theme-icon">🪟</span></button>
|
||
<button class="theme-btn" data-theme="custom" title="Custom"><span class="theme-icon" id="custom-theme-swatch">🎨</span></button>
|
||
<button class="theme-btn" data-theme="rgb" title="RGB"><span class="theme-icon">🌈</span></button>
|
||
<button class="theme-btn" data-theme="daylight" title="Daylight (Light)"><span class="theme-icon">☀️</span></button>
|
||
<button class="theme-btn" data-theme="cloudy" title="Cloudy (Light)"><span class="theme-icon">☁️</span></button>
|
||
</div>
|
||
<div class="auth-lang-bar">
|
||
<select id="auth-lang-select" class="settings-select">
|
||
<option value="auto" data-i18n="settings.language_section.auto">Automatic</option>
|
||
<option value="en">English</option>
|
||
<option value="fr">Français</option>
|
||
<option value="de">Deutsch</option>
|
||
<option value="es">Español</option>
|
||
<option value="pl">Polski</option>
|
||
<option value="ru">Русский</option>
|
||
<option value="zh">中文</option>
|
||
<option value="pt">Português (Brasil)</option>
|
||
<option value="fa">فارسی</option>
|
||
<option value="ja">日本語</option>
|
||
<option value="ko">한국어</option>
|
||
</select>
|
||
</div>
|
||
<p class="auth-version" id="auth-version"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- EULA Modal -->
|
||
<div class="modal-overlay" id="eula-modal" style="display:none">
|
||
<div class="modal eula-modal">
|
||
<h3>⚖️ <span data-i18n="auth.eula.modal_title_plain">Terms of Service & Release of Liability Agreement</span></h3> <div id="custom-tos-section" style="display:none">
|
||
<div class="eula-content" id="custom-tos-content" style="margin-bottom:0;border-bottom:1px solid var(--border,#444);padding-bottom:16px;"></div>
|
||
<p style="text-align:center;font-size:12px;color:var(--text-muted,#888);margin:12px 0;" data-i18n="auth.eula.default_terms">── Default Haven Terms of Service ──</p>
|
||
</div>
|
||
<div class="eula-content" data-i18n-html="auth.eula.terms_body">
|
||
<p><strong>HAVEN: TERMS OF SERVICE, END USER LICENSE AGREEMENT & RELEASE OF LIABILITY</strong></p>
|
||
<p><em>Effective Date: February 12, 2026 | Version 2.0</em></p>
|
||
|
||
<p>By accessing, registering for, or using this Haven server instance
|
||
(the "Service"), you ("User," "you," or "your") acknowledge that you have
|
||
read, understood, and agree to be legally bound by the following terms and
|
||
conditions ("Agreement"). If you do not agree to every provision of this
|
||
Agreement, you must immediately cease all use of the Service.</p>
|
||
|
||
<h4>1. AGE RESTRICTION & ELIGIBILITY</h4>
|
||
<p>1.1. <strong>Minimum Age.</strong> You must be at least <strong>eighteen
|
||
(18) years of age</strong> (or the age of legal majority in your
|
||
jurisdiction, whichever is greater) to access or use the Service. By
|
||
checking the age-confirmation box and using the Service, you represent
|
||
and warrant that you meet this requirement.</p>
|
||
<p>1.2. <strong>Parental Notice.</strong> The Service is not intended for
|
||
individuals under 18. We do not knowingly collect personal information
|
||
from minors. If we learn that a minor has provided personal information,
|
||
we will delete that information and terminate the associated account
|
||
without notice.</p>
|
||
<p>1.3. <strong>Legal Capacity.</strong> You represent that you have the
|
||
legal capacity to enter into a binding agreement in your jurisdiction.</p>
|
||
|
||
<h4>2. DESCRIPTION OF SERVICE</h4>
|
||
<p>2.1. Haven is self-hosted, open-source communication software provided
|
||
under the AGPL-3.0 license. The Service enables text messaging, voice
|
||
communication, file sharing, and related features between consenting
|
||
adult users on a privately operated server instance.</p>
|
||
<p>2.2. <strong>Work in Progress.</strong> The software is provided in an
|
||
active state of development. Features may be incomplete, unstable, or
|
||
subject to change without notice.</p>
|
||
|
||
<h4>3. NO WARRANTY</h4>
|
||
<p>3.1. THE SERVICE IS PROVIDED <strong>"AS IS"</strong> AND <strong>"AS
|
||
AVAILABLE"</strong>, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||
INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR
|
||
A PARTICULAR PURPOSE, NON-INFRINGEMENT, ACCURACY, RELIABILITY, OR
|
||
AVAILABILITY.</p>
|
||
<p>3.2. The Providers (defined below) do not warrant that the Service
|
||
will be uninterrupted, error-free, secure, or free from harmful
|
||
components.</p>
|
||
|
||
<h4>4. ASSUMPTION OF RISK</h4>
|
||
<p>4.1. You <strong>voluntarily assume all risk</strong> associated with
|
||
your use of the Service, including but not limited to: data loss, service
|
||
interruptions, security vulnerabilities, exposure to objectionable or
|
||
unlawful content posted by other users, and any consequences arising from
|
||
communications conducted through the Service.</p>
|
||
<p>4.2. You acknowledge that the Service involves real-time, user-generated
|
||
content that is not pre-screened, moderated by artificial intelligence, or
|
||
guaranteed to be free of harmful material.</p>
|
||
|
||
<h4>5. RELEASE OF LIABILITY & LIMITATION OF DAMAGES</h4>
|
||
<p>5.1. <strong>Release.</strong> To the fullest extent permitted by
|
||
applicable law, you hereby <strong>release, discharge, and hold
|
||
harmless</strong> the developer(s), server operator(s), host(s),
|
||
contributors, and any affiliated parties (collectively, "Providers")
|
||
from any and all claims, demands, causes of action, damages, losses,
|
||
liabilities, costs, and expenses (including but not limited to
|
||
attorneys' fees) arising out of or related to:</p>
|
||
<ul>
|
||
<li>your use of, or inability to use, the Service;</li>
|
||
<li>any content transmitted, received, or stored through the Service;</li>
|
||
<li>unauthorized access to or alteration of your data;</li>
|
||
<li>the conduct of any third party on the Service;</li>
|
||
<li>any violation of this Agreement.</li>
|
||
</ul>
|
||
<p>5.2. <strong>Limitation.</strong> In no event shall the aggregate
|
||
liability of the Providers exceed the amount you paid to use the Service
|
||
(which is $0.00 for free software). In no event shall any Provider be
|
||
liable for indirect, incidental, special, consequential, or punitive
|
||
damages of any kind.</p>
|
||
|
||
<h4>6. INDEMNIFICATION</h4>
|
||
<p>6.1. You agree to <strong>indemnify, defend, and hold harmless</strong>
|
||
the Providers from and against any and all claims, liabilities, damages,
|
||
losses, costs, and expenses (including reasonable attorneys' fees) arising
|
||
from or related to: (a) your use of the Service; (b) your violation of
|
||
this Agreement; (c) your violation of any applicable law or regulation;
|
||
or (d) any content you transmit through the Service.</p>
|
||
|
||
<h4>7. USER CONDUCT & CONTENT</h4>
|
||
<p>7.1. You are <strong>solely responsible</strong> for your conduct and
|
||
any content you create, transmit, or share through the Service.</p>
|
||
<p>7.2. You agree not to use the Service to: (a) violate any applicable
|
||
law or regulation; (b) transmit content that exploits or harms minors;
|
||
(c) harass, threaten, or abuse other users; (d) distribute malware or
|
||
engage in phishing; or (e) infringe on any third party's intellectual
|
||
property rights.</p>
|
||
<p>7.3. The server operator reserves the right to remove any content and
|
||
to restrict or terminate any user's access at any time, for any reason,
|
||
without prior notice.</p>
|
||
|
||
<h4>8. DATA HANDLING & PRIVACY</h4>
|
||
<p>8.1. Messages, files, and user data are stored on the server
|
||
operator's hardware and managed at their sole discretion. <strong>No
|
||
guarantee of privacy, backup, retention, or confidentiality is
|
||
made.</strong></p>
|
||
<p>8.2. You acknowledge that communications on the Service may be
|
||
accessed by the server operator and are not end-to-end encrypted by
|
||
default.</p>
|
||
<p>8.3. Haven does not transmit data to third-party analytics,
|
||
advertising, or tracking services. API calls to third-party services
|
||
(e.g., Tenor or GIPHY) are made only when explicitly triggered by user action.</p>
|
||
<p>8.4. You consent to the collection and storage of your IP address,
|
||
username, and agreement acceptance records for the purposes of
|
||
security, abuse prevention, and legal compliance.</p>
|
||
|
||
<h4>9. INTELLECTUAL PROPERTY</h4>
|
||
<p>9.1. Haven software is licensed under the AGPL-3.0 License. You retain
|
||
ownership of content you create, but grant the server operator a
|
||
non-exclusive, royalty-free license to store, display, and transmit your
|
||
content as necessary to operate the Service.</p>
|
||
<p>9.2. You represent that you have the right to share any content you
|
||
upload and that such content does not infringe any third party's rights.</p>
|
||
|
||
<h4>10. DISPUTE RESOLUTION & GOVERNING LAW</h4>
|
||
<p>10.1. <strong>Governing Law.</strong> This Agreement shall be governed
|
||
by and construed in accordance with the laws of the State in which the
|
||
server operator resides, without regard to conflict of law provisions.</p>
|
||
<p>10.2. <strong>Informal Resolution.</strong> Before initiating any
|
||
formal proceedings, you agree to first attempt to resolve any dispute
|
||
informally by contacting the server operator directly.</p>
|
||
<p>10.3. <strong>Limitation Period.</strong> Any claim arising out of
|
||
this Agreement must be brought within <strong>one (1) year</strong> of
|
||
the date on which the claim arose, or it is permanently barred.</p>
|
||
<p>10.4. <strong>Waiver of Class Action.</strong> You agree that any
|
||
dispute resolution proceedings will be conducted only on an individual
|
||
basis and not as a class, consolidated, or representative action.</p>
|
||
|
||
<h4>11. TERMINATION</h4>
|
||
<p>11.1. The server operator may terminate or suspend your access at
|
||
any time, with or without cause, with or without notice.</p>
|
||
<p>11.2. Upon termination, all rights granted to you under this
|
||
Agreement immediately cease. Sections 1, 3, 4, 5, 6, 8, 10, and 12
|
||
survive termination.</p>
|
||
|
||
<h4>12. GENERAL PROVISIONS</h4>
|
||
<p>12.1. <strong>Entire Agreement.</strong> This Agreement constitutes
|
||
the entire agreement between you and the Providers and supersedes all
|
||
prior agreements relating to the Service.</p>
|
||
<p>12.2. <strong>Severability.</strong> If any provision is found
|
||
unenforceable, the remaining provisions remain in full force and
|
||
effect.</p>
|
||
<p>12.3. <strong>Waiver.</strong> Failure to enforce any provision does
|
||
not constitute a waiver of that provision.</p>
|
||
<p>12.4. <strong>Modification.</strong> The Providers reserve the right
|
||
to modify this Agreement at any time. Continued use after changes are
|
||
posted constitutes acceptance. Material changes will be communicated
|
||
with a version number increment and, where feasible, an in-app notice.</p>
|
||
<p>12.5. <strong>No Legal Advice.</strong> This Agreement does not
|
||
constitute legal advice. If you require legal counsel, you should
|
||
consult a licensed attorney in your jurisdiction.</p>
|
||
<p>12.6. <strong>Assignment.</strong> You may not assign or transfer
|
||
your rights under this Agreement. The Providers may assign their rights
|
||
without restriction.</p>
|
||
|
||
<p class="eula-footer"><strong>BY CHECKING THE ACCEPTANCE BOXES AND USING
|
||
THIS SERVICE, YOU AFFIRM THAT YOU ARE AT LEAST 18 YEARS OLD (OR THE AGE
|
||
OF LEGAL MAJORITY IN YOUR JURISDICTION), THAT YOU HAVE READ AND
|
||
UNDERSTOOD THIS AGREEMENT IN ITS ENTIRETY, AND THAT YOU VOLUNTARILY
|
||
AGREE TO BE LEGALLY BOUND BY ALL OF ITS TERMS.</strong></p>
|
||
|
||
<p class="eula-footer"><em>If you do not agree to these terms, do not use
|
||
this Service.</em></p>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn-sm" id="eula-decline-btn" data-i18n="auth.eula.decline">Decline</button>
|
||
<button class="btn-sm btn-accent" id="eula-accept-btn" data-i18n="auth.eula.accept">I Accept</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/js/i18n.js"></script>
|
||
<script src="/js/theme.js"></script>
|
||
<script src="/js/auth.js"></script>
|
||
<script src="/js/password-eye.js?v=3.17.1"></script>
|
||
</body>
|
||
</html>
|