-
Notifications
You must be signed in to change notification settings - Fork 44
adding hackathon banner #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Sebastian Maniak <sebastian@maniak.io>
Signed-off-by: Sebastian Maniak <sebastian@maniak.io>
a144a67 to
8ff1d71
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds a floating hackathon promotion banner to the website. The banner advertises "MCP_HACK//26", a virtual hackathon running from Feb 2 – Mar 1, 2026, with $5,000 in prizes. The banner features a cyberpunk-themed design with animated glowing effects, scanlines, and flickering text. Users can dismiss the banner, and the dismissal state is stored in sessionStorage.
Changes:
- Added a new
HackathonBannercomponent with animated styling and dismissible functionality - Integrated the banner into the root layout to display site-wide
- Updated a navigation description and sitemap dates, and bumped a CloudFlare deployment dependency
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/hackathon-banner.tsx | New component implementing a fixed-position promotional banner with cyberpunk styling, animations, and sessionStorage-based dismissal |
| src/app/layout.tsx | Imported and rendered the HackathonBanner component in the root layout |
| src/config/navigation.json | Expanded "docs" to "documentation" in kmcp API reference description (unrelated to banner) |
| public/sitemap.xml | Updated all lastmod dates from 2026-01-12 to 2026-01-29 |
| package.json | Updated @opennextjs/cloudflare from 1.6.5 to 1.14.8 (unrelated to banner) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const wasDismissed = sessionStorage.getItem('hackathon-banner-dismissed'); | ||
| if (wasDismissed) { | ||
| setDismissed(true); | ||
| } else { | ||
| setVisible(true); | ||
| } | ||
| }, []); | ||
|
|
||
| const handleDismiss = () => { | ||
| setDismissed(true); | ||
| setVisible(false); | ||
| sessionStorage.setItem('hackathon-banner-dismissed', 'true'); |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessing sessionStorage directly in useEffect can cause hydration errors in Next.js since sessionStorage is undefined during server-side rendering. While the initial state is set to false which prevents rendering issues, it's better practice to wrap the sessionStorage access in a check for the window object or use a try-catch block to handle potential SSR errors more gracefully.
| const wasDismissed = sessionStorage.getItem('hackathon-banner-dismissed'); | |
| if (wasDismissed) { | |
| setDismissed(true); | |
| } else { | |
| setVisible(true); | |
| } | |
| }, []); | |
| const handleDismiss = () => { | |
| setDismissed(true); | |
| setVisible(false); | |
| sessionStorage.setItem('hackathon-banner-dismissed', 'true'); | |
| try { | |
| if (typeof window === 'undefined' || !window.sessionStorage) { | |
| return; | |
| } | |
| const wasDismissed = window.sessionStorage.getItem('hackathon-banner-dismissed'); | |
| if (wasDismissed) { | |
| setDismissed(true); | |
| } else { | |
| setVisible(true); | |
| } | |
| } catch { | |
| // If sessionStorage is unavailable or throws, fall back to default visibility. | |
| } | |
| }, []); | |
| const handleDismiss = () => { | |
| setDismissed(true); | |
| setVisible(false); | |
| try { | |
| if (typeof window !== 'undefined' && window.sessionStorage) { | |
| window.sessionStorage.setItem('hackathon-banner-dismissed', 'true'); | |
| } | |
| } catch { | |
| // Ignore storage errors; dismissal still applies for this session. | |
| } |
| @keyframes cyber-glow { | ||
| 0%, 100% { | ||
| box-shadow: 0 0 15px rgba(0, 255, 255, 0.4), | ||
| 0 0 30px rgba(0, 255, 255, 0.1), | ||
| inset 0 0 20px rgba(0, 255, 255, 0.05); | ||
| } | ||
| 50% { | ||
| box-shadow: 0 0 25px rgba(0, 255, 255, 0.6), | ||
| 0 0 50px rgba(0, 255, 255, 0.2), | ||
| inset 0 0 30px rgba(0, 255, 255, 0.1); | ||
| } | ||
| } | ||
| @keyframes scanline { | ||
| 0% { transform: translateY(-100%); } | ||
| 100% { transform: translateY(100%); } | ||
| } | ||
| @keyframes flicker { | ||
| 0%, 95%, 100% { opacity: 1; } | ||
| 96% { opacity: 0.8; } | ||
| 97% { opacity: 1; } | ||
| 98% { opacity: 0.6; } | ||
| } | ||
| .hackathon-float { | ||
| animation: cyber-glow 3s ease-in-out infinite; | ||
| } | ||
| .hackathon-float::after { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 0; left: 0; right: 0; bottom: 0; | ||
| background: linear-gradient(transparent 50%, rgba(0, 255, 255, 0.03) 50%); | ||
| background-size: 100% 4px; | ||
| pointer-events: none; | ||
| } | ||
| .hackathon-scanline { | ||
| position: absolute; | ||
| top: 0; left: 0; right: 0; | ||
| height: 8px; | ||
| background: linear-gradient(to bottom, transparent, rgba(0, 255, 255, 0.1), transparent); | ||
| animation: scanline 3s linear infinite; | ||
| pointer-events: none; | ||
| } | ||
| .hackathon-title { | ||
| animation: flicker 4s infinite; | ||
| text-shadow: 0 0 10px rgba(0, 255, 255, 0.8), | ||
| 0 0 20px rgba(0, 255, 255, 0.4); | ||
| } |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The banner animations (cyber-glow, scanline, flicker) run continuously even when not visible, which could impact performance. Consider adding 'prefers-reduced-motion' media queries to respect user accessibility preferences and disable animations for users who have motion sensitivity. This can be done by wrapping the animations in a '@media (prefers-reduced-motion: no-preference)' query.
| const wasDismissed = sessionStorage.getItem('hackathon-banner-dismissed'); | ||
| if (wasDismissed) { | ||
| setDismissed(true); | ||
| } else { | ||
| setVisible(true); | ||
| } | ||
| }, []); | ||
|
|
||
| const handleDismiss = () => { | ||
| setDismissed(true); | ||
| setVisible(false); | ||
| sessionStorage.setItem('hackathon-banner-dismissed', 'true'); |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The banner uses sessionStorage which means it will reappear every time the user opens a new browser session. For a promotional banner that users dismiss, localStorage might be more appropriate to remember the dismissal across sessions. Alternatively, if the intent is to show the banner in each session, this should be documented in a comment to make the behavior explicit.
| "title": "API docs", | ||
| "href": "/docs/kmcp/reference/api-ref", | ||
| "description": "kmcp API reference docs" | ||
| "description": "kmcp API reference documentation" |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change from "kmcp API reference docs" to "kmcp API reference documentation" appears unrelated to adding a hackathon banner. While this is a minor improvement in consistency (using the full word "documentation"), it should either be in a separate PR or the PR title/description should mention this change.
| // Virtual Hackathon | ||
| </div> | ||
| <div | ||
| className="hackathon-title text-xl font-bold mb-2 font-mono" | ||
| style={{ color: '#00ffff' }} | ||
| > | ||
| MCP_HACK//26 | ||
| </div> | ||
| <p | ||
| className="text-xs leading-relaxed mb-4" | ||
| style={{ color: 'rgba(200, 210, 255, 0.7)' }} | ||
| > | ||
| Build with <span style={{ color: '#00ffff' }}>MCP</span> &{' '} | ||
| <span style={{ color: '#ff00ff' }}>AI Agents</span>.<br /> | ||
| $5,000 in prizes. Feb 2 – Mar 1, 2026. | ||
| </p> | ||
| <a | ||
| href="https://aihackathon.dev/" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="inline-block w-full text-center text-sm font-bold rounded px-4 py-2.5 transition-all duration-200 uppercase tracking-widest hover:shadow-[0_0_20px_rgba(0,255,255,0.5)]" | ||
| style={{ | ||
| background: 'linear-gradient(90deg, #00ffff, #00ccff)', | ||
| color: '#0a0e1a', | ||
| letterSpacing: '0.1em', | ||
| }} | ||
| onMouseOver={(e) => { | ||
| e.currentTarget.style.background = 'linear-gradient(90deg, #00ffff, #ff00ff)'; | ||
| }} | ||
| onMouseOut={(e) => { | ||
| e.currentTarget.style.background = 'linear-gradient(90deg, #00ffff, #00ccff)'; | ||
| }} | ||
| > | ||
| Register Now → | ||
| </a> |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The banner content has hardcoded values including dates, URLs, and prize amounts. Consider extracting these to constants or a configuration object at the top of the file for easier updates when the hackathon details change or when creating similar banners in the future.
| "devDependencies": { | ||
| "@eslint/eslintrc": "^3", | ||
| "@opennextjs/cloudflare": "^1.6.5", | ||
| "@opennextjs/cloudflare": "^1.14.8", |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @opennextjs/cloudflare package is being updated from 1.6.5 to 1.14.8, which spans 8 minor versions. This change appears unrelated to adding a hackathon banner and should ideally be in a separate PR. Additionally, ensure that this update has been tested with the Cloudflare deployment workflow (build:worker, deploy:worker) and that there are no breaking changes in the intermediate versions that could affect the build or deployment process.
| }} | ||
| onMouseOut={(e) => { | ||
| e.currentTarget.style.background = 'linear-gradient(90deg, #00ffff, #00ccff)'; | ||
| }} |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inline onMouseOver and onMouseOut handlers change the background gradient on hover. While this works for mouse users, keyboard users navigating with Tab won't see this visual feedback. Consider adding corresponding onFocus and onBlur handlers with the same logic to ensure keyboard accessibility.
| }} | |
| }} | |
| onFocus={(e) => { | |
| e.currentTarget.style.background = 'linear-gradient(90deg, #00ffff, #ff00ff)'; | |
| }} | |
| onBlur={(e) => { | |
| e.currentTarget.style.background = 'linear-gradient(90deg, #00ffff, #00ccff)'; | |
| }} |
| } | ||
| `}</style> | ||
| <div | ||
| className="hackathon-float fixed top-24 right-6 z-50 max-w-xs rounded-xl overflow-hidden transition-all duration-300" |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fixed positioning with 'top-24' and 'right-6' may cause issues on mobile devices where the banner could overlap with content or extend beyond the viewport. Consider adding responsive positioning classes like 'md:top-24 md:right-6 top-4 right-4' and potentially adjusting the max-width for smaller screens to ensure the banner remains accessible and doesn't obstruct important content.
| className="hackathon-float fixed top-24 right-6 z-50 max-w-xs rounded-xl overflow-hidden transition-all duration-300" | |
| className="hackathon-float fixed top-4 right-4 md:top-24 md:right-6 z-50 max-w-[90vw] md:max-w-xs rounded-xl overflow-hidden transition-all duration-300" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.


No description provided.