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 ? (