diff --git a/src/components/AnnouncementBanner/banner.css b/src/components/AnnouncementBanner/banner.css new file mode 100644 index 00000000..2c017149 --- /dev/null +++ b/src/components/AnnouncementBanner/banner.css @@ -0,0 +1,129 @@ +:root { + --banner-height: 37px; +} + +/* Push page content below the banner; the fixed navbar already accounts for itself */ +body { + padding-top: var(--banner-height) !important; +} + +.announcement-banner { + color: #fff; +} + +.announcement-banner__inner { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + padding-top: 8px; + padding-bottom: 8px; +} + +.announcement-banner p.announcement-banner__text { + margin: 0; + font-size: 14px; + line-height: 21px; + color: #fff; +} + +.announcement-banner__text--clone { + display: none; +} + +.announcement-banner__text strong { + font-weight: 600; +} + +/* Shared link Button, adapted to the dark banner */ +.announcement-banner a.announcement-banner__cta.button-link { + flex-shrink: 0; + gap: 8px; + padding: 0; + color: #fff; + font-size: 14px; + line-height: 21px; + font-weight: 500; + white-space: nowrap; +} + +.announcement-banner a.announcement-banner__cta.button-link::after { + bottom: -2px; +} + +.announcement-banner a.announcement-banner__cta.button-link img { + filter: brightness(0) invert(1); +} + +.announcement-banner a.announcement-banner__cta.button-link:focus-visible { + outline: 2px solid #fff; + outline-offset: 4px; +} + +@media (max-width: 767px) { + .announcement-banner__inner { + gap: 12px; + } + + /* One line that scrolls; the CTA stays fixed on the right */ + .announcement-banner__marquee { + flex: 1; + min-width: 0; + overflow: hidden; + mask-image: linear-gradient(to right, transparent, #000 12px, #000 calc(100% - 12px), transparent); + } + + .announcement-banner__track { + display: flex; + width: max-content; + animation: announcement-marquee 20s linear infinite; + } + + .announcement-banner__marquee:hover .announcement-banner__track, + .announcement-banner__marquee:active .announcement-banner__track { + animation-play-state: paused; + } + + .announcement-banner p.announcement-banner__text { + padding-right: 32px; + white-space: nowrap; + font-size: 12px; + line-height: 18px; + } + + .announcement-banner__text--clone { + display: block; + } +} + +@keyframes announcement-marquee { + to { + transform: translateX(-50%); + } +} + +@media (prefers-reduced-motion: reduce) { + /* No scrolling: fall back to the static, wrapped message */ + .announcement-banner__marquee { + mask-image: none; + } + + .announcement-banner__track { + animation: none; + width: auto; + } + + .announcement-banner p.announcement-banner__text { + padding-right: 0; + white-space: normal; + } + + .announcement-banner__text--clone { + display: none; + } + + .announcement-banner a.announcement-banner__cta.button-link::after, + .announcement-banner a.announcement-banner__cta.button-link span { + transition: none; + } +} diff --git a/src/components/AnnouncementBanner/index.tsx b/src/components/AnnouncementBanner/index.tsx new file mode 100644 index 00000000..bf9de541 --- /dev/null +++ b/src/components/AnnouncementBanner/index.tsx @@ -0,0 +1,52 @@ +import React, { useEffect, useRef } from "react"; +import Button from "../Button"; +import "./banner.css"; + +const FRONTIER_URL = "https://nextflow.com/frontier"; + +const message = ( + <> + Nextflow.io is not changing. Our new product, Nextflow Frontier, is live, built on Nextflow by the + team who created it. + > +); + +const AnnouncementBanner = () => { + const bannerRef = useRef(null); + + // Keep the body offset in sync with the banner's real height (it wraps on small screens) + useEffect(() => { + const banner = bannerRef.current; + if (!banner) return; + const root = document.documentElement; + const observer = new ResizeObserver(([entry]) => { + root.style.setProperty("--banner-height", `${entry.target.getBoundingClientRect().height}px`); + }); + observer.observe(banner); + return () => { + observer.disconnect(); + root.style.removeProperty("--banner-height"); + }; + }, []); + + return ( + + + + + {message} + {/* Second copy makes the mobile scroll loop seamless */} + + {message} + + + + + Learn more + + + + ); +}; + +export default AnnouncementBanner; diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 7f20c71b..45e7e0c2 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState, useRef } from "react"; import "./menu.css"; import Hamburger from "../Hamburger"; +import AnnouncementBanner from "../AnnouncementBanner"; const Menu = ({}) => { const [isVisible, setIsVisible] = useState(true); @@ -143,6 +144,7 @@ const Menu = ({}) => { return ( + {isMobile && ( diff --git a/src/components/Menu/menu.css b/src/components/Menu/menu.css index bdec2974..67086daf 100644 --- a/src/components/Menu/menu.css +++ b/src/components/Menu/menu.css @@ -73,6 +73,11 @@ .dropdown-menu > li > a img.externalLink { opacity: 1; } + /* Anchor the toggle to the navbar row, not the header (which also holds the banner) */ + #header > .container { + position: relative; + } + .mobile-menu-toggle { position: absolute; right: 15px; diff --git a/src/pages/index.astro b/src/pages/index.astro index a2eaf090..d4f0d71d 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -298,7 +298,9 @@ import arrowsIcon from "../../public/img/assets/icons/arrow.svg"; Explore Seqera's AI-powered assistant to get instant answers about Nextflow, nf-core, and more — right from the source. - Ask Seqera AI + + Ask Seqera AI +
{message}
+ {message} +