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
47body.reduced-motion #bg1,
48body.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
1/* This is the main flex container for the entire drawer */66/* This is the main flex container for the entire drawer */
2#Backgrounds.drawer-content.openDrawer.bg-drawer-layout {67#Backgrounds.drawer-content.openDrawer.bg-drawer-layout {
3 display: flex;68 display: flex;
@@ -35,3 +100,113 @@
35#bg-filter {100#bg-filter {
36 font-size: calc(var(--mainFontSize) * 0.95);101 font-size: calc(var(--mainFontSize) * 0.95);
37}102}
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 @@
25 font-size: 15px;25 font-size: 15px;
26 }26 }
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
28 #bg-header-controls {59 #bg-header-controls {
29 flex-wrap: wrap;60 flex-wrap: wrap;
30 row-gap: 10px;61 row-gap: 10px;
@@ -461,10 +492,6 @@
461 flex-basis: max(calc(100% / 2 - 10px), 180px);492 flex-basis: max(calc(100% / 2 - 10px), 180px);
462 }493 }
463494
464 .BGSampleTitle {
465 display: none;
466 }
467
468 .tag.excluded:after {495 .tag.excluded:after {
469 top: unset;496 top: unset;
470 bottom: unset;497 bottom: unset;
public/index.html+12 -6
@@ -6257,14 +6257,20 @@
6257 </div>6257 </div>
6258 </div>6258 </div>
6259 <div id="background_template" class="template_element">6259 <div id="background_template" class="template_element">
6260 <div class="bg_example flex-container" bgfile="" class="bg_example_img" title="">6260 <div class="bg_example flex-container" bgfile="" title="">
6261 <div class="bg_example_img"></div>
6261 <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="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) -->
6264 <div title="Unlock" data-i18n="[title]Unlock" class="bg_button bg_example_unlock fa-solid fa-lock-open"></div>6265 <div data-action="lock" class="jg-button jg-lock fa-solid fa-lock fa-fw pointer" data-i18n="[title]Lock" title="Lock"></div>
6265 <div title="Delete background" data-i18n="[title]Delete background" class="bg_button bg_example_cross fa-solid fa-circle-xmark"></div>6266 <div data-action="unlock" class="jg-button jg-unlock fa-solid fa-lock-open 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>
6267 </div>6269 </div>
6270 <div class="mobile-only-menu-toggle">
6271 <i class="fa-solid fa-ellipsis-vertical"></i>
6272 </div>
6273 <div class="BGSampleTitle"></div>
6268 </div>6274 </div>
6269 </div>6275 </div>
6270 <!-- templates for JS to reuse when needed -->6276 <!-- templates for JS to reuse when needed -->
public/scripts/backgrounds.js+41 -11
@@ -265,9 +265,10 @@ async function onCopyToSystemBackgroundClick(e) {
265 * It caches the thumbnail in local storage and returns a blob URL for the thumbnail.265 * It caches the thumbnail in local storage and returns a blob URL for the thumbnail.
266 * If the thumbnail cannot be fetched, it returns a transparent PNG pixel as a fallback.266 * If the thumbnail cannot be fetched, it returns a transparent PNG pixel as a fallback.
267 * @param {string} bg Background URL267 * @param {string} bg Background URL
268 * @param {boolean} isCustom Is the background custom?
268 * @returns {Promise<string>} Blob URL of the thumbnail269 * @returns {Promise<string>} Blob URL of the thumbnail
269 */270 */
270async function getThumbnailFromStorage(bg) {271async function getThumbnailFromStorage(bg, isCustom) {
271 const cachedBlobUrl = THUMBNAIL_BLOBS.get(bg);272 const cachedBlobUrl = THUMBNAIL_BLOBS.get(bg);
272 if (cachedBlobUrl) {273 if (cachedBlobUrl) {
273 return cachedBlobUrl;274 return cachedBlobUrl;
@@ -281,7 +282,8 @@ async function getThumbnailFromStorage(bg) {
281 }282 }
282283
283 try {284 try {
284 const response = await fetch(getBackgroundPath(bg), { cache: 'force-cache' });285 const url = isCustom ? bg : getBackgroundPath(bg);
286 const response = await fetch(url, { cache: 'force-cache' });
285 if (!response.ok) {287 if (!response.ok) {
286 throw new Error('Fetch failed with status: ' + response.status);288 throw new Error('Fetch failed with status: ' + response.status);
287 }289 }
@@ -519,7 +521,7 @@ async function resolveImageUrl(bg, isCustom) {
519 const fileExtension = bg.split('.').pop().toLowerCase();521 const fileExtension = bg.split('.').pop().toLowerCase();
520 const isAnimated = ['mp4', 'webp'].includes(fileExtension);522 const isAnimated = ['mp4', 'webp'].includes(fileExtension);
521 const thumbnailUrl = isAnimated && !background_settings.animation523 const thumbnailUrl = isAnimated && !background_settings.animation
522 ? await getThumbnailFromStorage(bg)524 ? await getThumbnailFromStorage(bg, isCustom)
523 : isCustom525 : isCustom
524 ? bg526 ? bg
525 : getThumbnailUrl('bg', bg);527 : getThumbnailUrl('bg', bg);
@@ -583,7 +585,7 @@ async function onBackgroundUploadSelected() {
583 const formData = new FormData(form);585 const formData = new FormData(form);
584586
585 const file = formData.get('avatar');587 const file = formData.get('avatar');
586 if (!file || file.size === 0) {588 if (!(file instanceof File) || file.size === 0) {
587 form.reset();589 form.reset();
588 return;590 return;
589 }591 }
@@ -621,7 +623,7 @@ async function convertFileIfVideo(formData) {
621 const sourceBuffer = await file.arrayBuffer();623 const sourceBuffer = await file.arrayBuffer();
622 const convertedBuffer = await globalThis.convertVideoToAnimatedWebp({ buffer: new Uint8Array(sourceBuffer), name: file.name });624 const convertedBuffer = await globalThis.convertVideoToAnimatedWebp({ buffer: new Uint8Array(sourceBuffer), name: file.name });
623 const convertedFileName = file.name.replace(/\.[^/.]+$/, '.webp');625 const convertedFileName = file.name.replace(/\.[^/.]+$/, '.webp');
624 const convertedFile = new File([convertedBuffer], convertedFileName, { type: 'image/webp' });626 const convertedFile = new File([new Uint8Array(convertedBuffer)], convertedFileName, { type: 'image/webp' });
625 formData.set('avatar', convertedFile);627 formData.set('avatar', convertedFile);
626 toastMessage.remove();628 toastMessage.remove();
627 } catch (error) {629 } catch (error) {
@@ -700,12 +702,40 @@ function onBackgroundFilterInput() {
700export function initBackgrounds() {702export function initBackgrounds() {
701 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);703 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
702 eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground);704 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)
705 $(document).on('click', '.bg_example_unlock', onUnlockBackgroundClick);707 .off('click', '.bg_example').on('click', '.bg_example', onSelectBackgroundClick)
706 $(document).on('click', '.bg_example_edit', onRenameBackgroundClick);708 .off('click', '.bg_example_copy').on('click', '.bg_example_copy', onCopyToSystemBackgroundClick)
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
709 $('#auto_background').on('click', autoBackgroundCommand);739 $('#auto_background').on('click', autoBackgroundCommand);
710 $('#add_bg_button').on('change', onBackgroundUploadSelected);740 $('#add_bg_button').on('change', onBackgroundUploadSelected);
711 $('#bg-filter').on('input', onBackgroundFilterInput);741 $('#bg-filter').on('input', onBackgroundFilterInput);
public/style.css+0 -97
@@ -722,71 +722,11 @@ hr {
722 opacity: 0.2;722 opacity: 0.2;
723}723}
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
770body.reduced-motion #bg1,
771body.reduced-motion #bg_custom {
772 transition: none;
773}
774
775#version_display {725#version_display {
776 padding: 5px;726 padding: 5px;
777 opacity: 0.8;727 opacity: 0.8;
778}728}
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
790/*TOPPER margin*/730/*TOPPER margin*/
791731
792#top-bar {732#top-bar {
@@ -3305,22 +3245,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
3305 justify-content: space-evenly;3245 justify-content: space-evenly;
3306}3246}
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
3324.bg_example.locked {3248.bg_example.locked {
3325 outline: 2px solid var(--golden);3249 outline: 2px solid var(--golden);
3326}3250}
@@ -3345,27 +3269,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
3345 display: none;3269 display: none;
3346}3270}
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
3369.bg_button {3272.bg_button {
3370 padding: 4px;3273 padding: 4px;
3371 position: absolute;3274 position: absolute;