From 14bb6c611c39dd73fb234713302cc2bfc4252169 Mon Sep 17 00:00:00 2001 From: thomasgarrison Date: Mon, 9 Mar 2026 15:21:40 -0500 Subject: [PATCH] fix: restore SubNav tabs, QuickLinksDropdown, and ReadingProgressBar These components were accidentally removed in commit c150f5c. This restores the full original implementation from eb516bc including the SubNav doc section tabs, Quick Links dropdown, reading progress bar, updated icons, and navbar height adjustment. Also removes duplicate docs/about-arrow/index.md (replaced by index.mdx). Co-Authored-By: Claude Opus 4.6 --- docs/about-arrow/index.md | 35 --- src/css/custom.css | 2 +- src/theme/Navbar/DocsNavbar.module.css | 352 +++++++++++++++++++++++-- src/theme/Navbar/index.tsx | 311 ++++++++++++++++++++-- 4 files changed, 620 insertions(+), 80 deletions(-) delete mode 100644 docs/about-arrow/index.md diff --git a/docs/about-arrow/index.md b/docs/about-arrow/index.md deleted file mode 100644 index db40362..0000000 --- a/docs/about-arrow/index.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -sidebar_position: 1 -slug: /intro -hide_table_of_contents: true ---- - -# About Arrow - -#### What is Arrow? - -Arrow is a global community collaboratively designing and building open source aircraft. We're an internet native organization and use an Ethereum based token to govern ourselves and fund our projects. So far, we've designed a multipurpose quadcopter - Project Quiver. Quiver is currently complete as a devkit and we have units available to loan out to people who wish to develop attachments or features for it. Our projects in current development include a larger multirotor and a small fixed wing drone. We're working towards our goal of building cargo drones and manned aircraft at scale that will achieve Arrow's mission to **increase physical connectedness to the people and places you love**. We lean in heavily towards openness and collaboration. If you're interested in what we're building, please feel welcome to follow along and join in. - -#### Arrow's Organizational Structure - -Arrow has passionate community members and contributors scattered all over the world. We all work together online to design aircraft hardware and periodically have in person meetups to spend quality time together and assemble our designs. Meetings and informal chat mostly take place via Discord, and design and code all stays on Github. Our organization lives online first and foremost. We don't have any offices or "official" in-person presence. The individuals and companies in our community work to manufacture and deploy our designs in the real world. - -We're structured as a Decentralized Autonomous Organization (DAO) on the Ethereum blockchain. We have our own token that is distributed to our contributors. We use it to govern over our shared treasury and make governance decisions about where to focus the organization. Arrow is *not* a company in the traditional sense. We don't have any fiduciary responsibility to token holders and we mostly are doing this out of our own passion to build technology that we see as important for humanity. If you become a token holder, do so out of shared commitment to our vision rather than any expectation of future reward. - -If you want to learn more about or participate in Arrow's governance, please see our [DAO forum](https://dao.arrowair.com/) and [Snapshot](https://snapshot.box/#/s:arrowair.eth) voting page. - -#### Can Arrow really build hardware like this? - -We think so. The software world has clearly shown that open-source is a viable way to build large and successful projects, and we think hardware is starting to look a lot more like software. The tools for design and simulation have gotten advanced enough to solve many problems collaboratively and without requiring hands on hardware. AI tools are only going to increase the efficacy of designing hardware online. A group of engineers working online are able to accomplish things that previously would have required a large team working in person as AI handles much of the administration and legwork.Being internet-first allows us to welcome talented engineers from all over the world, not just from one city. The coordination penalty of a distributed community may not actually be much of a penalty at all. - -We believe that advances in physical AI will quickly follow the advances that we've already seen with LLM-style AI. Humanoid robots may replace our need for large numbers of technicians on the ground, and more specialized robots can handle factory tasks. On-demand manufacturing services that already exist today will likely evolve into something that more resembles AWS for the real world - an army of physical workers ready to be engaged programmatically, just like spinning up an AWS instance. - -A distributed community can also be a huge asset for scaling designs out into the world quickly. Instead of vertical scaling (think giant gigafactory), Arrow optimizes for *horizontal* scaling - we build our designs everywhere, all at once, using our global community. - -#### Why is this important? - -Arrow has a very optimistic culture. We believe that the world can and will be much better in the future, if we build it. That's not to say that we don't see problems in the world - we do. Aerospace especially is dominated by bloated defense contracts and a mentality of guarding secrets. Hardly anyone is working in public trying to genuinely build aircraft for the betterment of humanity. - -We believe that a scaled network of automated VTOL aircraft can improve life for humanity in an incredibly meaningful way. VTOL aircraft require minimal physical infrastructure on the ground and lend themselves well to direct point-to-point flights, instead of routing through a central hub. In that world, people can live anywhere they want and maintain an affordable and fast physical link to the people they love and the things they need, no matter where they are. - -Moreover, we think that building towards our vision as a decentralized community can set a positive example for how humanity can put aside differences and work together for an optimistic future. We hope that other communities will be inspired by Arrow to coordinate together online to build their visions for the future. diff --git a/src/css/custom.css b/src/css/custom.css index 75c4470..6c3dbcc 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -553,7 +553,7 @@ h1, .h1 { --ifm-global-spacing: 1.5rem; --ifm-navbar-item-padding-horizontal: 0.4rem; --ifm-navbar-item-padding-vertical: 0.4rem; - --ifm-navbar-height: 5rem; + --ifm-navbar-height: 115px; /* 72px DocsNavbar + 43px SubNav */ --ifm-color-primary: #535299; --ifm-color-secondary: #1b1464; diff --git a/src/theme/Navbar/DocsNavbar.module.css b/src/theme/Navbar/DocsNavbar.module.css index 4b035bd..563ef17 100644 --- a/src/theme/Navbar/DocsNavbar.module.css +++ b/src/theme/Navbar/DocsNavbar.module.css @@ -1,11 +1,18 @@ +.stickyHeader { + position: sticky; + top: 0; + z-index: 400; +} + .navbar { position: relative; - z-index: 100; + z-index: auto; background: #0943bf; border-bottom: none; height: 72px; display: flex; align-items: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18); } .navbar::after { @@ -54,21 +61,26 @@ display: flex; align-items: center; gap: 6px; - font-family: 'IBM Plex Mono', monospace; - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.05em; + font-family: 'IBM Plex Sans', sans-serif; + font-size: 0.85rem; + font-weight: 400; + letter-spacing: 0; color: rgba(255, 255, 255, 0.7); - text-decoration: none; - padding: 6px 12px; - border: 1px solid rgba(255, 255, 255, 0.2); - transition: color 0.15s ease, border-color 0.15s ease; + text-decoration: underline; + text-underline-offset: 3px; + text-decoration-color: rgba(255, 255, 255, 0.35); + height: 36px; + padding: 0 4px; + border: none; + background: transparent; + cursor: pointer; + transition: color 0.15s ease; } .mainSiteLink:hover { color: #ffffff; - border-color: rgba(255, 255, 255, 0.5); - text-decoration: none; + text-decoration: underline; + text-decoration-color: rgba(255, 255, 255, 0.6); } .searchWrapper { @@ -83,6 +95,7 @@ height: 36px; color: rgba(255, 255, 255, 0.7); border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; background: transparent; text-decoration: none; transition: color 0.15s ease, border-color 0.15s ease; @@ -156,7 +169,7 @@ padding: 4px 8px; background: #1a1f2e; color: #f3f4f6; - font-family: 'IBM Plex Mono', monospace; + font-family: 'Departure Mono', monospace; font-size: 0.7rem; white-space: nowrap; pointer-events: none; @@ -206,42 +219,92 @@ .dropdownMenu { position: absolute; top: calc(100% + 8px); - right: 0; + left: 0; z-index: 200; - background: #1a1f2e; - border: 1px solid rgba(255, 255, 255, 0.15); + background: #0943bf; + border: 1px solid rgba(255, 255, 255, 0.2); min-width: 140px; display: flex; flex-direction: column; + padding: 4px 0; + transform-origin: top left; + animation: dropdownEnter 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes dropdownEnter { + from { + opacity: 0; + transform: translateY(-6px) scale(0.97); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes itemEnter { + from { + opacity: 0; + transform: translateY(-6px); + } + to { + opacity: 1; + transform: translateY(0); + } } :global([data-theme='dark']) .dropdownMenu { - background: #374151; + background: #0943bf; } .dropdownItem { display: flex; align-items: center; gap: 0.5rem; - padding: 0.5rem 0.75rem; - font-family: 'IBM Plex Mono', monospace; - font-size: 0.7rem; - font-weight: 500; - letter-spacing: 0.04em; - color: #f3f4f6 !important; - text-decoration: none !important; - transition: background 0.15s ease; + margin: 2px 6px; + padding: 0.3rem 0.625rem; + border-radius: 4px; + font-family: 'IBM Plex Sans', sans-serif; + font-size: 0.85rem; + font-weight: 400; + letter-spacing: 0; + color: rgba(255, 255, 255, 0.8) !important; + text-decoration: underline !important; + text-underline-offset: 3px; + text-decoration-color: rgba(255, 255, 255, 0.25) !important; + transition: color 0.15s ease, background 0.15s ease; white-space: nowrap; + opacity: 0; + animation: itemEnter 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.chevronIcon { + display: inline-flex; + align-items: center; + transition: transform 0.18s ease; +} + +.chevronOpen { + transform: rotate(180deg); } .dropdownItem:hover { - background: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.12); color: #ffffff !important; + text-decoration-color: rgba(255, 255, 255, 0.5) !important; } -.dropdownItem svg { - width: 16px; - height: 16px; +.dropdownItemIcon { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + flex-shrink: 0; +} + +.dropdownItemIcon svg { + width: 15px; + height: 15px; } /* Hidden original navbar — bar invisible, sidebar overlay still works */ @@ -253,6 +316,224 @@ border: none; } +/* Reading progress bar */ +.progressBar { + height: 4px; + background: #dee1e4; + position: relative; + overflow: hidden; +} + +:global([data-theme='dark']) .progressBar { + background: #0b0f1a; +} + +.progressFill { + height: 100%; + background: rgba(0, 0, 0, 0.2); + transition: width 0.08s linear; +} + +:global([data-theme='dark']) .progressFill { + background: rgba(255, 255, 255, 0.2); +} + +/* SubNav fade — gradient stops at the divider line */ +.subNav::after { + content: ''; + position: absolute; + top: 0; + right: 36px; + bottom: 0; + width: 48px; + background: linear-gradient(to right, transparent, rgba(222, 225, 228, 0.85)); + pointer-events: none; + z-index: 2; + opacity: 0; + transition: opacity 0.2s ease; +} + +.subNavFade::after { + opacity: 1; +} + +:global([data-theme='dark']) .subNav::after { + background: linear-gradient(to right, transparent, rgba(11, 15, 26, 0.85)); +} + +/* Scroll arrow — solid panel with divider line + bouncing chevron */ +.scrollArrow { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 36px; + display: flex; + align-items: center; + justify-content: center; + background: #dee1e4; + border-left: 1px solid rgba(0, 0, 0, 0.15); + z-index: 3; + pointer-events: none; + color: rgba(0, 0, 0, 0.4); +} + +:global([data-theme='dark']) .scrollArrow { + background: #0b0f1a; + border-left-color: rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.45); +} + +.scrollArrow svg { + animation: arrowBounce 1.2s ease-in-out infinite; +} + +@keyframes arrowBounce { + 0%, 100% { transform: translateX(0); } + 50% { transform: translateX(3px); } +} + +/* SubNav bar */ +.subNav { + position: relative; + z-index: 300; + background: #dee1e4; + border-bottom: none; + box-shadow: inset 0 -1px 0 0 #B9BABD; + overflow: visible; +} + +:global([data-theme='dark']) .subNav { + background: #0b0f1a; + box-shadow: inset 0 -1px 0 0 #2e3340; +} + +.subNavInner { + display: flex; + align-items: flex-end; + width: 100%; + height: 43px; + max-width: 1600px; + margin: 0 auto; + padding: 0 0 0 8px; + gap: 0; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; +} + +.subNavInner::-webkit-scrollbar { + display: none; +} + +.subNavTab { + display: flex; + align-items: center; + gap: 8px; + padding: 0 16px; + height: 33px; + margin-top: 8px; + margin-right: 4px; + margin-bottom: -1px; + margin-left: 4px; + border-radius: 5px 5px 0 0; + border: 1px solid rgba(0, 0, 0, 0.12); + border-bottom-color: transparent; + font-family: 'Departure Mono', 'JetBrains Mono', monospace; + font-size: 0.75rem; + font-weight: 400; + letter-spacing: 0.03em; + text-transform: uppercase; + color: rgba(0, 0, 0, 0.45); + text-decoration: none !important; + transition: color 0.15s ease, background 0.15s ease; + white-space: nowrap; +} + +.subNavTabProject { + background: rgba(0, 0, 0, 0.06); + color: rgba(0, 0, 0, 0.55); +} + +:global([data-theme='dark']) .subNavTabProject { + background: rgba(255, 255, 255, 0.08); + color: rgba(255, 255, 255, 0.65); +} + +.subNavTab:hover { + color: rgba(0, 0, 0, 0.75); + background: rgba(0, 0, 0, 0.06); + text-decoration: none !important; +} + +.subNavTabActive:hover { + background: #ffffff; +} + +.subNavTabActive:hover::before { + content: ''; + position: absolute; + inset: 4px; + background: rgba(0, 0, 0, 0.05); + border-radius: 3px; + pointer-events: none; +} + +.subNavTab svg { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.subNavTabActive { + color: #0b0f1a; + background: #ffffff; + font-weight: 500; + height: 35px; + margin-top: 6px; + border-color: #b4b5b6; + border-bottom-color: #ffffff; + position: relative; + z-index: 1; + animation: tabRise 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; +} + +@keyframes tabRise { + from { + transform: translateY(8px); + opacity: 0.6; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.activeTabBg { + position: absolute; + inset: 0; + background: #ffffff; + border-radius: 5px 5px 0 0; + z-index: -1; +} + +:global([data-theme='dark']) .subNavTab { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.12); +} + +:global([data-theme='dark']) .subNavTab:hover { + color: rgba(255, 255, 255, 0.85); + background: rgba(255, 255, 255, 0.06); +} + +:global([data-theme='dark']) .subNavTabActive { + color: #ffffff; + background: var(--ifm-background-color); + border-color: #4a4f5a; + border-bottom-color: var(--ifm-background-color); +} + /* Mobile responsive */ @media (max-width: 996px) { .menuToggle { @@ -303,4 +584,19 @@ width: 90px; height: auto; } + + .subNavInner { + padding-left: 4px; + } + + .subNavTab { + padding: 0 10px; + font-size: 0.72rem; + gap: 6px; + } + + .subNavTab svg { + width: 14px; + height: 14px; + } } diff --git a/src/theme/Navbar/index.tsx b/src/theme/Navbar/index.tsx index 836c564..2c6c00f 100644 --- a/src/theme/Navbar/index.tsx +++ b/src/theme/Navbar/index.tsx @@ -1,7 +1,8 @@ -import React, {useState, useRef, useEffect} from 'react'; +import React, {useState, useRef, useEffect, useCallback} from 'react'; import NavbarOriginal from '@theme-original/Navbar'; import Link from '@docusaurus/Link'; import {useColorMode} from '@docusaurus/theme-common'; +import {useLocation} from '@docusaurus/router'; import SearchBar from '@theme/SearchBar'; import styles from './DocsNavbar.module.css'; @@ -36,17 +37,16 @@ function DiscordIcon() { function SunIcon() { return ( - - - + + ); } function MoonIcon() { return ( - - + + ); } @@ -146,6 +146,270 @@ function LinksDropdown() { ); } +function DocsIcon() { + return ( + + + + ); +} + +function BookIcon() { + return ( + + + + ); +} + +function QuiverIcon() { + return ( + + + + ); +} + +function SpearheadIcon() { + return ( + + + + ); +} + +function ChevronRightIcon() { + return ( + + + + ); +} + +function ChevronDownIcon() { + return ( + + + + ); +} + +const QUICK_LINKS = [ + { + label: 'Main Site', + href: 'https://www.arrowair.com', + external: true, + icon: ( + + + + ), + }, + { + label: 'Community', + href: 'https://www.arrowair.com/community', + external: true, + icon: ( + + + + ), + }, + { + label: 'Engineering', + href: 'https://www.arrowair.com/engineering', + external: true, + icon: ( + + + + ), + }, + { + label: 'Bounty Board', + href: '/bounty', + external: false, + icon: ( + + + + ), + }, +]; + +function QuickLinksDropdown() { + const [open, setOpen] = useState(false); + const ref = useRef(null); + + return ( +
setOpen(true)} + onMouseLeave={() => setOpen(false)} + > + + {open && ( +
+ {QUICK_LINKS.map((link, i) => ( + link.external ? ( + setOpen(false)} + > + {link.icon} + {link.label} + + ) : ( + setOpen(false)} + > + {link.icon} + {link.label} + + ) + ))} +
+ )} +
+ ); +} + +function FlightTrackingIcon() { + return ( + + + + ); +} + +function BountyIcon() { + return ( + + + + ); +} + +const SUBNAV_TABS = [ + { label: 'Arrow', path: '/docs', to: '/docs/intro', icon: }, + { label: 'Quiver', path: '/quiver', to: '/quiver', icon: }, + { label: 'Spearhead', path: '/spearhead', to: '/spearhead', icon: }, + { label: 'Flight Tracking App', path: '/flight-tracking', to: '/flight-tracking', icon: }, + { label: 'Bounty Board', path: '/bounty', to: '/bounty', icon: }, +]; + +function SubNav() { + const {pathname} = useLocation(); + const isDocPage = SUBNAV_TABS.some(t => pathname.startsWith(t.path)); + const innerRef = useRef(null); + const [showFade, setShowFade] = useState(false); + + useEffect(() => { + const inner = innerRef.current; + if (!inner) return; + + const checkFade = () => { + const isScrollable = inner.scrollWidth > inner.clientWidth; + const isAtEnd = inner.scrollLeft + inner.clientWidth >= inner.scrollWidth - 8; + setShowFade(isScrollable && !isAtEnd); + }; + + // Scroll active tab into view without scrolling the page + const activeTab = inner.querySelector(`[class*="subNavTabActive"]`) as HTMLElement; + if (activeTab) { + const tabRight = activeTab.offsetLeft + activeTab.offsetWidth; + const tabLeft = activeTab.offsetLeft; + if (tabRight > inner.clientWidth) { + inner.scrollLeft = tabRight - inner.clientWidth + 16; + } else if (tabLeft < inner.scrollLeft) { + inner.scrollLeft = tabLeft - 16; + } + } + + checkFade(); + inner.addEventListener('scroll', checkFade); + window.addEventListener('resize', checkFade); + return () => { + inner.removeEventListener('scroll', checkFade); + window.removeEventListener('resize', checkFade); + }; + }, [pathname]); + + if (!isDocPage) return null; + + return ( + + ); +} + +function ReadingProgressBar() { + const [progress, setProgress] = useState(0); + const {pathname} = useLocation(); + const isDocPage = SUBNAV_TABS.some(t => pathname.startsWith(t.path)); + + const update = useCallback(() => { + const scrollTop = window.scrollY; + const docHeight = document.documentElement.scrollHeight - window.innerHeight; + setProgress(docHeight > 0 ? (scrollTop / docHeight) * 100 : 0); + }, []); + + useEffect(() => { + if (!isDocPage) return; + window.addEventListener('scroll', update, {passive: true}); + update(); + return () => window.removeEventListener('scroll', update); + }, [isDocPage, update]); + + if (!isDocPage) return null; + + return ( +
+
+
+ ); +} + function DocsNavbar() { const openSidebar = () => { const toggle = document.querySelector('.navbar__toggle'); @@ -172,15 +436,27 @@ function DocsNavbar() { {/* Right Actions */}
- - - - - MAIN SITE - +
+ + + + Docs home +
+
+ + + + About Arrow +
+
@@ -223,7 +499,10 @@ function DocsNavbar() { export default function Navbar(props): JSX.Element { return ( <> - +
+ + +
{/* Original navbar hidden visually but sidebar overlay remains functional */}