Fix/cleanup and background interactability (keyboard navigation, focus/hover) (#4503) * refactor: remove unused background styles * feat: add support for nested @media, @supports, and @container rules in dynamic focus styles * fix: add keyboard focus support for background menu buttons back * fix: move "custom" background copy functionality to unified button UI and make it work again * refactor: simplify CSSContainerRule instance check using window global * fix: update hover and focus selector placeholder replacements to handle multiple occurrences in selector * Fix focused outline offset of background samples * Fix locked background outline color * Add a11y roles to background menu items * Add mobile-only button selector * fix: update button and list item selectors * Improve keyboard navigation in narrow layout * Hide rename button for custom=true backgrounds --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -59,8 +59,8 @@ body.reduced-motion #bg_custom { | |||
| 59 | z-index: -2; | 59 | z-index: -2; |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | .bg_example.locked { | 62 | .bg_example.flex-container.locked:not(:focus-visible) { |
| 63 | outline: 2px solid var(--golden); | 63 | outline-color: var(--golden); |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | /* This is the main flex container for the entire drawer */ | 66 | /* This is the main flex container for the entire drawer */ |
| @@ -122,6 +122,10 @@ body.reduced-motion #bg_custom { | |||
| 122 | outline-offset: -1px; | 122 | outline-offset: -1px; |
| 123 | } | 123 | } |
| 124 | 124 | ||
| 125 | .bg_example.flex-container:focus-visible { | ||
| 126 | outline-offset: inherit; | ||
| 127 | } | ||
| 128 | |||
| 125 | .bg_example_img { | 129 | .bg_example_img { |
| 126 | position: absolute; | 130 | position: absolute; |
| 127 | top: -2px; | 131 | top: -2px; |
| @@ -155,7 +159,8 @@ body.reduced-motion #bg_custom { | |||
| 155 | transition: opacity var(--animation-duration) ease-out, visibility var(--animation-duration) ease-out, transform var(--animation-duration) ease-out; | 159 | transition: opacity var(--animation-duration) ease-out, visibility var(--animation-duration) ease-out, transform var(--animation-duration) ease-out; |
| 156 | } | 160 | } |
| 157 | 161 | ||
| 158 | .bg_example:hover .jg-menu { | 162 | .bg_example:hover .jg-menu, |
| 163 | .bg_example:focus-within .jg-menu { | ||
| 159 | opacity: 1; | 164 | opacity: 1; |
| 160 | visibility: visible; | 165 | visibility: visible; |
| 161 | transform: scale(1); | 166 | transform: scale(1); |
| @@ -188,6 +193,11 @@ body.reduced-motion #bg_custom { | |||
| 188 | display: flex; | 193 | display: flex; |
| 189 | } | 194 | } |
| 190 | 195 | ||
| 196 | .bg_example:not([custom="true"]) .jg-copy, | ||
| 197 | .bg_example[custom="true"] .jg-edit { | ||
| 198 | display: none; | ||
| 199 | } | ||
| 200 | |||
| 191 | /* Thumbnail Title */ | 201 | /* Thumbnail Title */ |
| 192 | .bg_example .BGSampleTitle { | 202 | .bg_example .BGSampleTitle { |
| 193 | position: absolute; | 203 | position: absolute; |
| @@ -209,6 +219,7 @@ body.reduced-motion #bg_custom { | |||
| 209 | border-radius: 0 0 8px 8px; | 219 | border-radius: 0 0 8px 8px; |
| 210 | } | 220 | } |
| 211 | 221 | ||
| 212 | .bg_example:hover .BGSampleTitle { | 222 | .bg_example:hover .BGSampleTitle, |
| 223 | .bg_example:focus-within .BGSampleTitle { | ||
| 213 | opacity: 1; | 224 | opacity: 1; |
| 214 | } | 225 | } |
| @@ -30,7 +30,8 @@ | |||
| 30 | bottom: 0px; | 30 | bottom: 0px; |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | .bg_example:hover .jg-menu { | 33 | .bg_example:hover .jg-menu, |
| 34 | .bg_example:focus-within .jg-menu { | ||
| 34 | display: none; | 35 | display: none; |
| 35 | } | 36 | } |
| 36 | 37 | ||
| @@ -6274,17 +6274,17 @@ | |||
| 6274 | <div id="background_template" class="template_element"> | 6274 | <div id="background_template" class="template_element"> |
| 6275 | <div class="bg_example flex-container" bgfile="" title=""> | 6275 | <div class="bg_example flex-container" bgfile="" title=""> |
| 6276 | <div class="bg_example_img"></div> | 6276 | <div class="bg_example_img"></div> |
| 6277 | <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> | 6277 | <div class="mobile-only-menu-toggle"> |
| 6278 | <i class="fa-solid fa-ellipsis-vertical"></i> | ||
| 6279 | </div> | ||
| 6278 | <div class="jg-menu"> | 6280 | <div class="jg-menu"> |
| 6281 | <div data-action="copy" class="jg-button jg-copy fa-solid fa-file-arrow-up" data-i18n="[title]Copy to system backgrounds" title="Copy to system backgrounds"></div> | ||
| 6279 | <!-- temporarily moved lock icon here (will be moved to header) --> | 6282 | <!-- temporarily moved lock icon here (will be moved to header) --> |
| 6280 | <div data-action="lock" class="jg-button jg-lock fa-solid fa-lock fa-fw pointer" data-i18n="[title]Lock" title="Lock"></div> | 6283 | <div data-action="lock" class="jg-button jg-lock fa-solid fa-lock fa-fw pointer" data-i18n="[title]Lock" title="Lock"></div> |
| 6281 | <div data-action="unlock" class="jg-button jg-unlock fa-solid fa-lock-open fa-fw pointer" data-i18n="[title]Unlock" title="Unlock"></div> | 6284 | <div data-action="unlock" class="jg-button jg-unlock fa-solid fa-lock-open fa-fw pointer" data-i18n="[title]Unlock" title="Unlock"></div> |
| 6282 | <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> | 6285 | <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> |
| 6283 | <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> | 6286 | <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> |
| 6284 | </div> | 6287 | </div> |
| 6285 | <div class="mobile-only-menu-toggle"> | ||
| 6286 | <i class="fa-solid fa-ellipsis-vertical"></i> | ||
| 6287 | </div> | ||
| 6288 | <div class="BGSampleTitle"></div> | 6288 | <div class="BGSampleTitle"></div> |
| 6289 | </div> | 6289 | </div> |
| 6290 | </div> | 6290 | </div> |
| @@ -13,6 +13,9 @@ const buttonSelectors = [ | |||
| 13 | '.swipe_right', | 13 | '.swipe_right', |
| 14 | '.character_select', | 14 | '.character_select', |
| 15 | '.tags .tag', | 15 | '.tags .tag', |
| 16 | '.jg-menu .jg-button', | ||
| 17 | '.bg_example .mobile-only-menu-toggle', | ||
| 18 | '.paginationjs-pages li a', | ||
| 16 | ].join(', '); | 19 | ].join(', '); |
| 17 | 20 | ||
| 18 | const listSelectors = [ | 21 | const listSelectors = [ |
| @@ -26,10 +29,11 @@ const listSelectors = [ | |||
| 26 | '.recentChatList', | 29 | '.recentChatList', |
| 27 | '.dataMaidCategoryContent', | 30 | '.dataMaidCategoryContent', |
| 28 | '#userList', | 31 | '#userList', |
| 32 | '.bg_list', | ||
| 29 | ].join(', '); | 33 | ].join(', '); |
| 30 | 34 | ||
| 31 | const listItemSelectors = [ | 35 | const listItemSelectors = [ |
| 32 | '.options-content .interactable', | 36 | '.options-content .list-group-item', |
| 33 | '.list-group .list-group-item', | 37 | '.list-group .list-group-item', |
| 34 | '#rm_print_characters_block .entity_block', | 38 | '#rm_print_characters_block .entity_block', |
| 35 | '#rm_group_members .group_member', | 39 | '#rm_group_members .group_member', |
| @@ -39,6 +43,11 @@ const listItemSelectors = [ | |||
| 39 | '.recentChatList .recentChat', | 43 | '.recentChatList .recentChat', |
| 40 | '.dataMaidCategoryContent .dataMaidItem', | 44 | '.dataMaidCategoryContent .dataMaidItem', |
| 41 | '#userList .userSelect', | 45 | '#userList .userSelect', |
| 46 | '.bg_list .bg_example', | ||
| 47 | ].join(', '); | ||
| 48 | |||
| 49 | const toolbarSelectors = [ | ||
| 50 | '.jg-menu', | ||
| 42 | ].join(', '); | 51 | ].join(', '); |
| 43 | 52 | ||
| 44 | /** @type {Record<string, (element: Element) => void>} */ | 53 | /** @type {Record<string, (element: Element) => void>} */ |
| @@ -52,6 +61,9 @@ const a11yRules = { | |||
| 52 | [listItemSelectors]: (element) => { | 61 | [listItemSelectors]: (element) => { |
| 53 | element.setAttribute('role', 'listitem'); | 62 | element.setAttribute('role', 'listitem'); |
| 54 | }, | 63 | }, |
| 64 | [toolbarSelectors]: (element) => { | ||
| 65 | element.setAttribute('role', 'toolbar'); | ||
| 66 | }, | ||
| 55 | '#toast-container .toast-message': (element) => { | 67 | '#toast-container .toast-message': (element) => { |
| 56 | element.setAttribute('role', 'alert'); | 68 | element.setAttribute('role', 'alert'); |
| 57 | }, | 69 | }, |
| @@ -705,7 +705,6 @@ export function initBackgrounds() { | |||
| 705 | 705 | ||
| 706 | $(document) | 706 | $(document) |
| 707 | .off('click', '.bg_example').on('click', '.bg_example', onSelectBackgroundClick) | 707 | .off('click', '.bg_example').on('click', '.bg_example', onSelectBackgroundClick) |
| 708 | .off('click', '.bg_example_copy').on('click', '.bg_example_copy', onCopyToSystemBackgroundClick) | ||
| 709 | .off('click', '.bg_example .mobile-only-menu-toggle').on('click', '.bg_example .mobile-only-menu-toggle', function (e) { | 708 | .off('click', '.bg_example .mobile-only-menu-toggle').on('click', '.bg_example .mobile-only-menu-toggle', function (e) { |
| 710 | e.stopPropagation(); | 709 | e.stopPropagation(); |
| 711 | const $context = $(this).closest('.bg_example'); | 710 | const $context = $(this).closest('.bg_example'); |
| @@ -733,6 +732,9 @@ export function initBackgrounds() { | |||
| 733 | case 'delete': | 732 | case 'delete': |
| 734 | onDeleteBackgroundClick.call(this, e.originalEvent); | 733 | onDeleteBackgroundClick.call(this, e.originalEvent); |
| 735 | break; | 734 | break; |
| 735 | case 'copy': | ||
| 736 | onCopyToSystemBackgroundClick.call(this, e.originalEvent); | ||
| 737 | break; | ||
| 736 | } | 738 | } |
| 737 | }); | 739 | }); |
| 738 | 740 | ||
| @@ -33,7 +33,8 @@ const observer = new MutationObserver(mutations => { | |||
| 33 | * @param {boolean} [options.fromExtension=false] - Indicates if the styles are from an extension | 33 | * @param {boolean} [options.fromExtension=false] - Indicates if the styles are from an extension |
| 34 | */ | 34 | */ |
| 35 | function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { | 35 | function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { |
| 36 | /** @type {{baseSelector: string, rule: CSSStyleRule}[]} */ | 36 | /** @typedef {{ type: 'media'|'supports'|'container', conditionText: string }} WrapperCond */ |
| 37 | /** @type {{baseSelector: string, rule: CSSStyleRule, wrappers: WrapperCond[]}[]} */ | ||
| 37 | const hoverRules = []; | 38 | const hoverRules = []; |
| 38 | /** @type {Set<string>} */ | 39 | /** @type {Set<string>} */ |
| 39 | const focusRules = new Set(); | 40 | const focusRules = new Set(); |
| @@ -41,14 +42,28 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { | |||
| 41 | const PLACEHOLDER = ':__PLACEHOLDER__'; | 42 | const PLACEHOLDER = ':__PLACEHOLDER__'; |
| 42 | 43 | ||
| 43 | /** | 44 | /** |
| 45 | * Builds a stable signature string for a chain of wrapper conditions so we can distinguish | ||
| 46 | * identical selectors under different contexts (e.g., different @media queries) | ||
| 47 | * @param {WrapperCond[]} wrappers | ||
| 48 | * @returns {string} | ||
| 49 | */ | ||
| 50 | function wrapperSignature(wrappers) { | ||
| 51 | return wrappers.map(w => `${w.type}:${w.conditionText}`).join(';'); | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 44 | * Processes the CSS rules and separates selectors for hover and focus | 55 | * Processes the CSS rules and separates selectors for hover and focus |
| 45 | * @param {CSSRuleList} rules - The CSS rules to process | 56 | * @param {CSSRuleList} rules - The CSS rules to process |
| 57 | * @param {WrapperCond[]} wrappers - Current chain of wrapper conditions (@media/@supports/etc.) | ||
| 46 | */ | 58 | */ |
| 47 | function processRules(rules) { | 59 | function processRules(rules, wrappers = []) { |
| 48 | Array.from(rules).forEach(rule => { | 60 | Array.from(rules).forEach(rule => { |
| 49 | if (rule instanceof CSSImportRule) { | 61 | if (rule instanceof CSSImportRule) { |
| 50 | // Make sure that @import rules are processed recursively | 62 | // Make sure that @import rules are processed recursively |
| 51 | processImportedStylesheet(rule.styleSheet); | 63 | // If the @import has media conditions, treat them as wrappers as well |
| 64 | /** @type {WrapperCond[]} */ | ||
| 65 | const extra = (rule.media && rule.media.mediaText) ? [{ type: 'media', conditionText: rule.media.mediaText }] : []; | ||
| 66 | processImportedStylesheet(rule.styleSheet, [...wrappers, ...extra]); | ||
| 52 | } else if (rule instanceof CSSStyleRule) { | 67 | } else if (rule instanceof CSSStyleRule) { |
| 53 | // Separate multiple selectors on a rule | 68 | // Separate multiple selectors on a rule |
| 54 | const selectors = rule.selectorText.split(',').map(s => s.trim()); | 69 | const selectors = rule.selectorText.split(',').map(s => s.trim()); |
| @@ -60,17 +75,25 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { | |||
| 60 | // We currently do nothing here. Rules containing both hover and focus are very specific and should never be automatically touched | 75 | // We currently do nothing here. Rules containing both hover and focus are very specific and should never be automatically touched |
| 61 | } | 76 | } |
| 62 | else if (isHover) { | 77 | else if (isHover) { |
| 63 | const baseSelector = selector.replace(':hover', PLACEHOLDER).trim(); | 78 | const baseSelector = selector.replace(/:hover/g, PLACEHOLDER).trim(); |
| 64 | hoverRules.push({ baseSelector, rule }); | 79 | hoverRules.push({ baseSelector, rule, wrappers: [...wrappers] }); |
| 65 | } else if (isFocus) { | 80 | } else if (isFocus) { |
| 66 | // We need to make sure that we remember all existing :focus, :focus-within and :focus-visible rules | 81 | // We need to make sure that we remember all existing :focus, :focus-within and :focus-visible rules |
| 67 | const baseSelector = selector.replace(':focus-within', PLACEHOLDER).replace(':focus-visible', PLACEHOLDER).replace(':focus', PLACEHOLDER).trim(); | 82 | const baseSelector = selector.replace(/:focus(-within|-visible)?/g, PLACEHOLDER).trim(); |
| 68 | focusRules.add(baseSelector); | 83 | focusRules.add(`${baseSelector}|${wrapperSignature(wrappers)}`); |
| 69 | } | 84 | } |
| 70 | }); | 85 | }); |
| 71 | } else if (rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule) { | 86 | } else if (rule instanceof CSSMediaRule) { |
| 72 | // Recursively process nested rules | 87 | // Recursively process nested @media rules |
| 73 | processRules(rule.cssRules); | 88 | processRules(rule.cssRules, [...wrappers, { type: 'media', conditionText: rule.conditionText }]); |
| 89 | } else if (rule instanceof CSSSupportsRule) { | ||
| 90 | // Recursively process nested @supports rules | ||
| 91 | processRules(rule.cssRules, [...wrappers, { type: 'supports', conditionText: rule.conditionText }]); | ||
| 92 | } else if (rule instanceof window.CSSContainerRule) { | ||
| 93 | // Recursively process nested @container rules (if supported by the browser) | ||
| 94 | // Note: conditionText contains the query like "(min-width: 300px)" or "style(color)" | ||
| 95 | // Using 'container' as the type ensures uniqueness separate from @media/@supports | ||
| 96 | processRules(rule.cssRules, [...wrappers, { type: 'container', conditionText: rule.conditionText }]); | ||
| 74 | } | 97 | } |
| 75 | }); | 98 | }); |
| 76 | } | 99 | } |
| @@ -78,21 +101,22 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { | |||
| 78 | /** | 101 | /** |
| 79 | * Processes the CSS rules of an imported stylesheet recursively | 102 | * Processes the CSS rules of an imported stylesheet recursively |
| 80 | * @param {CSSStyleSheet} sheet - The imported stylesheet to process | 103 | * @param {CSSStyleSheet} sheet - The imported stylesheet to process |
| 104 | * @param {WrapperCond[]} wrappers - Wrapper conditions inherited from (at)import media | ||
| 81 | */ | 105 | */ |
| 82 | function processImportedStylesheet(sheet) { | 106 | function processImportedStylesheet(sheet, wrappers = []) { |
| 83 | if (sheet && sheet.cssRules) { | 107 | if (sheet && sheet.cssRules) { |
| 84 | processRules(sheet.cssRules); | 108 | processRules(sheet.cssRules, wrappers); |
| 85 | } | 109 | } |
| 86 | } | 110 | } |
| 87 | 111 | ||
| 88 | processRules(styleSheet.cssRules); | 112 | processRules(styleSheet.cssRules, []); |
| 89 | 113 | ||
| 90 | /** @type {CSSStyleSheet} */ | 114 | /** @type {CSSStyleSheet} */ |
| 91 | let targetStyleSheet = null; | 115 | let targetStyleSheet = null; |
| 92 | 116 | ||
| 93 | // Now finally create the dynamic focus rules | 117 | // Now finally create the dynamic focus rules |
| 94 | hoverRules.forEach(({ baseSelector, rule }) => { | 118 | hoverRules.forEach(({ baseSelector, rule, wrappers }) => { |
| 95 | if (!focusRules.has(baseSelector)) { | 119 | if (!focusRules.has(`${baseSelector}|${wrapperSignature(wrappers)}`)) { |
| 96 | // Only initialize the dynamic stylesheet if needed | 120 | // Only initialize the dynamic stylesheet if needed |
| 97 | targetStyleSheet ??= getDynamicStyleSheet({ fromExtension }); | 121 | targetStyleSheet ??= getDynamicStyleSheet({ fromExtension }); |
| 98 | 122 | ||
| @@ -103,7 +127,19 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { | |||
| 103 | // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside, | 127 | // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside, |
| 104 | // it should be manually defined in CSS. | 128 | // it should be manually defined in CSS. |
| 105 | const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible'); | 129 | const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible'); |
| 106 | const focusRule = `${focusSelector} { ${rule.style.cssText} }`; | 130 | let focusRule = `${focusSelector} { ${rule.style.cssText} }`; |
| 131 | |||
| 132 | // Wrap the generated rule into the same @media/@supports/@container chain (if any) | ||
| 133 | if (wrappers.length > 0) { | ||
| 134 | // Build nested blocks from outermost to innermost | ||
| 135 | // Example: @media (x) { @supports (y) { <rule> } } | ||
| 136 | focusRule = wrappers.reduceRight((inner, w) => { | ||
| 137 | if (w.type === 'media') return `@media ${w.conditionText} { ${inner} }`; | ||
| 138 | if (w.type === 'supports') return `@supports ${w.conditionText} { ${inner} }`; | ||
| 139 | if (w.type === 'container') return `@container ${w.conditionText} { ${inner} }`; | ||
| 140 | return inner; | ||
| 141 | }, focusRule); | ||
| 142 | } | ||
| 107 | 143 | ||
| 108 | try { | 144 | try { |
| 109 | targetStyleSheet.insertRule(focusRule, targetStyleSheet.cssRules.length); | 145 | targetStyleSheet.insertRule(focusRule, targetStyleSheet.cssRules.length); |
| @@ -11,7 +11,7 @@ const interactableSelectors = [ | |||
| 11 | '.avatar-container', // Persona list blocks | 11 | '.avatar-container', // Persona list blocks |
| 12 | '.tag .tag_remove', // Remove button in removable tags | 12 | '.tag .tag_remove', // Remove button in removable tags |
| 13 | '.bg_example', // Background elements in the background menu | 13 | '.bg_example', // Background elements in the background menu |
| 14 | '.bg_example .bg_button', // The inline buttons on the backgrounds | 14 | '.bg_example .jg-button, .bg_example .mobile-only-menu-toggle', // The inline buttons on the backgrounds |
| 15 | '#options a', // Option entries in the popup options menu | 15 | '#options a', // Option entries in the popup options menu |
| 16 | '.mes_buttons .mes_button', // Small inline buttons on the chat messages | 16 | '.mes_buttons .mes_button', // Small inline buttons on the chat messages |
| 17 | '.extraMesButtons>div:not(.mes_button)', // The extra/extension buttons inline on the chat messages | 17 | '.extraMesButtons>div:not(.mes_button)', // The extra/extension buttons inline on the chat messages |
| @@ -3245,80 +3245,6 @@ input[type=search]:focus::-webkit-search-cancel-button { | |||
| 3245 | justify-content: space-evenly; | 3245 | justify-content: space-evenly; |
| 3246 | } | 3246 | } |
| 3247 | 3247 | ||
| 3248 | .bg_example.locked { | ||
| 3249 | outline: 2px solid var(--golden); | ||
| 3250 | } | ||
| 3251 | |||
| 3252 | .bg_example:hover.locked .bg_example_lock, | ||
| 3253 | .bg_example:focus-within.locked .bg_example_lock { | ||
| 3254 | display: none; | ||
| 3255 | } | ||
| 3256 | |||
| 3257 | .bg_example:hover:not(.locked) .bg_example_unlock, | ||
| 3258 | .bg_example:focus-within:not(.locked) .bg_example_unlock { | ||
| 3259 | display: none; | ||
| 3260 | } | ||
| 3261 | |||
| 3262 | .bg_example:hover[custom="true"] .bg_example_edit, | ||
| 3263 | .bg_example:focus-within[custom="true"] .bg_example_edit { | ||
| 3264 | display: none; | ||
| 3265 | } | ||
| 3266 | |||
| 3267 | .bg_example:hover[custom="false"] .bg_example_copy, | ||
| 3268 | .bg_example:focus-within[custom="false"] .bg_example_copy { | ||
| 3269 | display: none; | ||
| 3270 | } | ||
| 3271 | |||
| 3272 | .bg_button { | ||
| 3273 | padding: 4px; | ||
| 3274 | position: absolute; | ||
| 3275 | top: 5px; | ||
| 3276 | cursor: pointer; | ||
| 3277 | opacity: 0.8; | ||
| 3278 | border-radius: 3px; | ||
| 3279 | font-size: 20px; | ||
| 3280 | color: var(--black70a); | ||
| 3281 | text-shadow: none; | ||
| 3282 | margin: 0; | ||
| 3283 | filter: drop-shadow(0px 0px 3px white); | ||
| 3284 | transition: opacity var(--animation-duration-2x) ease-in-out; | ||
| 3285 | display: none; | ||
| 3286 | } | ||
| 3287 | |||
| 3288 | .bg_example:hover .bg_button, | ||
| 3289 | .bg_example:focus-within .bg_button { | ||
| 3290 | display: block; | ||
| 3291 | } | ||
| 3292 | |||
| 3293 | .bg_button:hover { | ||
| 3294 | opacity: 1; | ||
| 3295 | } | ||
| 3296 | |||
| 3297 | .bg_example_cross { | ||
| 3298 | right: 6px; | ||
| 3299 | } | ||
| 3300 | |||
| 3301 | .bg_example_edit { | ||
| 3302 | left: 6px; | ||
| 3303 | } | ||
| 3304 | |||
| 3305 | .bg_example_copy { | ||
| 3306 | left: 6px; | ||
| 3307 | } | ||
| 3308 | |||
| 3309 | .bg_example_lock, | ||
| 3310 | .bg_example_unlock { | ||
| 3311 | left: 50%; | ||
| 3312 | transform: translateX(-50%); | ||
| 3313 | } | ||
| 3314 | |||
| 3315 | .add_bg_but { | ||
| 3316 | cursor: pointer; | ||
| 3317 | opacity: 0.1; | ||
| 3318 | height: 100%; | ||
| 3319 | width: 100%; | ||
| 3320 | } | ||
| 3321 | |||
| 3322 | .input-file { | 3248 | .input-file { |
| 3323 | display: flex; | 3249 | display: flex; |
| 3324 | justify-content: center; | 3250 | justify-content: center; |