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 */} + +
+
+ +
+
+ ); +}; + +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 (