diff --git a/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/Client.tsx b/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/Client.tsx
index 24655294c..0cf6ccb21 100644
--- a/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/Client.tsx
+++ b/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/Client.tsx
@@ -11,6 +11,8 @@ import ButtonLink from "@/components/ButtonLink"
import Modal from "@/components/Modal"
import useWakeLock from "@/hooks/useWakeLock"
+import DigitalTeamMemberCardContent from "./Content"
+
import styles from "./digitalTeamMemberCard.module.css"
import type { User } from "@/types/user"
@@ -57,66 +59,9 @@ export default function DigitalTeamMemberCardClient({
{intl.formatMessage({ defaultMessage: "Scandic Family" })}
-
{intl.formatMessage({
diff --git a/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/Content.tsx b/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/Content.tsx
new file mode 100644
index 000000000..0b880784d
--- /dev/null
+++ b/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/Content.tsx
@@ -0,0 +1,196 @@
+import {
+ type CSSProperties,
+ type MouseEvent,
+ type TouchEvent,
+ useEffect,
+ useRef,
+ useState,
+} from "react"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { debounce } from "@/utils/debounce"
+
+import styles from "./digitalTeamMemberCard.module.css"
+
+import type { User } from "@/types/user"
+
+interface DigitalTeamMemberCardCardProps {
+ user: User
+}
+
+export default function DigitalTeamMemberCardContent({
+ user,
+}: DigitalTeamMemberCardCardProps) {
+ const intl = useIntl()
+ const cardRef = useRef(null)
+ const [isHovering, setIsHovering] = useState(false)
+ const [coords, setCoords] = useState({ x: 0, y: 0 })
+ const [rect, setRect] = useState({
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ height: 0,
+ width: 0,
+ x: 0,
+ y: 0,
+ toJSON() {},
+ })
+ let ticking = false
+ let animationFrame = 0
+
+ useEffect(() => {
+ const observer = new ResizeObserver(
+ debounce(() => {
+ const el = cardRef.current
+ if (!el) return
+ const rect = el.getBoundingClientRect()
+ setRect(rect)
+ })
+ )
+ observer.observe(document.body)
+ return () => {
+ observer.unobserve(document.body)
+ }
+ }, [])
+
+ function requestTick(
+ evt: MouseEvent | TouchEvent
+ ) {
+ if (!ticking) {
+ animationFrame = requestAnimationFrame(update(evt))
+ }
+ ticking = true
+ }
+
+ function onInteractionMove(
+ evt: MouseEvent | TouchEvent
+ ) {
+ requestTick(evt)
+ }
+
+ function update(
+ evt: MouseEvent | TouchEvent
+ ) {
+ return () => {
+ let x, y
+ if ("touches" in evt) {
+ x = evt.touches[0].clientX - rect.left
+ y = evt.touches[0].clientY - rect.top
+ } else {
+ x = evt.clientX - rect.left
+ y = evt.clientY - rect.top
+ }
+ setCoords({ x, y })
+ }
+ }
+
+ function renderShimmer(): CSSProperties {
+ const { x, y } = coords
+ const oppositeX = 100 - (x / rect.width) * 100
+ const oppositeY = 100 - (y / rect.height) * 100
+ return {
+ background: `radial-gradient(circle at ${oppositeX}% ${oppositeY}%, rgb(233 171 163 / 40%) 0%, rgb(255 255 255 / 0%) 50%)`,
+ opacity: isHovering ? 1 : 0,
+ }
+ }
+
+ function renderSkew(): CSSProperties {
+ const { x, y } = coords
+ const centerX = rect.width / 2
+ const centerY = rect.height / 2
+ const rotateY = ((x - centerX) / centerX) * 5 // Max 5 degrees
+ const rotateX = ((centerY - y) / centerY) * 5 // Max 5 degrees
+
+ return {
+ transform: isHovering
+ ? `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
+ : "rotateX(0) rotateY(0)",
+ }
+ }
+
+ function handleInteractionStart() {
+ setIsHovering(true)
+ }
+
+ function handleInteractionEnd() {
+ setIsHovering(false)
+ cancelAnimationFrame(animationFrame)
+ }
+
+ return (
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Team Member" })}
+
+
+
+ {/* TODO: Should display country of employment */}
+ {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
+ SWE
+
+
+
+
+
+ {/* TODO: Should display employee number */}
+ {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
+ 123 456
+
+
+
+
+
+
+ {user.firstName} {user.lastName}
+
+
+
+
+
+
+ {/* TODO: Should display department of employment */}
+ {/* eslint-disable formatjs/no-literal-string-in-jsx */}
+ Haymarket by Scandic
+ {/* eslint-enable */}
+
+
+
+ {/* TODO: Should display current state of employment */}
+ {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
+ Employee
+
+
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/digitalTeamMemberCard.module.css b/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/digitalTeamMemberCard.module.css
index 6244f18c6..6ed99cff0 100644
--- a/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/digitalTeamMemberCard.module.css
+++ b/apps/scandic-web/components/MyPages/DigitalTeamMemberCard/digitalTeamMemberCard.module.css
@@ -33,8 +33,22 @@
color: var(--Text-Accent-Primary);
}
-.card {
+.cardContainer {
position: relative;
+ perspective: 1000px;
+ transform-style: preserve-3d;
+}
+
+.shimmer {
+ position: absolute;
+ inset: 0;
+ z-index: 3;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ mix-blend-mode: overlay;
+}
+
+.card {
overflow: hidden;
border-radius: var(--Corner-radius-lg);
box-shadow: 0 2px 1px rgb(255 255 255 / 11%) inset;
@@ -44,6 +58,10 @@
max-width: 100%;
color: var(--Text-Brand-OnPrimary-3-Accent);
background-color: var(--Surface-Brand-Primary-1-OnSurface-Default);
+ box-shadow: 0 4px 44px rgb(0 0 0 / 25%);
+ transition: transform 0.3s ease-out;
+ will-change: transform;
+ user-select: none;
&::before {
content: "";
@@ -54,7 +72,7 @@
width: 360px;
height: 360px;
opacity: 0.3;
- background: #e9aba3;
+ background-color: var(--Scandic-Peach-40);
box-shadow: 192px 192px 192px;
border-radius: 9999px;
filter: blur(96px);
@@ -76,7 +94,7 @@
.content {
position: relative;
- z-index: 3;
+ z-index: 4;
height: 100%;
display: flex;
flex-direction: column;