Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions components/Activity/PromoBar.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
.promoBar {
position: sticky;
top: var(--promo-bar-offset, 4.125rem);
z-index: 1020;
margin-top: var(--promo-bar-gap, 0.625rem);
box-shadow: 0 10px 32px rgb(0 0 0 / 35%);
background: linear-gradient(
135deg,
#0c0e2b 0%,
#1a0b4e 20%,
#312e81 40%,
#1e40af 60%,
#0891b2 80%,
#0d9488 100%
);
}

.promoBarInner {
width: min(100%, 1180px);
}

.promoBarContent {
flex: 1 1 auto;
gap: 1.25rem;
padding: 0.75rem 0;
min-width: 0;
color: inherit;

&:hover,
&:focus,
&:active {
color: inherit;
text-decoration: none;

.promoBarAction {
box-shadow: 0 0 12px rgb(139 92 246 / 40%);
background: linear-gradient(135deg, #818cf8 0%, #a78bfa 50%, #22d3ee 100%);
color: #ffffff;
}
}
}

.promoBarText {
flex: 0 0 auto;
gap: 0.7rem;
min-width: 0;
line-height: 1.35;

strong {
flex: 0 0 auto;
color: #ffffff;
font-size: 1rem;
text-shadow: 0 0 8px rgb(139 92 246 / 30%);
}

span {
min-width: 0;
color: #a5f3fc;
font-size: 0.92rem;
}
}

.promoBarEventName {
flex: 0 0 auto;
background: linear-gradient(90deg, #c4b5fd, #67e8f9);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
font-size: 0.92rem;
white-space: nowrap;
}

.promoBarAction {
flex: 0 0 auto;
transition: all 200ms ease;
box-shadow: 0 0 10px rgb(139 92 246 / 25%);
border: 1px solid rgb(255 255 255 / 20%);
border-radius: 999px;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #06b6d4 100%);
padding: 0.42rem 0.85rem;
color: #ffffff;
font-weight: 700;
font-size: 0.88rem;
line-height: 1.1;
white-space: nowrap;
}

.promoBarClose {
flex: 0 0 2.5rem;
opacity: 0.6;
margin: 0 0 0 0.5rem;
width: 2.5rem;

&:hover,
&:focus {
opacity: 1;
}
}

@media (max-width: 767.98px) {
.promoBar {
top: var(--promo-bar-offset, 4.125rem);
}

.promoBarInner {
padding: 0 0.75rem;
}

.promoBarContent {
flex-wrap: nowrap;
gap: 0.55rem 0.75rem;
padding: 0.58rem 0;
}

.promoBarText {
flex: 1 1 auto;
flex-direction: column;
align-items: flex-start;
gap: 0.15rem;

strong {
flex: 1 1 auto;
max-width: 100%;
overflow: hidden;
font-size: 0.9rem;
text-overflow: ellipsis;
white-space: nowrap;
}

span {
display: -webkit-box;
overflow: hidden;
font-size: 0.82rem;
line-height: 1.25;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
line-clamp: 1;
}
}

.promoBarAction {
margin-left: 0;
padding: 0.38rem 0.55rem;
font-size: 0.8rem;
}

.promoBarClose {
flex-basis: 2rem;
margin-left: 0.1rem;
width: 2rem;
}
}
87 changes: 87 additions & 0 deletions components/Activity/PromoBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { CSSProperties, FC, useContext, useEffect, useState } from 'react';
import { Alert, CloseButton } from 'react-bootstrap';

import { normalizeText, TableCellText } from 'mobx-lark';

import { Activity, ActivityModel } from '../../models/Activity';
import { I18nContext } from '../../models/Translation';
import styles from './PromoBar.module.less';

export const PromoBar: FC = () => {
const { t } = useContext(I18nContext);
const [isVisible, setIsVisible] = useState(true);
const [barStyle, setBarStyle] = useState<CSSProperties>();
const [activity, setActivity] = useState<Activity>();

useEffect(() => {
const navbar = document.querySelector('nav');
const syncTopBarOffset = () => {
const navbarHeight = navbar?.getBoundingClientRect().height || 56;

setBarStyle({
'--promo-bar-gap': `${Math.max(navbarHeight - 56, 0)}px`,
'--promo-bar-offset': `${navbarHeight}px`,
} as CSSProperties);
};
const observer =
typeof ResizeObserver === 'undefined' || !navbar
? undefined
: new ResizeObserver(syncTopBarOffset);

syncTopBarOffset();
if (navbar) observer?.observe(navbar);
window.addEventListener('resize', syncTopBarOffset);

return () => {
observer?.disconnect();
window.removeEventListener('resize', syncTopBarOffset);
};
}, []);

useEffect(() => {
(async () => {
try {
const model = new ActivityModel();
const data = await model.getOne('Labor-AI-hackathon-2026');
setActivity(data);
} catch (err) {
console.error('Failed to load activity:', err);
}
})();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}, []);

const closeBar = () => setIsVisible(false);

if (!isVisible) return null;

return (
<Alert
role="banner"
className={`${styles.promoBar} d-flex flex-column w-100 text-white mb-0 p-0 border-0 rounded-0`}
aria-label={t('home_hackathon_top_bar_aria_label')}
style={barStyle}
>
<div className={`${styles.promoBarInner} d-flex align-items-center mx-auto px-3`}>
<Alert.Link
className={`${styles.promoBarContent} d-flex justify-content-center align-items-center text-decoration-none`}
href={activity ? ActivityModel.getLink(activity) : '/hackathon/Labor-AI-hackathon-2026'}
>
<span className={`${styles.promoBarText} d-flex align-items-baseline`}>
<strong>{t('home_hackathon_top_bar_title')}</strong>
<span>{t('home_hackathon_top_bar_description')}</span>
</span>
<span className={styles.promoBarEventName}>
{activity ? normalizeText(activity.name as TableCellText) : 'Labor AI Hackathon 2026'}
</span>
<span className={styles.promoBarAction}>{t('home_hackathon_top_bar_action')}</span>
</Alert.Link>
<CloseButton
className={`${styles.promoBarClose} p-0 rounded`}
variant="white"
aria-label={t('home_hackathon_top_bar_close')}
onClick={closeBar}
/>
</div>
</Alert>
);
};
4 changes: 2 additions & 2 deletions components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const MainNavigator: FC<MainNavigatorProps> = observer(({ menu }) => {
return (
<Navbar bg="dark" variant="dark" fixed="top" expand="lg">
<Container>
<Navbar.Brand href="/" className="fw-bolder d-flex align-items-center gap-2">
<Navbar.Brand href="/" className="fw-bolder d-flex align-items-center gap-2 text-nowrap">
<Image width={40} src={DefaultImage} alt={t('open_source_bazaar')} />
{t('open_source_bazaar')}
</Navbar.Brand>
Expand All @@ -117,7 +117,7 @@ export const MainNavigator: FC<MainNavigatorProps> = observer(({ menu }) => {
<Nav.Link
key={`${href}-${title}`}
href={href}
className={pathname === `${href}` ? 'fw-bolder text-light' : ''}
className={`text-nowrap ${pathname === `${href}` ? 'fw-bolder text-light' : ''}`}
>
{title}
</Nav.Link>
Expand Down
3 changes: 3 additions & 0 deletions pages/index.tsx
Comment thread
dethan3 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Card, Col, Row } from 'react-bootstrap';
import { renderToStaticMarkup } from 'react-dom/server';
import ReactTyped from 'react-typed-component';

import { PromoBar } from '../components/Activity/PromoBar';
import { PageHead } from '../components/Layout/PageHead';
import { I18nContext } from '../models/Translation';
import styles from '../styles/Home.module.less';
Expand All @@ -15,6 +16,8 @@ const HomePage: FC = observer(() => {
<>
<PageHead />

<PromoBar />

<section
className={`flex-fill d-flex flex-column justify-content-center align-items-center bg-secondary bg-gradient text-dark bg-opacity-10 ${styles.main}`}
>
Expand Down
Loading
Loading