From de383710696b25d9b2b219c06f7479a0949e85f4 Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 11 Jun 2026 13:16:29 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20100%=20Guild=20Furni=20Catalog?= =?UTF-8?q?=20Page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../page/common/CatalogGridOfferView.tsx | 6 +-- .../CatalogLayoutGuildCustomFurniView.tsx | 31 +++++++---- .../CatalogGuildFurniRecolorFilter.tsx | 52 +++++++++++++++++++ src/css/catalog/CatalogClassicView.css | 20 ------- 4 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 src/components/catalog/views/page/widgets/CatalogGuildFurniRecolorFilter.tsx diff --git a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx index a949177..1927a1c 100644 --- a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx +++ b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx @@ -1,5 +1,5 @@ import { MouseEventType } from '@nitrots/nitro-renderer'; -import { CSSProperties, FC, MouseEvent, useMemo, useState } from 'react'; +import { FC, MouseEvent, useMemo, useState } from 'react'; import { FaHeart } from 'react-icons/fa'; import { CatalogType, GetConfigurationValue, IPurchasableOffer, Offer, ProductTypeEnum } from '../../../../../api'; import { LayoutAvatarImageView, LayoutGridItem, LayoutGridItemProps } from '../../../../../common'; @@ -114,10 +114,9 @@ export const CatalogGridOfferView: FC = props => return ( = props => className="nitro-catalog-classic-grid-offer-icon" src={ iconUrl } draggable={ false } + style={ tintColor ? { filter: 'url(#guild-furni-recolor)', transform: 'translateZ(0)' } : undefined } onError={ event => { const fallbackIconUrl = product.getIconUrl(offer); diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx index 6f381c7..f946726 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx @@ -1,10 +1,11 @@ import { StringDataType } from '@nitrots/nitro-renderer'; -import { FC, useMemo } from 'react'; +import { FC, useEffect, useMemo, useState } from 'react'; import { FaExchangeAlt, FaSyncAlt } from 'react-icons/fa'; import { Column } from '../../../../../common'; import { useCatalogData, useCatalogUiState, useUserGroups } from '../../../../../hooks'; import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView'; import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView'; +import { CatalogGuildFurniRecolorFilter } from '../widgets/CatalogGuildFurniRecolorFilter'; import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; @@ -18,28 +19,40 @@ export const CatalogLayouGuildCustomFurniView: FC = () => const { purchaseOptions = null } = useCatalogUiState(); const { data: groups = null } = useUserGroups(); const hasGroups = !!(groups && groups.length); + const [ groupColors, setGroupColors ] = useState<{ colorA: string; colorB: string } | null>(null); - const tintColor = useMemo(() => + useEffect(() => { const previewStuffData = purchaseOptions?.previewStuffData ?? null; - if(!previewStuffData) return null; + if(!previewStuffData) return; const colorA = (previewStuffData as StringDataType).getValue(3); const colorB = (previewStuffData as StringDataType).getValue(4); - if(!colorA || !colorA.length) return null; + if(!colorA || !colorA.length) return; - if(colorB && colorB.length && (colorB !== colorA)) - { - return `linear-gradient(90deg, #${ colorA } 0 50%, #${ colorB } 50% 100%)`; - } + const next = { colorA, colorB: (colorB && colorB.length) ? colorB : colorA }; + + setGroupColors(prev => (prev && (prev.colorA === next.colorA) && (prev.colorB === next.colorB)) ? prev : next); + }, [ purchaseOptions ]); + + + const tintColor = useMemo(() => + { + if(!groupColors) return null; + + const { colorA, colorB } = groupColors; + + if(colorB && (colorB !== colorA)) return `linear-gradient(90deg, #${ colorA } 0 50%, #${ colorB } 50% 100%)`; return `#${ colorA }`; - }, [ purchaseOptions ]); + }, [ groupColors ]); return ( <> + { !!groupColors && + } { !!currentOffer && diff --git a/src/components/catalog/views/page/widgets/CatalogGuildFurniRecolorFilter.tsx b/src/components/catalog/views/page/widgets/CatalogGuildFurniRecolorFilter.tsx new file mode 100644 index 0000000..3de982b --- /dev/null +++ b/src/components/catalog/views/page/widgets/CatalogGuildFurniRecolorFilter.tsx @@ -0,0 +1,52 @@ +import { memo, useMemo } from 'react'; + +interface CatalogGuildFurniRecolorFilterProps +{ + colorA?: string; + colorB?: string; +} + +export const GUILD_FURNI_RECOLOR_FILTER_ID = 'guild-furni-recolor'; +const OUTLINE_LEVEL = 0.08; + +const toUnit = (hex: string, offset: number): number => +{ + const value = parseInt(hex.substr(offset, 2), 16); + + return (isNaN(value) ? 0 : value) / 255; +}; + +export const CatalogGuildFurniRecolorFilter = memo((props: CatalogGuildFurniRecolorFilterProps) => +{ + const { colorA = null, colorB = null } = props; + + const tables = useMemo(() => + { + if(!colorA || (colorA.length < 6) || !colorB || (colorB.length < 6)) return null; + + const aR = toUnit(colorA, 0), aG = toUnit(colorA, 2), aB = toUnit(colorA, 4); + const bR = toUnit(colorB, 0), bG = toUnit(colorB, 2), bB = toUnit(colorB, 4); + + return { + r: `${ OUTLINE_LEVEL } ${ bR } ${ bR } ${ aR } ${ aR } ${ aR }`, + g: `${ OUTLINE_LEVEL } ${ bG } ${ bG } ${ aG } ${ aG } ${ aG }`, + b: `${ OUTLINE_LEVEL } ${ bB } ${ bB } ${ aB } ${ aB } ${ aB }` + }; + }, [ colorA, colorB ]); + + if(!tables) return null; + + return ( + + ); +}); + +CatalogGuildFurniRecolorFilter.displayName = 'CatalogGuildFurniRecolorFilter'; diff --git a/src/css/catalog/CatalogClassicView.css b/src/css/catalog/CatalogClassicView.css index b7c6293..57c4a81 100644 --- a/src/css/catalog/CatalogClassicView.css +++ b/src/css/catalog/CatalogClassicView.css @@ -866,10 +866,6 @@ border: 2px solid #62c4e8 !important; box-shadow: none !important; } - - .nitro-catalog-classic-window .layout-grid-item.has-guild-tint:not(.has-highlight):not(.is-active) { - background: var(--guild-tint) !important; - } } .nitro-catalog-classic-window .layout-grid-item.has-highlight { @@ -1474,9 +1470,6 @@ } .nitro-catalog-classic-window *::-webkit-scrollbar-thumb { - /* Grip: a single 2px #a0a0a0 stripe in an 8px-wide centered band, - repeated every 5px (2px stripe + 3px body gap). - Outline: 1px black border, then a 2px white inset frame inside it. */ min-height: 24px !important; background: url("data:image/svg+xml;utf8,") center top / 8px 5px repeat-y, @@ -1498,9 +1491,6 @@ #bcbcbc !important; } -/* Arrow buttons: light grey cap with rounded OUTER corners (up button - rounded at the top, down button rounded at the bottom), 1px black - border, dark chevron via inline SVG. */ .nitro-catalog-classic-window *::-webkit-scrollbar-button:single-button:vertical:decrement { display: block !important; width: 18px !important; @@ -1617,13 +1607,6 @@ display: none !important; } - /* Stack the navigation above the furni/preview layout and let the - whole content area scroll. The previous grid used - `grid-template-rows: auto minmax(0, 1fr)`, but on iOS Safari the - flex height chain is indefinite, so the 1fr layout-shell row - collapsed to 0 and only the sidebar (category list) was visible. - A flex column sized to content + a scrollable content-shell is - device-robust. */ .nitro-catalog-classic-stage, .nitro-catalog-classic-stage.is-navigation-hidden { display: flex; @@ -1639,9 +1622,6 @@ max-height: 30vh; } - /* The default layout's children (preview, grid, buy bar) are - absolutely positioned against a fixed ~460px box, so give the - shell a definite height and never clip it on mobile. */ .nitro-catalog-classic-layout-shell { flex: 0 0 auto; width: 100%;