This commit is contained in:
2026-08-19 21:14:42 +02:00
parent 619a6f9557
commit 63ea313742
3 changed files with 345 additions and 162 deletions

View File

@@ -17334,3 +17334,173 @@ a.lazy-thumb.filtered-upload-ghost:hover::after {
filter: brightness(0.6);
}
.edit-mode-active #inline-banner-edit-wrapper { display: block !important; }
/* Settings Direct-Editable Profile Card */
.editable-profile-card {
position: relative;
transition: box-shadow 0.2s ease, border-color 0.2s ease;
border-radius: 8px;
}
.editable-profile-card:hover {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
}
.card-banner-controls-overlay {
position: absolute;
top: 10px;
right: 10px;
display: flex;
gap: 6px;
z-index: 10;
}
.card-overlay-btn {
background: rgba(0, 0, 0, 0.7);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(4px);
font-size: 0.78rem;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
}
.card-overlay-btn:hover {
background: rgba(0, 0, 0, 0.9);
border-color: var(--accent);
color: var(--accent);
}
.card-overlay-btn.btn-danger:hover {
border-color: #ff6b6b;
color: #ff6b6b;
}
.editable-avatar-container {
position: relative;
cursor: pointer;
}
.avatar-hover-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
border-radius: 50%;
color: #fff;
font-size: 1.25rem;
}
.editable-avatar-container:hover .avatar-hover-overlay {
opacity: 1;
}
.avatar-quick-remove {
position: absolute;
top: -4px;
right: -4px;
background: #ff4757;
color: #fff;
border: none;
border-radius: 50%;
width: 22px;
height: 22px;
font-size: 0.72rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
transition: transform 0.15s ease, background 0.15s ease;
}
.avatar-quick-remove:hover {
transform: scale(1.15);
background: #ff6b6b;
}
.inline-edit-username-wrapper {
display: inline-flex;
align-items: center;
gap: 6px;
position: relative;
}
.user-infobox-username[contenteditable="true"] {
border-bottom: 1px dashed rgba(255, 255, 255, 0.3);
padding: 1px 4px;
border-radius: 3px;
transition: all 0.2s ease;
}
.user-infobox-username[contenteditable="true"]:hover {
border-bottom-color: var(--accent);
background: rgba(255, 255, 255, 0.05);
}
.user-infobox-username[contenteditable="true"]:focus {
outline: none;
border-bottom: 2px solid var(--accent);
background: rgba(0, 0, 0, 0.3);
}
.color-picker-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.8);
cursor: pointer;
font-size: 0.68rem;
color: #fff;
text-shadow: 0 0 2px rgba(0,0,0,0.8);
flex-shrink: 0;
transition: transform 0.2s ease, border-color 0.2s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
.color-picker-badge:hover {
transform: scale(1.18);
border-color: #fff;
}
.user-infobox-description[contenteditable="true"] {
min-height: 44px;
padding: 6px 8px;
border-radius: 4px;
border: 1px dashed rgba(255, 255, 255, 0.2);
transition: all 0.2s ease;
position: relative;
}
.user-infobox-description[contenteditable="true"]:empty::before {
content: attr(data-placeholder);
color: var(--text-muted);
font-style: italic;
}
.user-infobox-description[contenteditable="true"]:hover {
border-color: var(--accent);
background: rgba(255, 255, 255, 0.03);
}
.user-infobox-description[contenteditable="true"]:focus {
outline: none;
border-style: solid;
border-color: var(--accent);
background: rgba(0, 0, 0, 0.3);
}
.inline-save-btn {
background: var(--accent);
color: #fff;
border: none;
padding: 3px 10px;
font-size: 0.78rem;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.2s ease;
}
.inline-save-btn:hover {
opacity: 0.9;
}

View File

