diff --git a/src/app/(frontend)/awards/page.tsx b/src/app/(frontend)/awards/page.tsx index c45492c..ad8bb16 100644 --- a/src/app/(frontend)/awards/page.tsx +++ b/src/app/(frontend)/awards/page.tsx @@ -73,8 +73,17 @@ export default async function AwardsPage() { {section.items.map((award) => { const awardImage = typeof award.image === "object" && award.image ? (award.image as Media) : null; + const backgroundImage = + typeof award.backgroundImage === "object" && award.backgroundImage + ? (award.backgroundImage as Media) + : null; return ( - + ); })} diff --git a/src/app/(frontend)/profile/[username]/page.tsx b/src/app/(frontend)/profile/[username]/page.tsx index a22bff2..5d16ba0 100644 --- a/src/app/(frontend)/profile/[username]/page.tsx +++ b/src/app/(frontend)/profile/[username]/page.tsx @@ -623,7 +623,18 @@ export default async function ProfilePage({ params }: ProfilePageProps) { .map((award) => { const awardImage = award.image && typeof award.image === "object" ? (award.image as Media) : null; - return ; + const backgroundImage = + award.backgroundImage && typeof award.backgroundImage === "object" + ? (award.backgroundImage as Media) + : null; + return ( + + ); })} ) : ( diff --git a/src/app/(frontend)/styles.css b/src/app/(frontend)/styles.css index ef60743..5afc001 100644 --- a/src/app/(frontend)/styles.css +++ b/src/app/(frontend)/styles.css @@ -406,38 +406,69 @@ .medal-card::after { content: ""; + z-index: 2; background: radial-gradient(circle, transparent, rgb(253 224 71 / 0.1)); position: absolute; inset: 0; animation: pulse 5s cubic-bezier(0.4, 0, 0.6, 1) infinite; + pointer-events: none; +} + +.medal-card > .medal-card-bg, +.medal-card > .medal-card-scrim { + position: absolute !important; + inset: 0; + z-index: 0 !important; +} + +.medal-card > .medal-card-bg { + width: 100%; + height: 100%; + border-radius: inherit; +} + +.medal-card > .medal-card-scrim { + border-radius: inherit; + background-color: rgb(0 0 0 / 0.42); + box-shadow: inset 0 0 2.25rem rgb(0 0 0 / 0.38); +} + +.medal-card.medal-card-overflow { + overflow: visible; } .award-xp-high { - background: linear-gradient(110deg, rgb(245 158 11), rgb(254 249 195), rgb(250 204 21), rgb(245 158 11)); - background-size: 200% auto; - background-clip: text; - -webkit-background-clip: text; - color: transparent; - filter: drop-shadow(0 0 0.3rem rgb(250 204 21 / 0.35)); - animation: award-xp-shimmer 2.5s linear infinite; + background: linear-gradient( + 110deg, + rgb(245 158 11), + rgb(254 249 195), + rgb(250 204 21), + rgb(245 158 11) + ); + background-size: 200% auto; + background-clip: text; + -webkit-background-clip: text; + color: transparent; + filter: drop-shadow(0 0 0.3rem rgb(250 204 21 / 0.35)); + animation: award-xp-shimmer 2.5s linear infinite; } @keyframes award-xp-shimmer { - to { - background-position: 200% center; - } + to { + background-position: 200% center; + } } @media (prefers-reduced-motion: reduce) { - .award-xp-high { - animation: none; - } + .award-xp-high { + animation: none; + } } .commendation-card { - align-self: start; - gap: 0.5rem; - padding: 0.625rem 0.75rem; + align-self: start; + gap: 0.5rem; + padding: 0.625rem 0.75rem; } /* Scrollbars — styled to match the dark frontend theme */ diff --git a/src/components/frontend/account/AwardCard.tsx b/src/components/frontend/account/AwardCard.tsx index e2cf406..0eee1df 100644 --- a/src/components/frontend/account/AwardCard.tsx +++ b/src/components/frontend/account/AwardCard.tsx @@ -13,7 +13,11 @@ const getExperiencePointsClass = (experiencePoints: number) => { return "text-muted-foreground"; }; -export default function AwardCard(props: { awardImage: Media | null; award: Award }) { +export default function AwardCard(props: { + awardImage: Media | null; + award: Award; + backgroundImage?: Media | null; +}) { const isCommendation = props.award.type === "commendation" || !props.award.type; const commendationColor = isCommendation && @@ -22,12 +26,14 @@ export default function AwardCard(props: { awardImage: Media | null; award: Awar ? props.award.commendationColor : null; + const bgUrl = props.backgroundImage?.url; + return ( - + {bgUrl && ( + <> + +
+ + )} + {props.award.ribbonDesign && (
- + diff --git a/src/components/frontend/account/AwardListingCard.tsx b/src/components/frontend/account/AwardListingCard.tsx index 0d4ead9..39b9a87 100644 --- a/src/components/frontend/account/AwardListingCard.tsx +++ b/src/components/frontend/account/AwardListingCard.tsx @@ -20,6 +20,7 @@ import { AwardIcon, LockIcon } from "lucide-react"; interface AwardListingCardProps { award: Award; awardImage?: Media | null; + backgroundImage?: Media | null; showGrantButton?: boolean; onGrantClick?: () => void; canGrant?: boolean | null; @@ -29,6 +30,7 @@ interface AwardListingCardProps { export function AwardListingCard({ award, awardImage, + backgroundImage, showGrantButton = false, onGrantClick, canGrant, @@ -42,16 +44,31 @@ export function AwardListingCard({ typeof award.ribbonDesign === "object" && Array.isArray((award.ribbonDesign as RibbonDesign).stripes); const hasArt = hasRibbonArt || Boolean(awardImage?.url); + const bgUrl = backgroundImage?.url; return ( - + {bgUrl && ( + <> + +
+ + )} + {hasArt && (
- + {award.description ? convertLexicalToPlaintext({ data: award.description }) @@ -94,7 +111,7 @@ export function AwardListingCard({ {showGrantButton && ( -
+
{canGrant === false ? (