Conversation
notkaramel
commented
Feb 3, 2026
- Resolves Revamp to Council Card popup #84
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This pull request revamps the Council page and its associated components to improve the visual design, accessibility, and user experience. The changes resolve issue #84 regarding the Council Card popup.
Changes:
- Redesigned council page layout with improved visual hierarchy, section headers, and a responsive grid system
- Enhanced modal dialog for viewing council member profiles with better accessibility features
- Added new utility function for generating initials from names with fallback support for missing images
- Updated CouncilCard and CouncilCardPopUp components with improved styling, image error handling, and responsive design
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| src/routes/council/+page.svelte | Complete page redesign with new layout, modal implementation, keyboard navigation, and improved semantic structure |
| src/routes/council/+page.server.ts | Updated image resolution from 300px to 360px and added TypeScript type annotation |
| src/lib/format.ts | New utility function for extracting initials from names with null/undefined safety |
| src/components/layout/Section.svelte | Added contentStart prop for flexible vertical alignment of content |
| src/components/council/CouncilCardPopUp.svelte | Redesigned popup with better accessibility, image fallback, and responsive layout |
| src/components/council/CouncilCard.svelte | Complete redesign with featured/tag support, image fallback, and improved card styling |
| src/components/council/CouncilAvatar.svelte | Updated styling for consistency with new design system |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ); | ||
| return [...list].sort((a, b) => { | ||
| const aYear = years.findIndex((year) => a.position.includes(year)); | ||
| const bYear = years.findIndex((year) => b.position.includes(year)); |
There was a problem hiding this comment.
The sorting logic for U-reps uses findIndex which returns -1 when a year is not found in the position string. If a representative's position doesn't include any of the expected year markers (U4, U3, U2, U1, U0), they will be sorted to the beginning (since -1 < any valid index). Consider explicitly handling cases where no year is found, either by filtering them out, placing them at the end, or documenting this behavior.
| const bYear = years.findIndex((year) => b.position.includes(year)); | |
| const bYear = years.findIndex((year) => b.position.includes(year)); | |
| // Place representatives without a recognized U-year marker after those with one | |
| if (aYear === -1 && bYear === -1) return 0; | |
| if (aYear === -1) return 1; | |
| if (bYear === -1) return -1; |
| {#if image && !imageError} | ||
| <img | ||
| src={image} | ||
| alt={name} |
There was a problem hiding this comment.
The alt text for council member images simply uses the member's name. For better accessibility, consider making the alt text more descriptive by including context, such as "Photo of [name], [position]" to provide more information to screen reader users.
| alt={name} | |
| alt={`Photo of ${name}${position ? `, ${position}` : ''}`} |
| return () => { | ||
| document.body.style.overflow = ''; | ||
| }; |
There was a problem hiding this comment.
The cleanup function in the $effect block always resets document.body.style.overflow to an empty string, even when a modal might still be open. If multiple effects or components manage body overflow, this could cause conflicts. The cleanup function will run when the effect re-runs (e.g., when selectedMember changes from one member to another), briefly resetting overflow before the effect body sets it to 'hidden' again. Consider whether this cleanup is necessary given the effect body already handles both cases explicitly.
| return () => { | |
| document.body.style.overflow = ''; | |
| }; |
| position, | ||
| positionDescription, | ||
| "image": image.asset->url+"?h=300&fm=webp", | ||
| "image": image.asset->url+"?h=360&fm=webp", |
There was a problem hiding this comment.
The image height parameter was changed from 300px to 360px. However, the query still specifies only height ('?h=360&fm=webp') without a width parameter. For responsive images, consider whether a width parameter or srcset attributes would be beneficial for optimizing images across different screen sizes, especially since the cards now display at different sizes depending on the 'featured' prop.
| "image": image.asset->url+"?h=360&fm=webp", | |
| "image": image.asset->url+"?h=360&w=360&fm=webp", |
| let { | ||
| name, | ||
| position, | ||
| email, | ||
| positionDescription, | ||
| yearProgram, | ||
| image, | ||
| onClose, | ||
| id = 'popup-title' | ||
| } = $props(); |
There was a problem hiding this comment.
The props destructuring lacks TypeScript type annotations, which reduces type safety. Consider adding explicit types, especially since the 'onClose' callback prop is expected but not typed. The type should be something like onClose: () => void to ensure proper contract enforcement.
| role="dialog" | ||
| aria-modal="true" | ||
| aria-labelledby="popup-title" | ||
| onclick={(e) => e.target === e.currentTarget && closeModal()} |
There was a problem hiding this comment.
The click-outside-to-close functionality uses e.target === e.currentTarget to detect backdrop clicks. While this works, be aware that if the popup content has any margins or if users click between elements in certain edge cases, it might not behave as expected. Consider adding event.stopPropagation() to the inner content div's click handler to ensure clicks inside the modal don't bubble up and trigger closure.
| class="group bg-ecsess-800 ring-ecsess-500/50 ring-offset-ecsess-900 hover:ring-ecsess-300 hover:shadow-ecsess-400/50 focus-within:ring-ecsess-300 focus-within:ring-offset-ecsess-900 grid h-full min-w-0 grid-rows-[auto_1fr] overflow-hidden rounded-xl shadow-md ring-2 ring-offset-2 transition-all duration-500 ease-out focus-within:ring-2 focus-within:ring-offset-2 hover:shadow-xl {featured | ||
| ? 'w-full max-w-56 sm:max-w-64' | ||
| : 'w-full max-w-56 sm:max-w-64'}" |
There was a problem hiding this comment.
The conditional expression has identical branches for both featured true and false cases. Both branches return 'w-full max-w-56 sm:max-w-64'. This makes the conditional expression redundant. Either remove the conditional and use a single value, or implement different styling for the featured variant if that was the intention.
| class="group bg-ecsess-800 ring-ecsess-500/50 ring-offset-ecsess-900 hover:ring-ecsess-300 hover:shadow-ecsess-400/50 focus-within:ring-ecsess-300 focus-within:ring-offset-ecsess-900 grid h-full min-w-0 grid-rows-[auto_1fr] overflow-hidden rounded-xl shadow-md ring-2 ring-offset-2 transition-all duration-500 ease-out focus-within:ring-2 focus-within:ring-offset-2 hover:shadow-xl {featured | |
| ? 'w-full max-w-56 sm:max-w-64' | |
| : 'w-full max-w-56 sm:max-w-64'}" | |
| class="group bg-ecsess-800 ring-ecsess-500/50 ring-offset-ecsess-900 hover:ring-ecsess-300 hover:shadow-ecsess-400/50 focus-within:ring-ecsess-300 focus-within:ring-offset-ecsess-900 grid h-full min-w-0 grid-rows-[auto_1fr] overflow-hidden rounded-xl shadow-md ring-2 ring-offset-2 transition-all duration-500 ease-out focus-within:ring-2 focus-within:ring-offset-2 hover:shadow-xl w-full max-w-56 sm:max-w-64" |
src/routes/council/+page.svelte
Outdated
| <div class="mt-8 flex flex-wrap justify-center gap-4"> | ||
| {#each vps as vp} | ||
| <div | ||
| class="flex min-w-full justify-center sm:min-w-[calc(50%-0.5rem)] lg:min-w-[calc(33.333%-0.67rem)] xl:min-w-[calc(25%-0.75rem)]" |
There was a problem hiding this comment.
The responsive min-width classes use complex calc() expressions that may not behave as expected in a flex-wrap context. The values sm:min-w-[calc(50%-0.5rem)], lg:min-w-[calc(33.333%-0.67rem)], and xl:min-w-[calc(25%-0.75rem)] attempt to account for gaps, but in a flex container with gap-4 (1rem), these calculations don't correctly align with the actual gap size. Consider using a simpler approach with flex-basis or adjusting the calculations to match the actual gap value of 1rem.
| class="flex min-w-full justify-center sm:min-w-[calc(50%-0.5rem)] lg:min-w-[calc(33.333%-0.67rem)] xl:min-w-[calc(25%-0.75rem)]" | |
| class="flex justify-center basis-full shrink-0 sm:basis-1/2 lg:basis-1/3 xl:basis-1/4" |
|
Deployment failed with the following error: |