More modern backgrounds buttons and filename (#4484) * init * move background styles, enlarge and clip image to get rid of antialising bug * move styles from style.css to backgrounds.css * Reformat backgrounds.css * Improve selector specificity, fix type errors, increase font size on thumbnails, decrease mobile toggle size * Fix webp chat backgrounds thumbnails * Respect reduced motion preference * Remove transition delay --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

0ba317a31887ab5a1d97700596099124b3dd6ba5

L <123923688+Vibecoder9000@users.noreply.github.com>

Signed
5 files changed, +259 -118Showing whitespace changes
public/css/backgrounds.css+175 -0
@@ -1,3 +1,68 @@
1+/* Main Page Backgrounds */
2+#bg1,
3+#bg_custom {
4+ background-repeat: no-repeat;
5+ background-attachment: fixed;
6+ background-size: cover;
7+ position: absolute;
8+ width: 100%;
9+ height: 100%;
10+ transition: background-image var(--animation-duration-3x) ease-in-out;
11+}
12+
13+/* Fitting options */
14+#background_fitting {
15+ max-width: 6em;
16+}
17+
18+/* Fill/Cover - scales to fill width while maintaining aspect ratio */
19+#bg1.cover,
20+#bg_custom.cover {
21+ background-size: cover;
22+ background-position: center;
23+}
24+
25+/* Fit/Contain - shows entire image maintaining aspect ratio */
26+#bg1.contain,
27+#bg_custom.contain {
28+ background-size: contain;
29+ background-position: center;
30+ background-repeat: no-repeat;
31+}
32+
33+/* Stretch - stretches to fill entire space */
34+#bg1.stretch,
35+#bg_custom.stretch {
36+ background-size: 100% 100%;
37+}
38+
39+/* Center - centers without scaling */
40+#bg1.center,
41+#bg_custom.center {
42+ background-size: auto;
43+ background-position: center;
44+ background-repeat: no-repeat;
45+}
46+
47+body.reduced-motion #bg1,
48+body.reduced-motion #bg_custom {
49+ transition: none;
50+}
51+
52+#bg1 {
53+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');
54+ z-index: -3;
55+}
56+
57+#bg_custom {
58+ background-image: none;
59+ z-index: -2;
60+}
61+
62+.bg_example.locked {
63+ outline: 2px solid var(--golden);
64+}
65+
166/* This is the main flex container for the entire drawer */
267#Backgrounds.drawer-content.openDrawer.bg-drawer-layout {
368 display: flex;
@@ -35,3 +100,113 @@
35100#bg-filter {
36101 font-size: calc(var(--mainFontSize) * 0.95);
37102}
103+
104+/* Thumbnail Menu & Buttons */
105+.bg_example .mobile-only-menu-toggle {
106+ display: none;
107+}
108+
109+.bg_example.flex-container {
110+ width: 30%;
111+ max-width: 200px;
112+ margin: 5px;
113+ aspect-ratio: 16/9;
114+ cursor: pointer;
115+ box-shadow: 0 0 7px var(--black50a);
116+
117+ position: relative;
118+ overflow: hidden;
119+ border-radius: 10px;
120+ border: 1px solid var(--SmartThemeBorderColor);
121+}
122+
123+.bg_example_img {
124+ position: absolute;
125+ top: -2px;
126+ left: -2px;
127+ right: -2px;
128+ bottom: -2px;
129+
130+ background-image: inherit;
131+
132+ background-size: cover;
133+ background-position: center;
134+}
135+
136+.bg_example .jg-menu {
137+ display: flex;
138+ position: absolute;
139+ top: 2px;
140+ right: 2px;
141+ background-color: rgba(0, 0, 0, 0.5);
142+ border-radius: 8px;
143+ gap: 2px;
144+ padding: 3px;
145+ z-index: 3;
146+ backdrop-filter: blur(4px);
147+ border: 1px solid var(--SmartThemeBorderColor);
148+
149+ opacity: 0;
150+ visibility: hidden;
151+ transform: scale(0.9);
152+ transform-origin: center;
153+ transition: opacity var(--animation-duration) ease-out, visibility var(--animation-duration) ease-out, transform var(--animation-duration) ease-out;
154+}
155+
156+.bg_example:hover .jg-menu {
157+ opacity: 1;
158+ visibility: visible;
159+ transform: scale(1);
160+}
161+
162+.bg_example .jg-button {
163+ display: flex;
164+ align-items: center;
165+ justify-content: center;
166+ color: white;
167+ padding: 5px;
168+ font-size: 1.2em;
169+ border-radius: 6px;
170+ transition: background-color var(--animation-duration) ease;
171+}
172+
173+.bg_example .jg-button:hover {
174+ background-color: rgba(255, 255, 255, 0.2);
175+}
176+
177+.bg_example .jg-unlock {
178+ display: none;
179+}
180+
181+.bg_example.locked .jg-lock {
182+ display: none;
183+}
184+
185+.bg_example.locked .jg-unlock {
186+ display: flex;
187+}
188+
189+/* Thumbnail Title */
190+.bg_example .BGSampleTitle {
191+ position: absolute;
192+ bottom: 0;
193+ left: 0;
194+ right: 0;
195+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.9));
196+ color: var(--SmartThemeBodyColor);
197+ font-size: 0.9em;
198+ font-weight: 600;
199+ padding: 0px 6px 2px;
200+ text-align: center;
201+ white-space: nowrap;
202+ overflow: hidden;
203+ text-overflow: ellipsis;
204+ opacity: 0;
205+ transition: opacity var(--animation-duration) ease-in-out;
206+ pointer-events: none;
207+ border-radius: 0 0 8px 8px;
208+}
209+
210+.bg_example:hover .BGSampleTitle {
211+ opacity: 1;
212+}
public/css/mobile-styles.css+31 -4
@@ -25,6 +25,37 @@
2525 font-size: 15px;
2626 }
2727
28+ #Backgrounds .bg_example .BGSampleTitle {
29+ opacity: 1;
30+ bottom: 0px;
31+ }
32+
33+ .bg_example:hover .jg-menu {
34+ display: none;
35+ }
36+
37+ .bg_example.mobile-menu-open .jg-menu {
38+ display: flex;
39+ z-index: 4;
40+ }
41+
42+ .bg_example .mobile-only-menu-toggle {
43+ display: flex;
44+ align-items: center;
45+ justify-content: center;
46+ position: absolute;
47+ top: 5px;
48+ right: 5px;
49+ width: 28px;
50+ height: 28px;
51+ background-color: rgba(0, 0, 0, 0.4);
52+ color: white;
53+ border-radius: 6px;
54+ z-index: 3;
55+ cursor: pointer;
56+ backdrop-filter: blur(2px);
57+ }
58+
2859 #bg-header-controls {
2960 flex-wrap: wrap;
3061 row-gap: 10px;
@@ -461,10 +492,6 @@
461492 flex-basis: max(calc(100% / 2 - 10px), 180px);
462493 }
463494
464- .BGSampleTitle {
465- display: none;
466- }
467-
468495 .tag.excluded:after {
469496 top: unset;
470497 bottom: unset;
public/index.html+12 -6
@@ -6257,14 +6257,20 @@
62576257 </div>
62586258 </div>
62596259 <div id="background_template" class="template_element">
62606260 <div class="bg_example flex-container" bgfile="" class="bg_example_img" title="">
6261+ <div class="bg_example_img"></div>
62616262 <div title="Copy to system backgrounds" data-i18n="[title]Copy to system backgrounds" class="bg_button bg_example_copy fa-solid fa-file-arrow-up"></div>
6262- <div title="Rename background" data-i18n="[title]Rename background" class="bg_button bg_example_edit fa-solid fa-pencil"></div>
6263+ <div class="jg-menu">
6263- <div title="Lock" data-i18n="[title]Lock" class="bg_button bg_example_lock fa-solid fa-lock"></div>
6264+ <!-- temporarily moved lock icon here (will be moved to header) -->
62646265 <div title="Unlock" data-i18naction="[title]Unlocklock" class="bg_buttonjg-button bg_example_unlockjg-lock fa-solid fa-lock fa-openfw pointer" data-i18n="[title]Lock" title="Lock"></div>
62656266 <div title="Delete background" data-i18naction="[title]Delete backgroundunlock" class="bg_buttonjg-button bg_example_crossjg-unlock fa-solid fa-circlelock-xmarkopen fa-fw pointer" data-i18n="[title]Unlock" title="Unlock"></div>
6266- <div class="BGSampleTitle">
6267+ <div data-action="edit" class="jg-button jg-edit fa-solid fa-pen-to-square fa-fw pointer" data-i18n="[title]Rename Background" title="Rename Background"></div>
6268+ <div data-action="delete" class="jg-button jg-delete fa-solid fa-trash-can fa-fw pointer" data-i18n="[title]Delete Background" title="Delete Background"></div>
62676269 </div>
6270+ <div class="mobile-only-menu-toggle">
6271+ <i class="fa-solid fa-ellipsis-vertical"></i>
6272+ </div>
6273+ <div class="BGSampleTitle"></div>
62686274 </div>
62696275 </div>
62706276 <!-- templates for JS to reuse when needed -->
public/scripts/backgrounds.js+41 -11
@@ -265,9 +265,10 @@ async function onCopyToSystemBackgroundClick(e) {
265265 * It caches the thumbnail in local storage and returns a blob URL for the thumbnail.
266266 * If the thumbnail cannot be fetched, it returns a transparent PNG pixel as a fallback.
267267 * @param {string} bg Background URL
268+ * @param {boolean} isCustom Is the background custom?
268269 * @returns {Promise<string>} Blob URL of the thumbnail
269270 */
270271async function getThumbnailFromStorage(bg, isCustom) {
271272 const cachedBlobUrl = THUMBNAIL_BLOBS.get(bg);
272273 if (cachedBlobUrl) {
273274 return cachedBlobUrl;
@@ -281,7 +282,8 @@ async function getThumbnailFromStorage(bg) {
281282 }
282283
283284 try {
284285 const responseurl = awaitisCustom fetch(getBackgroundPath(bg),? {bg cache: 'force-cache' }getBackgroundPath(bg);
286+ const response = await fetch(url, { cache: 'force-cache' });
285287 if (!response.ok) {
286288 throw new Error('Fetch failed with status: ' + response.status);
287289 }
@@ -519,7 +521,7 @@ async function resolveImageUrl(bg, isCustom) {
519521 const fileExtension = bg.split('.').pop().toLowerCase();
520522 const isAnimated = ['mp4', 'webp'].includes(fileExtension);
521523 const thumbnailUrl = isAnimated && !background_settings.animation
522524 ? await getThumbnailFromStorage(bg, isCustom)
523525 : isCustom
524526 ? bg
525527 : getThumbnailUrl('bg', bg);
@@ -583,7 +585,7 @@ async function onBackgroundUploadSelected() {
583585 const formData = new FormData(form);
584586
585587 const file = formData.get('avatar');
586588 if (!(file instanceof File) || file.size === 0) {
587589 form.reset();
588590 return;
589591 }
@@ -621,7 +623,7 @@ async function convertFileIfVideo(formData) {
621623 const sourceBuffer = await file.arrayBuffer();
622624 const convertedBuffer = await globalThis.convertVideoToAnimatedWebp({ buffer: new Uint8Array(sourceBuffer), name: file.name });
623625 const convertedFileName = file.name.replace(/\.[^/.]+$/, '.webp');
624626 const convertedFile = new File([new Uint8Array(convertedBuffer)], convertedFileName, { type: 'image/webp' });
625627 formData.set('avatar', convertedFile);
626628 toastMessage.remove();
627629 } catch (error) {
@@ -700,12 +702,40 @@ function onBackgroundFilterInput() {
700702export function initBackgrounds() {
701703 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
702704 eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground);
703- $(document).on('click', '.bg_example', onSelectBackgroundClick);
705+
704- $(document).on('click', '.bg_example_lock', onLockBackgroundClick);
706+ $(document)
705707 $ .off(document'click', '.bg_example').on('click', '.bg_example_unlockbg_example', onUnlockBackgroundClickonSelectBackgroundClick);
706708 $ .off(document'click', '.bg_example_copy').on('click', '.bg_example_editbg_example_copy', onRenameBackgroundClickonCopyToSystemBackgroundClick);
707- $(document).on('click', '.bg_example_cross', onDeleteBackgroundClick);
709+ .off('click', '.bg_example .mobile-only-menu-toggle').on('click', '.bg_example .mobile-only-menu-toggle', function (e) {
708- $(document).on('click', '.bg_example_copy', onCopyToSystemBackgroundClick);
710+ e.stopPropagation();
711+ const $context = $(this).closest('.bg_example');
712+ const wasOpen = $context.hasClass('mobile-menu-open');
713+ // Close all other open menus before opening a new one.
714+ $('.bg_example.mobile-menu-open').removeClass('mobile-menu-open');
715+ if (!wasOpen) {
716+ $context.addClass('mobile-menu-open');
717+ }
718+ })
719+ .off('click', '.jg-button').on('click', '.jg-button', function (e) {
720+ e.stopPropagation();
721+ const action = $(this).data('action');
722+
723+ switch (action) {
724+ case 'lock':
725+ onLockBackgroundClick.call(this, e.originalEvent);
726+ break;
727+ case 'unlock':
728+ onUnlockBackgroundClick.call(this, e.originalEvent);
729+ break;
730+ case 'edit':
731+ onRenameBackgroundClick.call(this, e.originalEvent);
732+ break;
733+ case 'delete':
734+ onDeleteBackgroundClick.call(this, e.originalEvent);
735+ break;
736+ }
737+ });
738+
709739 $('#auto_background').on('click', autoBackgroundCommand);
710740 $('#add_bg_button').on('change', onBackgroundUploadSelected);
711741 $('#bg-filter').on('input', onBackgroundFilterInput);
public/style.css+0 -97
@@ -722,71 +722,11 @@ hr {
722722 opacity: 0.2;
723723}
724724
725-#bg1,
726-#bg_custom {
727- background-repeat: no-repeat;
728- background-attachment: fixed;
729- background-size: cover;
730- position: absolute;
731- width: 100%;
732- height: 100%;
733- transition: background-image var(--animation-duration-3x) ease-in-out;
734-}
735-
736-/* Background fitting options */
737-#background_fitting {
738- max-width: 6em;
739-}
740-
741-/* Fill/Cover - scales to fill width while maintaining aspect ratio */
742-#bg1.cover,
743-#bg_custom.cover {
744- background-size: cover;
745- background-position: center;
746-}
747-
748-/* Fit/Contain - shows entire image maintaining aspect ratio */
749-#bg1.contain,
750-#bg_custom.contain {
751- background-size: contain;
752- background-position: center;
753- background-repeat: no-repeat;
754-}
755-
756-/* Stretch - stretches to fill entire space */
757-#bg1.stretch,
758-#bg_custom.stretch {
759- background-size: 100% 100%;
760-}
761-
762-/* Center - centers without scaling */
763-#bg1.center,
764-#bg_custom.center {
765- background-size: auto;
766- background-position: center;
767- background-repeat: no-repeat;
768-}
769-
770-body.reduced-motion #bg1,
771-body.reduced-motion #bg_custom {
772- transition: none;
773-}
774-
775725#version_display {
776726 padding: 5px;
777727 opacity: 0.8;
778728}
779729
780-#bg1 {
781- background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');
782- z-index: -3;
783-}
784-
785-#bg_custom {
786- background-image: none;
787- z-index: -2;
788-}
789-
790730/*TOPPER margin*/
791731
792732#top-bar {
@@ -3305,22 +3245,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
33053245 justify-content: space-evenly;
33063246}
33073247
3308-.bg_example {
3309- width: 30%;
3310- max-width: 200px;
3311- background-repeat: no-repeat;
3312- background-size: cover;
3313- background-position: center;
3314- border-radius: 10px;
3315- border: 1px solid var(--SmartThemeBorderColor);
3316- box-shadow: 0 0 7px var(--black50a);
3317- margin: 5px;
3318- cursor: pointer;
3319- aspect-ratio: 16/9;
3320- justify-content: flex-end;
3321- position: relative;
3322-}
3323-
33243248.bg_example.locked {
33253249 outline: 2px solid var(--golden);
33263250}
@@ -3345,27 +3269,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
33453269 display: none;
33463270}
33473271
3348-.BGSampleTitle {
3349- display: flex;
3350- width: 100%;
3351- height: min-content;
3352- text-align: center;
3353- justify-content: center;
3354- align-self: flex-end;
3355- bottom: 0;
3356- position: relative;
3357- word-break: break-word;
3358- background-color: var(--SmartThemeBlurTintColor);
3359- font-size: calc(var(--fontScale) * 0.9em);
3360- max-height: 50%;
3361- overflow-y: clip;
3362- border-radius: 0 0 7px 7px;
3363-}
3364-
3365-.bg_example[custom="true"] .BGSampleTitle {
3366- display: none;
3367-}
3368-
33693272.bg_button {
33703273 padding: 4px;
33713274 position: absolute;