@@ -137,10 +137,13 @@
const statusDiv = document.getElementById('avatar-upload-status');
const preview = document.getElementById('avatar-preview');
const showStatus = (msg, type) => {
const showStatus = (msg, type = 'info') => {
if (msg && window.flashMessage) {
window.flashMessage(msg, 2500, type);
}
if (statusDiv) {
statusDiv.textContent = msg;
statusDiv.className = 'avatar-status ' + type;
statusDiv.className = 'avatar-status ' + (type || '');
}
};
@@ -177,6 +180,7 @@
filenameSpan.textContent = file.name;
uploadBtn.disabled = false;
showStatus('', '');
uploadBtn.click();
});
}
@@ -1674,8 +1678,11 @@
// Two-way sync: swatch → text input
if (colorPicker && colorHex) {
const colorBadge = document.getElementById('color-picker-badge');
colorPicker.addEventListener('input', () => {
colorHex.value = colorPicker.value;
if (colorBadge) colorBadge.style.background = colorPicker.value;
const liveBox = document.getElementById('live-profile-preview-box');
if (liveBox) {
liveBox.style.setProperty('--author-accent', colorPicker.value);
@@ -1683,6 +1690,11 @@
}
});
colorPicker.addEventListener('change', () => {
if (colorBadge) colorBadge.style.background = colorPicker.value;
if (saveColorBtn) saveColorBtn.click();
});
// Text input → swatch (validate on each keystroke)
colorHex.addEventListener('input', () => {
const val = colorHex.value.trim();
@@ -1852,7 +1864,9 @@
const data = await res.json();
if (data.success) {
await applyFontLive(font);
showStatus('Website font updated!', 'success');
if (window.flashMessage) {
window.flashMessage('Website font updated!', 2500, 'success');
}
} else {
alert(data.msg || 'Error saving font preference');
}
@@ -1868,6 +1882,35 @@
const saveDescriptionBtn = document.getElementById('btn-save-description');
const descriptionStatus = document.getElementById('description-status');
const liveDesc = document.getElementById('live-preview-description');
const descControls = document.getElementById('desc-edit-controls');
if (liveDesc && descriptionTextarea) {
liveDesc.addEventListener('focus', () => {
if (descControls) descControls.style.display = 'flex';
});
liveDesc.addEventListener('input', () => {
descriptionTextarea.value = liveDesc.innerText.trim();
});
liveDesc.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
liveDesc.blur();
if (saveDescriptionBtn) saveDescriptionBtn.click();
}
});
}
if (descriptionTextarea) {
descriptionTextarea.addEventListener('input', () => {
if (liveDesc && document.activeElement !== liveDesc) {
liveDesc.textContent = descriptionTextarea.value || '';
}
});
}
if (saveDescriptionBtn && descriptionTextarea) {
saveDescriptionBtn.addEventListener('click', async () => {
const description = descriptionTextarea.value;
@@ -1948,10 +1991,13 @@
const emailStatus = document.getElementById('email-status');
const displayEmail = document.getElementById('display-email');
const showAccountStatus = (el, msg, type) => {
const showAccountStatus = (el, msg, type = 'info') => {
if (msg && window.flashMessage) {
window.flashMessage(msg, 2500, type);
}
if (el) {
el.textContent = msg;
el.className = 'avatar-status ' + type;
el.className = 'avatar-status ' + (type || '');
if (type === 'success') {
setTimeout(() => { el.textContent = ''; el.className = 'avatar-status'; }, 5000);
}
@@ -2037,11 +2083,36 @@
const displayNameBtn = document.getElementById('btn-update-display-name');
const displayNameInput = document.getElementById('display_name_input');
const displayNameStatus = document.getElementById('display-name-status');
const liveUsernameEl = document.getElementById('live-preview-username');
if (liveUsernameEl && displayNameInput && displayNameBtn) {
liveUsernameEl.addEventListener('input', () => {
displayNameInput.value = liveUsernameEl.textContent.trim();
});
liveUsernameEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
liveUsernameEl.blur();
}
});
liveUsernameEl.addEventListener('blur', () => {
const val = liveUsernameEl.textContent.trim();
if (!val) {
liveUsernameEl.textContent = window.f0ckSession?.user || 'Username';
displayNameInput.value = window.f0ckSession?.user || '';
} else {
displayNameInput.value = val;
}
displayNameBtn.click();
});
}
if (displayNameBtn && displayNameInput) {
displayNameInput.addEventListener('input', () => {
const liveName = document.getElementById('live-preview-username');
if (liveName) {
liveName.textContent = displayNameInput.value.trim() || window.f0ckSession?.user || 'Username';
if (liveUsernameEl && document.activeElement !== liveUsernameEl) {
liveUsernameEl.textContent = displayNameInput.value.trim() || window.f0ckSession?.user || 'Username';
}
});

View File

@@ -77,176 +77,118 @@
<!-- ═══════════════════════════════ PROFILE ═══════════════════════════════ -->
<h2 id="profile"><i class="fa-solid fa-user-pen"></i> {{ t('settings.profile') }}</h2>
<!-- Identity & Display Settings -->
<fieldset style="border: 1px solid var(--nav-border-color); padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; background: rgba(0,0,0,0.06);">
<legend style="width: auto; padding: 0 8px; font-size: 1.05em; font-weight: bold;"><i class="fa-solid fa-signature"></i> {{ t('settings.display_name') }}</legend>
<div class="setting-item">
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
<input type="text" id="display_name_input" class="input"
placeholder="{{ t('settings.display_name_placeholder') }}" value="{!! session.display_name || '' !!}"
maxlength="32" style="max-width: 240px; height: 32px; font-size: 0.9em;">
<button type="button" id="btn-update-display-name" class="button"
style="padding: 5px 14px; font-size: 0.85em;">{{ t('settings.save') }}</button>
</div>
<div id="display-name-status" class="avatar-status"></div>
</div>
</fieldset>
<p class="text-muted" style="margin-bottom: 16px; font-size: 0.9em; display: flex; align-items: center; gap: 8px;">
<i class="fa-solid fa-wand-magic-sparkles" style="color: var(--accent);"></i> Click directly on your profile card below to edit your avatar, banner, name, color, or bio.
</p>
<fieldset style="border: 1px solid var(--nav-border-color); padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; background: rgba(0,0,0,0.06);">
<legend style="width: auto; padding: 0 8px; font-size: 1.05em; font-weight: bold;"><i class="fa-solid fa-palette"></i> {{ t('settings.username_color') }}</legend>
<div class="setting-item">
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
<input type="color" id="username_color_picker" value="{{ session.username_color || '#ffffff' }}"
style="width: 50px; height: 32px; padding: 0; border: 1px solid var(--nav-border-color); cursor: pointer; background: none; border-radius: 4px;">
<input type="text" id="username_color_hex" value="{{ session.username_color || '#ffffff' }}" maxlength="7"
placeholder="#ffffff" class="input"
style="width: 95px; font-family: monospace; font-size: 0.9em; padding: 4px 8px; height: 32px;">
<button type="button" id="btn-save-username-color" class="button"
style="padding: 5px 14px; font-size: 0.85em;">{{ t('settings.save_color') }}</button>
<button type="button" id="btn-reset-username-color" class="button button-secondary"
style="padding: 5px 14px; font-size: 0.85em;">{{ t('settings.reset') }}</button>
</div>
<small class="text-muted" style="display: block; margin-top: 6px;">{{ t('settings.username_color_hint') }}</small>
</div>
</fieldset>
<div class="direct-edit-profile-card-wrapper" style="position: relative; max-width: 750px; margin-bottom: 30px;">
@if(enable_profile_description)
<fieldset style="border: 1px solid var(--nav-border-color); padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; background: rgba(0,0,0,0.06);">
<legend style="width: auto; padding: 0 8px; font-size: 1.05em; font-weight: bold;"><i class="fa-solid fa-align-left"></i> {{ t('settings.custom_description') }}</legend>
<div class="setting-item">
<textarea id="profile_description" class="input" placeholder="{{ t('settings.description_placeholder') }}"
maxlength="2000" style="min-height: 80px;">{!! session.description || '' !!}</textarea>
<div class="profile-settings-actions" style="margin-top: 10px;">
<button type="button" id="btn-save-description" class="button">{{ t('settings.save_description') }}</button>
<button type="button" id="btn-clear-description" class="button button-danger">{{ t('settings.clear') }}</button>
</div>
<div id="description-status" class="avatar-status"></div>
</div>
</fieldset>
@endif
<!-- Interactive User Card -->
<div class="user-infobox-block editable-profile-card" id="live-profile-preview-box"
style="--author-accent: {{ session.username_color || 'var(--accent)' }}; --author-border: {{ session.username_color || 'var(--accent)' }}; @if(banner_file && user_banner_enabled) --author-banner: url('/a/{{ banner_file }}?t={{ Date.now() }}'); --author-banner-position: {{ banner_position === 'center' ? 'center top' : (banner_position || 'center top') }}; --author-banner-size: {{ banner_size || 'cover' }}; @endif">
<!-- Avatar & Media Uploads -->
<fieldset style="border: 1px solid var(--nav-border-color); padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; background: rgba(0,0,0,0.06);">
<legend style="width: auto; padding: 0 8px; font-size: 1.05em; font-weight: bold;"><i class="fa-solid fa-user-gear"></i> {{ t('settings.avatar') }}</legend>
<div class="avatar-settings-wrapper">
<div class="avatar-preview-container">
<div class="avatar-preview-label">{{ t('settings.current_avatar') }}</div>
<!-- Avatar with Hover Overlay -->
<div class="user-infobox-avatar editable-avatar-container" id="avatar-choose-btn" title="Click to change avatar">
<a href="#" onclick="event.preventDefault(); document.getElementById('avatar-file-input').click();" style="display:block; position:relative; width:100%; height:100%;">
@if(avatar_file)
<a href="/user/{!! session.user !!}" target="_blank">
<img id="avatar-preview" class="avatar-preview-img" src="/a/{{ avatar_file }}">
</a>
<img id="live-preview-avatar" class="avatar-preview-img" src="/a/{{ avatar_file }}" />
@elseif(session.avatar && session.avatar > 0)
<a href="/user/{!! session.user !!}" target="_blank">
<img id="avatar-preview" class="avatar-preview-img" src="/t/{{ session.avatar }}.webp">
</a>
<img id="live-preview-avatar" class="avatar-preview-img" src="/t/{{ session.avatar }}.webp" />
@else
<div id="avatar-preview" class="avatar-preview-img avatar-placeholder">?</div>
<img id="live-preview-avatar" class="avatar-preview-img" src="/a/default.png" style="border-color: #444;" />
@endif
<div class="avatar-hover-overlay">
<i class="fa-solid fa-camera"></i>
</div>
<div class="avatar-upload-section">
<p class="avatar-hint">{{ t('settings.avatar_hint') }}</p>
<div class="avatar-upload-wrapper">
<input type="file" id="avatar-file-input" accept="image/gif,image/jpeg,image/png,image/webp" hidden>
<button type="button" id="avatar-choose-btn" class="button">{{ t('settings.choose_file') }}</button>
<span id="avatar-filename" class="avatar-filename">{{ t('settings.no_file_selected') }}</span>
</div>
<div class="avatar-progress-wrapper" id="avatar-progress-wrapper" style="display: none;">
<div class="avatar-progress-bar">
<div class="avatar-progress-fill" id="avatar-progress-fill"></div>
</div>
<span class="avatar-progress-text" id="avatar-progress-text">0%</span>
</div>
<div class="avatar-upload-actions">
<button type="button" id="avatar-upload-btn" class="button" disabled>{{ t('settings.upload_btn') }}</button>
</a>
@if(avatar_file)
<button type="button" id="avatar-remove-btn" class="button button-danger">{{ t('settings.remove_custom') }}</button>
@endif
</div>
<div id="avatar-upload-status" class="avatar-status"></div>
</div>
</div>
</fieldset>
@if(user_banner_enabled)
<fieldset style="border: 1px solid var(--nav-border-color); padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; background: rgba(0,0,0,0.06);">
<legend style="width: auto; padding: 0 8px; font-size: 1.05em; font-weight: bold;"><i class="fa-solid fa-image"></i> Profile Banner</legend>
<div class="avatar-settings-wrapper">
<div class="avatar-preview-container" style="width: 360px; max-width: 100%;">
<div class="avatar-preview-label">{{ t('settings.current_banner') }}</div>
@if(banner_file)
<a href="/user/{!! session.user !!}" target="_blank" style="display:block; width: 100%;">
<div id="banner-preview" class="banner-preview-img" style="width:100%;aspect-ratio:400/140;border-radius:4px;border:1px solid var(--nav-border-color);background-image:url('/a/{{ banner_file }}');background-size:{{ banner_size || 'cover' }};background-position:{{ banner_position === 'center' ? 'center top' : (banner_position || 'center top') }};background-repeat:no-repeat;"></div>
</a>
@else
<div id="banner-preview" class="banner-preview-img banner-placeholder" style="width:100%;aspect-ratio:400/140;display:flex;align-items:center;justify-content:center;border:1px dashed var(--nav-border-color);border-radius:4px;color:var(--text-muted);font-size:0.85em;">{{ t('settings.no_banner_set') }}</div>
<button type="button" class="avatar-quick-remove" id="avatar-remove-btn" title="Remove custom avatar">
<i class="fa-solid fa-xmark"></i>
</button>
@endif
</div>
<div class="avatar-upload-section">
<p class="avatar-hint">{{ t('settings.banner_hint') }}</p>
<div class="avatar-upload-wrapper">
<input type="file" id="banner-file-input" accept="image/gif,image/jpeg,image/png,image/webp" hidden>
<button type="button" id="banner-choose-btn" class="button">{{ t('settings.choose_file') }}</button>
<span id="banner-filename" class="avatar-filename">{{ t('settings.no_file_selected') }}</span>
</div>
<div class="avatar-progress-wrapper" id="banner-progress-wrapper" style="display: none;">
<div class="avatar-progress-bar">
<div class="avatar-progress-fill" id="banner-progress-fill"></div>
</div>
<span class="avatar-progress-text" id="banner-progress-text">0%</span>
</div>
<div class="avatar-upload-actions">
<button type="button" id="banner-upload-btn" class="button" disabled>Upload Banner</button>
@if(banner_file)
<button type="button" id="banner-edit-btn" class="button">Edit Position</button>
<button type="button" id="banner-remove-btn" class="button button-danger">Remove Banner</button>
@endif
</div>
<div id="banner-upload-status" class="avatar-status"></div>
</div>
</div>
</fieldset>
@endif
<!-- Live Profile Preview -->
<fieldset style="border: 1px solid var(--nav-border-color); padding: 12px 16px; border-radius: 6px; margin-bottom: 25px; background: rgba(0,0,0,0.06);">
<legend style="width: auto; padding: 0 8px; font-size: 1.05em; font-weight: bold;"><i class="fa-solid fa-eye"></i> Live Profile Preview</legend>
<div class="setting-item" style="max-width: 750px;">
<div class="user-infobox-block" id="live-profile-preview-box" style="--author-accent: {{ session.username_color || 'var(--accent)' }}; --author-border: {{ session.username_color || 'var(--accent)' }}; @if(banner_file && user_banner_enabled) --author-banner: url('/a/{{ banner_file }}?t={{ Date.now() }}'); --author-banner-position: {{ banner_position === 'center' ? 'center top' : (banner_position || 'center top') }}; --author-banner-size: {{ banner_size || 'cover' }}; @endif">
<div class="user-infobox-avatar">
<a href="#">
@if(avatar_file)
<img id="live-preview-avatar" src="/a/{{ avatar_file }}" />
@else
<img id="live-preview-avatar" src="/a/default.png" style="border-color: #444;" />
@endif
</a>
</div>
<div class="user-infobox-info">
<div class="user-infobox-header">
<div class="user-infobox-username-container">
<a id="live-preview-username" href="#" class="user-infobox-username">{!! session.display_name || session.user !!}</a>
<div class="user-infobox-username-container" style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
<!-- Editable Display Name -->
<div class="inline-edit-username-wrapper">
<span id="live-preview-username" class="user-infobox-username" contenteditable="true" spellcheck="false" title="Click to edit display name">{!! session.display_name || session.user !!}</span>
<i class="fa-solid fa-pen" style="font-size: 0.65em; opacity: 0.5; margin-left: 2px;" title="Editable"></i>
</div>
<!-- Color Picker Badge -->
<div class="inline-color-picker-wrapper" style="display: inline-flex; align-items: center;">
<label for="username_color_picker" class="color-picker-badge" id="color-picker-badge" style="background: {{ session.username_color || 'var(--accent)' }};" title="Click to change username color">
<i class="fa-solid fa-palette"></i>
</label>
<input type="color" id="username_color_picker" value="{{ session.username_color || '#ffffff' }}" style="position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;">
<input type="text" id="username_color_hex" value="{{ session.username_color || '#ffffff' }}" style="display: none;">
</div>
<!-- Hidden Backing Save Buttons for JS event trigger -->
<button type="button" id="btn-update-display-name" class="inline-save-btn" style="display: none;"><i class="fa-solid fa-check"></i> Save Name</button>
<button type="button" id="btn-save-username-color" style="display: none;">Save Color</button>
<button type="button" id="btn-reset-username-color" style="display: none;">Reset</button>
</div>
<span class="user-infobox-timestamp"><a href="#" class="timestamp-link"><time>just now</time></a></span>
</div>
<div class="user-infobox-body">
<div class="user-infobox-description">
{!! session.description || 'Welcome to my profile! This is a live preview.' !!}
<!-- Editable Description -->
<div class="inline-edit-description-wrapper">
<div class="user-infobox-description" id="live-preview-description" contenteditable="true" spellcheck="false" title="Click to edit description" data-placeholder="Click to write a bio...">
{!! session.description || '' !!}
</div>
<div class="description-edit-controls" id="desc-edit-controls" style="display: flex; margin-top: 10px; gap: 8px; align-items: center; flex-wrap: wrap;">
<button type="button" id="btn-save-description" class="button button-sm" style="padding: 4px 10px; font-size: 0.8em;"><i class="fa-solid fa-floppy-disk"></i> {{ t('settings.save_description') }}</button>
<button type="button" id="btn-clear-description" class="button button-danger button-sm" style="padding: 4px 10px; font-size: 0.8em;"><i class="fa-solid fa-trash"></i> {{ t('settings.clear') }}</button>
@if(user_banner_enabled)
<button type="button" id="banner-choose-btn" class="button button-sm" style="padding: 4px 10px; font-size: 0.8em;"><i class="fa-solid fa-camera"></i> Change Banner</button>
@if(banner_file)
<button type="button" id="banner-edit-btn" class="button button-sm" style="padding: 4px 10px; font-size: 0.8em;"><i class="fa-solid fa-arrows-up-down-left-right"></i> Reposition</button>
<button type="button" id="banner-remove-btn" class="button button-danger button-sm" style="padding: 4px 10px; font-size: 0.8em;"><i class="fa-solid fa-trash"></i> Remove Banner</button>
@endif
@endif
</div>
</div>
<div class="user-infobox-actions">
<i class="iconset fa-solid fa-heart" title="Favorite"></i>
</div>
</div>
</div>
</div>
<!-- Hidden Backing Elements for JavaScript compatibility -->
<input type="file" id="avatar-file-input" accept="image/gif,image/jpeg,image/png,image/webp" style="display: none;">
<span id="avatar-filename" style="display: none;"></span>
<button type="button" id="avatar-upload-btn" style="display: none;"></button>
<div id="avatar-progress-wrapper" style="display: none;">
<div id="avatar-progress-fill"></div>
<span id="avatar-progress-text"></span>
</div>
</fieldset>
<input type="file" id="banner-file-input" accept="image/gif,image/jpeg,image/png,image/webp" style="display: none;">
<span id="banner-filename" style="display: none;"></span>
<button type="button" id="banner-upload-btn" style="display: none;"></button>
<div id="banner-progress-wrapper" style="display: none;">
<div id="banner-progress-fill"></div>
<span id="banner-progress-text"></span>
</div>
<input type="text" id="display_name_input" value="{!! session.display_name || '' !!}" style="display: none;">
@if(enable_profile_description)
<textarea id="profile_description" style="display: none;">{!! session.description || '' !!}</textarea>
@endif
</div>
<!-- ═══════════════════════════════ PREFERENCES ═══════════════════════════════ -->
<h2 id="preferences"><i class="fa-solid fa-sliders"></i> {{ t('settings.preferences') }}</h2>