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 = (
+
+ );
return (
-
-
-
+ {achievement.code === "hytalemodjam_2026" ? (
+
+ {achievementIcon}
+
+ ) : (
+
+ {achievementIcon}
+
+ )}
);
})}
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/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;
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;
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;