From 7f66fd97352a3e225ea613ccbce50805c0bceae1 Mon Sep 17 00:00:00 2001 From: optrader Date: Wed, 8 Oct 2025 21:38:22 +0900 Subject: [PATCH] =?UTF-8?q?[251008]=20feat=20v2=20=ED=83=AD=20=EC=BB=A8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=84=88=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?UI=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2 탭 컨테이너를 도입하고, 아이템 크기와 간격을 조정해 UI를 개선했습니다. 🕐 커밋 시간: 2025. 10. 08. 21:38:17 📊 변경 통계: • 총 파일: 11개 • 추가: +56줄 • 삭제: -22줄 📁 추가된 파일: + com.twin.app.shoptime/src/components/TItemCard/TItemCard.v2.module.less + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.v2.module.less + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.v2.module.less ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 소규모 기능 개선 • 모듈 구조 개선 --- .../src/components/TItemCard/TItemCard.jsx | 5 +- .../TItemCard/TItemCard.v2.module.less | 140 ++++++++++++++++ .../src/views/PlayerPanel/PlayerPanel.jsx | 29 +++- .../TabContents/LiveChannelContents.jsx | 6 +- .../LiveChannelContents.v2.module.less | 2 +- .../TabContents/ShopNowContents.jsx | 19 ++- .../ShopNowContents.v2.module.less | 52 ++++++ .../TabContents/YouMayLikeContents.jsx | 17 +- .../YouMayLikeContents.v2.module.less | 37 +++++ .../PlayerTabContents/v2/TabContainer.v2.jsx | 156 ++++++++++++++++++ .../v2/TabContainer.v2.module.less | 140 ++++++++++++++++ 11 files changed, 581 insertions(+), 22 deletions(-) create mode 100644 com.twin.app.shoptime/src/components/TItemCard/TItemCard.v2.module.less create mode 100644 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less create mode 100644 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.v2.module.less create mode 100644 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx create mode 100644 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less diff --git a/com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx index 3be99ce1..09f6e37e 100644 --- a/com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx +++ b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx @@ -13,7 +13,8 @@ import IcLiveShow from "../../../assets/images/tag/tag-liveshow.png"; import usePriceInfo from "../../hooks/usePriceInfo"; import { $L, removeSpecificTags } from "../../utils/helperMethods"; import CustomImage from "../CustomImage/CustomImage"; -import css from "./TItemCard.module.less"; +import css1 from "./TItemCard.module.less"; +import css2 from "./TItemCard.v2.module.less"; import { sendLogTotalRecommend } from "../../actions/logActions"; const SpottableComponent = Spottable("div"); @@ -81,8 +82,10 @@ export default memo(function TItemCard({ nowCategory, nowProductTitle, contentId, + version = 1, ...rest }) { + const css = version === 2 ? css2 : css1; const dispatch = useDispatch(); const [defaultImage, setDefaultImage] = useState(null); const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd); diff --git a/com.twin.app.shoptime/src/components/TItemCard/TItemCard.v2.module.less b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.v2.module.less new file mode 100644 index 00000000..9d88e9bc --- /dev/null +++ b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.v2.module.less @@ -0,0 +1,140 @@ +@import "../../style/CommonStyle.module.less"; +@import "../../style/utils.module.less"; + +// horizontal type for v2 (ShopNowItem.module.less 스타일 적용, TItemCard.jsx 클래스명 구조 유지) +.horizontal { + width: 310px; + padding: 18px; + background: rgba(51, 51, 51, 0.95); + border-radius: 12px; + border: 1px solid transparent; + .flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column); + gap: 15px; + flex-shrink: 0; + transition: all 0.3s ease; + + .imageWrap { + align-self: stretch; + height: 274px; + padding: 5px 4px; + position: relative; + + > img { + width: 100%; + height: 100%; + object-fit: contain; + } + + // discount rate + > span { + .position(@position: absolute, @right: 12px, @bottom: 12px); + .size(@w: 60px, @h: 60px); + border-radius: 60px; + background-color: @PRIMARY_COLOR_RED; + font-size: 26px; + font-weight: 600; + text-align: center; + line-height: 60px; + color: @COLOR_WHITE; + } + + // sold out + .soldout { + .position(@position: absolute, @top: 0, @right: 0); + .flex(); + .size(@w: 100%, @h: 100%); + background-color: rgba(26, 26, 26, 0.6); + font-weight: bold; + font-size: 24px; + color: @COLOR_WHITE; + } + } + + .descWrap { + align-self: stretch; + .flex(@display: flex, @justifyCenter: center, @alignCenter: flex-start, @direction: column); + gap: 15px; + + .logo { + width: 60px; + height: 60px; + > img { + width: 60px; + min-height: 60px; + } + } + + .title { + align-self: stretch; + + .productNameTitle { + align-self: stretch; + color: #EAEAEA; + font-size: 24px; + font-family: @baseFont; + font-weight: 700; + line-height: 31px; + word-wrap: break-word; + .elip(@clamp: 2); + } + } + + .priceInfo { + align-self: stretch; + .flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center); + gap: 11px; + font-weight: 700; + font-size: 30px; + color: @PRIMARY_COLOR_RED; + font-family: @baseFont; + line-height: 35px; + word-wrap: break-word; + + > span { + color: #808080; + font-size: 24px; + font-family: @baseFont; + font-weight: 400; + text-decoration: line-through; + line-height: 18px; + word-wrap: break-word; + } + + > strong { + color: @PRIMARY_COLOR_RED; + font-size: 30px; + font-family: @baseFont; + font-weight: 700; + line-height: 35px; + } + } + + .offerInfo { + align-self: stretch; + color: @PRIMARY_COLOR_RED; + font-size: 30px; + font-family: @baseFont; + font-weight: 700; + line-height: 35px; + word-wrap: break-word; + } + } + + &:focus { + border-color: @PRIMARY_COLOR_RED; + outline: 4px @PRIMARY_COLOR_RED solid; + outline-offset: -4px; + + &::after { + .focused(@boxShadow: 22px, @borderRadius: 12px); + } + } +} + +// vertical type (v2에서는 사용하지 않음) +.vertical { +} + +// videoShow type (v2에서는 사용하지 않음) +.videoShow { +} diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index 4d451d1b..276f8927 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -55,9 +55,10 @@ import PlayerOverlayQRCode from './PlayerOverlay/PlayerOverlayQRCode'; import css from './PlayerPanel.module.less'; import PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton'; import TabContainer from './PlayerTabContents/TabContainer'; -import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer'; -import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer'; -import ShopNowButton from './PlayerTabContents/v2/ShopNowButton'; +import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2'; +// import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer'; +// import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer'; +// import ShopNowButton from './PlayerTabContents/v2/ShopNowButton'; const Container = SpotlightContainerDecorator( { enterTo: 'default-element', preserveld: true }, @@ -203,6 +204,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }); const [isVODPaused, setIsVODPaused] = USE_STATE('isVODPaused', false); const [belowTabMode, setBelowTabMode] = USE_STATE('belowTabMode', 'liveShow'); + const [tabIndexV2, setTabIndexV2] = USE_STATE('tabIndexV2', 1); // 0: ShopNow, 1: LiveChannel, 2: ShopNowButton const panels = USE_SELECTOR('panels', (state) => state.panels.panels); const chatData = USE_SELECTOR('chatData', (state) => state.play.chatData); @@ -2076,7 +2078,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props /> )} - {shouldShowBelowTab && ( + {/* {shouldShowBelowTab && ( <> {belowTabMode === 'liveShow' && ( )} + )} */} + + {shouldShowBelowTab && ( + setTabIndexV2(0)} + onLiveChannelButtonClick={() => setTabIndexV2(2)} + /> )} diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx index 40f3195f..1bc418c0 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx @@ -142,9 +142,9 @@ export default function LiveChannelContents({ dataSize={liveInfos.length} direction={direction} renderItem={renderItem} - itemWidth={version === 2 ? 280 : videoVerticalVisible ? 540 : 600} - itemHeight={version === 2 ? 78 : 236} - spacing={12} + itemWidth={version === 2 ? 470 : videoVerticalVisible ? 540 : 600} + itemHeight={version === 2 ? 155 : 236} + spacing={version === 2 ? 30 : 12} noScrollByWheel={false} /> ) : ( diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.v2.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.v2.module.less index 7946b9d5..b59d08f1 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.v2.module.less +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.v2.module.less @@ -3,7 +3,7 @@ .container { width: 100%; - height: 175px; + height: 155px; > div:nth-child(1) { .size(@w: 100%, @h: 100%); diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx index 885aa3c0..a2de1f68 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx @@ -22,7 +22,8 @@ import useScrollTo from "../../../../hooks/useScrollTo"; import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from "../../../../utils/Config"; import { scaleH } from "../../../../utils/helperMethods"; import ListEmptyContents from "../TabContents/ListEmptyContents/ListEmptyContents"; -import css from "./ShopNowContents.module.less"; +import css1 from "./ShopNowContents.module.less"; +import cssV2 from "./ShopNowContents.v2.module.less"; import { sendLogTotalRecommend } from "../../../../actions/logActions"; @@ -49,8 +50,11 @@ export default function ShopNowContents({ tabIndex, handleItemFocus, panelInfo, - tabTitle + tabTitle, + version = 1, + direction = "vertical" }) { + const css = version === 2 ? cssV2 : css1; const { getScrollTo, scrollTop } = useScrollTo(); const dispatch = useDispatch(); const paenls = useSelector((state) => state.panels.panels[1]?.panelInfo); @@ -175,10 +179,11 @@ export default function ShopNowContents({ onClick={handleItemClick} onFocus={handleFocus()} type={TYPES.horizontal} + version={version} /> ); }, - [shopNowInfo, videoVerticalVisible, panelInfo?.shptmBanrTpNm, priceInfoMap, tabTitle, tabIndex, playListInfo, dispatch] + [shopNowInfo, videoVerticalVisible, panelInfo?.shptmBanrTpNm, priceInfoMap, tabTitle, tabIndex, playListInfo, dispatch, version] ); return ( @@ -189,11 +194,11 @@ export default function ShopNowContents({ style={gridStyle} cbScrollTo={getScrollTo} dataSize={shopNowInfo.length} - direction="vertical" + direction={direction} renderItem={renderItem} - itemWidth={videoVerticalVisible ? 540 : 600} - itemHeight={236} - spacing={12} + itemWidth={version === 2 ? 310 : (videoVerticalVisible ? 540 : 600)} + itemHeight={version === 2 ? 420 : 236} + spacing={version === 2 ? 30 : 12} className={ videoVerticalVisible ? css.verticalItemList : css.itemList } diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less new file mode 100644 index 00000000..2e44e0fa --- /dev/null +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less @@ -0,0 +1,52 @@ +@import "../../../../style/CommonStyle.module.less"; +@import "../../../../style/utils.module.less"; + +.container { + width: 100%; + height: 420px; + overflow: hidden; + + > div:nth-child(1) { + .size(@w: 100%, @h: 100%); + } +} + +.itemList { + width: 100%; + overflow: hidden; + + > div { + > div { + > div { + > div { + > div { + .size(@w: 310px, @h: 420px); + > div:nth-child(1) { + flex: 1 0 auto; + } + } + } + } + } + } +} + +.verticalItemList { + width: 100%; + overflow: hidden; + + > div { + > div { + > div { + > div { + > div { + .size(@w: 310px, @h: 420px); + > div:nth-child(1) { + flex: 1 0 auto; + } + } + } + } + } + } +} diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx index a3d9c008..dd3d5b6c 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx @@ -7,13 +7,17 @@ import TItemCard, { TYPES } from "../../../../components/TItemCard/TItemCard"; import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList"; import { LOG_MENU, panel_names } from "../../../../utils/Config"; import { $L, scaleH } from "../../../../utils/helperMethods"; -import css from "./YouMayLikeContents.module.less"; +import css1 from "./YouMayLikeContents.module.less"; +import cssV2 from "./YouMayLikeContents.v2.module.less"; export default function YouMayLikeContents({ shopNowInfo, handleItemFocus, playListInfo, + version = 1, + direction = "vertical" }) { + const css = version === 2 ? cssV2 : css1; const dispatch = useDispatch(); const [height, setHeight] = useState(scaleH(236)); const youmaylikeInfos = useSelector((state) => state.main.youmaylikeInfos); @@ -72,10 +76,11 @@ export default function YouMayLikeContents({ onClick={handleItemClick} onFocus={handleFocus()} type={TYPES.horizontal} + version={version} /> ); }, - [youmaylikeInfos] + [youmaylikeInfos, version, handleFocus, dispatch, playListInfo] ); return ( @@ -88,11 +93,11 @@ export default function YouMayLikeContents({ diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.v2.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.v2.module.less new file mode 100644 index 00000000..ff5cc17b --- /dev/null +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.v2.module.less @@ -0,0 +1,37 @@ +@import "../../../../style/CommonStyle.module.less"; +@import "../../../../style/utils.module.less"; + +.title { + color: #EAEAEA; + font-size: 30px; + font-weight: 700; + margin-top: 40px; + margin-bottom: 20px; +} + +.line { + .size(@w: 100%, @h: 1px); + opacity: 0.3; + background-color: #d9d9d9; + margin-bottom: 24px; +} + +.itemList { + width: 100%; + overflow: hidden; + + > div { + > div { + > div { + > div { + > div { + .size(@w: 310px, @h: 420px); + > div:nth-child(1) { + flex: 1 0 auto; + } + } + } + } + } + } +} diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx new file mode 100644 index 00000000..c2889548 --- /dev/null +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx @@ -0,0 +1,156 @@ +import React, { useCallback, useEffect } from "react"; + +import classNames from "classnames"; + +import Spotlight from "@enact/spotlight"; +import Spottable from "@enact/spotlight/Spottable"; +import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; + +import { LOG_MENU } from "../../../../utils/Config"; +import css from "./TabContainer.v2.module.less"; +import LiveChannelContents from "../TabContents/LiveChannelContents"; +import ShopNowContents from "../TabContents/ShopNowContents"; +import YouMayLikeContents from "../TabContents/YouMayLikeContents"; +import ShopNowButton from "./ShopNowButton"; +import icon_arrow_dwon from "../../../../../assets/images/player/icon_tabcontainer_arrow_down.png"; +import icon_shop_now from "../../../../../assets/images/player/icon_tabcontainer_shopnow.png"; + +const Container = SpotlightContainerDecorator( + { enterTo: "last-focused" }, + "div" +); + +const SpottableDiv = Spottable("div"); + +export default function TabContainerV2({ + panelInfo, + playListInfo, + shopNowInfo, + selectedIndex, + setSelectedIndex, + liveChannelInfos, + videoVerticalVisible, + handleItemFocus, + prevChannelIndex, + currentTime, + spotlightId, + tabIndex = 1, // tabIndex prop으로 제어 (0: ShopNow, 1: LiveChannel, 2: ShopNowButton) + onShopNowButtonClick, + onLiveChannelButtonClick, +}) { + useEffect(() => { + let nowMenu; + + if (tabIndex === 0) { + nowMenu = LOG_MENU.FULL_SHOP_NOW; + } + + if (tabIndex === 1) { + const isLive = panelInfo?.shptmBanrTpNm === "LIVE"; + nowMenu = isLive + ? LOG_MENU.FULL_LIVE_CHANNELS + : LOG_MENU.FULL_FEATURED_SHOWS; + } + + if (nowMenu) { + handleItemFocus(nowMenu); + } + }, [handleItemFocus, panelInfo?.shptmBanrTpNm, tabIndex]); + + const _handleItemFocus = useCallback( + (nowMenu) => { + if (handleItemFocus) { + handleItemFocus(nowMenu); + } + }, + [handleItemFocus] + ); + + const onSpotlightIndicatorUpButton = useCallback( + (e) => { + if (videoVerticalVisible) { + e.stopPropagation(); + e.preventDefault(); + Spotlight.focus("spotlightId-video-contaienr"); + } + }, + [videoVerticalVisible] + ); + + return ( + + {tabIndex === 0 && ( + <> +
+
+ shop now icon +
+
SHOP NOW
+
+ + + )} + + {tabIndex === 1 && ( + <> + + LIVE CHANNEL +
+ arrow down +
+
+ + {panelInfo?.shptmBanrTpNm === "LIVE" && playListInfo && ( + + )} + + )} + + {tabIndex === 2 && ( + + )} + + {shopNowInfo && shopNowInfo.length < 3 && tabIndex === 0 && ( + + )} +
+ ); +} diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less new file mode 100644 index 00000000..370f0430 --- /dev/null +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less @@ -0,0 +1,140 @@ +@import "../../../../style/CommonStyle.module.less"; +@import "../../../../style/utils.module.less"; + +.tabContainer { + .position(@position: fixed, @bottom: 0, @left: 0); + .flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column); + overflow: hidden; + z-index: 5; + + // tabIndex = 0: ShopNow 스타일 (ShopNowContainer 참고) + &.tabIndex0 { + .size(@w: 100%, @h: 675px); + padding: 60px; + background: linear-gradient(270deg, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0.62) 30%, rgba(0, 0, 0, 0) 65%), + linear-gradient(0deg, rgba(0, 0, 0, 0.53) 0%, rgba(20.56, 4.68, 32.71, 0.53) 60%, rgba(199, 32, 84, 0) 98%), + linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, black 45%, black 100%), + rgba(0, 0, 0, 0.56); + border-top: 1px solid rgba(234, 234, 234, 0.8); + gap: 40px; + } + + // tabIndex = 1: LiveShow 스타일 (LiveShowContainer 참고) + &.tabIndex1 { + .size(@w: 100%, @h: 365px); + padding: 60px; + background: linear-gradient( + to top, + rgba(0, 0, 0, 0.95) 0%, + rgba(0, 0, 0, 0.85) 40%, + rgba(0, 0, 0, 0.6) 70%, + rgba(0, 0, 0, 0) 100% + ); + border-top: 1px solid rgba(234, 234, 234, 0.3); + gap: 20px; + + &::before { + content: ""; + .position(@position: absolute, @top: 0, @left: 0); + .size(@w: 100%, @h: 100%); + background: linear-gradient( + 90deg, + rgba(30, 30, 30, 0.4) 0%, + rgba(50, 50, 50, 0.25) 30%, + transparent 50%, + rgba(50, 50, 50, 0.25) 70%, + rgba(30, 30, 30, 0.4) 100% + ); + pointer-events: none; + z-index: -1; + } + } + + // tabIndex = 2: ShopNowButton만 (특별한 컨테이너 스타일 없음) + &.tabIndex2 { + background: transparent; + border: none; + } + + &.vertical { + // vertical 모드일 때 추가 스타일 (필요시) + } +} + +// SHOP NOW 헤더 스타일 (ShopNowContainer 참고) +.shopNowHeader { + width: 300px; + height: 70px; + padding: 20px 0; + overflow: hidden; + border-radius: 100px; + .flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center); + gap: 15px; +} + +.shopNowIconWrapper { + .size(@w: 40px, @h: 40px); + .position(@position: relative); + background: white; + border-radius: 100px; + .flex(@display: flex, @justifyCenter: center, @alignCenter: center); +} + +.shopNowIcon { + .size(@w: 20.67px, @h: 20.67px); + object-fit: contain; +} + +.shopNowHeaderText { + color: #EAEAEA; + font-size: 24px; + font-family: @baseFont; + font-weight: 700; + line-height: 31px; + word-wrap: break-word; +} + +// LIVE CHANNEL 버튼 스타일 (LiveShowContainer 참고) +.liveChannelButton { + .size(@w: 300px, @h: 70px); + padding: 20px 30px; + border-radius: 100px; + border: 1px solid rgba(234, 234, 234, 0.5); + overflow: hidden; + .flex(@display: flex, @justifyCenter: space-between, @alignCenter: center); + background: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(5px); + cursor: pointer; + transition: all 0.3s ease; + + .buttonText { + color: @COLOR_WHITE; + font-size: 24px; + font-family: @baseFont; + font-weight: 600; + line-height: 35px; + } + + .arrowIcon { + .size(@w: 26.25px, @h: 15.63px); + img { + width: 100%; + height: 100%; + object-fit: contain; + } + } + + &:hover { + background: rgba(0, 0, 0, 0.5); + border-color: rgba(234, 234, 234, 0.7); + } + + &:focus { + background: rgba(199, 8, 80, 0.2); + border-color: @PRIMARY_COLOR_RED; + + &::after { + .focused(@boxShadow: 22px, @borderRadius: 100px); + } + } +}