From 26a9aa90c10dae93e262376a5992d2ad876d8ffb Mon Sep 17 00:00:00 2001 From: FlaminSarge Date: Sun, 28 Dec 2025 02:21:49 -0700 Subject: [PATCH 1/4] Fix: setCSSVariable now only ignores nullish values instead of all falsy values --- src/app/css-variables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/css-variables.ts b/src/app/css-variables.ts index aa7c0ea5bd..035204bde2 100644 --- a/src/app/css-variables.ts +++ b/src/app/css-variables.ts @@ -11,7 +11,7 @@ import { StoreObserver } from './store/observerMiddleware'; const KEYBOARD_THRESHOLD = 50; function setCSSVariable(property: string, value: { toString: () => string }) { - if (value) { + if (value !== undefined && value !== null) { document.querySelector('html')!.style.setProperty(property, value.toString()); } } From 110ba400fac09362edb52ea2c77f0808802b5285 Mon Sep 17 00:00:00 2001 From: FlaminSarge Date: Sun, 28 Dec 2025 02:24:23 -0700 Subject: [PATCH 2/4] Add: shiny animated background now has fade-in effect to reduce jarring pop-in --- src/app/inventory/ItemIcon.m.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/inventory/ItemIcon.m.scss b/src/app/inventory/ItemIcon.m.scss index c83929abb0..63160696b8 100644 --- a/src/app/inventory/ItemIcon.m.scss +++ b/src/app/inventory/ItemIcon.m.scss @@ -71,11 +71,15 @@ $commonBg: #366f42; } .animatedBackground { - display: none; + display: block; + opacity: 0; + content-visibility: hidden; @media (prefers-reduced-motion: no-preference) { + transition: opacity 0.2s ease-out; :global(.item):hover & { - display: block; + opacity: 1; + content-visibility: auto; } } } From 077a523a369c3e4ae191b20ef025b1993905e80d Mon Sep 17 00:00:00 2001 From: FlaminSarge Date: Sun, 28 Dec 2025 02:28:44 -0700 Subject: [PATCH 3/4] Add: setting to toggle display of ornaments, behavior for viewing ornaments when hovering over items Includes: icon fades into alternate icon with 0.2s transition icon fade is instant for reduced-motion preference masterworkGlow now shows up on top of foreground/altIcon content-visibility is used to hide the inactive icon example ornamented item is previewed on settings page --- config/i18n.json | 3 +++ src/Index.tsx | 2 ++ src/app/css-variables.ts | 19 ++++++++++++++++ src/app/inventory/ItemIcon.m.scss | 30 +++++++++++++++++++++++++ src/app/inventory/ItemIcon.m.scss.d.ts | 2 ++ src/app/inventory/ItemIcon.tsx | 16 ++++++++++--- src/app/main.scss | 5 +++++ src/app/settings/SettingsPage.tsx | 31 ++++++++++++++++++++++++-- src/locale/en.json | 3 +++ 9 files changed, 106 insertions(+), 5 deletions(-) diff --git a/config/i18n.json b/config/i18n.json index dccb5b9de4..0f10b9cb71 100644 --- a/config/i18n.json +++ b/config/i18n.json @@ -1258,6 +1258,9 @@ "Masterworked": "Masterworked", "MaxParallelCores": "Maximum cores for parallel tasks", "MaxParallelCoresExplanation": "Controls how many CPU cores DIM can use for intensive tasks like Loadout Optimizer and Loadout Analyzer. Higher values may improve performance but use more system resources.", + "OrnamentDisplay": "Show Ornaments on item tiles", + "OrnamentDisplayExplanationHide": "Hovering or long-pressing an item will hide its ornament", + "OrnamentDisplayExplanationShow": "Hovering or long-pressing an item will show its ornament", "ResetToDefault": "Reset", "ReverseSort": "Toggle forward/reverse sort", "SetSort": "Sort items by:", diff --git a/src/Index.tsx b/src/Index.tsx index cfd8019738..bc5474bf82 100644 --- a/src/Index.tsx +++ b/src/Index.tsx @@ -7,6 +7,7 @@ import './app/utils/sentry'; import { createSaveAccountsObserver } from 'app/accounts/observers'; import { createItemSizeObserver, + createOrnamentDisplayObserver, createThemeObserver, createTilesPerCharColumnObserver, setCssVariableEventListeners, @@ -64,6 +65,7 @@ const i18nPromise = initi18n(); } store.dispatch(observe(createSaveAccountsObserver())); store.dispatch(observe(createItemSizeObserver())); + store.dispatch(observe(createOrnamentDisplayObserver())); store.dispatch(observe(createThemeObserver())); store.dispatch(observe(createTilesPerCharColumnObserver())); setCssVariableEventListeners(); diff --git a/src/app/css-variables.ts b/src/app/css-variables.ts index 035204bde2..ad24355611 100644 --- a/src/app/css-variables.ts +++ b/src/app/css-variables.ts @@ -1,3 +1,4 @@ +import { OrnamentDisplay } from '@destinyitemmanager/dim-api-types'; import { settingsSelector } from 'app/dim-api/selectors'; import { deepEqual } from 'fast-equals'; import { isPhonePortraitSelector } from './shell/selectors'; @@ -26,6 +27,24 @@ export function createItemSizeObserver(): StoreObserver { }; } +export function createOrnamentDisplayObserver(): StoreObserver { + return { + id: 'ornament-display-observer', + getObserved: (rs) => settingsSelector(rs).ornamentDisplay, + sideEffect: ({ current }) => { + setCSSVariable('--ornament-display-opacity', current === OrnamentDisplay.All ? 1 : 0); + setCSSVariable( + '--ornament-display-visibility', + current === OrnamentDisplay.All ? 'auto' : 'hidden', + ); + setCSSVariable( + '--ornament-display-visibility-inverse', + current === OrnamentDisplay.All ? 'hidden' : 'auto', + ); + }, + }; +} + export function createThemeObserver(): StoreObserver<{ theme: string; isPhonePortrait: boolean }> { return { id: 'theme-observer', diff --git a/src/app/inventory/ItemIcon.m.scss b/src/app/inventory/ItemIcon.m.scss index 63160696b8..6b88cf88cf 100644 --- a/src/app/inventory/ItemIcon.m.scss +++ b/src/app/inventory/ItemIcon.m.scss @@ -84,6 +84,36 @@ $commonBg: #366f42; } } +.hasAltIcon { + display: block; + opacity: calc(1 - var(--ornament-display-opacity, 1)); + content-visibility: var(--ornament-display-visibility-inverse, hidden); + + @media (prefers-reduced-motion: no-preference) { + transition: opacity 0.2s ease-out; + } + + :global(.item):hover & { + opacity: var(--ornament-display-opacity, 1); + content-visibility: var(--ornament-display-visibility, auto); + } +} + +.altIcon { + display: block; + opacity: var(--ornament-display-opacity, 1); + content-visibility: var(--ornament-display-visibility, auto); + + @media (prefers-reduced-motion: no-preference) { + transition: opacity 0.2s ease-out; + } + + :global(.item):hover & { + opacity: calc(1 - var(--ornament-display-opacity, 1)); + content-visibility: var(--ornament-display-visibility-inverse, hidden); + } +} + // This is the same size as the item image but shifted up and left by 1px to overlap the border .shiftedLayer { top: -1 * $item-border-width; diff --git a/src/app/inventory/ItemIcon.m.scss.d.ts b/src/app/inventory/ItemIcon.m.scss.d.ts index 2167055463..895a20041a 100644 --- a/src/app/inventory/ItemIcon.m.scss.d.ts +++ b/src/app/inventory/ItemIcon.m.scss.d.ts @@ -2,6 +2,7 @@ // Please do not change this file! interface CssExports { 'adjustOpacity': string; + 'altIcon': string; 'animatedBackground': string; 'basic': string; 'borderless': string; @@ -11,6 +12,7 @@ interface CssExports { 'deepsight': string; 'energyCost': string; 'exotic': string; + 'hasAltIcon': string; 'highlightedObjective': string; 'inverted': string; 'legendary': string; diff --git a/src/app/inventory/ItemIcon.tsx b/src/app/inventory/ItemIcon.tsx index e53f98961e..bdc1871de3 100644 --- a/src/app/inventory/ItemIcon.tsx +++ b/src/app/inventory/ItemIcon.tsx @@ -113,9 +113,13 @@ export default function ItemIcon({ item, className }: { item: DimItem; className : undefined; // The actual item icon. Use the ornamented version where available. - let foreground = (item.ornamentIconDef ?? item.iconDef)?.foreground ?? item.icon; + let foreground = (item.iconDef?.foreground ?? item.icon) || ''; + let altIcon = ''; + if (item.ornamentIconDef) { + altIcon = item.ornamentIconDef.foreground; + } - if (!animatedBackground) { + if (!animatedBackground && !altIcon) { backgrounds.unshift(foreground); foreground = ''; } @@ -173,10 +177,16 @@ export default function ItemIcon({ item, className }: { item: DimItem; className {animatedBackground && ( )} + {foreground && ( +
+ )} + {altIcon &&
} {masterworkGlow && (
)} - {foreground &&
} {seasonBanner && (
)} diff --git a/src/app/main.scss b/src/app/main.scss index 9a6c091c0e..ef694940f0 100644 --- a/src/app/main.scss +++ b/src/app/main.scss @@ -82,6 +82,11 @@ // it was 16px. --scrollbar-size: 17px; + // Whether to display ornament or base icons + --ornament-display-opacity: 1; + --ornament-display-visibility: auto; + --ornament-display-visibility-inverse: hidden; + // prevents content shift with persistent scroll-y bar overflow-y: scroll; } diff --git a/src/app/settings/SettingsPage.tsx b/src/app/settings/SettingsPage.tsx index c882324f85..ae99843501 100644 --- a/src/app/settings/SettingsPage.tsx +++ b/src/app/settings/SettingsPage.tsx @@ -1,4 +1,4 @@ -import { VaultWeaponGroupingStyle } from '@destinyitemmanager/dim-api-types'; +import { OrnamentDisplay, VaultWeaponGroupingStyle } from '@destinyitemmanager/dim-api-types'; import { currentAccountSelector, hasD1AccountSelector } from 'app/accounts/selectors'; import { clarityDiscordLink, clarityLink } from 'app/clarity/about'; import { settingsSelector } from 'app/dim-api/selectors'; @@ -81,8 +81,12 @@ export default function SettingsPage() { (i) => i.bucket.inWeapons && !i.isExotic && i.masterwork && !i.deepsightInfo, ); const exampleArmor = allItems.find((i) => i.bucket.inArmor && !i.isExotic); + const exampleOrnament = + allItems.find( + (i) => i !== exampleArmor && i.bucket.inArmor && i.isExotic && i.ornamentIconDef, + ) || allItems.find((i) => i !== exampleArmor && i.ornamentIconDef); const exampleArchivedArmor = allItems.find( - (i) => i !== exampleArmor && i.bucket.inArmor && !i.isExotic, + (i) => i !== exampleArmor && i !== exampleOrnament && i.bucket.inArmor && !i.isExotic, ); const godRoll = { wishListPerks: new Set(), @@ -472,6 +476,13 @@ export default function SettingsPage() { autoLockTagged={settings.autoLockTagged} /> )} + {exampleOrnament && ( + + )} {exampleArchivedArmor && ( )} +
+ + setSetting(name, checked ? OrnamentDisplay.All : OrnamentDisplay.None) + } + /> +
+ {settings.ornamentDisplay === OrnamentDisplay.All + ? t('Settings.OrnamentDisplayExplanationHide') + : t('Settings.OrnamentDisplayExplanationShow')} +
+
+ {$featureFlags.newItems && (
Date: Sun, 28 Dec 2025 02:56:22 -0700 Subject: [PATCH 4/4] Fix: animatedBackground and altIcon content-visibility now uses a 0.2s transition to match opacity --- src/app/inventory/ItemIcon.m.scss | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/inventory/ItemIcon.m.scss b/src/app/inventory/ItemIcon.m.scss index 6b88cf88cf..529b1e8352 100644 --- a/src/app/inventory/ItemIcon.m.scss +++ b/src/app/inventory/ItemIcon.m.scss @@ -76,7 +76,9 @@ $commonBg: #366f42; content-visibility: hidden; @media (prefers-reduced-motion: no-preference) { - transition: opacity 0.2s ease-out; + transition: + opacity 0.2s ease-out, + content-visibility 0.2s allow-discrete; :global(.item):hover & { opacity: 1; content-visibility: auto; @@ -90,7 +92,9 @@ $commonBg: #366f42; content-visibility: var(--ornament-display-visibility-inverse, hidden); @media (prefers-reduced-motion: no-preference) { - transition: opacity 0.2s ease-out; + transition: + opacity 0.2s ease-out, + content-visibility 0.2s allow-discrete; } :global(.item):hover & { @@ -105,7 +109,9 @@ $commonBg: #366f42; content-visibility: var(--ornament-display-visibility, auto); @media (prefers-reduced-motion: no-preference) { - transition: opacity 0.2s ease-out; + transition: + opacity 0.2s ease-out, + content-visibility 0.2s allow-discrete; } :global(.item):hover & {