Repository: alveusgg/extension
Pull request #257: Expand ambassador image on card hover
Author: login: mirchez
Created: 2025-03-15T00:01:59Z | Merged: 2025-03-20T02:12:37Z

# Feat: Improve image responsiveness and interaction

##  Changes:
1. **Removed `overflow-hidden`** so images can have a minimum height when users zoom in on the page.  
2. **Added `rounded` to the image and ambassador name** to maintain a consistent layout.  
3. **Webpage:** Implemented `hover` pseudo-class to expand the image when the cursor is over it.  
4. **Mobile:** Implemented `active` pseudo-class to expand images when users tap on them.  

## Concerns  
- Since images have different widths and heights, some may appear zoomed in when expanded.  

## Before & After Screenshots  
![extensionBefore](https://github.com/user-attachments/assets/1d4ce1b1-4c0a-4199-b660-962616f26c5c)
---------------------------------------
![extensionAfter](https://github.com/user-attachments/assets/aba183fa-44a4-476f-a37b-a62674697681)

---

**Related to #256**  



Diff:
diff --git a/src/components/AmbassadorCard.tsx b/src/components/AmbassadorCard.tsx
index 4d18f2d8..55efc9d8 100644
--- a/src/components/AmbassadorCard.tsx
+++ b/src/components/AmbassadorCard.tsx
@@ -8,6 +8,7 @@ import { camelToKebab } from "../utils/helpers";
 import { classes } from "../utils/classes";
 
 import IconInfo from "./icons/IconInfo";
+import IconBack from "./icons/IconBack";
 
 import Tooltip from "./Tooltip";
 
@@ -16,18 +17,6 @@ import partyHat from "../assets/party.svg";
 
 const headingClass = "text-base text-alveus-green-400";
 
-const offsetPosition = (position?: `${number}% ${number}%`) => {
-  const [x, y] = (position || "50% 50%").split(" ") as [
-    `${number}%`,
-    `${number}%`,
-  ];
-
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
-  return `${x} ${y}`;
-};
-
 const stringifyLifespan = (value: number | { min: number; max: number }) => {
   return typeof value === "number" ? `${value}` : `${value.min}-${value.max}`;
 };
@@ -144,38 +133,32 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
-          <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
-            src={ambassador.image.src}
-            alt={ambassador.image.alt}
-            style={{
-              objectPosition: offsetPosition(ambassador.image.position),
-            }}
-            loading="lazy"
-          />
+        <img
+          className="max-h-32 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out hover:max-h-96 active:max-h-96"
+          src={ambassador.image.src}
+          alt={ambassador.image.alt}
+          style={{
+            objectPosition: ambassador.image.position,
+          }}
+          loading="lazy"
+        />
 
-          <div className="absolute inset-x-0 top-0 flex h-9 w-full bg-alveus-green-900/50 backdrop-blur-xs transition-[opacity,backdrop-filter] peer-hover:opacity-10 peer-hover:backdrop-blur-[calc(var(--blur-xs)/2)]">
-            {props.onClose && (
-              <button
-                className="absolute top-1/2 right-1 block w-8 -translate-y-1/2 cursor-pointer text-center text-2xl transition-colors hover:text-highlight focus:text-highlight"
-                onClick={onClose}
-                type="button"
-                aria-label="Close"
-              >
-                &times;
-              </button>
-            )}
-
-            <h2
-              className="w-full shrink-0 self-center overflow-hidden py-1 pr-10 pl-2 text-xl text-nowrap overflow-ellipsis"
-              title={ambassador.name}
+        <div className="relative flex w-full items-center justify-center bg-alveus-green px-8 py-1">
+          {onClose && (
+            <button
+              className="absolute left-0 p-1 transition-colors hover:text-highlight active:text-highlight sm:hidden"
+              onClick={onClose}
+              type="button"
+              aria-label="Close"
             >
-              {ambassador.name}
-            </h2>
-          </div>
-        </div>
+              <IconBack size={20} alt="Back arrow" />
+            </button>
+          )}
 
+          <h2 className="text-base text-balance text-white">
+            {ambassador.name}
+          </h2>
+        </div>
         <div className="mb-2 scrollbar-thin flex flex-auto flex-col gap-1 overflow-y-auto p-2 scrollbar-thumb-alveus-green scrollbar-track-alveus-green-900">
           {mod && (
             <div className="flex items-center gap-2">
diff --git a/src/components/icons/IconBack.tsx b/src/components/icons/IconBack.tsx
new file mode 100644
index 00000000..b6bf09b0
--- /dev/null
+++ b/src/components/icons/IconBack.tsx
@@ -0,0 +1,13 @@
+import { BaseIcon, type IconProps } from "./BaseIcon";
+
+// This SVG code is derived from FontAwesome (https://fontawesome.com/icons/arrow-left)
+export default function IconBack(props: IconProps) {
+  return (
+    <BaseIcon viewBox="0 0 448 512" {...props}>
+      <path
+        fill="currentColor"
+        d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"
+      />
+    </BaseIcon>
+  );
+}


Reviews:
user: login: MattIPv4
body: Did you take a look at what's happening with the object position? I suspect that's part of the problem as I'm not sure it's consistent across browsers/viewports?
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-15T00:06:36Z
user: login: mirchez
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T02:18:15Z
user: login: mirchez
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T02:20:07Z
user: login: MattIPv4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-15T21:21:53Z
user: login: mirchez
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T22:12:48Z
user: login: MattIPv4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T02:31:33Z
user: login: mirchez
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T02:44:38Z
user: login: MattIPv4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T04:20:31Z
user: login: mirchez
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:50:03Z
user: login: MattIPv4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T14:38:08Z
user: login: MattIPv4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T14:39:26Z
user: login: mirchez
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:36:04Z
user: login: MattIPv4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T03:45:49Z
user: login: MattIPv4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T03:46:02Z
user: login: MattIPv4
body: Thank you very much for your work on this 💚 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T02:10:58Z

Review comments:
diff_hunk: @@ -144,9 +144,9 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer aspect-[2.2] h-30 w-full rounded-t-lg object-cover transition-all duration-400 ease-in-out active:h-60 sm:aspect-[1.8] sm:h-32 sm:hover:h-64"
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: 1. If you're setting both a width and a height here, aspect is no longer needed

2. When hovered can we use the intrinsic height instead? This will then avoid your concern about not all images being the same size

3. Nit, but can we just transition height, not everything?
created_at: 2025-03-15T00:05:32Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,3 @@
 REACT_APP_TEST_CHANNEL_NAMES=abdullahmorrison
 REACT_APP_CHAT_COMMANDS_PRIVILEGED_USERS=abdullahmorrison
-REACT_APP_API_BASE_URL=http://localhost:3000
+REACT_APP_API_BASE_URL=http://localhost:3000
path: .env.development.sample
user: login: MattIPv4
body: Can you revert your change here?
created_at: 2025-03-15T00:05:53Z
author_association: MEMBER
diff_hunk: @@ -144,9 +144,9 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer aspect-[2.2] h-30 w-full rounded-t-lg object-cover transition-all duration-400 ease-in-out active:h-60 sm:aspect-[1.8] sm:h-32 sm:hover:h-64"
path: src/components/AmbassadorCard.tsx
user: login: mirchez
body: 1. Removed aspec.

2. Added transition only to height.

3. Transition doesn't work with intrinsic height and the expand it's very abrupt. 

I want to know if you want me to only use the intrinsic height ( maybe I can add some function to get the original image size ), or if it's okay to set a certain height as long as the zoom doesn't interfere with the user experience.

created_at: 2025-03-15T02:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,3 +1,3 @@
 REACT_APP_TEST_CHANNEL_NAMES=abdullahmorrison
 REACT_APP_CHAT_COMMANDS_PRIVILEGED_USERS=abdullahmorrison
-REACT_APP_API_BASE_URL=http://localhost:3000
+REACT_APP_API_BASE_URL=http://localhost:3000
path: .env.development.sample
user: login: mirchez
body: Done.
created_at: 2025-03-15T02:20:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -144,18 +144,23 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer max-h-30 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out active:max-h-96 sm:max-h-32 sm:hover:max-h-96"
             src={ambassador.image.src}
             alt={ambassador.image.alt}
             style={{
-              objectPosition: offsetPosition(ambassador.image.position),
+              objectPosition: offsetPosition(
+                ambassador.image.position
+                  ? ambassador.image.position
+                  : "50% 75%",
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: I'm not sure I follow what this solves for -- `offsetPosition` already handles applying a default. I'm pretty sure the alignment issues stem from the way the min is applying itself currently within `offsetPosition` (the aim being to offset the images slightly beyond their normal positions to account for the title overlay)
created_at: 2025-03-15T21:21:53Z
author_association: MEMBER
diff_hunk: @@ -144,18 +144,23 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer max-h-30 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out active:max-h-96 sm:max-h-32 sm:hover:max-h-96"
             src={ambassador.image.src}
             alt={ambassador.image.alt}
             style={{
-              objectPosition: offsetPosition(ambassador.image.position),
+              objectPosition: offsetPosition(
+                ambassador.image.position
+                  ? ambassador.image.position
+                  : "50% 75%",
path: src/components/AmbassadorCard.tsx
user: login: mirchez
body: I see what you mean, I'm going to delete that part, it's not necessary.
This part:
(ambassador.image.position
                  ? ambassador.image.position
                  : "50% 75%",)
created_at: 2025-03-15T22:12:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,9 +22,6 @@ const offsetPosition = (position?: `${number}% ${number}%`) => {
     `${number}%`,
   ];
 
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
   return `${x} ${y}`;
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: This is now just a no-op -- we can just remove the call to `offsetPosition` and apply `ambassador.image.position` directly
created_at: 2025-03-17T02:31:33Z
author_association: MEMBER
diff_hunk: @@ -22,9 +22,6 @@ const offsetPosition = (position?: `${number}% ${number}%`) => {
     `${number}%`,
   ];
 
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
   return `${x} ${y}`;
path: src/components/AmbassadorCard.tsx
user: login: mirchez
body: Alright I'll remove it. as some of the ambassador.image.position will return undefined, we should add a default value for objectPosition, maybe like this: 

```
 style={{
              objectPosition: ambassador.image.position
                  ? ambassador.image.position
                  : "50% 50%" 
            }}
```
created_at: 2025-03-17T02:44:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,9 +22,6 @@ const offsetPosition = (position?: `${number}% ${number}%`) => {
     `${number}%`,
   ];
 
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
   return `${x} ${y}`;
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: `undefined` is fine, it'll just get dropped (and then the browser will use its default, which is to center it -- 50% 50%).
created_at: 2025-03-17T04:20:31Z
author_association: MEMBER
diff_hunk: @@ -22,9 +22,6 @@ const offsetPosition = (position?: `${number}% ${number}%`) => {
     `${number}%`,
   ];
 
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
   return `${x} ${y}`;
path: src/components/AmbassadorCard.tsx
user: login: mirchez
body: Perfect, I'll commit the changes so you can review them.
created_at: 2025-03-17T10:50:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,37 +16,18 @@ import partyHat from "../assets/party.svg";
 
 const headingClass = "text-base text-alveus-green-400";
 
-const offsetPosition = (position?: `${number}% ${number}%`) => {
-  const [x, y] = (position || "50% 50%").split(" ") as [
-    `${number}%`,
-    `${number}%`,
-  ];
-
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
-  return `${x} ${y}`;
-};
-
 const stringifyLifespan = (value: number | { min: number; max: number }) => {
   return typeof value === "number" ? `${value}` : `${value.min}-${value.max}`;
 };
 
 export interface AmbassadorCardProps {
   ambassador: string;
-  onClose?: () => void;
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: I think we're going to need a mechanism for closing still -- on mobile the cards fill the entire screen so there is now no way to close them?
created_at: 2025-03-18T14:38:08Z
author_association: MEMBER
diff_hunk: @@ -16,37 +16,18 @@ import partyHat from "../assets/party.svg";
 
 const headingClass = "text-base text-alveus-green-400";
 
-const offsetPosition = (position?: `${number}% ${number}%`) => {
-  const [x, y] = (position || "50% 50%").split(" ") as [
-    `${number}%`,
-    `${number}%`,
-  ];
-
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
-  return `${x} ${y}`;
-};
-
 const stringifyLifespan = (value: number | { min: number; max: number }) => {
   return typeof value === "number" ? `${value}` : `${value.min}-${value.max}`;
 };
 
 export interface AmbassadorCardProps {
   ambassador: string;
-  onClose?: () => void;
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: Maybe we show a back arrow in the bar for the name, only when in the mobile/panel view?
created_at: 2025-03-18T14:39:26Z
author_association: MEMBER
diff_hunk: @@ -16,37 +16,18 @@ import partyHat from "../assets/party.svg";
 
 const headingClass = "text-base text-alveus-green-400";
 
-const offsetPosition = (position?: `${number}% ${number}%`) => {
-  const [x, y] = (position || "50% 50%").split(" ") as [
-    `${number}%`,
-    `${number}%`,
-  ];
-
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
-  return `${x} ${y}`;
-};
-
 const stringifyLifespan = (value: number | { min: number; max: number }) => {
   return typeof value === "number" ? `${value}` : `${value.min}-${value.max}`;
 };
 
 export interface AmbassadorCardProps {
   ambassador: string;
-  onClose?: () => void;
path: src/components/AmbassadorCard.tsx
user: login: mirchez
body: No problems, I'll work on it.
created_at: 2025-03-18T22:36:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,7 +36,6 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
     ...extras
   } = props;
   const ambassador = useAmbassador(ambassadorKey);
-
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: Nit, can we undo this unrelated line removal?
created_at: 2025-03-19T03:41:57Z
author_association: MEMBER
diff_hunk: @@ -144,36 +131,29 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: Do we even need this div now?
created_at: 2025-03-19T03:42:15Z
author_association: MEMBER
diff_hunk: @@ -144,36 +131,29 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer max-h-30 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out active:max-h-96 sm:max-h-32 sm:hover:max-h-96"
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: I don't think we need `peer` now as we're not doing any styling based on that?
created_at: 2025-03-19T03:42:30Z
author_association: MEMBER
diff_hunk: @@ -144,36 +131,29 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer max-h-30 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out active:max-h-96 sm:max-h-32 sm:hover:max-h-96"
             src={ambassador.image.src}
             alt={ambassador.image.alt}
             style={{
-              objectPosition: offsetPosition(ambassador.image.position),
+              objectPosition: ambassador.image.position,
             }}
             loading="lazy"
           />
+        </div>
 
-          <div className="absolute inset-x-0 top-0 flex h-9 w-full bg-alveus-green-900/50 backdrop-blur-xs transition-[opacity,backdrop-filter] peer-hover:opacity-10 peer-hover:backdrop-blur-[calc(var(--blur-xs)/2)]">
-            {props.onClose && (
-              <button
-                className="absolute top-1/2 right-1 block w-8 -translate-y-1/2 cursor-pointer text-center text-2xl transition-colors hover:text-highlight focus:text-highlight"
-                onClick={onClose}
-                type="button"
-                aria-label="Close"
-              >
-                &times;
-              </button>
-            )}
+        <div className="flex w-full items-center justify-center bg-alveus-green py-1">
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: I think we want relative here so the arrow is positioned relative to here?
created_at: 2025-03-19T03:44:38Z
author_association: MEMBER
diff_hunk: @@ -144,36 +131,29 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer max-h-30 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out active:max-h-96 sm:max-h-32 sm:hover:max-h-96"
             src={ambassador.image.src}
             alt={ambassador.image.alt}
             style={{
-              objectPosition: offsetPosition(ambassador.image.position),
+              objectPosition: ambassador.image.position,
             }}
             loading="lazy"
           />
+        </div>
 
-          <div className="absolute inset-x-0 top-0 flex h-9 w-full bg-alveus-green-900/50 backdrop-blur-xs transition-[opacity,backdrop-filter] peer-hover:opacity-10 peer-hover:backdrop-blur-[calc(var(--blur-xs)/2)]">
-            {props.onClose && (
-              <button
-                className="absolute top-1/2 right-1 block w-8 -translate-y-1/2 cursor-pointer text-center text-2xl transition-colors hover:text-highlight focus:text-highlight"
-                onClick={onClose}
-                type="button"
-                aria-label="Close"
-              >
-                &times;
-              </button>
-            )}
+        <div className="flex w-full items-center justify-center bg-alveus-green py-1">
+          <button
+            className="absolute w-8 cursor-pointer text-2xl text-alveus-green-200 transition-colors hover:text-highlight focus:text-highlight sm:hidden"
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: I think we need to set `left-0` on this?
created_at: 2025-03-19T03:44:45Z
author_association: MEMBER
diff_hunk: @@ -144,36 +131,29 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer max-h-30 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out active:max-h-96 sm:max-h-32 sm:hover:max-h-96"
             src={ambassador.image.src}
             alt={ambassador.image.alt}
             style={{
-              objectPosition: offsetPosition(ambassador.image.position),
+              objectPosition: ambassador.image.position,
             }}
             loading="lazy"
           />
+        </div>
 
-          <div className="absolute inset-x-0 top-0 flex h-9 w-full bg-alveus-green-900/50 backdrop-blur-xs transition-[opacity,backdrop-filter] peer-hover:opacity-10 peer-hover:backdrop-blur-[calc(var(--blur-xs)/2)]">
-            {props.onClose && (
-              <button
-                className="absolute top-1/2 right-1 block w-8 -translate-y-1/2 cursor-pointer text-center text-2xl transition-colors hover:text-highlight focus:text-highlight"
-                onClick={onClose}
-                type="button"
-                aria-label="Close"
-              >
-                &times;
-              </button>
-            )}
+        <div className="flex w-full items-center justify-center bg-alveus-green py-1">
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: Also, we should have a `px-?` set so that the ambassador name can never intersect with the arrow, please
created_at: 2025-03-19T03:45:20Z
author_association: MEMBER
diff_hunk: @@ -144,36 +131,29 @@ export default function AmbassadorCard(props: AmbassadorCardProps) {
             className="absolute top-0 left-1/2 z-10 h-auto w-16 -translate-x-1/2 -translate-y-[85%]"
           />
         )}
-        <div className="relative w-full overflow-hidden rounded-t-lg">
+        <div className="relative w-full rounded-t-lg">
           <img
-            className="peer aspect-[2.2] w-full object-cover sm:aspect-[1.8]"
+            className="peer max-h-30 w-full rounded-t-lg object-cover transition-[max-height] duration-700 ease-in-out active:max-h-96 sm:max-h-32 sm:hover:max-h-96"
             src={ambassador.image.src}
             alt={ambassador.image.alt}
             style={{
-              objectPosition: offsetPosition(ambassador.image.position),
+              objectPosition: ambassador.image.position,
             }}
             loading="lazy"
           />
+        </div>
 
-          <div className="absolute inset-x-0 top-0 flex h-9 w-full bg-alveus-green-900/50 backdrop-blur-xs transition-[opacity,backdrop-filter] peer-hover:opacity-10 peer-hover:backdrop-blur-[calc(var(--blur-xs)/2)]">
-            {props.onClose && (
-              <button
-                className="absolute top-1/2 right-1 block w-8 -translate-y-1/2 cursor-pointer text-center text-2xl transition-colors hover:text-highlight focus:text-highlight"
-                onClick={onClose}
-                type="button"
-                aria-label="Close"
-              >
-                &times;
-              </button>
-            )}
+        <div className="flex w-full items-center justify-center bg-alveus-green py-1">
+          <button
+            className="absolute w-8 cursor-pointer text-2xl text-alveus-green-200 transition-colors hover:text-highlight focus:text-highlight sm:hidden"
+            onClick={onClose}
+            type="button"
+            aria-label="Close"
+          >
+            &lt;
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: Can we use an actual icon here, I'm not sure a `<` looks particularly nice?
created_at: 2025-03-19T03:45:44Z
author_association: MEMBER
diff_hunk: @@ -16,37 +16,18 @@ import partyHat from "../assets/party.svg";
 
 const headingClass = "text-base text-alveus-green-400";
 
-const offsetPosition = (position?: `${number}% ${number}%`) => {
-  const [x, y] = (position || "50% 50%").split(" ") as [
-    `${number}%`,
-    `${number}%`,
-  ];
-
-  const yPct = Number(y.replace("%", ""));
-  if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;
-
-  return `${x} ${y}`;
-};
-
 const stringifyLifespan = (value: number | { min: number; max: number }) => {
   return typeof value === "number" ? `${value}` : `${value.min}-${value.max}`;
 };
 
 export interface AmbassadorCardProps {
   ambassador: string;
-  onClose?: () => void;
path: src/components/AmbassadorCard.tsx
user: login: MattIPv4
body: Thank you 💚 
created_at: 2025-03-19T03:46:02Z
author_association: MEMBER

PR comments:
user: login: mirchez
created_at: 2025-03-15T00:19:47Z
body: No problem, I'll work on it.
author_association: CONTRIBUTOR
user: login: mirchez
created_at: 2025-03-15T20:35:32Z
body: I did some changes

author_association: CONTRIBUTOR
user: login: mirchez
created_at: 2025-03-15T20:35:56Z
body: ## Add max-height and fix objectPosition bug  

### Changes  
1. **Add `max-height`** to fix zoom when hovering; now the ambassador images can expand to their intrinsic height.  
2. **Set a default value for `objectPosition`** to prevent issues when the value is `undefined`, solving the alignment bug from before.  
3. **Apply transition only to `max-height`** for smoother expansion effects.  
4. **Remove `aspect` property**, as it is no longer needed.  

### Related to #256  
author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-16T03:33:42Z
body: Have you had a chance to investigate the alignment issues that were mentioned? I'm wondering if there's a better approach when can take than what happens in the `offsetPosition` method that's more consistent across viewports/browsers? Showing the full image on hover definitely partially solves for the issue, but also having the image aligned correctly when the card first shows is important too.
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-16T22:12:29Z
body: **`if (yPct <= 50) return `${x} min(calc(${y} + 1.5rem), 0%)`;`**

I quite don't understand why we use min here; it'll always return 0%, isn't it?. When we just return x and y, it seems to aling better.  
author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-17T00:05:11Z
body: So it shouldn't always be 0, the idea is that we shift the center of the image slightly so that it doesn't end up under the overlaid title -- I suspect this isn't always working as we expect. If you can't figure out what's wrong with it, it might be best we just use the normal center of the images without the 1.5rem offset, especially now that the full image shows on hover.
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-17T02:21:16Z
body: Agreed, I was trying to add some extra calculation, but I think it’s difficult to align because some ambassador images are larger than others. Now It just returns the normal center of the images, and it seems to align a little better. I'll commit it so you can take a look.
author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-17T12:21:14Z
body: Hm, with that calc/min offset removed we're definitely running into issues now where the set center of the image results in the ambassador's head being under the title:

![image](https://github.com/user-attachments/assets/4aebc77a-de5e-4cf4-9a10-e2bce26f2313)
![image](https://github.com/user-attachments/assets/09a097aa-da27-4047-ad99-a71f05015f8f)

If we can't figure out how to make that calc/min work more consistently, I think maybe its best if we don't have the title overlay? @pjeweb thoughts?
author_association: MEMBER
user: login: pjeweb
created_at: 2025-03-17T21:52:17Z
body: Yeah I'd be just fine with moving the name to the main content.
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-17T22:59:13Z
body: What about something like this? 

![image](https://github.com/user-attachments/assets/57881762-afe0-4431-8fa0-8dc822f890ba)


author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-17T23:35:33Z
body: I would maybe do it full width instead of having the margin, but yeh I think that's fine 👍 
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-18T01:25:19Z
body: Here's how it would look with full width, but it seems to interfere with the scroll.


            <h2 className="-mx-2 -mt-2 mb-1 bg-alveus-green py-1 text-center text-base text-white transition-[scale] duration-300 ease-in-out hover:scale-102">
              {ambassador.name}
            </h2>


![image](https://github.com/user-attachments/assets/bef73bd6-0f2c-4f3f-a745-2eb97b849eaa)


author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-18T01:40:16Z
body: Can we put it outside the scroll container? If not the margin you had before is probably the better looking option
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-18T02:09:22Z
body: I think it looks better now. Should I commit it?

![image](https://github.com/user-attachments/assets/0e16b740-1091-4a2d-b3ac-a0d67c18ac45)

author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-18T02:20:20Z
body: 👍 looks fine to me
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-19T23:06:49Z
body: I've added a new Icon here:

![image](https://github.com/user-attachments/assets/6e62b8b1-7a66-4a1b-9a33-2e7066907d50)

So the arrow now looks like this:

![image](https://github.com/user-attachments/assets/d2155224-9e46-41e0-8d27-0db8ee6f0815)

Here's the bar code:

```
   <div className="relative flex w-full items-center justify-center bg-alveus-green px-15 py-1">
          <button
            className="absolute left-0 p-1 text-alveus-green-300 transition-colors active:text-highlight sm:hidden"
            onClick={onClose}
            type="button"
            aria-label="Close"
          >
            <IconBack size={20} alt="Back arrow" />
          </button>

          <h2 className="text-base text-white">{ambassador.name}</h2>
     </div>
```

Is it okay?

author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-19T23:10:17Z
body: I'd maybe make the arrow slightly lighter if we can, and as a super nit, can the px-15 be px-16? Otherwise, in principle, that looks great -- thank you!
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-19T23:15:15Z
body: Perfect, I'll do those changes and then commit.
author_association: CONTRIBUTOR
user: login: mirchez
created_at: 2025-03-19T23:19:56Z
body: Does this look good??

![image](https://github.com/user-attachments/assets/0a639b5e-fbc8-41b2-8bbd-98adc071244d)

author_association: CONTRIBUTOR
user: login: MattIPv4
created_at: 2025-03-19T23:23:25Z
body: 👍 Looks great
author_association: MEMBER
user: login: mirchez
created_at: 2025-03-20T02:25:54Z
body: Thanks Matt for the patience. I really appreciate it. I wish you all the best.
author_association: CONTRIBUTOR