From 197b153b17f72e3d6870dbea2d9880207ac6c431 Mon Sep 17 00:00:00 2001 From: bogdanmironov67 Date: Tue, 30 Jun 2026 22:23:45 +0300 Subject: [PATCH 1/3] refactor: render tooltips through portal --- pegasus/components/ui/Tooltip.js | 76 +++++++++++++++++++++++++++++--- pegasus/styles/Settings.css | 18 +++++++- 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/pegasus/components/ui/Tooltip.js b/pegasus/components/ui/Tooltip.js index 0791069..66b8962 100644 --- a/pegasus/components/ui/Tooltip.js +++ b/pegasus/components/ui/Tooltip.js @@ -1,11 +1,45 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; + +const TOOLTIP_GAP = 10; +const VIEWPORT_PADDING = 8; export default function Tooltip({ content, children, position = "top", delay = 500 }) { const [isVisible, setIsVisible] = useState(false); + const [tooltipStyle, setTooltipStyle] = useState(null); + const [isMounted, setIsMounted] = useState(false); + const triggerRef = useRef(null); + const bubbleRef = useRef(null); const timeoutRef = useRef(null); + const updateTooltipPosition = useCallback(() => { + const trigger = triggerRef.current; + const bubble = bubbleRef.current; + + if(!trigger || !bubble) { + return; + } + + const triggerRect = trigger.getBoundingClientRect(); + const bubbleRect = bubble.getBoundingClientRect(); + const normalizedPosition = ["top", "bottom"].includes(position) ? position : "top"; + const maxLeft = Math.max(VIEWPORT_PADDING, window.innerWidth - bubbleRect.width - VIEWPORT_PADDING); + const left = Math.min( + Math.max(triggerRect.left + triggerRect.width / 2 - bubbleRect.width / 2, VIEWPORT_PADDING), + maxLeft + ); + const top = normalizedPosition === "bottom" + ? triggerRect.bottom + TOOLTIP_GAP + : triggerRect.top - bubbleRect.height - TOOLTIP_GAP; + + setTooltipStyle({ + left: `${left}px`, + top: `${Math.max(VIEWPORT_PADDING, top)}px`, + }); + }, [position]); + const clearShowTimeout = () => { if(timeoutRef.current !== null) { clearTimeout(timeoutRef.current); @@ -24,21 +58,51 @@ export default function Tooltip({ content, children, position = "top", delay = 5 const hideTooltip = () => { clearShowTimeout(); setIsVisible(false); + setTooltipStyle(null); }; + useEffect(() => { + setIsMounted(true); + }, []); + useEffect(() => () => clearShowTimeout(), []); + useLayoutEffect(() => { + if(!isVisible) { + return; + } + + updateTooltipPosition(); + }, [isVisible, content, updateTooltipPosition]); + + useEffect(() => { + if(!isVisible) { + return; + } + + window.addEventListener("resize", updateTooltipPosition); + window.addEventListener("scroll", updateTooltipPosition, true); + + return () => { + window.removeEventListener("resize", updateTooltipPosition); + window.removeEventListener("scroll", updateTooltipPosition, true); + }; + }, [isVisible, updateTooltipPosition]); + if(!content) { return children; } return ( - + {children} - - - {content} - + + {isMounted && isVisible ? createPortal( + + {content} + , + document.body + ) : null} ); } \ No newline at end of file diff --git a/pegasus/styles/Settings.css b/pegasus/styles/Settings.css index 83c8d22..a2c9ca9 100644 --- a/pegasus/styles/Settings.css +++ b/pegasus/styles/Settings.css @@ -458,7 +458,7 @@ left: 50%; bottom: calc(100% + 10px); transform: translateX(-50%); - z-index: 20; + z-index: 40; box-shadow: var(--theme-shadow-popover); background: var(--theme-color-background-content); color: var(--theme-color-text-primary); @@ -489,6 +489,22 @@ opacity: 1; visibility: visible; } +.mf-tooltip__bubble--portal { + position: fixed; + left: auto; + bottom: auto; + transform: none; +} +.mf-tooltip__bubble--portal[data-position="bottom"]::after { + top: auto; + bottom: 100%; + border-top: 0; + border-bottom: 7px solid var(--theme-color-background-content); +} +.mf-tooltip__bubble--visible { + opacity: 1; + visibility: visible; +} .gallery-modal-preview { border-radius: 16px; overflow: hidden; From 87e4c49f0f77e19a189e54ba330865e35b5ca924 Mon Sep 17 00:00:00 2001 From: bogdanmironov67 Date: Tue, 30 Jun 2026 22:30:03 +0300 Subject: [PATCH 2/3] feat: link modjam achievement to submissions --- pegasus/components/ui/ProfileAchievements.js | 15 ++++++++++++--- pegasus/styles/Subsite.css | 6 ++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pegasus/components/ui/ProfileAchievements.js b/pegasus/components/ui/ProfileAchievements.js index 6f33136..b9a3d72 100644 --- a/pegasus/components/ui/ProfileAchievements.js +++ b/pegasus/components/ui/ProfileAchievements.js @@ -80,12 +80,21 @@ export default function ProfileAchievements({ achievements = [], isBanned = fals {visibleAchievements.map((achievement) => { const title = getAchievementTitle(achievement); const iconUrl = achievement.icon_url || "/badges/creator.webp"; + const achievementIcon = ( + {title} + ); return ( - - {title} - + {achievement.code === "hytalemodjam_2026" ? ( + + {achievementIcon} + + ) : ( + + {achievementIcon} + + )} ); })} diff --git a/pegasus/styles/Subsite.css b/pegasus/styles/Subsite.css index d693dcb..678e8b6 100644 --- a/pegasus/styles/Subsite.css +++ b/pegasus/styles/Subsite.css @@ -54,6 +54,12 @@ overflow: hidden; outline: none; } +.subsite-achievements__item--link { + cursor: pointer; +} +.subsite-achievements__item--link img:hover { + opacity: 0.9; +} .subsite-achievements__item img { display: block; width: 56px; From 0a238df7055917bed0eba9dc86ab84a1f1e40b8a Mon Sep 17 00:00:00 2001 From: bogdanmironov67 Date: Tue, 30 Jun 2026 22:35:55 +0300 Subject: [PATCH 3/3] fix: update modjam achievement translations --- pegasus/i18n/messages/en.json | 2 +- pegasus/i18n/messages/es.json | 2 +- pegasus/i18n/messages/pt.json | 2 +- pegasus/i18n/messages/ru.json | 2 +- pegasus/i18n/messages/tr.json | 2 +- pegasus/i18n/messages/uk.json | 2 +- pegasus/styles/Index.css | 10 +++++----- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pegasus/i18n/messages/en.json b/pegasus/i18n/messages/en.json index c3df1f5..bfc30de 100644 --- a/pegasus/i18n/messages/en.json +++ b/pegasus/i18n/messages/en.json @@ -1046,7 +1046,7 @@ "description": "For 10,000 downloads on one project." }, "hytalemodjam_2026": { - "title": "Hytale x HytaleModding ModJam 2026", + "title": "Hytale x HytaleModding ModJam", "description": "Participant of Hytale x HytaleModding ModJam 2026." } } diff --git a/pegasus/i18n/messages/es.json b/pegasus/i18n/messages/es.json index dffdfc9..0c07d0f 100644 --- a/pegasus/i18n/messages/es.json +++ b/pegasus/i18n/messages/es.json @@ -1046,7 +1046,7 @@ "description": "Por 10.000 descargas en un proyecto." }, "hytalemodjam_2026": { - "title": "Hytale x HytaleModding ModJam 2026", + "title": "Hytale x HytaleModding ModJam", "description": "Participante de Hytale x HytaleModding ModJam 2026." } } diff --git a/pegasus/i18n/messages/pt.json b/pegasus/i18n/messages/pt.json index 6daf6a7..a2aa077 100644 --- a/pegasus/i18n/messages/pt.json +++ b/pegasus/i18n/messages/pt.json @@ -1046,7 +1046,7 @@ "description": "Por 10.000 downloads em um projeto." }, "hytalemodjam_2026": { - "title": "Hytale x HytaleModding ModJam 2026", + "title": "Hytale x HytaleModding ModJam", "description": "Participante do Hytale x HytaleModding ModJam 2026." } } diff --git a/pegasus/i18n/messages/ru.json b/pegasus/i18n/messages/ru.json index f1a3e79..789ec40 100644 --- a/pegasus/i18n/messages/ru.json +++ b/pegasus/i18n/messages/ru.json @@ -1046,7 +1046,7 @@ "description": "За 10 000 скачиваний на одном проекте." }, "hytalemodjam_2026": { - "title": "Hytale x HytaleModding ModJam 2026", + "title": "Hytale x HytaleModding ModJam", "description": "Участник Hytale x HytaleModding ModJam 2026." } } diff --git a/pegasus/i18n/messages/tr.json b/pegasus/i18n/messages/tr.json index 1eb5231..6cdefb6 100644 --- a/pegasus/i18n/messages/tr.json +++ b/pegasus/i18n/messages/tr.json @@ -1046,7 +1046,7 @@ "description": "Tek bir projede 10.000 indirme için." }, "hytalemodjam_2026": { - "title": "Hytale x HytaleModding ModJam 2026", + "title": "Hytale x HytaleModding ModJam", "description": "Hytale x HytaleModding ModJam 2026 katılımcısı." } } diff --git a/pegasus/i18n/messages/uk.json b/pegasus/i18n/messages/uk.json index 196488d..0616f6c 100644 --- a/pegasus/i18n/messages/uk.json +++ b/pegasus/i18n/messages/uk.json @@ -1047,7 +1047,7 @@ "description": "За 10 000 завантажень одного проєкту." }, "hytalemodjam_2026": { - "title": "Hytale x HytaleModding ModJam 2026", + "title": "Hytale x HytaleModding ModJam", "description": "Учасник Hytale x HytaleModding ModJam 2026." } } diff --git a/pegasus/styles/Index.css b/pegasus/styles/Index.css index 1986a11..1a6106b 100644 --- a/pegasus/styles/Index.css +++ b/pegasus/styles/Index.css @@ -4524,11 +4524,11 @@ body.light div .image-container::before { background: #ffa347; border-radius: 999px; } -.badge--developer { - background: var(--theme-color-background); - font-size: 14px; - line-height: 22px; - padding: 2px 10px; +.badge--developer { + background: var(--theme-color-background); + font-size: 14px; + line-height: 22px; + padding: 2px 10px; border-radius: 999px; display: flex; align-items: center;