From 200b7a66126592eb209f22976b38809b7ad55e78 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:17:20 -0500 Subject: [PATCH 01/21] update to new product picker look --- packages/LOCAL-DEV.md | 39 +++- packages/docusaurus-theme/css/mega-menu.css | 179 ++++++++++++++++++ packages/docusaurus-theme/package.json | 1 + .../DropdownNavbarItem/Desktop/index.tsx | 125 ++++++++++++ .../theme/docusaurus-theme-modules.d.ts | 17 ++ packages/docusaurus-theme/tsconfig.json | 7 +- packages/test-site/docusaurus.config.ts | 35 +++- 7 files changed, 387 insertions(+), 16 deletions(-) create mode 100644 packages/docusaurus-theme/css/mega-menu.css create mode 100644 packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx create mode 100644 packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts diff --git a/packages/LOCAL-DEV.md b/packages/LOCAL-DEV.md index a931f64..7fe9a0a 100644 --- a/packages/LOCAL-DEV.md +++ b/packages/LOCAL-DEV.md @@ -38,22 +38,44 @@ lives in `remotes/` with its own `docusaurus.config.ts` (for standalone use) and ## Step 1: Develop with test-site -The test-site references the theme via local path — React component changes reflect -instantly with hot reload: +The test-site references the theme via local path. Start the dev server: ```bash cd packages/test-site yarn start ``` -Edit files in `packages/docusaurus-theme/src/*` — HMR works automatically. +### What needs a rebuild -**No build step needed** for component/JS changes. +| What you changed | What to do | +|---|---| +| `src/` components (React/TS) | Nothing — webpack HMR picks them up instantly | +| `theme/` component overrides | Rebuild needed — see watch mode below | +| `css/` stylesheets | Rebuild needed — see below | -### CSS changes require a theme build +### Watching `theme/` changes during development -Theme CSS files (in `packages/docusaurus-theme/css/`) are loaded from the built -package, not watched by the dev server. After editing CSS: +`theme/` overrides (e.g. swizzled Docusaurus components) are compiled to `dist/theme/` +and served from there. Run the TypeScript compiler in watch mode in a second terminal +so changes are recompiled automatically: + +```bash +# Terminal 1 — theme watcher +cd packages/docusaurus-theme +yarn watch + +# Terminal 2 — test site dev server +cd packages/test-site +yarn start +``` + +On every save in `theme/`, `tsc --watch` recompiles in ~1 second and Docusaurus +hot-reloads the result. + +### CSS changes + +Theme CSS files (`packages/docusaurus-theme/css/`) are not watched automatically. +After editing CSS run a full build: ```bash cd packages/docusaurus-theme @@ -156,7 +178,8 @@ yarn build | Stage | Theme build? | Command | |-------|-------------|---------| -| Dev with test-site (JS/React) | No | `cd packages/test-site && yarn start` | +| Dev with test-site (`src/` components) | No | `cd packages/test-site && yarn start` | +| Dev with test-site (`theme/` overrides) | Watch mode | `cd packages/docusaurus-theme && yarn watch` | | Dev with test-site (CSS) | Yes | `cd packages/docusaurus-theme && yarn build` | | Test with file: | Yes | `yarn build` then remote `yarn install --force` | | Publish | Yes | `npm version patch && npm publish` | diff --git a/packages/docusaurus-theme/css/mega-menu.css b/packages/docusaurus-theme/css/mega-menu.css new file mode 100644 index 0000000..229ac9a --- /dev/null +++ b/packages/docusaurus-theme/css/mega-menu.css @@ -0,0 +1,179 @@ +/* ========================================================================= + MEGA-MENU / PRODUCT PICKER + Styles for Products and Resources mega-menu dropdowns in the navbar. + + Import in docusaurus.config.ts customCss: + require.resolve('@netfoundry/docusaurus-theme/css/mega-menu.css') + ========================================================================= */ + +/* ── Product / resource icons inside the menu ───────────────────────────── */ +.mega-logo { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; margin-right: 0.8rem; } +.mega-icon { width: 20px; height: 20px; margin-right: 0.8rem; background-color: var(--ifm-color-primary); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; display: inline-block; } + +/* zrok logo needs a subtle shadow in light mode (white logo on white bg) */ +[data-theme='light'] img[src*="zrok-logo"] { filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.55)); } + +/* ── Dropdown trigger button in the navbar ──────────────────────────────── */ +.nf-mega-dropdown, +.nf-resources-dropdown { + font-weight: 600 !important; + color: var(--ifm-font-color-base) !important; + position: relative; + padding-right: 1.2rem !important; + transition: color 0.3s ease; +} + +.nf-mega-dropdown:hover, +.nf-resources-dropdown:hover, +.navbar__item.dropdown--show .nf-mega-dropdown, +.navbar__item.dropdown--show .nf-resources-dropdown { + color: var(--ifm-color-primary) !important; + text-decoration: none !important; +} + +/* Animated chevron */ +.nf-mega-dropdown::after, +.nf-resources-dropdown::after { + content: ''; + position: absolute; + right: 0.2rem; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid currentColor; + transition: all 0.3s ease; + opacity: 0.6; +} + +/* Hover: chevron drops slightly */ +.navbar__link.nf-mega-dropdown:hover::after, +.navbar__link.nf-resources-dropdown:hover::after { + transform: translateY(-20%); + opacity: 1; +} + +/* Open: chevron rotates 180° */ +.navbar__item.dropdown--show .nf-mega-dropdown::after, +.navbar__item.dropdown--show .nf-resources-dropdown::after { + transform: translateY(-50%) rotate(180deg); + opacity: 1; +} + +/* Dark mode: cyan accent on hover */ +[data-theme='dark'] .nf-mega-dropdown:hover, +[data-theme='dark'] .nf-resources-dropdown:hover, +[data-theme='dark'] .navbar__item.dropdown--show .nf-mega-dropdown, +[data-theme='dark'] .navbar__item.dropdown--show .nf-resources-dropdown { + color: #22d3ee !important; +} + +/* ── Mega-menu panel (fixed dropdown) ───────────────────────────────────── */ + +/* + * CSS bridge: an invisible pseudo-element that extends the panel's hit area + * upward by 20 px. When the cursor moves from the trigger button down into + * this transparent strip, mouseenter fires on the panel (ul) immediately — + * cancelling the hide timer before it expires — so the menu never flickers + * during diagonal movement across the gap between navbar and panel. + */ +.dropdown__menu:has(.mega-menu-content)::before { + content: ''; + display: block; + position: absolute; + top: -20px; + left: 0; + right: 0; + height: 20px; +} + +.dropdown__menu:has(.mega-menu-content) { + position: fixed !important; + top: 3.75rem !important; + left: 50% !important; + transform: translateX(-50%) !important; + width: 85vw !important; + max-width: 1000px !important; + padding: 1.5rem 2rem !important; + border-radius: 12px !important; + border: 1px solid rgba(0, 118, 255, 0.12) !important; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15) !important; + background: var(--ifm-card-background-color) !important; + z-index: 1000 !important; +} + +/* Narrower panel for the 2-column Resources menu */ +.dropdown__menu:has(.mega-menu-resources) { max-width: 700px !important; } + +/* Ensure visibility when open */ +.dropdown--show > .dropdown__menu, +.dropdown:hover > .dropdown__menu { + display: block !important; + visibility: visible !important; + opacity: 1 !important; +} + +/* ── Grid layout ────────────────────────────────────────────────────────── */ +.mega-menu-content { display: grid !important; grid-template-columns: 1fr 1fr 1fr !important; gap: 2rem !important; } +.mega-menu-resources { grid-template-columns: 1fr 1fr !important; } + +/* ── Column headers ─────────────────────────────────────────────────────── */ +.mega-header { + font-size: 0.7rem !important; + font-weight: 900 !important; + color: #94a3b8 !important; + text-transform: uppercase !important; + letter-spacing: 0.1em !important; + border-bottom: 2px solid rgba(148, 163, 184, 0.2) !important; + display: block !important; + padding-bottom: 0.5rem !important; + margin-bottom: 0.75rem !important; +} +.mega-header--managed { color: #22d3ee !important; border-bottom-color: rgba(34, 211, 238, 0.3) !important; } +.mega-header--opensource { color: #22c55e !important; border-bottom-color: rgba(34, 197, 94, 0.3) !important; } +.mega-header--infra { color: #94a3b8 !important; border-bottom-color: rgba(148, 163, 184, 0.2) !important; } + +[data-theme='light'] .mega-header--managed { color: #0891b2 !important; border-bottom-color: rgba(8, 145, 178, 0.3) !important; } +[data-theme='light'] .mega-header--opensource { color: #16a34a !important; border-bottom-color: rgba(22, 163, 74, 0.3) !important; } + +/* ── Product / resource links ───────────────────────────────────────────── */ +.mega-link { + display: flex !important; + align-items: flex-start !important; + gap: 0.75rem !important; + padding: 0.55rem 0.65rem !important; + text-decoration: none !important; + transition: all 0.2s ease !important; + border-radius: 8px; +} +.mega-link:hover { background: rgba(0, 118, 255, 0.06) !important; transform: translateX(3px) !important; } +.mega-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block !important; } +.mega-link span { color: #64748b; font-size: 0.82rem; display: block !important; margin-top: 2px; line-height: 1.35; } + +/* ── Mobile (<= 996 px) ─────────────────────────────────────────────────── */ +@media (max-width: 996px) { + /* Fixed grid makes no sense in the narrow sidebar — hide the raw HTML blob */ + .menu__list-item:has(> .mega-menu-content) { display: none !important; } + + /* Docusaurus already renders a chevron; hide ours to avoid doubles */ + .nf-mega-dropdown::after, + .nf-resources-dropdown::after { display: none !important; } + + .nf-mega-dropdown, + .nf-resources-dropdown { padding-right: 0.5rem !important; } +} + +/* ── Desktop (>= 997 px) ────────────────────────────────────────────────── */ +@media (min-width: 997px) { + /* Hide mobile-only fallback links when mega menu is active */ + .dropdown__menu .mobile-nav-link { display: none !important; } + + /* Force panel visible when JS sets dropdown--show (swizzled component) */ + .navbar__item.dropdown--show .dropdown__menu:has(.mega-menu-content) { + display: block !important; + visibility: visible !important; + opacity: 1 !important; + } +} diff --git a/packages/docusaurus-theme/package.json b/packages/docusaurus-theme/package.json index 6686b7d..a8e0794 100644 --- a/packages/docusaurus-theme/package.json +++ b/packages/docusaurus-theme/package.json @@ -12,6 +12,7 @@ "scripts": { "clean": "node scripts/clean.mjs", "build": "yarn clean && tsc && yarn build:css", + "watch": "tsc --watch", "build:css": "node scripts/build-css.mjs", "test": "jest", "prepublishOnly": "yarn build && yarn test" diff --git a/packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx new file mode 100644 index 0000000..5a70570 --- /dev/null +++ b/packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx @@ -0,0 +1,125 @@ +/** + * Swizzled DropdownNavbarItemDesktop + * + * Opens on hover. The panel stays open until the user actually moves their + * cursor into it — so the gap between the trigger and the fixed panel never + * causes a flicker. Once the cursor has entered the panel, leaving it + * (or clicking outside) closes it normally. + */ +import React, {useState, useRef, useEffect, useCallback} from 'react'; +import clsx from 'clsx'; +import NavbarNavLinkOrig from '@theme/NavbarItem/NavbarNavLink'; +import NavbarItemOrig from '@theme/NavbarItem'; + +const NavbarNavLink = NavbarNavLinkOrig as React.ComponentType; +const NavbarItem = NavbarItemOrig as React.ComponentType; + +export default function DropdownNavbarItemDesktop({ + items, + position, + className, + onClick, + ...props +}: any) { + const dropdownRef = useRef(null); + const hasEnteredPanel = useRef(false); + const [showDropdown, setShowDropdown] = useState(false); + + // Close on click / touch outside + useEffect(() => { + const close = (e: MouseEvent | TouchEvent) => { + if (!dropdownRef.current?.contains(e.target as Node)) { + setShowDropdown(false); + hasEnteredPanel.current = false; + } + }; + document.addEventListener('mousedown', close); + document.addEventListener('touchstart', close); + return () => { + document.removeEventListener('mousedown', close); + document.removeEventListener('touchstart', close); + }; + }, []); + + // Sync: close other open megamenus when this one opens + useEffect(() => { + const onOtherOpen = (e: any) => { + if (e.detail.label !== props.label) { + setShowDropdown(false); + hasEnteredPanel.current = false; + } + }; + window.addEventListener('nf-megamenu:open', onOtherOpen); + return () => window.removeEventListener('nf-megamenu:open', onOtherOpen); + }, [props.label]); + + // Open on hover — reset entry state each time + const handleMouseEnter = useCallback(() => { + hasEnteredPanel.current = false; + window.dispatchEvent(new CustomEvent('nf-megamenu:open', {detail: {label: props.label}})); + setShowDropdown(true); + console.log('[mega-menu] popped open:', props.label); + }, [props.label]); + + // Leaving the trigger: do nothing — the panel stays open until the user + // either enters it (then leaves) or clicks outside. + const handleTriggerLeave = useCallback(() => { + console.log('[mega-menu] trigger leave — hasEnteredPanel:', hasEnteredPanel.current); + }, []); + + // Once the cursor enters the panel, normal leave/blur can close it + const handlePanelEnter = useCallback(() => { + hasEnteredPanel.current = true; + console.log('[mega-menu] panel focus obtained'); + }, []); + + const handlePanelLeave = useCallback(() => { + console.log('[mega-menu] panel focus lost — hasEnteredPanel:', hasEnteredPanel.current); + if (hasEnteredPanel.current) { + setShowDropdown(false); + hasEnteredPanel.current = false; + console.log('[mega-menu] closing — cursor left panel'); + } + }, []); + + return ( +
+ e.preventDefault()} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + setShowDropdown(prev => !prev); + } + }}> + {props.children ?? props.label} + +
    + {items.map((childItemProps: any, i: number) => ( + + ))} +
+
+ ); +} diff --git a/packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts b/packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts new file mode 100644 index 0000000..6f15d81 --- /dev/null +++ b/packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts @@ -0,0 +1,17 @@ +/** + * Type stubs for Docusaurus @theme/* virtual modules. + * These are resolved at runtime by Docusaurus's webpack aliases and are not + * available to the standalone TypeScript compiler. Declared as `any`-based + * components here since all call sites cast them to ComponentType anyway. + */ +declare module '@theme/NavbarItem/NavbarNavLink' { + import type React from 'react'; + const NavbarNavLink: React.ComponentType; + export default NavbarNavLink; +} + +declare module '@theme/NavbarItem' { + import type React from 'react'; + const NavbarItem: React.ComponentType; + export default NavbarItem; +} diff --git a/packages/docusaurus-theme/tsconfig.json b/packages/docusaurus-theme/tsconfig.json index 480d588..56ae35a 100644 --- a/packages/docusaurus-theme/tsconfig.json +++ b/packages/docusaurus-theme/tsconfig.json @@ -17,5 +17,10 @@ } }, "include": ["src/**/*", "theme/**/*"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist"], + "watchOptions": { + "watchFile": "dynamicPriorityPolling", + "watchDirectory": "dynamicPriorityPolling", + "fallbackPolling": "dynamicPriority" + } } diff --git a/packages/test-site/docusaurus.config.ts b/packages/test-site/docusaurus.config.ts index 5e5d16c..b6d4c1b 100644 --- a/packages/test-site/docusaurus.config.ts +++ b/packages/test-site/docusaurus.config.ts @@ -112,7 +112,10 @@ export default { }, blog: false, theme: { - customCss: require.resolve('./src/custom/custom.css'), + customCss: [ + require.resolve('./src/custom/custom.css'), + require.resolve('../docusaurus-theme/css/mega-menu.css'), + ], } } ] @@ -147,14 +150,32 @@ export default { }, items: [ { - label: 'Docs', + type: 'dropdown', + label: 'Products', position: 'left', + className: 'nf-mega-dropdown', items: [ - { to: '/docs/openziti', label: 'OpenZiti' }, - { to: '/docs/frontdoor', label: 'Frontdoor' }, - { to: '/docs/onprem', label: 'On-Prem' }, - { to: '/docs/zlan', label: 'zLAN' }, - { to: '/docs/zrok', label: 'zrok' }, + { + type: 'html', + value: ` + `, + }, ], }, { From 2e9beba23535b64a0f24f0501fcd2ed57ed81000 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:15:13 -0500 Subject: [PATCH 02/21] updates to prodcut menu --- packages/docusaurus-theme/css/mega-menu.css | 29 ++++- .../theme/NavbarItem/ComponentTypes.tsx | 14 +++ .../types/ProductsMegaMenu/index.tsx | 116 ++++++++++++++++++ .../theme/docusaurus-theme-modules.d.ts | 15 +++ packages/test-site/docusaurus.config.ts | 48 ++++---- 5 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx create mode 100644 packages/docusaurus-theme/theme/NavbarItem/types/ProductsMegaMenu/index.tsx diff --git a/packages/docusaurus-theme/css/mega-menu.css b/packages/docusaurus-theme/css/mega-menu.css index 229ac9a..2817c9e 100644 --- a/packages/docusaurus-theme/css/mega-menu.css +++ b/packages/docusaurus-theme/css/mega-menu.css @@ -70,7 +70,34 @@ color: #22d3ee !important; } -/* ── Mega-menu panel (fixed dropdown) ───────────────────────────────────── */ +/* ── ProductsMegaMenu panel (custom navbar item) ────────────────────────── */ +.nf-mega-panel { + position: fixed; + top: 3.75rem; + left: 50%; + transform: translateX(-50%); + width: 85vw; + max-width: 1000px; + padding: 1.5rem 2rem; + border-radius: 12px; + border: 1px solid rgba(0, 118, 255, 0.12); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15); + background: var(--ifm-card-background-color); + z-index: 1000; +} + +/* CSS bridge: transparent strip above the panel eats the gap */ +.nf-mega-panel::before { + content: ''; + display: block; + position: absolute; + top: -20px; + left: 0; + right: 0; + height: 20px; +} + +/* ── Mega-menu panel (legacy html-type dropdown) ─────────────────────────── */ /* * CSS bridge: an invisible pseudo-element that extends the panel's hit area diff --git a/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx b/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx new file mode 100644 index 0000000..256e601 --- /dev/null +++ b/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx @@ -0,0 +1,14 @@ +import ProductsMegaMenu from './types/ProductsMegaMenu'; + +// @theme-original resolves to OUR OWN file in a plugin theme (Docusaurus sets +// both @theme and @theme-original to the plugin file). @theme-init resolves to +// the version from the upstream theme (theme-classic) — which is what we want. +// require() (not import) prevents webpack from hoisting this into the ESM init +// order, which would cause "__WEBPACK_DEFAULT_EXPORT__ before initialization". +// eslint-disable-next-line @typescript-eslint/no-var-requires +const ComponentTypesOrig = require('@theme-init/NavbarItem/ComponentTypes').default as Record; + +export default { + ...ComponentTypesOrig, + 'custom-productsMegaMenu': ProductsMegaMenu, +}; diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/ProductsMegaMenu/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/ProductsMegaMenu/index.tsx new file mode 100644 index 0000000..03765dc --- /dev/null +++ b/packages/docusaurus-theme/theme/NavbarItem/types/ProductsMegaMenu/index.tsx @@ -0,0 +1,116 @@ +import React, {useState, useRef, useEffect, useCallback} from 'react'; +import Link from '@docusaurus/Link'; +import clsx from 'clsx'; + +export type MegaMenuLink = { + label: string; + to: string; + logo?: string; + description?: string; +}; + +export type MegaMenuColumn = { + header: string; + headerClass?: string; + links: MegaMenuLink[]; +}; + +type Props = { + label?: string; + position?: 'left' | 'right'; + columns: MegaMenuColumn[]; + className?: string; +}; + +export default function ProductsMegaMenu({label = 'Products', columns, className}: Props) { + const wrapRef = useRef(null); + const hasEnteredPanel = useRef(false); + const [open, setOpen] = useState(false); + + const close = useCallback(() => { + setOpen(false); + hasEnteredPanel.current = false; + }, []); + + // Close on outside click/touch + useEffect(() => { + const onOutside = (e: MouseEvent | TouchEvent) => { + if (!wrapRef.current?.contains(e.target as Node)) close(); + }; + document.addEventListener('mousedown', onOutside); + document.addEventListener('touchstart', onOutside); + return () => { + document.removeEventListener('mousedown', onOutside); + document.removeEventListener('touchstart', onOutside); + }; + }, [close]); + + // Sync: close when another megamenu opens + useEffect(() => { + const onOtherOpen = (e: any) => { + if (e.detail.label !== label) close(); + }; + window.addEventListener('nf-megamenu:open', onOtherOpen); + return () => window.removeEventListener('nf-megamenu:open', onOtherOpen); + }, [label, close]); + + const handleTriggerEnter = useCallback(() => { + hasEnteredPanel.current = false; + window.dispatchEvent(new CustomEvent('nf-megamenu:open', {detail: {label}})); + setOpen(true); + }, [label]); + + // Stay open until user enters the panel — no timer + const handleTriggerLeave = useCallback(() => {}, []); + + const handlePanelEnter = useCallback(() => { + hasEnteredPanel.current = true; + }, []); + + const handlePanelLeave = useCallback(() => { + if (hasEnteredPanel.current) close(); + }, [close]); + + return ( +
+ { e.preventDefault(); setOpen(o => !o); }} + onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); setOpen(o => !o); } }}> + {label} + + {open && ( +
+
+ {columns.map((col, i) => ( +
+ {col.header} + {col.links.map((link, j) => ( + + {link.logo && } +
+ {link.label} + {link.description && {link.description}} +
+ + ))} +
+ ))} +
+
+ )} +
+ ); +} diff --git a/packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts b/packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts index 6f15d81..1e35bd3 100644 --- a/packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts +++ b/packages/docusaurus-theme/theme/docusaurus-theme-modules.d.ts @@ -4,6 +4,21 @@ * available to the standalone TypeScript compiler. Declared as `any`-based * components here since all call sites cast them to ComponentType anyway. */ +declare module '@docusaurus/Link' { + const Link: any; + export default Link; +} + +declare module '@theme-original/NavbarItem/ComponentTypes' { + const ComponentTypes: any; + export default ComponentTypes; +} + +declare module '@theme-init/NavbarItem/ComponentTypes' { + const ComponentTypes: any; + export default ComponentTypes; +} + declare module '@theme/NavbarItem/NavbarNavLink' { import type React from 'react'; const NavbarNavLink: React.ComponentType; diff --git a/packages/test-site/docusaurus.config.ts b/packages/test-site/docusaurus.config.ts index b6d4c1b..0b575fd 100644 --- a/packages/test-site/docusaurus.config.ts +++ b/packages/test-site/docusaurus.config.ts @@ -150,31 +150,33 @@ export default { }, items: [ { - type: 'dropdown', - label: 'Products', + type: 'custom-productsMegaMenu', position: 'left', - className: 'nf-mega-dropdown', - items: [ + label: 'Products', + columns: [ + { + header: 'Managed Cloud', + headerClass: 'mega-header--managed', + links: [ + { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, + { label: 'Frontdoor', to: '/docs/frontdoor', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Secure application access gateway.' }, + ], + }, + { + header: 'Open Source', + headerClass: 'mega-header--opensource', + links: [ + { label: 'OpenZiti', to: '/docs/openziti', description: 'Programmable zero-trust mesh infrastructure.' }, + { label: 'zrok', to: '/docs/zrok', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, + ], + }, { - type: 'html', - value: ` - `, + header: 'Your own infrastructure', + headerClass: 'mega-header--infra', + links: [ + { label: 'Self-Hosted', to: '/docs/onprem', description: 'Deploy the full stack in your own environment.' }, + { label: 'zLAN', to: '/docs/zlan', description: 'Zero-trust access for OT networks.' }, + ], }, ], }, From cbbf22f0f1f9ab588547003ff1030db4340a6189 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:36:04 -0500 Subject: [PATCH 03/21] rename from megamenu to productpicker --- .../css/{mega-menu.css => product-picker.css} | 76 +++++++++---------- .../theme/NavbarItem/ComponentTypes.tsx | 4 +- .../DropdownNavbarItem/Desktop/index.tsx | 16 ++-- .../index.tsx | 36 ++++----- packages/test-site/docusaurus.config.ts | 10 +-- 5 files changed, 71 insertions(+), 71 deletions(-) rename packages/docusaurus-theme/css/{mega-menu.css => product-picker.css} (66%) rename packages/docusaurus-theme/theme/NavbarItem/types/{ProductsMegaMenu => ProductPicker}/index.tsx (70%) diff --git a/packages/docusaurus-theme/css/mega-menu.css b/packages/docusaurus-theme/css/product-picker.css similarity index 66% rename from packages/docusaurus-theme/css/mega-menu.css rename to packages/docusaurus-theme/css/product-picker.css index 2817c9e..8473577 100644 --- a/packages/docusaurus-theme/css/mega-menu.css +++ b/packages/docusaurus-theme/css/product-picker.css @@ -1,20 +1,20 @@ /* ========================================================================= - MEGA-MENU / PRODUCT PICKER - Styles for Products and Resources mega-menu dropdowns in the navbar. + PRODUCT PICKER + Styles for the Products product-picker dropdown in the navbar. Import in docusaurus.config.ts customCss: - require.resolve('@netfoundry/docusaurus-theme/css/mega-menu.css') + require.resolve('@netfoundry/docusaurus-theme/css/product-picker.css') ========================================================================= */ -/* ── Product / resource icons inside the menu ───────────────────────────── */ -.mega-logo { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; margin-right: 0.8rem; } -.mega-icon { width: 20px; height: 20px; margin-right: 0.8rem; background-color: var(--ifm-color-primary); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; display: inline-block; } +/* ── Product / resource icons inside the picker ─────────────────────────── */ +.picker-logo { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; margin-right: 0.8rem; } +.picker-icon { width: 20px; height: 20px; margin-right: 0.8rem; background-color: var(--ifm-color-primary); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; display: inline-block; } /* zrok logo needs a subtle shadow in light mode (white logo on white bg) */ [data-theme='light'] img[src*="zrok-logo"] { filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.55)); } /* ── Dropdown trigger button in the navbar ──────────────────────────────── */ -.nf-mega-dropdown, +.nf-picker-trigger, .nf-resources-dropdown { font-weight: 600 !important; color: var(--ifm-font-color-base) !important; @@ -23,16 +23,16 @@ transition: color 0.3s ease; } -.nf-mega-dropdown:hover, +.nf-picker-trigger:hover, .nf-resources-dropdown:hover, -.navbar__item.dropdown--show .nf-mega-dropdown, +.navbar__item.dropdown--show .nf-picker-trigger, .navbar__item.dropdown--show .nf-resources-dropdown { color: var(--ifm-color-primary) !important; text-decoration: none !important; } /* Animated chevron */ -.nf-mega-dropdown::after, +.nf-picker-trigger::after, .nf-resources-dropdown::after { content: ''; position: absolute; @@ -49,29 +49,29 @@ } /* Hover: chevron drops slightly */ -.navbar__link.nf-mega-dropdown:hover::after, +.navbar__link.nf-picker-trigger:hover::after, .navbar__link.nf-resources-dropdown:hover::after { transform: translateY(-20%); opacity: 1; } /* Open: chevron rotates 180° */ -.navbar__item.dropdown--show .nf-mega-dropdown::after, +.nf-picker--open .nf-picker-trigger::after, .navbar__item.dropdown--show .nf-resources-dropdown::after { transform: translateY(-50%) rotate(180deg); opacity: 1; } /* Dark mode: cyan accent on hover */ -[data-theme='dark'] .nf-mega-dropdown:hover, +[data-theme='dark'] .nf-picker-trigger:hover, [data-theme='dark'] .nf-resources-dropdown:hover, -[data-theme='dark'] .navbar__item.dropdown--show .nf-mega-dropdown, +[data-theme='dark'] .nf-picker--open .nf-picker-trigger, [data-theme='dark'] .navbar__item.dropdown--show .nf-resources-dropdown { color: #22d3ee !important; } -/* ── ProductsMegaMenu panel (custom navbar item) ────────────────────────── */ -.nf-mega-panel { +/* ── ProductPicker panel (custom navbar item) ───────────────────────────── */ +.nf-picker-panel { position: fixed; top: 3.75rem; left: 50%; @@ -87,7 +87,7 @@ } /* CSS bridge: transparent strip above the panel eats the gap */ -.nf-mega-panel::before { +.nf-picker-panel::before { content: ''; display: block; position: absolute; @@ -97,7 +97,7 @@ height: 20px; } -/* ── Mega-menu panel (legacy html-type dropdown) ─────────────────────────── */ +/* ── Legacy html-type dropdown panel ────────────────────────────────────── */ /* * CSS bridge: an invisible pseudo-element that extends the panel's hit area @@ -106,7 +106,7 @@ * cancelling the hide timer before it expires — so the menu never flickers * during diagonal movement across the gap between navbar and panel. */ -.dropdown__menu:has(.mega-menu-content)::before { +.dropdown__menu:has(.picker-content)::before { content: ''; display: block; position: absolute; @@ -116,7 +116,7 @@ height: 20px; } -.dropdown__menu:has(.mega-menu-content) { +.dropdown__menu:has(.picker-content) { position: fixed !important; top: 3.75rem !important; left: 50% !important; @@ -132,7 +132,7 @@ } /* Narrower panel for the 2-column Resources menu */ -.dropdown__menu:has(.mega-menu-resources) { max-width: 700px !important; } +.dropdown__menu:has(.picker-resources) { max-width: 700px !important; } /* Ensure visibility when open */ .dropdown--show > .dropdown__menu, @@ -143,11 +143,11 @@ } /* ── Grid layout ────────────────────────────────────────────────────────── */ -.mega-menu-content { display: grid !important; grid-template-columns: 1fr 1fr 1fr !important; gap: 2rem !important; } -.mega-menu-resources { grid-template-columns: 1fr 1fr !important; } +.picker-content { display: grid !important; grid-template-columns: 1fr 1fr 1fr !important; gap: 2rem !important; } +.picker-resources { grid-template-columns: 1fr 1fr !important; } /* ── Column headers ─────────────────────────────────────────────────────── */ -.mega-header { +.picker-header { font-size: 0.7rem !important; font-weight: 900 !important; color: #94a3b8 !important; @@ -158,15 +158,15 @@ padding-bottom: 0.5rem !important; margin-bottom: 0.75rem !important; } -.mega-header--managed { color: #22d3ee !important; border-bottom-color: rgba(34, 211, 238, 0.3) !important; } -.mega-header--opensource { color: #22c55e !important; border-bottom-color: rgba(34, 197, 94, 0.3) !important; } -.mega-header--infra { color: #94a3b8 !important; border-bottom-color: rgba(148, 163, 184, 0.2) !important; } +.picker-header--managed { color: #22d3ee !important; border-bottom-color: rgba(34, 211, 238, 0.3) !important; } +.picker-header--opensource { color: #22c55e !important; border-bottom-color: rgba(34, 197, 94, 0.3) !important; } +.picker-header--infra { color: #94a3b8 !important; border-bottom-color: rgba(148, 163, 184, 0.2) !important; } -[data-theme='light'] .mega-header--managed { color: #0891b2 !important; border-bottom-color: rgba(8, 145, 178, 0.3) !important; } -[data-theme='light'] .mega-header--opensource { color: #16a34a !important; border-bottom-color: rgba(22, 163, 74, 0.3) !important; } +[data-theme='light'] .picker-header--managed { color: #0891b2 !important; border-bottom-color: rgba(8, 145, 178, 0.3) !important; } +[data-theme='light'] .picker-header--opensource { color: #16a34a !important; border-bottom-color: rgba(22, 163, 74, 0.3) !important; } /* ── Product / resource links ───────────────────────────────────────────── */ -.mega-link { +.picker-link { display: flex !important; align-items: flex-start !important; gap: 0.75rem !important; @@ -175,30 +175,30 @@ transition: all 0.2s ease !important; border-radius: 8px; } -.mega-link:hover { background: rgba(0, 118, 255, 0.06) !important; transform: translateX(3px) !important; } -.mega-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block !important; } -.mega-link span { color: #64748b; font-size: 0.82rem; display: block !important; margin-top: 2px; line-height: 1.35; } +.picker-link:hover { background: rgba(0, 118, 255, 0.06) !important; transform: translateX(3px) !important; } +.picker-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block !important; } +.picker-link span { color: #64748b; font-size: 0.82rem; display: block !important; margin-top: 2px; line-height: 1.35; } /* ── Mobile (<= 996 px) ─────────────────────────────────────────────────── */ @media (max-width: 996px) { /* Fixed grid makes no sense in the narrow sidebar — hide the raw HTML blob */ - .menu__list-item:has(> .mega-menu-content) { display: none !important; } + .menu__list-item:has(> .picker-content) { display: none !important; } /* Docusaurus already renders a chevron; hide ours to avoid doubles */ - .nf-mega-dropdown::after, + .nf-picker-trigger::after, .nf-resources-dropdown::after { display: none !important; } - .nf-mega-dropdown, + .nf-picker-trigger, .nf-resources-dropdown { padding-right: 0.5rem !important; } } /* ── Desktop (>= 997 px) ────────────────────────────────────────────────── */ @media (min-width: 997px) { - /* Hide mobile-only fallback links when mega menu is active */ + /* Hide mobile-only fallback links when product picker is active */ .dropdown__menu .mobile-nav-link { display: none !important; } /* Force panel visible when JS sets dropdown--show (swizzled component) */ - .navbar__item.dropdown--show .dropdown__menu:has(.mega-menu-content) { + .navbar__item.dropdown--show .dropdown__menu:has(.picker-content) { display: block !important; visibility: visible !important; opacity: 1 !important; diff --git a/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx b/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx index 256e601..0053ff0 100644 --- a/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx +++ b/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx @@ -1,4 +1,4 @@ -import ProductsMegaMenu from './types/ProductsMegaMenu'; +import ProductPicker from './types/ProductPicker'; // @theme-original resolves to OUR OWN file in a plugin theme (Docusaurus sets // both @theme and @theme-original to the plugin file). @theme-init resolves to @@ -10,5 +10,5 @@ const ComponentTypesOrig = require('@theme-init/NavbarItem/ComponentTypes').defa export default { ...ComponentTypesOrig, - 'custom-productsMegaMenu': ProductsMegaMenu, + 'custom-productPicker': ProductPicker, }; diff --git a/packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx index 5a70570..d23dc83 100644 --- a/packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx +++ b/packages/docusaurus-theme/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx @@ -49,36 +49,36 @@ export default function DropdownNavbarItemDesktop({ hasEnteredPanel.current = false; } }; - window.addEventListener('nf-megamenu:open', onOtherOpen); - return () => window.removeEventListener('nf-megamenu:open', onOtherOpen); + window.addEventListener('nf-picker:open', onOtherOpen); + return () => window.removeEventListener('nf-picker:open', onOtherOpen); }, [props.label]); // Open on hover — reset entry state each time const handleMouseEnter = useCallback(() => { hasEnteredPanel.current = false; - window.dispatchEvent(new CustomEvent('nf-megamenu:open', {detail: {label: props.label}})); + window.dispatchEvent(new CustomEvent('nf-picker:open', {detail: {label: props.label}})); setShowDropdown(true); - console.log('[mega-menu] popped open:', props.label); + console.log('[product-picker] popped open:', props.label); }, [props.label]); // Leaving the trigger: do nothing — the panel stays open until the user // either enters it (then leaves) or clicks outside. const handleTriggerLeave = useCallback(() => { - console.log('[mega-menu] trigger leave — hasEnteredPanel:', hasEnteredPanel.current); + console.log('[product-picker] trigger leave — hasEnteredPanel:', hasEnteredPanel.current); }, []); // Once the cursor enters the panel, normal leave/blur can close it const handlePanelEnter = useCallback(() => { hasEnteredPanel.current = true; - console.log('[mega-menu] panel focus obtained'); + console.log('[product-picker] panel focus obtained'); }, []); const handlePanelLeave = useCallback(() => { - console.log('[mega-menu] panel focus lost — hasEnteredPanel:', hasEnteredPanel.current); + console.log('[product-picker] panel focus lost — hasEnteredPanel:', hasEnteredPanel.current); if (hasEnteredPanel.current) { setShowDropdown(false); hasEnteredPanel.current = false; - console.log('[mega-menu] closing — cursor left panel'); + console.log('[product-picker] closing — cursor left panel'); } }, []); diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/ProductsMegaMenu/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx similarity index 70% rename from packages/docusaurus-theme/theme/NavbarItem/types/ProductsMegaMenu/index.tsx rename to packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx index 03765dc..951d545 100644 --- a/packages/docusaurus-theme/theme/NavbarItem/types/ProductsMegaMenu/index.tsx +++ b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx @@ -2,27 +2,27 @@ import React, {useState, useRef, useEffect, useCallback} from 'react'; import Link from '@docusaurus/Link'; import clsx from 'clsx'; -export type MegaMenuLink = { +export type PickerLink = { label: string; to: string; logo?: string; description?: string; }; -export type MegaMenuColumn = { +export type PickerColumn = { header: string; headerClass?: string; - links: MegaMenuLink[]; + links: PickerLink[]; }; type Props = { label?: string; position?: 'left' | 'right'; - columns: MegaMenuColumn[]; + columns: PickerColumn[]; className?: string; }; -export default function ProductsMegaMenu({label = 'Products', columns, className}: Props) { +export default function ProductPicker({label = 'Products', columns, className}: Props) { const wrapRef = useRef(null); const hasEnteredPanel = useRef(false); const [open, setOpen] = useState(false); @@ -45,18 +45,18 @@ export default function ProductsMegaMenu({label = 'Products', columns, className }; }, [close]); - // Sync: close when another megamenu opens + // Sync: close when another product picker opens useEffect(() => { const onOtherOpen = (e: any) => { if (e.detail.label !== label) close(); }; - window.addEventListener('nf-megamenu:open', onOtherOpen); - return () => window.removeEventListener('nf-megamenu:open', onOtherOpen); + window.addEventListener('nf-picker:open', onOtherOpen); + return () => window.removeEventListener('nf-picker:open', onOtherOpen); }, [label, close]); const handleTriggerEnter = useCallback(() => { hasEnteredPanel.current = false; - window.dispatchEvent(new CustomEvent('nf-megamenu:open', {detail: {label}})); + window.dispatchEvent(new CustomEvent('nf-picker:open', {detail: {label}})); setOpen(true); }, [label]); @@ -74,7 +74,7 @@ export default function ProductsMegaMenu({label = 'Products', columns, className return (
{ e.preventDefault(); setOpen(o => !o); }} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); setOpen(o => !o); } }}> {label} {open && (
-
+
{columns.map((col, i) => ( -
- {col.header} +
+ {col.header} {col.links.map((link, j) => ( - - {link.logo && } -
+ + {link.logo && } +
{link.label} {link.description && {link.description}}
diff --git a/packages/test-site/docusaurus.config.ts b/packages/test-site/docusaurus.config.ts index 0b575fd..b88f670 100644 --- a/packages/test-site/docusaurus.config.ts +++ b/packages/test-site/docusaurus.config.ts @@ -114,7 +114,7 @@ export default { theme: { customCss: [ require.resolve('./src/custom/custom.css'), - require.resolve('../docusaurus-theme/css/mega-menu.css'), + require.resolve('../docusaurus-theme/css/product-picker.css'), ], } } @@ -150,13 +150,13 @@ export default { }, items: [ { - type: 'custom-productsMegaMenu', + type: 'custom-productPicker', position: 'left', label: 'Products', columns: [ { header: 'Managed Cloud', - headerClass: 'mega-header--managed', + headerClass: 'picker-header--managed', links: [ { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, { label: 'Frontdoor', to: '/docs/frontdoor', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Secure application access gateway.' }, @@ -164,7 +164,7 @@ export default { }, { header: 'Open Source', - headerClass: 'mega-header--opensource', + headerClass: 'picker-header--opensource', links: [ { label: 'OpenZiti', to: '/docs/openziti', description: 'Programmable zero-trust mesh infrastructure.' }, { label: 'zrok', to: '/docs/zrok', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, @@ -172,7 +172,7 @@ export default { }, { header: 'Your own infrastructure', - headerClass: 'mega-header--infra', + headerClass: 'picker-header--infra', links: [ { label: 'Self-Hosted', to: '/docs/onprem', description: 'Deploy the full stack in your own environment.' }, { label: 'zLAN', to: '/docs/zlan', description: 'Zero-trust access for OT networks.' }, From d8924be4ec5a468e8f0d9f0178f25c8b7ae78a2b Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:46:29 -0500 Subject: [PATCH 04/21] add icons to picker --- packages/docusaurus-theme/css/product-picker.css | 5 +++++ .../theme/NavbarItem/types/ProductPicker/index.tsx | 4 +++- packages/test-site/docusaurus.config.ts | 10 +++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/docusaurus-theme/css/product-picker.css b/packages/docusaurus-theme/css/product-picker.css index 8473577..d2ccfc3 100644 --- a/packages/docusaurus-theme/css/product-picker.css +++ b/packages/docusaurus-theme/css/product-picker.css @@ -10,6 +10,11 @@ .picker-logo { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; margin-right: 0.8rem; } .picker-icon { width: 20px; height: 20px; margin-right: 0.8rem; background-color: var(--ifm-color-primary); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; display: inline-block; } +/* Light/dark logo switching */ +.picker-logo--dark { display: none; } +[data-theme='dark'] .picker-logo--light { display: none; } +[data-theme='dark'] .picker-logo--dark { display: block; } + /* zrok logo needs a subtle shadow in light mode (white logo on white bg) */ [data-theme='light'] img[src*="zrok-logo"] { filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.55)); } diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx index 951d545..0bb277a 100644 --- a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx +++ b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx @@ -6,6 +6,7 @@ export type PickerLink = { label: string; to: string; logo?: string; + logoDark?: string; description?: string; }; @@ -99,7 +100,8 @@ export default function ProductPicker({label = 'Products', columns, className}: {col.header} {col.links.map((link, j) => ( - {link.logo && } + {link.logo && } + {link.logoDark && }
{link.label} {link.description && {link.description}} diff --git a/packages/test-site/docusaurus.config.ts b/packages/test-site/docusaurus.config.ts index b88f670..a026dac 100644 --- a/packages/test-site/docusaurus.config.ts +++ b/packages/test-site/docusaurus.config.ts @@ -159,23 +159,23 @@ export default { headerClass: 'picker-header--managed', links: [ { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, - { label: 'Frontdoor', to: '/docs/frontdoor', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Secure application access gateway.' }, + { label: 'Frontdoor', to: '/docs/frontdoor', logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.' }, ], }, { header: 'Open Source', headerClass: 'picker-header--opensource', links: [ - { label: 'OpenZiti', to: '/docs/openziti', description: 'Programmable zero-trust mesh infrastructure.' }, - { label: 'zrok', to: '/docs/zrok', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, + { label: 'OpenZiti', to: '/docs/openziti', logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.' }, + { label: 'zrok', to: '/docs/zrok', logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, ], }, { header: 'Your own infrastructure', headerClass: 'picker-header--infra', links: [ - { label: 'Self-Hosted', to: '/docs/onprem', description: 'Deploy the full stack in your own environment.' }, - { label: 'zLAN', to: '/docs/zlan', description: 'Zero-trust access for OT networks.' }, + { label: 'Self-Hosted', to: '/docs/onprem', logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.' }, + { label: 'zLAN', to: '/docs/zlan', logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.' }, ], }, ], From 76a82d7c5328bd621e6ad1a77e33f16760911759 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:51:15 -0500 Subject: [PATCH 05/21] add new look to existing doc --- unified-doc/README.md | 21 +++++++++ unified-doc/dev-link.sh | 36 +++++++++++++++ unified-doc/docusaurus.config.ts | 38 ++++++++++++--- .../src/theme/Navbar/Content/index.tsx | 46 ++++++++++++++++--- 4 files changed, 129 insertions(+), 12 deletions(-) create mode 100755 unified-doc/dev-link.sh diff --git a/unified-doc/README.md b/unified-doc/README.md index b85d2c7..700e51e 100644 --- a/unified-doc/README.md +++ b/unified-doc/README.md @@ -16,6 +16,27 @@ yarn start This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. +### Developing the theme locally + +To test local changes to `packages/docusaurus-theme` without publishing: + +```bash +# Link the local theme (run once) +./dev-link.sh + +# Terminal 1 — recompile theme on change +cd ../packages/docusaurus-theme && yarn watch + +# Terminal 2 — run the site +yarn start +``` + +To restore the published npm version: + +```bash +./dev-link.sh unlink +``` + ## Build ```bash diff --git a/unified-doc/dev-link.sh b/unified-doc/dev-link.sh new file mode 100755 index 0000000..9bb40f4 --- /dev/null +++ b/unified-doc/dev-link.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Links the local docusaurus-theme into unified-doc for development. +# Run once to set up, then use `yarn watch` in packages/docusaurus-theme +# and `yarn start` in unified-doc as normal. +# +# Usage: +# ./dev-link.sh # link +# ./dev-link.sh unlink # restore npm version + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +THEME_DIR="$SCRIPT_DIR/../packages/docusaurus-theme" +UNIFIED_DIR="$SCRIPT_DIR" + +if [ ! -d "$UNIFIED_DIR" ]; then + echo "ERROR: unified-doc not found at $UNIFIED_DIR" + exit 1 +fi + +if [ "${1}" = "unlink" ]; then + echo "→ Unlinking @netfoundry/docusaurus-theme from unified-doc..." + cd "$UNIFIED_DIR" && yarn unlink @netfoundry/docusaurus-theme && yarn install --force + echo "✓ Restored npm version" +else + echo "→ Registering theme package for linking..." + cd "$THEME_DIR" && yarn link + + echo "→ Linking into unified-doc..." + cd "$UNIFIED_DIR" && yarn link @netfoundry/docusaurus-theme + + echo "" + echo "✓ Done. Now run in two terminals:" + echo " [1] cd packages/docusaurus-theme && yarn watch" + echo " [2] cd $UNIFIED_DIR && yarn start" +fi diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index b43818d..2a0d497 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -217,8 +217,12 @@ const config: Config = { }, themes: [ ['@docusaurus/theme-classic', { - customCss: require.resolve('./src/css/custom.css'), + customCss: [ + require.resolve('./src/css/custom.css'), + require.resolve('@netfoundry/docusaurus-theme/css/product-picker.css'), + ], }], + '@netfoundry/docusaurus-theme', '@docusaurus/theme-mermaid', '@docusaurus/theme-search-algolia', ], @@ -373,12 +377,34 @@ const config: Config = { }, items: [ { - label: 'Docs', + type: 'custom-productPicker', position: 'left', - items: [ - { to: '/selfhosted/intro', label: 'Self-Hosted' }, - { to: '/frontdoor/intro', label: 'Frontdoor' }, - { to: '/openziti/learn/introduction', label: 'OpenZiti' }, + label: 'Products', + columns: [ + { + header: 'Managed Cloud', + headerClass: 'picker-header--managed', + links: [ + { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, + { label: 'Frontdoor', to: '/frontdoor/intro', logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.' }, + ], + }, + { + header: 'Open Source', + headerClass: 'picker-header--opensource', + links: [ + { label: 'OpenZiti', to: '/openziti/learn/introduction', logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.' }, + { label: 'zrok', to: '/zrok', logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, + ], + }, + { + header: 'Your own infrastructure', + headerClass: 'picker-header--infra', + links: [ + { label: 'Self-Hosted', to: '/selfhosted/intro', logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.' }, + { label: 'zLAN', to: '/zlan', logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.' }, + ], + }, ], }, ], diff --git a/unified-doc/src/theme/Navbar/Content/index.tsx b/unified-doc/src/theme/Navbar/Content/index.tsx index 2d794ea..cb82402 100644 --- a/unified-doc/src/theme/Navbar/Content/index.tsx +++ b/unified-doc/src/theme/Navbar/Content/index.tsx @@ -17,6 +17,38 @@ type Item = any; // change to '' if you don't use /docs const DOCS_PREFIX = '/docs'; +const productPicker: Item = { + type: 'custom-productPicker', + position: 'left', + label: 'Products', + columns: [ + { + header: 'Managed Cloud', + headerClass: 'picker-header--managed', + links: [ + { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, + { label: 'Frontdoor', to: `${DOCS_PREFIX}/frontdoor/intro`, logo: '/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.' }, + ], + }, + { + header: 'Open Source', + headerClass: 'picker-header--opensource', + links: [ + { label: 'OpenZiti', to: `${DOCS_PREFIX}/openziti/learn/introduction`, logo: '/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.' }, + { label: 'zrok', to: `${DOCS_PREFIX}/zrok/getting-started`, logo: '/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: '/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, + ], + }, + { + header: 'Your own infrastructure', + headerClass: 'picker-header--infra', + links: [ + { label: 'Self-Hosted', to: `${DOCS_PREFIX}/selfhosted/intro`, logo: '/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.' }, + { label: 'zLAN', to: `${DOCS_PREFIX}/zlan/intro`, logo: '/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.' }, + ], + }, + ], +}; + const defaultItems: Item[] = [ // {label: 'NetFoundry', to: '/', position: 'left'}, // {label: 'Downloads', to: '/downloads', position: 'left'}, @@ -117,12 +149,14 @@ const zrokNav: Item[] = [ ]; const mapNavbar = (p: string): Item[] => { - if (p.startsWith(`${DOCS_PREFIX}/frontdoor`)) return frontdoorNav; - if (p.startsWith(`${DOCS_PREFIX}/selfhosted`)) return onpremNav; - if (p.startsWith(`${DOCS_PREFIX}/openziti`)) return openZitiNav; - if (p.startsWith(`${DOCS_PREFIX}/zlan`)) return zlanNav; - if (p.startsWith(`${DOCS_PREFIX}/zrok`)) return zrokNav; - return defaultItems; + let items: Item[] = []; + if (p.startsWith(`${DOCS_PREFIX}/frontdoor`)) items = frontdoorNav; + else if (p.startsWith(`${DOCS_PREFIX}/selfhosted`)) items = onpremNav; + else if (p.startsWith(`${DOCS_PREFIX}/openziti`)) items = openZitiNav; + else if (p.startsWith(`${DOCS_PREFIX}/zlan`)) items = zlanNav; + else if (p.startsWith(`${DOCS_PREFIX}/zrok`)) items = zrokNav; + else items = defaultItems; + return [productPicker, ...items]; }; export default function NavbarContent(props: Props): JSX.Element { From bf2777eec30dc028121156fe664cac8243df35d9 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:53:59 -0500 Subject: [PATCH 06/21] reflect the product name in the picker --- unified-doc/src/theme/Navbar/Content/index.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/unified-doc/src/theme/Navbar/Content/index.tsx b/unified-doc/src/theme/Navbar/Content/index.tsx index cb82402..f3e9925 100644 --- a/unified-doc/src/theme/Navbar/Content/index.tsx +++ b/unified-doc/src/theme/Navbar/Content/index.tsx @@ -148,15 +148,17 @@ const zrokNav: Item[] = [ }, ]; +const sectionLabel = (p: string): string => { + if (p.startsWith(`${DOCS_PREFIX}/frontdoor`)) return 'Frontdoor'; + if (p.startsWith(`${DOCS_PREFIX}/selfhosted`)) return 'Self-Hosted'; + if (p.startsWith(`${DOCS_PREFIX}/openziti`)) return 'OpenZiti'; + if (p.startsWith(`${DOCS_PREFIX}/zlan`)) return 'zLAN'; + if (p.startsWith(`${DOCS_PREFIX}/zrok`)) return 'zrok'; + return 'Products'; +}; + const mapNavbar = (p: string): Item[] => { - let items: Item[] = []; - if (p.startsWith(`${DOCS_PREFIX}/frontdoor`)) items = frontdoorNav; - else if (p.startsWith(`${DOCS_PREFIX}/selfhosted`)) items = onpremNav; - else if (p.startsWith(`${DOCS_PREFIX}/openziti`)) items = openZitiNav; - else if (p.startsWith(`${DOCS_PREFIX}/zlan`)) items = zlanNav; - else if (p.startsWith(`${DOCS_PREFIX}/zrok`)) items = zrokNav; - else items = defaultItems; - return [productPicker, ...items]; + return [{...productPicker, label: sectionLabel(p)}]; }; export default function NavbarContent(props: Props): JSX.Element { From bad79ee8417c1dfe9b9cd663355f04c60e580b21 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:44:33 -0500 Subject: [PATCH 07/21] colors are based on NF colors - not loving them but it's what it is for now --- .../docusaurus-theme/css/product-picker.css | 110 +++++++------- packages/docusaurus-theme/css/vars.css | 8 ++ packages/docusaurus-theme/src/options.ts | 2 + .../NavbarItem/types/ProductPicker/index.tsx | 42 +++++- packages/test-site/docusaurus.config.ts | 32 +---- unified-doc/docusaurus.config.ts | 6 +- .../src/theme/Navbar/Content/index.tsx | 134 +----------------- 7 files changed, 109 insertions(+), 225 deletions(-) diff --git a/packages/docusaurus-theme/css/product-picker.css b/packages/docusaurus-theme/css/product-picker.css index d2ccfc3..9b729fd 100644 --- a/packages/docusaurus-theme/css/product-picker.css +++ b/packages/docusaurus-theme/css/product-picker.css @@ -21,10 +21,10 @@ /* ── Dropdown trigger button in the navbar ──────────────────────────────── */ .nf-picker-trigger, .nf-resources-dropdown { - font-weight: 600 !important; - color: var(--ifm-font-color-base) !important; + font-weight: 600; + color: var(--ifm-font-color-base); position: relative; - padding-right: 1.2rem !important; + padding-right: 1.2rem; transition: color 0.3s ease; } @@ -32,8 +32,8 @@ .nf-resources-dropdown:hover, .navbar__item.dropdown--show .nf-picker-trigger, .navbar__item.dropdown--show .nf-resources-dropdown { - color: var(--ifm-color-primary) !important; - text-decoration: none !important; + color: var(--ifm-color-primary); + text-decoration: none; } /* Animated chevron */ @@ -72,7 +72,7 @@ [data-theme='dark'] .nf-resources-dropdown:hover, [data-theme='dark'] .nf-picker--open .nf-picker-trigger, [data-theme='dark'] .navbar__item.dropdown--show .nf-resources-dropdown { - color: #22d3ee !important; + color: #22d3ee; } /* ── ProductPicker panel (custom navbar item) ───────────────────────────── */ @@ -121,91 +121,91 @@ height: 20px; } +/* :has() raises specificity above plain .dropdown__menu — no !important needed */ .dropdown__menu:has(.picker-content) { - position: fixed !important; - top: 3.75rem !important; - left: 50% !important; - transform: translateX(-50%) !important; - width: 85vw !important; - max-width: 1000px !important; - padding: 1.5rem 2rem !important; - border-radius: 12px !important; - border: 1px solid rgba(0, 118, 255, 0.12) !important; - box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15) !important; - background: var(--ifm-card-background-color) !important; - z-index: 1000 !important; + position: fixed; + top: 3.75rem; + left: 50%; + transform: translateX(-50%); + width: 85vw; + max-width: 1000px; + padding: 1.5rem 2rem; + border-radius: 12px; + border: 1px solid rgba(0, 118, 255, 0.12); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15); + background: var(--ifm-card-background-color); + z-index: 1000; } /* Narrower panel for the 2-column Resources menu */ -.dropdown__menu:has(.picker-resources) { max-width: 700px !important; } +.dropdown__menu:has(.picker-resources) { max-width: 700px; } /* Ensure visibility when open */ .dropdown--show > .dropdown__menu, .dropdown:hover > .dropdown__menu { - display: block !important; - visibility: visible !important; - opacity: 1 !important; + display: block; + visibility: visible; + opacity: 1; } /* ── Grid layout ────────────────────────────────────────────────────────── */ -.picker-content { display: grid !important; grid-template-columns: 1fr 1fr 1fr !important; gap: 2rem !important; } -.picker-resources { grid-template-columns: 1fr 1fr !important; } +.picker-content { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 2rem; } +.picker-resources { grid-template-columns: 1fr 1fr; } /* ── Column headers ─────────────────────────────────────────────────────── */ .picker-header { - font-size: 0.7rem !important; - font-weight: 900 !important; - color: #94a3b8 !important; - text-transform: uppercase !important; - letter-spacing: 0.1em !important; - border-bottom: 2px solid rgba(148, 163, 184, 0.2) !important; - display: block !important; - padding-bottom: 0.5rem !important; - margin-bottom: 0.75rem !important; + font-size: 0.7rem; + font-weight: 900; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.1em; + border-bottom: 2px solid rgba(148, 163, 184, 0.2); + display: block; + padding-bottom: 0.5rem; + margin-bottom: 0.75rem; } -.picker-header--managed { color: #22d3ee !important; border-bottom-color: rgba(34, 211, 238, 0.3) !important; } -.picker-header--opensource { color: #22c55e !important; border-bottom-color: rgba(34, 197, 94, 0.3) !important; } -.picker-header--infra { color: #94a3b8 !important; border-bottom-color: rgba(148, 163, 184, 0.2) !important; } - -[data-theme='light'] .picker-header--managed { color: #0891b2 !important; border-bottom-color: rgba(8, 145, 178, 0.3) !important; } -[data-theme='light'] .picker-header--opensource { color: #16a34a !important; border-bottom-color: rgba(22, 163, 74, 0.3) !important; } +.picker-header--nf-primary { color: var(--nf-primary); border-bottom-color: rgba(var(--nf-color-primary), 0.3); } +.picker-header--nf-secondary { color: var(--nf-secondary); border-bottom-color: rgba(var(--nf-color-secondary), 0.3); } +.picker-header--nf-tertiary { color: var(--nf-tertiary); border-bottom-color: rgba(var(--nf-color-tertiary), 0.3); } +[data-theme='light'] .picker-header { filter: brightness(0.6); } +[data-theme='dark'] .picker-header--nf-tertiary { filter: brightness(1.6); } /* ── Product / resource links ───────────────────────────────────────────── */ .picker-link { - display: flex !important; - align-items: flex-start !important; - gap: 0.75rem !important; - padding: 0.55rem 0.65rem !important; - text-decoration: none !important; - transition: all 0.2s ease !important; + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.55rem 0.65rem; + text-decoration: none; + transition: all 0.2s ease; border-radius: 8px; } -.picker-link:hover { background: rgba(0, 118, 255, 0.06) !important; transform: translateX(3px) !important; } -.picker-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block !important; } -.picker-link span { color: #64748b; font-size: 0.82rem; display: block !important; margin-top: 2px; line-height: 1.35; } +.picker-link:hover { background: rgba(0, 118, 255, 0.06); transform: translateX(3px); } +.picker-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block; } +.picker-link span { color: #64748b; font-size: 0.82rem; display: block; margin-top: 2px; line-height: 1.35; } /* ── Mobile (<= 996 px) ─────────────────────────────────────────────────── */ @media (max-width: 996px) { /* Fixed grid makes no sense in the narrow sidebar — hide the raw HTML blob */ - .menu__list-item:has(> .picker-content) { display: none !important; } + .menu__list .menu__list-item:has(> .picker-content) { display: none; } /* Docusaurus already renders a chevron; hide ours to avoid doubles */ .nf-picker-trigger::after, - .nf-resources-dropdown::after { display: none !important; } + .nf-resources-dropdown::after { display: none; } .nf-picker-trigger, - .nf-resources-dropdown { padding-right: 0.5rem !important; } + .nf-resources-dropdown { padding-right: 0.5rem; } } /* ── Desktop (>= 997 px) ────────────────────────────────────────────────── */ @media (min-width: 997px) { /* Hide mobile-only fallback links when product picker is active */ - .dropdown__menu .mobile-nav-link { display: none !important; } + .dropdown__menu .mobile-nav-link { display: none; } /* Force panel visible when JS sets dropdown--show (swizzled component) */ .navbar__item.dropdown--show .dropdown__menu:has(.picker-content) { - display: block !important; - visibility: visible !important; - opacity: 1 !important; + display: block; + visibility: visible; + opacity: 1; } } diff --git a/packages/docusaurus-theme/css/vars.css b/packages/docusaurus-theme/css/vars.css index 0f330d5..cb9a025 100644 --- a/packages/docusaurus-theme/css/vars.css +++ b/packages/docusaurus-theme/css/vars.css @@ -1,6 +1,14 @@ :root { --ifm-navbar-height: 50px; --nf-docs-max-width: 1400px; + + --nf-color-primary: 119, 194, 252; + --nf-color-secondary: 78, 219, 63; + --nf-color-tertiary: 3, 92, 230; + + --nf-primary: rgb(var(--nf-color-primary)); + --nf-secondary: rgb(var(--nf-color-secondary)); + --nf-tertiary: rgb(var(--nf-color-tertiary)); /*--nf-docs-main-color: purple;*/ .container { /*background: sandybrown;*/ diff --git a/packages/docusaurus-theme/src/options.ts b/packages/docusaurus-theme/src/options.ts index 7bb87bc..05338b4 100644 --- a/packages/docusaurus-theme/src/options.ts +++ b/packages/docusaurus-theme/src/options.ts @@ -84,6 +84,8 @@ export interface NetFoundryThemeConfig { starBanner?: StarBannerConfig; /** Whether to show the star banner (default: false) */ showStarBanner?: boolean; + /** Logo URL for the NetFoundry Console link in the product picker (overrides the default NetFoundry branding icon) */ + consoleLogo?: string; } /** diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx index 0bb277a..1a5e9a1 100644 --- a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx +++ b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx @@ -1,6 +1,8 @@ import React, {useState, useRef, useEffect, useCallback} from 'react'; import Link from '@docusaurus/Link'; import clsx from 'clsx'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import {useThemeConfig} from '@docusaurus/theme-common'; export type PickerLink = { label: string; @@ -19,11 +21,45 @@ export type PickerColumn = { type Props = { label?: string; position?: 'left' | 'right'; - columns: PickerColumn[]; + columns?: PickerColumn[]; className?: string; }; +const NF_LOGO_DEFAULT = 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg'; + +const buildDefaultColumns = (img: string, consoleLogo: string): PickerColumn[] => [ + { + header: 'Managed Cloud', + headerClass: 'picker-header--nf-primary', + links: [ + { label: 'NetFoundry Console', to: '#', logo: consoleLogo, description: 'Cloud-managed orchestration and global fabric control.' }, + { label: 'Frontdoor', to: '/docs/frontdoor', logo: `${img}/frontdoor-sm-logo.svg`, description: 'Secure application access gateway.' }, + ], + }, + { + header: 'Open Source', + headerClass: 'picker-header--nf-secondary', + links: [ + { label: 'OpenZiti', to: '/docs/openziti', logo: `${img}/openziti-sm-logo.svg`, description: 'Programmable zero-trust mesh infrastructure.' }, + { label: 'zrok', to: '/docs/zrok', logo: `${img}/zrok-1.0.0-rocket-purple.svg`, logoDark: `${img}/zrok-1.0.0-rocket-green.svg`, description: 'Secure peer-to-peer sharing built on OpenZiti.' }, + ], + }, + { + header: 'Your own infrastructure', + headerClass: 'picker-header--nf-tertiary', + links: [ + { label: 'Self-Hosted', to: '/docs/selfhosted', logo: `${img}/onprem-sm-logo.svg`, description: 'Deploy the full stack in your own environment.' }, + { label: 'zLAN', to: '/docs/zlan', logo: `${img}/zlan-logo.svg`, description: 'Zero-trust access for OT networks.' }, + ], + }, +]; + export default function ProductPicker({label = 'Products', columns, className}: Props) { + const {siteConfig} = useDocusaurusContext(); + const themeConfig = useThemeConfig() as any; + const consoleLogo = themeConfig?.netfoundry?.consoleLogo ?? NF_LOGO_DEFAULT; + const img = `${siteConfig.url}${siteConfig.baseUrl}img`; + columns ??= buildDefaultColumns(img, consoleLogo); const wrapRef = useRef(null); const hasEnteredPanel = useRef(false); const [open, setOpen] = useState(false); @@ -91,9 +127,9 @@ export default function ProductPicker({label = 'Products', columns, className}: {open && (
e.stopPropagation()} onMouseEnter={handlePanelEnter} - onMouseLeave={handlePanelLeave} - onClick={close}> + onMouseLeave={handlePanelLeave}>
{columns.map((col, i) => (
diff --git a/packages/test-site/docusaurus.config.ts b/packages/test-site/docusaurus.config.ts index a026dac..6c17e9a 100644 --- a/packages/test-site/docusaurus.config.ts +++ b/packages/test-site/docusaurus.config.ts @@ -149,37 +149,7 @@ export default { src: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', }, items: [ - { - type: 'custom-productPicker', - position: 'left', - label: 'Products', - columns: [ - { - header: 'Managed Cloud', - headerClass: 'picker-header--managed', - links: [ - { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, - { label: 'Frontdoor', to: '/docs/frontdoor', logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.' }, - ], - }, - { - header: 'Open Source', - headerClass: 'picker-header--opensource', - links: [ - { label: 'OpenZiti', to: '/docs/openziti', logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.' }, - { label: 'zrok', to: '/docs/zrok', logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, - ], - }, - { - header: 'Your own infrastructure', - headerClass: 'picker-header--infra', - links: [ - { label: 'Self-Hosted', to: '/docs/onprem', logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.' }, - { label: 'zLAN', to: '/docs/zlan', logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.' }, - ], - }, - ], - }, + { type: 'custom-productPicker', position: 'left' }, { to: '/docs', label: 'Main Docs', diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index 2a0d497..50007c3 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -383,7 +383,7 @@ const config: Config = { columns: [ { header: 'Managed Cloud', - headerClass: 'picker-header--managed', + headerClass: 'picker-header--nf-primary', links: [ { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, { label: 'Frontdoor', to: '/frontdoor/intro', logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.' }, @@ -391,7 +391,7 @@ const config: Config = { }, { header: 'Open Source', - headerClass: 'picker-header--opensource', + headerClass: 'picker-header--nf-secondary', links: [ { label: 'OpenZiti', to: '/openziti/learn/introduction', logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.' }, { label: 'zrok', to: '/zrok', logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, @@ -399,7 +399,7 @@ const config: Config = { }, { header: 'Your own infrastructure', - headerClass: 'picker-header--infra', + headerClass: 'picker-header--nf-tertiary', links: [ { label: 'Self-Hosted', to: '/selfhosted/intro', logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.' }, { label: 'zLAN', to: '/zlan', logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.' }, diff --git a/unified-doc/src/theme/Navbar/Content/index.tsx b/unified-doc/src/theme/Navbar/Content/index.tsx index f3e9925..e661dd3 100644 --- a/unified-doc/src/theme/Navbar/Content/index.tsx +++ b/unified-doc/src/theme/Navbar/Content/index.tsx @@ -14,139 +14,9 @@ import {useNavbarMobileSidebar} from "@docusaurus/theme-common/internal"; type Props = React.ComponentProps; type Item = any; -// change to '' if you don't use /docs const DOCS_PREFIX = '/docs'; -const productPicker: Item = { - type: 'custom-productPicker', - position: 'left', - label: 'Products', - columns: [ - { - header: 'Managed Cloud', - headerClass: 'picker-header--managed', - links: [ - { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, - { label: 'Frontdoor', to: `${DOCS_PREFIX}/frontdoor/intro`, logo: '/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.' }, - ], - }, - { - header: 'Open Source', - headerClass: 'picker-header--opensource', - links: [ - { label: 'OpenZiti', to: `${DOCS_PREFIX}/openziti/learn/introduction`, logo: '/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.' }, - { label: 'zrok', to: `${DOCS_PREFIX}/zrok/getting-started`, logo: '/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: '/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, - ], - }, - { - header: 'Your own infrastructure', - headerClass: 'picker-header--infra', - links: [ - { label: 'Self-Hosted', to: `${DOCS_PREFIX}/selfhosted/intro`, logo: '/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.' }, - { label: 'zLAN', to: `${DOCS_PREFIX}/zlan/intro`, logo: '/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.' }, - ], - }, - ], -}; - -const defaultItems: Item[] = [ - // {label: 'NetFoundry', to: '/', position: 'left'}, - // {label: 'Downloads', to: '/downloads', position: 'left'}, - // {label: 'Blog', to: '/blog', position: 'left'}, -]; - -const netfoundryDocs = {to: `https://support.netfoundry.io/hc/en-us/categories/360000991011-Docs-Guides`, label: 'NetFoundry SaaS'}; -const nfFrontDoorDocs = {to: `${DOCS_PREFIX}/frontdoor/intro`, label: 'Frontdoor'}; -const onPremDocs = {to: `${DOCS_PREFIX}/selfhosted/intro`, label: 'Self-Hosted'}; -const zlanDocs = {to: `${DOCS_PREFIX}/zlan/intro`, label: 'zLAN'}; -const ozDocs = {to: `${DOCS_PREFIX}/openziti/learn/introduction`, label: 'OpenZiti'}; -const zrokDocs = {to: `${DOCS_PREFIX}/zrok/getting-started`, label: 'zrok'}; - -const openZitiNav: Item[] = [ - { - label: 'OpenZiti Docs', - to: `${DOCS_PREFIX}/openziti/learn/introduction`, - position: 'left', - type: 'dropdown', - items: [ - netfoundryDocs, - nfFrontDoorDocs, - onPremDocs, - zlanDocs, - zrokDocs, - ], - } -]; - -const onpremNav: Item[] = [ - { - label: 'Self-Hosted Docs', - to: `${DOCS_PREFIX}/selfhosted/intro`, - position: 'left', - type: 'dropdown', - items: [ - netfoundryDocs, - nfFrontDoorDocs, - ozDocs, - zlanDocs, - zrokDocs, - ], - }, -]; - -const frontdoorNav: Item[] = [ - { - label: 'Frontdoor Docs', - to: `${DOCS_PREFIX}/frontdoor/intro`, - position: 'left', - type: 'dropdown', - items: [ - netfoundryDocs, - onPremDocs, - ozDocs, - zlanDocs, - zrokDocs, - ], - }, -]; - -const zlanNav: Item[] = [ - { - label: 'zLAN Docs', - to: `${DOCS_PREFIX}/zlan/intro`, - position: 'left', - type: 'dropdown', - items: [ - netfoundryDocs, - nfFrontDoorDocs, - onPremDocs, - ozDocs, - zrokDocs, - ], - }, -]; - -const zrokNav: Item[] = [ - { - label: 'zrok Docs', - to: `${DOCS_PREFIX}/zrok/getting-started`, - position: 'left', - type: 'dropdown', - items: [ - netfoundryDocs, - nfFrontDoorDocs, - onPremDocs, - ozDocs, - zlanDocs, - ], - }, - { - type: 'docsVersionDropdown', - docsPluginId: 'zrok', - dropdownItemsBefore: [], - dropdownItemsAfter: [], - }, -]; +const productPicker: Item = { type: 'custom-productPicker', position: 'left' }; const sectionLabel = (p: string): string => { if (p.startsWith(`${DOCS_PREFIX}/frontdoor`)) return 'Frontdoor'; @@ -172,10 +42,8 @@ export default function NavbarContent(props: Props): JSX.Element { const left = items.filter((i) => i.position !== 'right'); const right = items.filter((i) => i.position === 'right'); - const mobileSidebar = useNavbarMobileSidebar(); - return (
From 9e26842c8cd4e95b78280f41f22e78bde2b7c7f3 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:09:06 -0500 Subject: [PATCH 08/21] add the landing page --- packages/test-site/src/pages/index.tsx | 97 ++++++++--- .../test-site/src/pages/landing.module.css | 160 ++++++++++++++++++ 2 files changed, 236 insertions(+), 21 deletions(-) create mode 100644 packages/test-site/src/pages/landing.module.css diff --git a/packages/test-site/src/pages/index.tsx b/packages/test-site/src/pages/index.tsx index 8fd4e4d..8c4367f 100644 --- a/packages/test-site/src/pages/index.tsx +++ b/packages/test-site/src/pages/index.tsx @@ -1,26 +1,81 @@ import React, {JSX} from 'react'; import Layout from '@theme/Layout'; -import {Alert, NetFoundryHorizontalSection} from '@netfoundry/docusaurus-theme/ui'; +import Link from '@docusaurus/Link'; +import clsx from 'clsx'; +import styles from './landing.module.css'; + +const CYAN = '#22d3ee'; +const GREEN = '#22c55e'; +const IMG = 'https://netfoundry.io/docs/img'; +const NF_LOGO = 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg'; + +const products = [ + { id: 'console', title: 'NetFoundry Console', logo: NF_LOGO, tag: 'Managed', accent: CYAN, link: '#', features: ['Fully managed SaaS', 'Global edge fabric', 'No infra to operate', 'Policy-based access'], description: "The cloud-managed control plane for NetFoundry's global zero-trust fabric. Orchestrate identities, policies, and edge routers — no infrastructure to run." }, + { id: 'openziti', title: 'OpenZiti', logo: `${IMG}/openziti-sm-logo.svg`, tag: 'Open Source', accent: GREEN, link: '/docs/openziti', description: 'The open-source zero-trust networking framework at the heart of the NetFoundry platform. Embed dark, app-native security directly in your code — no VPN, no perimeter.' }, + { id: 'frontdoor', title: 'Frontdoor', logo: `${IMG}/frontdoor-sm-logo.svg`, tag: 'Managed', accent: CYAN, link: '/docs/frontdoor', features: ['No agent or VPN required', 'Zero firewall rules', 'Identity-based access', 'Any app, any browser'], description: 'Secure, clientless access to any application — without a VPN or firewall rule. Expose nothing to the internet while giving authorized users instant access.' }, + { id: 'zrok', title: 'zrok', logo: `${IMG}/zrok-1.0.0-rocket-purple.svg`, tag: 'Open Source', accent: GREEN, link: '/docs/zrok', description: 'Geo-scale secure sharing built on the OpenZiti mesh. Share services, files, or HTTP endpoints peer-to-peer — no open ports, no NAT traversal tricks.' }, + { id: 'selfhosted', title: 'NetFoundry Self-Hosted', logo: `${IMG}/onprem-sm-logo.svg`, tag: 'Self-Hosted', accent: CYAN, link: '/docs/onprem', features: ['Full infrastructure control', 'Air-gap compatible', 'On-prem or any cloud', 'Enterprise SLA'], description: 'Deploy the full NetFoundry control plane and fabric in your own environment. Full sovereignty over your zero-trust infrastructure — on-prem, air-gapped, or any cloud.' }, + { id: 'zlan', title: 'zLAN', logo: `${IMG}/zlan-logo.svg`, tag: 'OT Security', accent: CYAN, link: '/docs/zlan', features: ['Deep OT/IT traffic visibility', 'Identity-aware micro-segmentation', 'Centralized zero-trust policy', 'Built on NetFoundry Self-Hosted'], description: 'Identity-aware micro-segmentation firewall for operational technology networks. Deep traffic visibility, centralized policy, and zero-trust access control for OT environments.' }, +]; + +type Product = (typeof products)[number]; +const byId = Object.fromEntries(products.map(p => [p.id, p])) as Record; + +function BentoCard({product, featured = false}: {product: Product; featured?: boolean}): JSX.Element { + const accentMod = product.accent === CYAN ? styles['nf-bento-card--accent-cyan'] : styles['nf-bento-card--accent-green']; + return ( +
+ + {product.tag} +
+ {product.logo && {product.title}} +

{product.title}

+
+

{product.description}

+ {product.features && ( +
    + {product.features.map(f =>
  • {f}
  • )} +
+ )} +
Explore →
+ +
+ ); +} export default function Home(): JSX.Element { - const title = 'Home AA'; - const desc = "TheDescriptiond"; - return ( - -
- - -

Hello

-

This is a basic Docusaurus page in TSX.

-

This is a basic Docusaurus page in TSX.

-

This is a basic Docusaurus page in TSX.

- - - - -
-
-
-
- ); + return ( + +
+
+

NetFoundry Docs

+

Secure, high-performance networking for the modern era.

+
+ Get Started + Request Demo +
+
+
+
+
+
+
Managed Cloud
+
+ +
open-source counterpart
+ +
+
+ +
open-source counterpart
+ +
+
Run on your own infrastructure
+ + +
+
+
+
+ ); } diff --git a/packages/test-site/src/pages/landing.module.css b/packages/test-site/src/pages/landing.module.css new file mode 100644 index 0000000..f7a749b --- /dev/null +++ b/packages/test-site/src/pages/landing.module.css @@ -0,0 +1,160 @@ +.nf-hero-stage { + position: relative; width: 100%; min-height: 370px; + display: flex; align-items: center; justify-content: center; + overflow: hidden; text-align: center; background: #020617; z-index: 4; +} +.nf-hero-stage::after { + content: ''; position: absolute; bottom: 0; left: 0; right: 0; + height: 220px; background: linear-gradient(to bottom, transparent 0%, #0f172a 100%); + pointer-events: none; z-index: 1; +} +:global([data-theme='light']) .nf-hero-stage::after { + height: 80px; + background: linear-gradient(to bottom, transparent 0%, #020617 100%); +} +.nf-hero-overlay { display: none; } +.nf-hero-content { + position: relative; z-index: 2; padding: 2.5rem 3.5rem; + background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; +} +.nf-hero-title { + font-size: 4rem; font-weight: 900; color: #ffffff; margin-bottom: 0.75rem; + letter-spacing: -0.02em; line-height: 1.05; + text-shadow: 0 0 20px rgba(34, 211, 238, 0.8), 0 2px 12px rgba(0, 0, 0, 0.9); +} +.nf-green-text { + background: linear-gradient(to right, #22c55e 0%, #86efac 100%); + -webkit-background-clip: text; background-clip: text; + -webkit-text-fill-color: transparent; display: inline-block; +} +.nf-hero-subtext { + color: rgba(203, 213, 225, 0.95); font-size: 1.15rem; max-width: 560px; + margin: 0 auto 2rem; line-height: 1.65; text-shadow: 0 1px 8px rgba(0, 0, 0, 0.95); +} +.nf-hero-ctas { display: flex; gap: 1rem; justify-content: center; } +.nf-btn-primary { + display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; + background: #0076FF; color: #ffffff; border-radius: 8px; font-weight: 700; + font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; border: 2px solid #0076FF; +} +.nf-btn-primary:hover { + background: #005ce6; border-color: #005ce6; transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 118, 255, 0.4); color: #ffffff; +} +.nf-btn-ghost { + display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; + background: transparent; color: #ffffff; border-radius: 8px; font-weight: 700; + font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; + border: 2px solid rgba(255, 255, 255, 0.25); +} +.nf-btn-ghost:hover { + background: rgba(255, 255, 255, 0.06); border-color: rgba(34, 211, 238, 0.5); + transform: translateY(-2px); color: #ffffff; +} + +.nf-features-section { width: 100%; background: #0f172a; padding: 5rem 0 2.5rem; } +:global([data-theme='light']) .nf-features-section { + background: linear-gradient(to bottom, + #020617 0px, #020617 80px, #404350 18%, #7d808a 28%, + #b8bbc1 38%, #e4e7ea 48%, #f8fafc 100%); +} + +.nf-bento-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } + +.nf-bento-divider { + grid-column: 1 / -1; display: flex; align-items: center; gap: 1rem; + padding: 1.5rem 0 0.65rem; color: #94a3b8; font-size: 0.82rem; font-weight: 800; + letter-spacing: 0.12em; text-transform: uppercase; white-space: nowrap; +} +.nf-bento-divider::before, .nf-bento-divider::after { + content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); +} +.nf-divider--top { padding-top: 0; } +.nf-divider--managed { + color: #22d3ee; font-size: 1.05rem; letter-spacing: 0.15em; + text-shadow: 0 0 18px rgba(34, 211, 238, 0.5); +} +.nf-divider--managed::before, .nf-divider--managed::after { background: rgba(34, 211, 238, 0.4); height: 2px; } +:global([data-theme='light']) .nf-bento-divider { color: #64748b; } +:global([data-theme='light']) .nf-bento-divider::before, +:global([data-theme='light']) .nf-bento-divider::after { background: rgba(100, 116, 139, 0.25); } +:global([data-theme='light']) .nf-divider--managed { color: #0891b2; text-shadow: none; } +:global([data-theme='light']) .nf-divider--managed::before, +:global([data-theme='light']) .nf-divider--managed::after { background: rgba(8, 145, 178, 0.35); height: 2px; } + +.nf-pair { display: flex; flex-direction: column; } +.nf-pair-connector { + display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0; + font-size: 0.7rem; font-weight: 800; letter-spacing: 0.12em; + text-transform: uppercase; color: #94a3b8; +} +.nf-pair-connector::before, .nf-pair-connector::after { + content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); +} +:global([data-theme='light']) .nf-pair-connector { color: #64748b; } +:global([data-theme='light']) .nf-pair-connector::before, +:global([data-theme='light']) .nf-pair-connector::after { background: rgba(148, 163, 184, 0.35); } + +.nf-bento-wrap { display: flex; flex-direction: column; } + +.nf-bento-card { + position: relative; display: flex; flex-direction: column; flex: 1; + padding: 1rem; border-radius: 12px; text-decoration: none; + background: #1a1b2e; border: 1px solid rgba(148, 163, 184, 0.1); + border-top: 2px solid #22d3ee; + box-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.3), 0 16px 40px rgba(0,0,0,0.2); + transition: transform 0.2s ease, box-shadow 0.2s ease, border-top-color 0.2s ease; +} +.nf-bento-card:hover { + transform: translateY(-4px); border-top-color: #22c55e; + box-shadow: 0 2px 6px rgba(0,0,0,0.4), 0 12px 32px rgba(0,0,0,0.45), + 0 24px 60px rgba(0,0,0,0.25), 0 0 0 1px rgba(34,197,94,0.12); +} +:global([data-theme='light']) .nf-bento-card { + background: #edf3f8; border-color: rgba(0,0,0,0.08); + box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 14px rgba(0,0,0,0.05); +} +:global([data-theme='light']) .nf-bento-card:hover { + box-shadow: 0 2px 6px rgba(0,0,0,0.09), 0 8px 24px rgba(0,0,0,0.07), 0 0 0 1px rgba(34,197,94,0.18); +} +:global([data-theme='light']) .nf-bento-card--accent-cyan { border-top-color: #0891b2; } +:global([data-theme='light']) .nf-bento-card--accent-green { border-top-color: #16a34a; } +.nf-bento-card--featured { padding: 1.25rem; border-top-width: 3px; } +.nf-bento-card--featured .nf-card-logo { width: 48px; height: 48px; } +.nf-bento-card--featured .nf-card-header h3 { font-size: 1.25rem; } + +.nf-card-badge { + position: absolute; top: 1rem; right: 1rem; display: inline-flex; width: fit-content; + background: rgba(34,197,94,0.1); color: #4ade80; border: 1px solid rgba(34,197,94,0.2); + font-size: 0.6rem; font-weight: 800; letter-spacing: 0.1em; + padding: 2px 8px; border-radius: 4px; text-transform: uppercase; +} +:global([data-theme='light']) .nf-card-badge { color: #15803d; border-color: rgba(34,197,94,0.25); } + +.nf-card-header { + display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; padding-right: 3rem; +} +.nf-card-header h3 { margin: 0; } +.nf-bento-card h3 { color: #f1f5f9; font-weight: 900; font-size: 1.05rem; line-height: 1.3; letter-spacing: -0.02em; } +:global([data-theme='light']) .nf-bento-card h3 { color: #0f172a; } +.nf-bento-card p { color: #94a3b8; font-size: 0.875rem; line-height: 1.65; flex-grow: 1; margin: 0 0 0.5rem; } +:global([data-theme='light']) .nf-bento-card p { color: #475569; } + +.nf-bento-features { list-style: none; padding: 0; margin: 0 0 0.5rem; display: flex; flex-direction: column; gap: 0.25rem; } +.nf-bento-features li { display: flex; align-items: center; gap: 0.4rem; font-size: 0.775rem; color: #64748b; } +.nf-bento-features li::before { content: '✓'; color: #22c55e; font-weight: 800; font-size: 0.75rem; flex-shrink: 0; } +:global([data-theme='light']) .nf-bento-features li::before { color: #16a34a; } + +.nf-card-link { color: #22d3ee; font-size: 0.8rem; font-weight: 700; margin-top: auto; padding-top: 0.5rem; letter-spacing: 0.03em; } +:global([data-theme='light']) .nf-card-link { color: #0284c7; } +.nf-card-logo { width: 40px; height: 40px; object-fit: contain; flex-shrink: 0; } + +@media (max-width: 996px) { + .nf-hero-title { font-size: 2.2rem; } + .nf-hero-content { padding: 2rem 1.25rem; } + .nf-hero-ctas { flex-wrap: wrap; } + .nf-btn-primary, .nf-btn-ghost { padding: 0.55rem 1.25rem; font-size: 0.9rem; } +} +@media (max-width: 640px) { + .nf-bento-grid { grid-template-columns: 1fr; } +} From 984b54e649ca02e04b7134d64302b60634065dbd Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:41:52 -0500 Subject: [PATCH 09/21] refactor to allow specifying links from docusaurus config/theme --- packages/docusaurus-theme/package.json | 6 +- packages/docusaurus-theme/src/options.ts | 36 ++- .../docusaurus-theme/theme/Layout/index.tsx | 17 +- .../NavbarItem/types/ProductPicker/index.tsx | 11 +- packages/test-site/docusaurus.config.ts | 63 ++++- unified-doc/src/pages/index.tsx | 219 ++++++------------ unified-doc/src/pages/landing.module.css | 160 +++++++++++++ 7 files changed, 345 insertions(+), 167 deletions(-) create mode 100644 unified-doc/src/pages/landing.module.css diff --git a/packages/docusaurus-theme/package.json b/packages/docusaurus-theme/package.json index a8e0794..028ecb9 100644 --- a/packages/docusaurus-theme/package.json +++ b/packages/docusaurus-theme/package.json @@ -37,7 +37,11 @@ "types": "./dist/src/node.d.ts", "default": "./dist/src/node.js" }, - "./css/*": "./css/*" + "./css/*": "./css/*", + "./theme/NavbarItem/types/ProductPicker": { + "types": "./theme/NavbarItem/types/ProductPicker/index.tsx", + "default": "./theme/NavbarItem/types/ProductPicker/index.tsx" + } }, "peerDependencies": { "@docusaurus/core": "^3", diff --git a/packages/docusaurus-theme/src/options.ts b/packages/docusaurus-theme/src/options.ts index 05338b4..a93dc1c 100644 --- a/packages/docusaurus-theme/src/options.ts +++ b/packages/docusaurus-theme/src/options.ts @@ -36,6 +36,34 @@ export interface StarBannerConfig { repoUrl: string; /** Label text for the star button */ label: string; + /** Only show banner when the current path starts with this prefix (e.g. '/docs/openziti') */ + pathPrefix?: string; +} + +/** + * A single link entry in the product picker + */ +export interface ProductPickerLink { + /** Display name */ + label: string; + /** Route or URL */ + to: string; + /** Logo shown in light mode (and dark mode if logoDark is absent) */ + logo?: string; + /** Logo shown only in dark mode */ + logoDark?: string; + /** Short description shown beneath the label */ + description?: string; +} + +/** + * A column in the product picker dropdown. + * Header color is assigned automatically by column index (primary → secondary → tertiary). + */ +export interface ProductPickerColumn { + /** Column heading text */ + header: string; + links: ProductPickerLink[]; } /** @@ -80,10 +108,10 @@ export interface NetFoundryThemeOptions { export interface NetFoundryThemeConfig { /** Footer configuration */ footer?: FooterConfig; - /** Star banner configuration */ - starBanner?: StarBannerConfig; - /** Whether to show the star banner (default: false) */ - showStarBanner?: boolean; + /** Path-aware star banners — each entry shows only when the current path starts with pathPrefix (omit pathPrefix to show everywhere) */ + starBanners?: StarBannerConfig[]; + /** Product picker columns. If omitted, the theme falls back to built-in NetFoundry defaults. */ + productPickerColumns?: ProductPickerColumn[]; /** Logo URL for the NetFoundry Console link in the product picker (overrides the default NetFoundry branding icon) */ consoleLogo?: string; } diff --git a/packages/docusaurus-theme/theme/Layout/index.tsx b/packages/docusaurus-theme/theme/Layout/index.tsx index 545773a..ad5a103 100644 --- a/packages/docusaurus-theme/theme/Layout/index.tsx +++ b/packages/docusaurus-theme/theme/Layout/index.tsx @@ -1,5 +1,6 @@ import React, { type ReactNode } from 'react'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import {useLocation} from 'react-router-dom'; import { NetFoundryLayout, defaultNetFoundryFooterProps, @@ -32,6 +33,7 @@ export default function Layout({ description, }: LayoutProps): ReactNode { const { siteConfig } = useDocusaurusContext(); + const {pathname} = useLocation(); const themeConfig = siteConfig.themeConfig as ThemeConfigWithNetFoundry; const nfConfig = themeConfig.netfoundry ?? {}; @@ -47,14 +49,13 @@ export default function Layout({ }, }; - // Build star props if enabled - const starProps = - nfConfig.showStarBanner && nfConfig.starBanner - ? { - repoUrl: nfConfig.starBanner.repoUrl, - label: nfConfig.starBanner.label, - } - : undefined; + // Pick the first banner whose pathPrefix matches (or has no prefix) + const matchedBanner = nfConfig.starBanners?.find(b => + !b.pathPrefix || pathname.startsWith(b.pathPrefix) + ); + const starProps = matchedBanner + ? {repoUrl: matchedBanner.repoUrl, label: matchedBanner.label} + : undefined; return ( [ { header: 'Managed Cloud', - headerClass: 'picker-header--nf-primary', + headerClass: HEADER_CLASSES[0], links: [ { label: 'NetFoundry Console', to: '#', logo: consoleLogo, description: 'Cloud-managed orchestration and global fabric control.' }, { label: 'Frontdoor', to: '/docs/frontdoor', logo: `${img}/frontdoor-sm-logo.svg`, description: 'Secure application access gateway.' }, @@ -38,7 +39,7 @@ const buildDefaultColumns = (img: string, consoleLogo: string): PickerColumn[] = }, { header: 'Open Source', - headerClass: 'picker-header--nf-secondary', + headerClass: HEADER_CLASSES[1], links: [ { label: 'OpenZiti', to: '/docs/openziti', logo: `${img}/openziti-sm-logo.svg`, description: 'Programmable zero-trust mesh infrastructure.' }, { label: 'zrok', to: '/docs/zrok', logo: `${img}/zrok-1.0.0-rocket-purple.svg`, logoDark: `${img}/zrok-1.0.0-rocket-green.svg`, description: 'Secure peer-to-peer sharing built on OpenZiti.' }, @@ -46,7 +47,7 @@ const buildDefaultColumns = (img: string, consoleLogo: string): PickerColumn[] = }, { header: 'Your own infrastructure', - headerClass: 'picker-header--nf-tertiary', + headerClass: HEADER_CLASSES[2], links: [ { label: 'Self-Hosted', to: '/docs/selfhosted', logo: `${img}/onprem-sm-logo.svg`, description: 'Deploy the full stack in your own environment.' }, { label: 'zLAN', to: '/docs/zlan', logo: `${img}/zlan-logo.svg`, description: 'Zero-trust access for OT networks.' }, @@ -59,7 +60,9 @@ export default function ProductPicker({label = 'Products', columns, className}: const themeConfig = useThemeConfig() as any; const consoleLogo = themeConfig?.netfoundry?.consoleLogo ?? NF_LOGO_DEFAULT; const img = `${siteConfig.url}${siteConfig.baseUrl}img`; - columns ??= buildDefaultColumns(img, consoleLogo); + const configColumns: PickerColumn[] | undefined = themeConfig?.netfoundry?.productPickerColumns + ?.map((col: any, i: number) => ({...col, headerClass: HEADER_CLASSES[i] ?? ''})); + columns ??= configColumns ?? buildDefaultColumns(img, consoleLogo); const wrapRef = useRef(null); const hasEnteredPanel = useRef(false); const [open, setOpen] = useState(false); diff --git a/packages/test-site/docusaurus.config.ts b/packages/test-site/docusaurus.config.ts index 6c17e9a..4661032 100644 --- a/packages/test-site/docusaurus.config.ts +++ b/packages/test-site/docusaurus.config.ts @@ -124,11 +124,64 @@ export default { themeConfig: { // NetFoundry theme configuration netfoundry: { - showStarBanner: true, - starBanner: { - repoUrl: 'https://github.com/openziti/ziti', - label: 'Star OpenZiti on GitHub', - }, + starBanners: [ + { pathPrefix: '/docs/openziti', repoUrl: 'https://github.com/openziti/ziti', label: 'Star OpenZiti on GitHub' }, + { pathPrefix: '/docs/zrok', repoUrl: 'https://github.com/openziti/zrok', label: 'Star zrok on GitHub' }, + ], + productPickerColumns: [ + { + header: 'Managed Cloud', + links: [ + { + label: 'NetFoundry Console', + to: '#', + logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', + description: 'Cloud-managed orchestration and global fabric control.', + }, + { + label: 'Frontdoor', + to: '/docs/frontdoor', + logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', + description: 'Secure application access gateway.', + }, + ], + }, + { + header: 'Open Source', + links: [ + { + label: 'OpenZiti', + to: '/docs/openziti', + logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', + description: 'Programmable zero-trust mesh infrastructure.', + }, + { + label: 'zrok', + to: '/docs/zrok', + logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', + logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', + description: 'Secure peer-to-peer sharing built on OpenZiti.', + }, + ], + }, + { + header: 'Your own infrastructure', + links: [ + { + label: 'Self-Hosted', + to: '/docs/selfhosted', + logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', + description: 'Deploy the full stack in your own environment.', + }, + { + label: 'zLAN', + to: '/docs/zlan', + logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', + description: 'Zero-trust access for OT networks.', + }, + ], + }, + ], footer: { description: 'This is just a test site for the NetFoundry Docusaurus theme.', socialProps: { diff --git a/unified-doc/src/pages/index.tsx b/unified-doc/src/pages/index.tsx index 6d579b3..8c4367f 100644 --- a/unified-doc/src/pages/index.tsx +++ b/unified-doc/src/pages/index.tsx @@ -1,152 +1,81 @@ -// src/pages/index.tsx -import type {ReactNode} from 'react'; +import React, {JSX} from 'react'; +import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import { - defaultNetFoundryFooterProps, - NetFoundryHorizontalSection, - NetFoundryLayout, -} from '@netfoundry/docusaurus-theme/ui'; -import styles from "./index.module.css"; +import clsx from 'clsx'; +import styles from './landing.module.css'; -export default function Home(): ReactNode { - const {siteConfig} = useDocusaurusContext(); - const fp = defaultNetFoundryFooterProps(); - fp.description = 'NetFoundry documentation for open source projects and products'; +const CYAN = '#22d3ee'; +const GREEN = '#22c55e'; +const IMG = 'https://netfoundry.io/docs/img'; +const NF_LOGO = 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg'; - return ( - -
- -
-

NetFoundry Docs

-

- Guides, references, and how-tos for NetFoundry products and OpenZiti. -

-
- NetFoundry SaaS Docs - - Self-Hosted Docs - - Frontdoor Docs - - OpenZiti Docs - - zLAN Docs -
-
-
+const products = [ + { id: 'console', title: 'NetFoundry Console', logo: NF_LOGO, tag: 'Managed', accent: CYAN, link: '#', features: ['Fully managed SaaS', 'Global edge fabric', 'No infra to operate', 'Policy-based access'], description: "The cloud-managed control plane for NetFoundry's global zero-trust fabric. Orchestrate identities, policies, and edge routers — no infrastructure to run." }, + { id: 'openziti', title: 'OpenZiti', logo: `${IMG}/openziti-sm-logo.svg`, tag: 'Open Source', accent: GREEN, link: '/docs/openziti', description: 'The open-source zero-trust networking framework at the heart of the NetFoundry platform. Embed dark, app-native security directly in your code — no VPN, no perimeter.' }, + { id: 'frontdoor', title: 'Frontdoor', logo: `${IMG}/frontdoor-sm-logo.svg`, tag: 'Managed', accent: CYAN, link: '/docs/frontdoor', features: ['No agent or VPN required', 'Zero firewall rules', 'Identity-based access', 'Any app, any browser'], description: 'Secure, clientless access to any application — without a VPN or firewall rule. Expose nothing to the internet while giving authorized users instant access.' }, + { id: 'zrok', title: 'zrok', logo: `${IMG}/zrok-1.0.0-rocket-purple.svg`, tag: 'Open Source', accent: GREEN, link: '/docs/zrok', description: 'Geo-scale secure sharing built on the OpenZiti mesh. Share services, files, or HTTP endpoints peer-to-peer — no open ports, no NAT traversal tricks.' }, + { id: 'selfhosted', title: 'NetFoundry Self-Hosted', logo: `${IMG}/onprem-sm-logo.svg`, tag: 'Self-Hosted', accent: CYAN, link: '/docs/onprem', features: ['Full infrastructure control', 'Air-gap compatible', 'On-prem or any cloud', 'Enterprise SLA'], description: 'Deploy the full NetFoundry control plane and fabric in your own environment. Full sovereignty over your zero-trust infrastructure — on-prem, air-gapped, or any cloud.' }, + { id: 'zlan', title: 'zLAN', logo: `${IMG}/zlan-logo.svg`, tag: 'OT Security', accent: CYAN, link: '/docs/zlan', features: ['Deep OT/IT traffic visibility', 'Identity-aware micro-segmentation', 'Centralized zero-trust policy', 'Built on NetFoundry Self-Hosted'], description: 'Identity-aware micro-segmentation firewall for operational technology networks. Deep traffic visibility, centralized policy, and zero-trust access control for OT environments.' }, +]; - -
-
-
-
-

NetFoundry SaaS

-
- Enterprise cloud-hosted platform for OpenZiti overlays. -
-
- Go to NetFoundry SaaS -
-
-
-
-
-

NetFoundry Self-Hosted

-
- Enterprise self-hosted platform for OpenZiti overlays. -
-
- Go to NetFoundry Self-Hosted -
-
-
-
-
-

NetFoundry Frontdoor

-
- Zero-trust inbound access to private apps and services. -
-
- Go to NetFoundry Frontdoor -
-
-
-
-
-

OpenZiti

-
- Open-source zero-trust networking project and SDKs. -
-
- Go to OpenZiti -
-
-
-
-
-

zLAN

-
- Built on the robust foundation of NetFoundy OpenZiti, NetFoundry zLAN combines advanced firewall capabilities with the power of zero trust and secure network overlay -
-
- Go to zLAN -
-
-
-
-
-

zrok

-
- zrok is an open-source, self-hostable sharing platform that simplifies shielding and sharing network services or files. -
-
- Go to zrok -
-
-
-
+type Product = (typeof products)[number]; +const byId = Object.fromEntries(products.map(p => [p.id, p])) as Record; -
-
-
-

Quick Links

-
-
    -
  • NetFoundry Troubleshooting
  • -
  • Self-Hosted Deployment
  • -
  • Frontdoor Getting Started
  • -
  • OpenZiti CLI Reference
  • -
  • zLAN FAQ
  • -
-
-
-
-
-
-

Support

-
-
    -
  • NetFoundry Troubleshooting
  • -
  • Self-Hosted Troubleshooting
  • -
  • Frontdoor Troubleshooting
  • -
  • OpenZiti FAQ
  • -
  • zLAN FAQ
  • -
-
-
-
-
+function BentoCard({product, featured = false}: {product: Product; featured?: boolean}): JSX.Element { + const accentMod = product.accent === CYAN ? styles['nf-bento-card--accent-cyan'] : styles['nf-bento-card--accent-green']; + return ( +
+ + {product.tag} +
+ {product.logo && {product.title}} +

{product.title}

+
+

{product.description}

+ {product.features && ( +
    + {product.features.map(f =>
  • {f}
  • )} +
+ )} +
Explore →
+ +
+ ); +} -
-
-
-
- ); +export default function Home(): JSX.Element { + return ( + +
+
+

NetFoundry Docs

+

Secure, high-performance networking for the modern era.

+
+ Get Started + Request Demo +
+
+
+
+
+
+
Managed Cloud
+
+ +
open-source counterpart
+ +
+
+ +
open-source counterpart
+ +
+
Run on your own infrastructure
+ + +
+
+
+
+ ); } diff --git a/unified-doc/src/pages/landing.module.css b/unified-doc/src/pages/landing.module.css new file mode 100644 index 0000000..f7a749b --- /dev/null +++ b/unified-doc/src/pages/landing.module.css @@ -0,0 +1,160 @@ +.nf-hero-stage { + position: relative; width: 100%; min-height: 370px; + display: flex; align-items: center; justify-content: center; + overflow: hidden; text-align: center; background: #020617; z-index: 4; +} +.nf-hero-stage::after { + content: ''; position: absolute; bottom: 0; left: 0; right: 0; + height: 220px; background: linear-gradient(to bottom, transparent 0%, #0f172a 100%); + pointer-events: none; z-index: 1; +} +:global([data-theme='light']) .nf-hero-stage::after { + height: 80px; + background: linear-gradient(to bottom, transparent 0%, #020617 100%); +} +.nf-hero-overlay { display: none; } +.nf-hero-content { + position: relative; z-index: 2; padding: 2.5rem 3.5rem; + background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; +} +.nf-hero-title { + font-size: 4rem; font-weight: 900; color: #ffffff; margin-bottom: 0.75rem; + letter-spacing: -0.02em; line-height: 1.05; + text-shadow: 0 0 20px rgba(34, 211, 238, 0.8), 0 2px 12px rgba(0, 0, 0, 0.9); +} +.nf-green-text { + background: linear-gradient(to right, #22c55e 0%, #86efac 100%); + -webkit-background-clip: text; background-clip: text; + -webkit-text-fill-color: transparent; display: inline-block; +} +.nf-hero-subtext { + color: rgba(203, 213, 225, 0.95); font-size: 1.15rem; max-width: 560px; + margin: 0 auto 2rem; line-height: 1.65; text-shadow: 0 1px 8px rgba(0, 0, 0, 0.95); +} +.nf-hero-ctas { display: flex; gap: 1rem; justify-content: center; } +.nf-btn-primary { + display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; + background: #0076FF; color: #ffffff; border-radius: 8px; font-weight: 700; + font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; border: 2px solid #0076FF; +} +.nf-btn-primary:hover { + background: #005ce6; border-color: #005ce6; transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 118, 255, 0.4); color: #ffffff; +} +.nf-btn-ghost { + display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; + background: transparent; color: #ffffff; border-radius: 8px; font-weight: 700; + font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; + border: 2px solid rgba(255, 255, 255, 0.25); +} +.nf-btn-ghost:hover { + background: rgba(255, 255, 255, 0.06); border-color: rgba(34, 211, 238, 0.5); + transform: translateY(-2px); color: #ffffff; +} + +.nf-features-section { width: 100%; background: #0f172a; padding: 5rem 0 2.5rem; } +:global([data-theme='light']) .nf-features-section { + background: linear-gradient(to bottom, + #020617 0px, #020617 80px, #404350 18%, #7d808a 28%, + #b8bbc1 38%, #e4e7ea 48%, #f8fafc 100%); +} + +.nf-bento-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } + +.nf-bento-divider { + grid-column: 1 / -1; display: flex; align-items: center; gap: 1rem; + padding: 1.5rem 0 0.65rem; color: #94a3b8; font-size: 0.82rem; font-weight: 800; + letter-spacing: 0.12em; text-transform: uppercase; white-space: nowrap; +} +.nf-bento-divider::before, .nf-bento-divider::after { + content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); +} +.nf-divider--top { padding-top: 0; } +.nf-divider--managed { + color: #22d3ee; font-size: 1.05rem; letter-spacing: 0.15em; + text-shadow: 0 0 18px rgba(34, 211, 238, 0.5); +} +.nf-divider--managed::before, .nf-divider--managed::after { background: rgba(34, 211, 238, 0.4); height: 2px; } +:global([data-theme='light']) .nf-bento-divider { color: #64748b; } +:global([data-theme='light']) .nf-bento-divider::before, +:global([data-theme='light']) .nf-bento-divider::after { background: rgba(100, 116, 139, 0.25); } +:global([data-theme='light']) .nf-divider--managed { color: #0891b2; text-shadow: none; } +:global([data-theme='light']) .nf-divider--managed::before, +:global([data-theme='light']) .nf-divider--managed::after { background: rgba(8, 145, 178, 0.35); height: 2px; } + +.nf-pair { display: flex; flex-direction: column; } +.nf-pair-connector { + display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0; + font-size: 0.7rem; font-weight: 800; letter-spacing: 0.12em; + text-transform: uppercase; color: #94a3b8; +} +.nf-pair-connector::before, .nf-pair-connector::after { + content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); +} +:global([data-theme='light']) .nf-pair-connector { color: #64748b; } +:global([data-theme='light']) .nf-pair-connector::before, +:global([data-theme='light']) .nf-pair-connector::after { background: rgba(148, 163, 184, 0.35); } + +.nf-bento-wrap { display: flex; flex-direction: column; } + +.nf-bento-card { + position: relative; display: flex; flex-direction: column; flex: 1; + padding: 1rem; border-radius: 12px; text-decoration: none; + background: #1a1b2e; border: 1px solid rgba(148, 163, 184, 0.1); + border-top: 2px solid #22d3ee; + box-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.3), 0 16px 40px rgba(0,0,0,0.2); + transition: transform 0.2s ease, box-shadow 0.2s ease, border-top-color 0.2s ease; +} +.nf-bento-card:hover { + transform: translateY(-4px); border-top-color: #22c55e; + box-shadow: 0 2px 6px rgba(0,0,0,0.4), 0 12px 32px rgba(0,0,0,0.45), + 0 24px 60px rgba(0,0,0,0.25), 0 0 0 1px rgba(34,197,94,0.12); +} +:global([data-theme='light']) .nf-bento-card { + background: #edf3f8; border-color: rgba(0,0,0,0.08); + box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 14px rgba(0,0,0,0.05); +} +:global([data-theme='light']) .nf-bento-card:hover { + box-shadow: 0 2px 6px rgba(0,0,0,0.09), 0 8px 24px rgba(0,0,0,0.07), 0 0 0 1px rgba(34,197,94,0.18); +} +:global([data-theme='light']) .nf-bento-card--accent-cyan { border-top-color: #0891b2; } +:global([data-theme='light']) .nf-bento-card--accent-green { border-top-color: #16a34a; } +.nf-bento-card--featured { padding: 1.25rem; border-top-width: 3px; } +.nf-bento-card--featured .nf-card-logo { width: 48px; height: 48px; } +.nf-bento-card--featured .nf-card-header h3 { font-size: 1.25rem; } + +.nf-card-badge { + position: absolute; top: 1rem; right: 1rem; display: inline-flex; width: fit-content; + background: rgba(34,197,94,0.1); color: #4ade80; border: 1px solid rgba(34,197,94,0.2); + font-size: 0.6rem; font-weight: 800; letter-spacing: 0.1em; + padding: 2px 8px; border-radius: 4px; text-transform: uppercase; +} +:global([data-theme='light']) .nf-card-badge { color: #15803d; border-color: rgba(34,197,94,0.25); } + +.nf-card-header { + display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; padding-right: 3rem; +} +.nf-card-header h3 { margin: 0; } +.nf-bento-card h3 { color: #f1f5f9; font-weight: 900; font-size: 1.05rem; line-height: 1.3; letter-spacing: -0.02em; } +:global([data-theme='light']) .nf-bento-card h3 { color: #0f172a; } +.nf-bento-card p { color: #94a3b8; font-size: 0.875rem; line-height: 1.65; flex-grow: 1; margin: 0 0 0.5rem; } +:global([data-theme='light']) .nf-bento-card p { color: #475569; } + +.nf-bento-features { list-style: none; padding: 0; margin: 0 0 0.5rem; display: flex; flex-direction: column; gap: 0.25rem; } +.nf-bento-features li { display: flex; align-items: center; gap: 0.4rem; font-size: 0.775rem; color: #64748b; } +.nf-bento-features li::before { content: '✓'; color: #22c55e; font-weight: 800; font-size: 0.75rem; flex-shrink: 0; } +:global([data-theme='light']) .nf-bento-features li::before { color: #16a34a; } + +.nf-card-link { color: #22d3ee; font-size: 0.8rem; font-weight: 700; margin-top: auto; padding-top: 0.5rem; letter-spacing: 0.03em; } +:global([data-theme='light']) .nf-card-link { color: #0284c7; } +.nf-card-logo { width: 40px; height: 40px; object-fit: contain; flex-shrink: 0; } + +@media (max-width: 996px) { + .nf-hero-title { font-size: 2.2rem; } + .nf-hero-content { padding: 2rem 1.25rem; } + .nf-hero-ctas { flex-wrap: wrap; } + .nf-btn-primary, .nf-btn-ghost { padding: 0.55rem 1.25rem; font-size: 0.9rem; } +} +@media (max-width: 640px) { + .nf-bento-grid { grid-template-columns: 1fr; } +} From 96967354dea5ed901e1ce7a758eb5449f8706aa0 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:11:18 -0500 Subject: [PATCH 10/21] continued to test/use/update --- .../docusaurus-theme/css/product-picker.css | 1 - .../NavbarItem/types/ProductPicker/index.tsx | 17 +- unified-doc/docusaurus.config.ts | 82 +++++--- unified-doc/src/pages/index.module.css | 189 +++++++++++++++--- unified-doc/src/pages/index.tsx | 2 +- unified-doc/src/pages/landing.module.css | 160 --------------- 6 files changed, 225 insertions(+), 226 deletions(-) delete mode 100644 unified-doc/src/pages/landing.module.css diff --git a/packages/docusaurus-theme/css/product-picker.css b/packages/docusaurus-theme/css/product-picker.css index 9b729fd..3270882 100644 --- a/packages/docusaurus-theme/css/product-picker.css +++ b/packages/docusaurus-theme/css/product-picker.css @@ -21,7 +21,6 @@ /* ── Dropdown trigger button in the navbar ──────────────────────────────── */ .nf-picker-trigger, .nf-resources-dropdown { - font-weight: 600; color: var(--ifm-font-color-base); position: relative; padding-right: 1.2rem; diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx index 5a64522..6e26792 100644 --- a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx +++ b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx @@ -21,7 +21,6 @@ export type PickerColumn = { type Props = { label?: string; position?: 'left' | 'right'; - columns?: PickerColumn[]; className?: string; }; @@ -55,14 +54,14 @@ const buildDefaultColumns = (img: string, consoleLogo: string): PickerColumn[] = }, ]; -export default function ProductPicker({label = 'Products', columns, className}: Props) { +export default function ProductPicker({label = 'Products', className}: Props) { const {siteConfig} = useDocusaurusContext(); const themeConfig = useThemeConfig() as any; const consoleLogo = themeConfig?.netfoundry?.consoleLogo ?? NF_LOGO_DEFAULT; const img = `${siteConfig.url}${siteConfig.baseUrl}img`; - const configColumns: PickerColumn[] | undefined = themeConfig?.netfoundry?.productPickerColumns - ?.map((col: any, i: number) => ({...col, headerClass: HEADER_CLASSES[i] ?? ''})); - columns ??= configColumns ?? buildDefaultColumns(img, consoleLogo); + const columns: PickerColumn[] = (themeConfig?.netfoundry?.productPickerColumns ?? []) + .map((col: any, i: number) => ({...col, headerClass: HEADER_CLASSES[i] ?? ''})); + const resolvedColumns = columns.length ? columns : buildDefaultColumns(img, consoleLogo); const wrapRef = useRef(null); const hasEnteredPanel = useRef(false); const [open, setOpen] = useState(false); @@ -114,15 +113,15 @@ export default function ProductPicker({label = 'Products', columns, className}: return (
+ className={clsx('navbar__item', {'nf-picker--open': open})}> { e.preventDefault(); setOpen(o => !o); }} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); setOpen(o => !o); } }}> {label} @@ -134,7 +133,7 @@ export default function ProductPicker({label = 'Products', columns, className}: onMouseEnter={handlePanelEnter} onMouseLeave={handlePanelLeave}>
- {columns.map((col, i) => ( + {resolvedColumns.map((col, i) => (
{col.header} {col.links.map((link, j) => ( diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index 50007c3..3ceb6d3 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -357,6 +357,62 @@ const config: Config = { build(BUILD_FLAGS.SELFHOSTED) && onpremRedirects(routeBase('selfhosted')), ].filter(Boolean), themeConfig: { + netfoundry: { + productPickerColumns: [ + { + header: 'Managed Cloud', + links: [ + { + label: 'NetFoundry Console', + to: '#', + logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', + description: 'Cloud-managed orchestration and global fabric control.', + }, + { + label: 'Frontdoor', + to: '/frontdoor/intro', + logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', + description: 'Secure application access gateway.', + }, + ], + }, + { + header: 'Open Source', + links: [ + { + label: 'OpenZiti', + to: '/openziti/learn/introduction', + logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', + description: 'Programmable zero-trust mesh infrastructure.', + }, + { + label: 'zrok', + to: '/zrok', + logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', + logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', + description: 'Secure peer-to-peer sharing built on OpenZiti.', + }, + ], + }, + { + header: 'Your own infrastructure', + links: [ + { + label: 'Self-Hosted', + to: '/selfhosted/intro', + logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', + description: 'Deploy the full stack in your own environment.', + }, + { + label: 'zLAN', + to: '/zlan/intro', + logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', + description: 'Zero-trust access for OT networks.', + }, + ], + }, + ], + }, docs: { sidebar: { hideable: false, @@ -380,32 +436,6 @@ const config: Config = { type: 'custom-productPicker', position: 'left', label: 'Products', - columns: [ - { - header: 'Managed Cloud', - headerClass: 'picker-header--nf-primary', - links: [ - { label: 'NetFoundry Console', to: '#', logo: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', description: 'Cloud-managed orchestration and global fabric control.' }, - { label: 'Frontdoor', to: '/frontdoor/intro', logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.' }, - ], - }, - { - header: 'Open Source', - headerClass: 'picker-header--nf-secondary', - links: [ - { label: 'OpenZiti', to: '/openziti/learn/introduction', logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.' }, - { label: 'zrok', to: '/zrok', logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.' }, - ], - }, - { - header: 'Your own infrastructure', - headerClass: 'picker-header--nf-tertiary', - links: [ - { label: 'Self-Hosted', to: '/selfhosted/intro', logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.' }, - { label: 'zLAN', to: '/zlan', logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.' }, - ], - }, - ], }, ], }, diff --git a/unified-doc/src/pages/index.module.css b/unified-doc/src/pages/index.module.css index 11782a8..f7a749b 100644 --- a/unified-doc/src/pages/index.module.css +++ b/unified-doc/src/pages/index.module.css @@ -1,29 +1,160 @@ -/** - * CSS files with the .module.css suffix will be treated as CSS modules - * and scoped locally. - */ - -.heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; -} - -@media screen and (max-width: 996px) { - .heroBanner { - padding: 2rem; - } -} - -.buttons { - display: flex; - align-items: center; - justify-content: center; -} - -.idxcard { - min-height: 200px; - display: flex; - flex-direction: column; -} \ No newline at end of file +.nf-hero-stage { + position: relative; width: 100%; min-height: 370px; + display: flex; align-items: center; justify-content: center; + overflow: hidden; text-align: center; background: #020617; z-index: 4; +} +.nf-hero-stage::after { + content: ''; position: absolute; bottom: 0; left: 0; right: 0; + height: 220px; background: linear-gradient(to bottom, transparent 0%, #0f172a 100%); + pointer-events: none; z-index: 1; +} +:global([data-theme='light']) .nf-hero-stage::after { + height: 80px; + background: linear-gradient(to bottom, transparent 0%, #020617 100%); +} +.nf-hero-overlay { display: none; } +.nf-hero-content { + position: relative; z-index: 2; padding: 2.5rem 3.5rem; + background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; +} +.nf-hero-title { + font-size: 4rem; font-weight: 900; color: #ffffff; margin-bottom: 0.75rem; + letter-spacing: -0.02em; line-height: 1.05; + text-shadow: 0 0 20px rgba(34, 211, 238, 0.8), 0 2px 12px rgba(0, 0, 0, 0.9); +} +.nf-green-text { + background: linear-gradient(to right, #22c55e 0%, #86efac 100%); + -webkit-background-clip: text; background-clip: text; + -webkit-text-fill-color: transparent; display: inline-block; +} +.nf-hero-subtext { + color: rgba(203, 213, 225, 0.95); font-size: 1.15rem; max-width: 560px; + margin: 0 auto 2rem; line-height: 1.65; text-shadow: 0 1px 8px rgba(0, 0, 0, 0.95); +} +.nf-hero-ctas { display: flex; gap: 1rem; justify-content: center; } +.nf-btn-primary { + display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; + background: #0076FF; color: #ffffff; border-radius: 8px; font-weight: 700; + font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; border: 2px solid #0076FF; +} +.nf-btn-primary:hover { + background: #005ce6; border-color: #005ce6; transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 118, 255, 0.4); color: #ffffff; +} +.nf-btn-ghost { + display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; + background: transparent; color: #ffffff; border-radius: 8px; font-weight: 700; + font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; + border: 2px solid rgba(255, 255, 255, 0.25); +} +.nf-btn-ghost:hover { + background: rgba(255, 255, 255, 0.06); border-color: rgba(34, 211, 238, 0.5); + transform: translateY(-2px); color: #ffffff; +} + +.nf-features-section { width: 100%; background: #0f172a; padding: 5rem 0 2.5rem; } +:global([data-theme='light']) .nf-features-section { + background: linear-gradient(to bottom, + #020617 0px, #020617 80px, #404350 18%, #7d808a 28%, + #b8bbc1 38%, #e4e7ea 48%, #f8fafc 100%); +} + +.nf-bento-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } + +.nf-bento-divider { + grid-column: 1 / -1; display: flex; align-items: center; gap: 1rem; + padding: 1.5rem 0 0.65rem; color: #94a3b8; font-size: 0.82rem; font-weight: 800; + letter-spacing: 0.12em; text-transform: uppercase; white-space: nowrap; +} +.nf-bento-divider::before, .nf-bento-divider::after { + content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); +} +.nf-divider--top { padding-top: 0; } +.nf-divider--managed { + color: #22d3ee; font-size: 1.05rem; letter-spacing: 0.15em; + text-shadow: 0 0 18px rgba(34, 211, 238, 0.5); +} +.nf-divider--managed::before, .nf-divider--managed::after { background: rgba(34, 211, 238, 0.4); height: 2px; } +:global([data-theme='light']) .nf-bento-divider { color: #64748b; } +:global([data-theme='light']) .nf-bento-divider::before, +:global([data-theme='light']) .nf-bento-divider::after { background: rgba(100, 116, 139, 0.25); } +:global([data-theme='light']) .nf-divider--managed { color: #0891b2; text-shadow: none; } +:global([data-theme='light']) .nf-divider--managed::before, +:global([data-theme='light']) .nf-divider--managed::after { background: rgba(8, 145, 178, 0.35); height: 2px; } + +.nf-pair { display: flex; flex-direction: column; } +.nf-pair-connector { + display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0; + font-size: 0.7rem; font-weight: 800; letter-spacing: 0.12em; + text-transform: uppercase; color: #94a3b8; +} +.nf-pair-connector::before, .nf-pair-connector::after { + content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); +} +:global([data-theme='light']) .nf-pair-connector { color: #64748b; } +:global([data-theme='light']) .nf-pair-connector::before, +:global([data-theme='light']) .nf-pair-connector::after { background: rgba(148, 163, 184, 0.35); } + +.nf-bento-wrap { display: flex; flex-direction: column; } + +.nf-bento-card { + position: relative; display: flex; flex-direction: column; flex: 1; + padding: 1rem; border-radius: 12px; text-decoration: none; + background: #1a1b2e; border: 1px solid rgba(148, 163, 184, 0.1); + border-top: 2px solid #22d3ee; + box-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.3), 0 16px 40px rgba(0,0,0,0.2); + transition: transform 0.2s ease, box-shadow 0.2s ease, border-top-color 0.2s ease; +} +.nf-bento-card:hover { + transform: translateY(-4px); border-top-color: #22c55e; + box-shadow: 0 2px 6px rgba(0,0,0,0.4), 0 12px 32px rgba(0,0,0,0.45), + 0 24px 60px rgba(0,0,0,0.25), 0 0 0 1px rgba(34,197,94,0.12); +} +:global([data-theme='light']) .nf-bento-card { + background: #edf3f8; border-color: rgba(0,0,0,0.08); + box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 14px rgba(0,0,0,0.05); +} +:global([data-theme='light']) .nf-bento-card:hover { + box-shadow: 0 2px 6px rgba(0,0,0,0.09), 0 8px 24px rgba(0,0,0,0.07), 0 0 0 1px rgba(34,197,94,0.18); +} +:global([data-theme='light']) .nf-bento-card--accent-cyan { border-top-color: #0891b2; } +:global([data-theme='light']) .nf-bento-card--accent-green { border-top-color: #16a34a; } +.nf-bento-card--featured { padding: 1.25rem; border-top-width: 3px; } +.nf-bento-card--featured .nf-card-logo { width: 48px; height: 48px; } +.nf-bento-card--featured .nf-card-header h3 { font-size: 1.25rem; } + +.nf-card-badge { + position: absolute; top: 1rem; right: 1rem; display: inline-flex; width: fit-content; + background: rgba(34,197,94,0.1); color: #4ade80; border: 1px solid rgba(34,197,94,0.2); + font-size: 0.6rem; font-weight: 800; letter-spacing: 0.1em; + padding: 2px 8px; border-radius: 4px; text-transform: uppercase; +} +:global([data-theme='light']) .nf-card-badge { color: #15803d; border-color: rgba(34,197,94,0.25); } + +.nf-card-header { + display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; padding-right: 3rem; +} +.nf-card-header h3 { margin: 0; } +.nf-bento-card h3 { color: #f1f5f9; font-weight: 900; font-size: 1.05rem; line-height: 1.3; letter-spacing: -0.02em; } +:global([data-theme='light']) .nf-bento-card h3 { color: #0f172a; } +.nf-bento-card p { color: #94a3b8; font-size: 0.875rem; line-height: 1.65; flex-grow: 1; margin: 0 0 0.5rem; } +:global([data-theme='light']) .nf-bento-card p { color: #475569; } + +.nf-bento-features { list-style: none; padding: 0; margin: 0 0 0.5rem; display: flex; flex-direction: column; gap: 0.25rem; } +.nf-bento-features li { display: flex; align-items: center; gap: 0.4rem; font-size: 0.775rem; color: #64748b; } +.nf-bento-features li::before { content: '✓'; color: #22c55e; font-weight: 800; font-size: 0.75rem; flex-shrink: 0; } +:global([data-theme='light']) .nf-bento-features li::before { color: #16a34a; } + +.nf-card-link { color: #22d3ee; font-size: 0.8rem; font-weight: 700; margin-top: auto; padding-top: 0.5rem; letter-spacing: 0.03em; } +:global([data-theme='light']) .nf-card-link { color: #0284c7; } +.nf-card-logo { width: 40px; height: 40px; object-fit: contain; flex-shrink: 0; } + +@media (max-width: 996px) { + .nf-hero-title { font-size: 2.2rem; } + .nf-hero-content { padding: 2rem 1.25rem; } + .nf-hero-ctas { flex-wrap: wrap; } + .nf-btn-primary, .nf-btn-ghost { padding: 0.55rem 1.25rem; font-size: 0.9rem; } +} +@media (max-width: 640px) { + .nf-bento-grid { grid-template-columns: 1fr; } +} diff --git a/unified-doc/src/pages/index.tsx b/unified-doc/src/pages/index.tsx index 8c4367f..653d3b2 100644 --- a/unified-doc/src/pages/index.tsx +++ b/unified-doc/src/pages/index.tsx @@ -2,7 +2,7 @@ import React, {JSX} from 'react'; import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import clsx from 'clsx'; -import styles from './landing.module.css'; +import styles from './index.module.css'; const CYAN = '#22d3ee'; const GREEN = '#22c55e'; diff --git a/unified-doc/src/pages/landing.module.css b/unified-doc/src/pages/landing.module.css deleted file mode 100644 index f7a749b..0000000 --- a/unified-doc/src/pages/landing.module.css +++ /dev/null @@ -1,160 +0,0 @@ -.nf-hero-stage { - position: relative; width: 100%; min-height: 370px; - display: flex; align-items: center; justify-content: center; - overflow: hidden; text-align: center; background: #020617; z-index: 4; -} -.nf-hero-stage::after { - content: ''; position: absolute; bottom: 0; left: 0; right: 0; - height: 220px; background: linear-gradient(to bottom, transparent 0%, #0f172a 100%); - pointer-events: none; z-index: 1; -} -:global([data-theme='light']) .nf-hero-stage::after { - height: 80px; - background: linear-gradient(to bottom, transparent 0%, #020617 100%); -} -.nf-hero-overlay { display: none; } -.nf-hero-content { - position: relative; z-index: 2; padding: 2.5rem 3.5rem; - background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; -} -.nf-hero-title { - font-size: 4rem; font-weight: 900; color: #ffffff; margin-bottom: 0.75rem; - letter-spacing: -0.02em; line-height: 1.05; - text-shadow: 0 0 20px rgba(34, 211, 238, 0.8), 0 2px 12px rgba(0, 0, 0, 0.9); -} -.nf-green-text { - background: linear-gradient(to right, #22c55e 0%, #86efac 100%); - -webkit-background-clip: text; background-clip: text; - -webkit-text-fill-color: transparent; display: inline-block; -} -.nf-hero-subtext { - color: rgba(203, 213, 225, 0.95); font-size: 1.15rem; max-width: 560px; - margin: 0 auto 2rem; line-height: 1.65; text-shadow: 0 1px 8px rgba(0, 0, 0, 0.95); -} -.nf-hero-ctas { display: flex; gap: 1rem; justify-content: center; } -.nf-btn-primary { - display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; - background: #0076FF; color: #ffffff; border-radius: 8px; font-weight: 700; - font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; border: 2px solid #0076FF; -} -.nf-btn-primary:hover { - background: #005ce6; border-color: #005ce6; transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(0, 118, 255, 0.4); color: #ffffff; -} -.nf-btn-ghost { - display: inline-flex; align-items: center; padding: 0.65rem 1.75rem; - background: transparent; color: #ffffff; border-radius: 8px; font-weight: 700; - font-size: 0.95rem; text-decoration: none; transition: all 0.25s ease; - border: 2px solid rgba(255, 255, 255, 0.25); -} -.nf-btn-ghost:hover { - background: rgba(255, 255, 255, 0.06); border-color: rgba(34, 211, 238, 0.5); - transform: translateY(-2px); color: #ffffff; -} - -.nf-features-section { width: 100%; background: #0f172a; padding: 5rem 0 2.5rem; } -:global([data-theme='light']) .nf-features-section { - background: linear-gradient(to bottom, - #020617 0px, #020617 80px, #404350 18%, #7d808a 28%, - #b8bbc1 38%, #e4e7ea 48%, #f8fafc 100%); -} - -.nf-bento-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } - -.nf-bento-divider { - grid-column: 1 / -1; display: flex; align-items: center; gap: 1rem; - padding: 1.5rem 0 0.65rem; color: #94a3b8; font-size: 0.82rem; font-weight: 800; - letter-spacing: 0.12em; text-transform: uppercase; white-space: nowrap; -} -.nf-bento-divider::before, .nf-bento-divider::after { - content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); -} -.nf-divider--top { padding-top: 0; } -.nf-divider--managed { - color: #22d3ee; font-size: 1.05rem; letter-spacing: 0.15em; - text-shadow: 0 0 18px rgba(34, 211, 238, 0.5); -} -.nf-divider--managed::before, .nf-divider--managed::after { background: rgba(34, 211, 238, 0.4); height: 2px; } -:global([data-theme='light']) .nf-bento-divider { color: #64748b; } -:global([data-theme='light']) .nf-bento-divider::before, -:global([data-theme='light']) .nf-bento-divider::after { background: rgba(100, 116, 139, 0.25); } -:global([data-theme='light']) .nf-divider--managed { color: #0891b2; text-shadow: none; } -:global([data-theme='light']) .nf-divider--managed::before, -:global([data-theme='light']) .nf-divider--managed::after { background: rgba(8, 145, 178, 0.35); height: 2px; } - -.nf-pair { display: flex; flex-direction: column; } -.nf-pair-connector { - display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0; - font-size: 0.7rem; font-weight: 800; letter-spacing: 0.12em; - text-transform: uppercase; color: #94a3b8; -} -.nf-pair-connector::before, .nf-pair-connector::after { - content: ''; flex: 1; height: 1px; background: rgba(148, 163, 184, 0.2); -} -:global([data-theme='light']) .nf-pair-connector { color: #64748b; } -:global([data-theme='light']) .nf-pair-connector::before, -:global([data-theme='light']) .nf-pair-connector::after { background: rgba(148, 163, 184, 0.35); } - -.nf-bento-wrap { display: flex; flex-direction: column; } - -.nf-bento-card { - position: relative; display: flex; flex-direction: column; flex: 1; - padding: 1rem; border-radius: 12px; text-decoration: none; - background: #1a1b2e; border: 1px solid rgba(148, 163, 184, 0.1); - border-top: 2px solid #22d3ee; - box-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.3), 0 16px 40px rgba(0,0,0,0.2); - transition: transform 0.2s ease, box-shadow 0.2s ease, border-top-color 0.2s ease; -} -.nf-bento-card:hover { - transform: translateY(-4px); border-top-color: #22c55e; - box-shadow: 0 2px 6px rgba(0,0,0,0.4), 0 12px 32px rgba(0,0,0,0.45), - 0 24px 60px rgba(0,0,0,0.25), 0 0 0 1px rgba(34,197,94,0.12); -} -:global([data-theme='light']) .nf-bento-card { - background: #edf3f8; border-color: rgba(0,0,0,0.08); - box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 14px rgba(0,0,0,0.05); -} -:global([data-theme='light']) .nf-bento-card:hover { - box-shadow: 0 2px 6px rgba(0,0,0,0.09), 0 8px 24px rgba(0,0,0,0.07), 0 0 0 1px rgba(34,197,94,0.18); -} -:global([data-theme='light']) .nf-bento-card--accent-cyan { border-top-color: #0891b2; } -:global([data-theme='light']) .nf-bento-card--accent-green { border-top-color: #16a34a; } -.nf-bento-card--featured { padding: 1.25rem; border-top-width: 3px; } -.nf-bento-card--featured .nf-card-logo { width: 48px; height: 48px; } -.nf-bento-card--featured .nf-card-header h3 { font-size: 1.25rem; } - -.nf-card-badge { - position: absolute; top: 1rem; right: 1rem; display: inline-flex; width: fit-content; - background: rgba(34,197,94,0.1); color: #4ade80; border: 1px solid rgba(34,197,94,0.2); - font-size: 0.6rem; font-weight: 800; letter-spacing: 0.1em; - padding: 2px 8px; border-radius: 4px; text-transform: uppercase; -} -:global([data-theme='light']) .nf-card-badge { color: #15803d; border-color: rgba(34,197,94,0.25); } - -.nf-card-header { - display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; padding-right: 3rem; -} -.nf-card-header h3 { margin: 0; } -.nf-bento-card h3 { color: #f1f5f9; font-weight: 900; font-size: 1.05rem; line-height: 1.3; letter-spacing: -0.02em; } -:global([data-theme='light']) .nf-bento-card h3 { color: #0f172a; } -.nf-bento-card p { color: #94a3b8; font-size: 0.875rem; line-height: 1.65; flex-grow: 1; margin: 0 0 0.5rem; } -:global([data-theme='light']) .nf-bento-card p { color: #475569; } - -.nf-bento-features { list-style: none; padding: 0; margin: 0 0 0.5rem; display: flex; flex-direction: column; gap: 0.25rem; } -.nf-bento-features li { display: flex; align-items: center; gap: 0.4rem; font-size: 0.775rem; color: #64748b; } -.nf-bento-features li::before { content: '✓'; color: #22c55e; font-weight: 800; font-size: 0.75rem; flex-shrink: 0; } -:global([data-theme='light']) .nf-bento-features li::before { color: #16a34a; } - -.nf-card-link { color: #22d3ee; font-size: 0.8rem; font-weight: 700; margin-top: auto; padding-top: 0.5rem; letter-spacing: 0.03em; } -:global([data-theme='light']) .nf-card-link { color: #0284c7; } -.nf-card-logo { width: 40px; height: 40px; object-fit: contain; flex-shrink: 0; } - -@media (max-width: 996px) { - .nf-hero-title { font-size: 2.2rem; } - .nf-hero-content { padding: 2rem 1.25rem; } - .nf-hero-ctas { flex-wrap: wrap; } - .nf-btn-primary, .nf-btn-ghost { padding: 0.55rem 1.25rem; font-size: 0.9rem; } -} -@media (max-width: 640px) { - .nf-bento-grid { grid-template-columns: 1fr; } -} From 0aa495012586476822882650d8a4f03196b4901c Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:21:09 -0500 Subject: [PATCH 11/21] update landing page --- unified-doc/src/pages/index.tsx | 67 +++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/unified-doc/src/pages/index.tsx b/unified-doc/src/pages/index.tsx index 653d3b2..3b56c4e 100644 --- a/unified-doc/src/pages/index.tsx +++ b/unified-doc/src/pages/index.tsx @@ -10,12 +10,65 @@ const IMG = 'https://netfoundry.io/docs/img'; const NF_LOGO = 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg'; const products = [ - { id: 'console', title: 'NetFoundry Console', logo: NF_LOGO, tag: 'Managed', accent: CYAN, link: '#', features: ['Fully managed SaaS', 'Global edge fabric', 'No infra to operate', 'Policy-based access'], description: "The cloud-managed control plane for NetFoundry's global zero-trust fabric. Orchestrate identities, policies, and edge routers — no infrastructure to run." }, - { id: 'openziti', title: 'OpenZiti', logo: `${IMG}/openziti-sm-logo.svg`, tag: 'Open Source', accent: GREEN, link: '/docs/openziti', description: 'The open-source zero-trust networking framework at the heart of the NetFoundry platform. Embed dark, app-native security directly in your code — no VPN, no perimeter.' }, - { id: 'frontdoor', title: 'Frontdoor', logo: `${IMG}/frontdoor-sm-logo.svg`, tag: 'Managed', accent: CYAN, link: '/docs/frontdoor', features: ['No agent or VPN required', 'Zero firewall rules', 'Identity-based access', 'Any app, any browser'], description: 'Secure, clientless access to any application — without a VPN or firewall rule. Expose nothing to the internet while giving authorized users instant access.' }, - { id: 'zrok', title: 'zrok', logo: `${IMG}/zrok-1.0.0-rocket-purple.svg`, tag: 'Open Source', accent: GREEN, link: '/docs/zrok', description: 'Geo-scale secure sharing built on the OpenZiti mesh. Share services, files, or HTTP endpoints peer-to-peer — no open ports, no NAT traversal tricks.' }, - { id: 'selfhosted', title: 'NetFoundry Self-Hosted', logo: `${IMG}/onprem-sm-logo.svg`, tag: 'Self-Hosted', accent: CYAN, link: '/docs/onprem', features: ['Full infrastructure control', 'Air-gap compatible', 'On-prem or any cloud', 'Enterprise SLA'], description: 'Deploy the full NetFoundry control plane and fabric in your own environment. Full sovereignty over your zero-trust infrastructure — on-prem, air-gapped, or any cloud.' }, - { id: 'zlan', title: 'zLAN', logo: `${IMG}/zlan-logo.svg`, tag: 'OT Security', accent: CYAN, link: '/docs/zlan', features: ['Deep OT/IT traffic visibility', 'Identity-aware micro-segmentation', 'Centralized zero-trust policy', 'Built on NetFoundry Self-Hosted'], description: 'Identity-aware micro-segmentation firewall for operational technology networks. Deep traffic visibility, centralized policy, and zero-trust access control for OT environments.' }, + { + id: 'console', + title: 'NetFoundry Console', + logo: NF_LOGO, + tag: 'Managed', + accent: CYAN, + link: '#', + features: ['Fully managed SaaS', 'Global edge fabric', 'No infra to operate', 'Policy-based access'], + description: "The cloud-managed control plane for NetFoundry's global zero-trust fabric. Orchestrate identities, policies, and edge routers — no infrastructure to run." + }, + { + id: 'openziti', + title: 'OpenZiti', + logo: `${IMG}/openziti-sm-logo.svg`, + tag: 'Open Source', + accent: GREEN, + link: '/openziti/learn/introduction', + description: 'The open-source zero-trust networking framework at the heart of the NetFoundry platform. Embed dark, app-native security directly in your code — no VPN, no perimeter.' + }, + { + id: 'frontdoor', + title: 'Frontdoor', + logo: `${IMG}/frontdoor-sm-logo.svg`, + tag: 'Managed', + accent: CYAN, + link: '/frontdoor/intro', + features: ['No agent or VPN required', 'Zero firewall rules', 'Identity-based access', 'Any app, any browser'], + description: 'Secure, clientless access to any application — without a VPN or firewall rule. Expose nothing to the internet while giving authorized users instant access.' + }, + { + id: 'zrok', + title: 'zrok', + logo: `${IMG}/zrok-1.0.0-rocket-purple.svg`, + tag: 'Open Source', + accent: GREEN, + link: '/zrok', + description: 'Geo-scale secure sharing built on the OpenZiti mesh. Share services, files, or HTTP endpoints peer-to-peer — no open ports, no NAT traversal tricks.' + }, + { + id: + 'selfhosted', + title: 'NetFoundry Self-Hosted', + logo: `${IMG}/onprem-sm-logo.svg`, + tag: 'Self-Hosted', + accent: CYAN, + link: '/selfhosted/intro', + features: ['Full infrastructure control', 'Air-gap compatible', 'On-prem or any cloud', 'Enterprise SLA'], + description: 'Deploy the full NetFoundry control plane and fabric in your own environment. Full sovereignty over your zero-trust infrastructure — on-prem, air-gapped, or any cloud.' + }, + { + id: 'zlan', + title: 'zLAN', + logo: `${IMG}/zlan-logo.svg`, + tag: 'OT Security', + accent: CYAN, + link: '/zlan/intro', + features: ['Deep OT/IT traffic visibility', 'Identity-aware micro-segmentation', 'Centralized zero-trust policy', 'Built on NetFoundry Self-Hosted'], + description: 'Identity-aware micro-segmentation firewall for operational technology networks. Deep traffic visibility, centralized policy, and zero-trust access control for OT environments.' + }, ]; type Product = (typeof products)[number]; @@ -51,7 +104,7 @@ export default function Home(): JSX.Element {

NetFoundry Docs

Secure, high-performance networking for the modern era.

From 4a81a57220977d870cf4e4c44c538a3cfb84a325 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:38:18 -0500 Subject: [PATCH 12/21] remove duplicate logo --- unified-doc/docusaurus.config.ts | 6 +----- unified-doc/src/theme/Navbar/Logo/index.tsx | 13 +++++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index 3ceb6d3..e092aeb 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -426,11 +426,7 @@ const config: Config = { image: 'https://netfoundry.io/wp-content/uploads/2024/07/netfoundry-logo-tag-color-stacked-1.svg', navbar: { hideOnScroll: false, - title: 'NetFoundry Documentation', - logo: { - alt: 'NetFoundry Logo', - src: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', - }, + title: '', items: [ { type: 'custom-productPicker', diff --git a/unified-doc/src/theme/Navbar/Logo/index.tsx b/unified-doc/src/theme/Navbar/Logo/index.tsx index c21ecc6..93ea7a9 100644 --- a/unified-doc/src/theme/Navbar/Logo/index.tsx +++ b/unified-doc/src/theme/Navbar/Logo/index.tsx @@ -21,8 +21,8 @@ const mapTitle = (p: string) => { includeNFLogo: false, to: '/', alt:'NetFoundry', - logoLight: `/img/netfoundry-name-and-logo.svg`, - logoDark: `/img/netfoundry-name-and-logo-dark.svg` + logoLight: '', + logoDark: '' }; }; @@ -53,17 +53,22 @@ export default function NavbarLogo(): JSX.Element { }} /> + {console.log('[NavbarLogo] title:', JSON.stringify(title))} + {(title.logoLight || title.text) && ( + {title.logoLight && ( - {title.text} + )} + {title.text && {title.text}} + )} ); } \ No newline at end of file From f35bd4bfaaa48228dc02e5d625a19c71019dddb6 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:39:07 -0500 Subject: [PATCH 13/21] v0.8.0 --- packages/docusaurus-theme/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docusaurus-theme/package.json b/packages/docusaurus-theme/package.json index 028ecb9..f59c710 100644 --- a/packages/docusaurus-theme/package.json +++ b/packages/docusaurus-theme/package.json @@ -1,6 +1,6 @@ { "name": "@netfoundry/docusaurus-theme", - "version": "0.7.0", + "version": "0.8.0", "description": "NetFoundry Docusaurus theme with shared layout, footer, and styling", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", From af03d83c522e5b2d3a9c250588c2237e6095701c Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:40:01 -0500 Subject: [PATCH 14/21] use the deployed theme --- unified-doc/package.json | 2 +- unified-doc/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/unified-doc/package.json b/unified-doc/package.json index 8ddd5d0..33ee4a3 100644 --- a/unified-doc/package.json +++ b/unified-doc/package.json @@ -53,7 +53,7 @@ "@docusaurus/theme-mermaid": "^3.9.2", "@hotjar/browser": "^1.0.9", "@mdx-js/react": "^3.0.0", - "@netfoundry/docusaurus-theme": "^0.7.0", + "@netfoundry/docusaurus-theme": "0.8.0", "algoliasearch": "^5.36.0", "asciinema-player": "^3.10.0", "clsx": "^2.0.0", diff --git a/unified-doc/yarn.lock b/unified-doc/yarn.lock index ebbffbd..370f5b3 100644 --- a/unified-doc/yarn.lock +++ b/unified-doc/yarn.lock @@ -2599,10 +2599,10 @@ hls.js "~1.6.6" mux-embed "^5.8.3" -"@netfoundry/docusaurus-theme@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@netfoundry/docusaurus-theme/-/docusaurus-theme-0.7.0.tgz#a02dba7d8aede49bb8bd14d1e135ac6b6a1e6aa0" - integrity sha512-duxsWjmhigQTQs7ikKxQ1UzLdzPu2NKPjr18t/pjD9U9YV0eABCD1GQRJbwbfgHYEuqoGrV1LLEVn+xPkoTGDg== +"@netfoundry/docusaurus-theme@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@netfoundry/docusaurus-theme/-/docusaurus-theme-0.8.0.tgz#d2d714fa76c8b67bb819e1b8142a2c34f4fd64da" + integrity sha512-h+Wxx8tB3L9v673VlIBREzPJj1pEtdk388s2WpZWaek3ZVsv/Nva+f5h1HmZakJWAA76Agm0rWaiSSzg7b+wDA== dependencies: "@docsearch/react" "^3" algoliasearch "^5" From 1e9ce024c9bddce89a4692499dd7c96526b3132c Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:01:04 -0500 Subject: [PATCH 15/21] fix indentation and remove console logging --- unified-doc/src/theme/Navbar/Logo/index.tsx | 49 ++++++++++----------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/unified-doc/src/theme/Navbar/Logo/index.tsx b/unified-doc/src/theme/Navbar/Logo/index.tsx index 93ea7a9..7d5c641 100644 --- a/unified-doc/src/theme/Navbar/Logo/index.tsx +++ b/unified-doc/src/theme/Navbar/Logo/index.tsx @@ -43,32 +43,31 @@ export default function NavbarLogo(): JSX.Element { return ( <> - - - - {console.log('[NavbarLogo] title:', JSON.stringify(title))} - {(title.logoLight || title.text) && ( - - {title.logoLight && ( - + + + + {(title.logoLight || title.text) && ( + + {title.logoLight && ( + + )} + {title.text && {title.text}} + )} - {title.text && {title.text}} - - )} ); } \ No newline at end of file From e0f3b82a411e8b096e71c6105356911074c1a4a4 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:06:02 -0500 Subject: [PATCH 16/21] fixing deployment on vercel --- unified-doc/src/pages/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/unified-doc/src/pages/index.tsx b/unified-doc/src/pages/index.tsx index 3b56c4e..755ad38 100644 --- a/unified-doc/src/pages/index.tsx +++ b/unified-doc/src/pages/index.tsx @@ -1,6 +1,7 @@ import React, {JSX} from 'react'; import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; import clsx from 'clsx'; import styles from './index.module.css'; @@ -76,9 +77,10 @@ const byId = Object.fromEntries(products.map(p => [p.id, p])) as Record - + {product.tag}
{product.logo && {product.title}} @@ -97,6 +99,7 @@ function BentoCard({product, featured = false}: {product: Product; featured?: bo } export default function Home(): JSX.Element { + const getStartedUrl = useBaseUrl('/frontdoor/intro'); return (
@@ -104,7 +107,7 @@ export default function Home(): JSX.Element {

NetFoundry Docs

Secure, high-performance networking for the modern era.

- Get Started + Get Started Request Demo
From 8188ea47d0b5b74d3bdae6d4b682d3891389c569 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:46:17 -0500 Subject: [PATCH 17/21] fix vercel build again --- unified-doc/.gitignore | 3 +- unified-doc/docusaurus.config.ts | 15 + unified-doc/docusaurus.config.ts.back | 434 -------------------------- unified-doc/src/pages/index.tsx | 18 +- 4 files changed, 24 insertions(+), 446 deletions(-) delete mode 100644 unified-doc/docusaurus.config.ts.back diff --git a/unified-doc/.gitignore b/unified-doc/.gitignore index 24f464f..8ecf707 100644 --- a/unified-doc/.gitignore +++ b/unified-doc/.gitignore @@ -35,5 +35,4 @@ backstop_data/bitmaps_* backstop_data/html_report* backstop_data/ci_report* - - +src/generated/ diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index e092aeb..887d275 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -227,6 +227,21 @@ const config: Config = { '@docusaurus/theme-search-algolia', ], plugins: [ + function emitDocsBase() { + return { + name: 'emit-docs-base', + async loadContent() { + const fs = require('fs'); + const dir = path.resolve(__dirname, 'src/generated'); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, {recursive: true}); + const outPath = path.resolve(dir, 'docsBase.ts'); + const docsLinkBase = `${docsBase}${isVercel ? 'docs/' : ''}`; + const content = `// auto-generated — do not commit\nexport const DOCS_BASE = '${docsLinkBase}';\n`; + fs.writeFileSync(outPath, content); + console.log(`\n✅ [emit-docs-base] wrote DOCS_BASE='${docsLinkBase}' → ${outPath}\n`); + }, + }; + }, '@docusaurus/plugin-debug', function webpackAliases() { return { diff --git a/unified-doc/docusaurus.config.ts.back b/unified-doc/docusaurus.config.ts.back deleted file mode 100644 index 39d541c..0000000 --- a/unified-doc/docusaurus.config.ts.back +++ /dev/null @@ -1,434 +0,0 @@ -import {themes as prismThemes} from 'prism-react-renderer'; -import type {Config, PluginConfig} from '@docusaurus/types'; -import type * as Preset from '@docusaurus/preset-classic'; -import * as path from "node:path"; -import { - LogLevel, - remarkCodeSections, - remarkReplaceMetaUrl, - remarkScopedPath, - remarkYouTube -} from "@netfoundry/docusaurus-theme/plugins"; -import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives"; -import {pluginHotjar} from "@netfoundry/docusaurus-theme/node"; -import {PublishConfig} from 'src/components/docusaurus' -import {zrokDocsPluginConfig} from "./_remotes/zrok/website/docusaurus-plugin-zrok-docs.ts"; - -// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) -const frontdoor = `./_remotes/frontdoor`; -const onprem = `./_remotes/onprem`; -const openziti = `./_remotes/openziti`; -const zrokRoot = `./_remotes/zrok/website`; -const zlan = `./_remotes/zlan`; - -const isVercel = process.env.IS_VERCEL === 'true'; -const docsBase = isVercel ? '/' : '/docs/'; - -const buildMask = parseInt(process.env.DOCUSAURUS_BUILD_MASK ?? "0xFF", 16); - -const BUILD_FLAGS = { - NONE: 0x0, - OPENZITI: 0x1, - FRONTDOOR: 0x2, - ONPREM: 0x4, - ZROK: 0x8, - ZLAN: 0x10, -}; - -function build(flag: number) { - return (buildMask & flag) !== 0; -} - -const staging: PublishConfig = { - docusaurus: { - url: 'https://netfoundry.io' - }, - algolia: { - appId: 'QRGW6TJXHP', - apiKey: '267457291182a398c5ee19fcb0bcae77', - indexName: 'nfdocs_stg', - }, - hotjar: { - id: "6443487" - }, - google: { - tag: 'GTM-5SF399H3' - } -} - -const prod: PublishConfig = { - docusaurus: { - url: 'https://netfoundry.io' - }, - algolia: { - appId: 'UWUTF7ESUI', - apiKey: '3a4a0691d0e8e3bb7c27c702c6a86ea9', - indexName: 'nfdocs', - }, - hotjar: { - id: "6506483" - }, - google: { - tag: 'GTM-NHX4DX56' - } -} - -const cfg: PublishConfig = process.env.DOCUSAURUS_PUBLISH_ENV === 'prod' ? prod : staging; - -const REMARK_MAPPINGS = [ - { from: '@onpremdocs', to: `${docsBase}onprem` }, - { from: '@openzitidocs', to: `${docsBase}openziti`}, - { from: '@zrokdocs', to: `${docsBase}zrok`}, - { from: '@static', to: docsBase}, -]; - -console.log("CANONICAL URL : " + cfg.docusaurus.url); -console.log("DOCUSAURUS_PUBLISH_ENV : " + process.env.DOCUSAURUS_PUBLISH_ENV) -console.log(" docsBase : " + docsBase); -console.log(" algolia index : " + cfg.algolia.indexName); -console.log(" build mask : " + buildMask); -console.log(" hotjar app : " + cfg.hotjar.id); -console.log('REMARK_MAPPINGS:', JSON.stringify(REMARK_MAPPINGS, null, 2)); - - -function extendDocsPlugins(plugin: PluginConfig): PluginConfig { - if (!Array.isArray(plugin)) return plugin; - - const [pluginName, config] = plugin; - - config.beforeDefaultRemarkPlugins = [ - ...(config.beforeDefaultRemarkPlugins || []), - remarkGithubAdmonitionsToDirectives, - ]; - - config.remarkPlugins = [ - ...(config.remarkPlugins || []), - [remarkScopedPath, { mappings: REMARK_MAPPINGS, logLevel: LogLevel.Silent }], - [remarkCodeSections, { logLevel: LogLevel.Silent }], - ]; - - return [pluginName, config]; -} - -function dumpRoutes() { - return { - name: 'dump-routes', - async allContentLoaded({allContent, actions}: any) { - const fs = require('node:fs'); - - // route list (most stable in v3) - const routes = actions.routesPaths ?? actions.routePaths ?? []; - - // optional: also dump plugin content ids so you can correlate routes - fs.writeFileSync( - 'routes.json', - JSON.stringify({routes, allContent}, null, 2), - ); - }, - }; -} - -function assertNoDocsPrefix() { - return (tree: any, file: any) => { - const p = String(file.path || ''); - const {visit} = require('unist-util-visit'); - - visit(tree, 'link', (node: any) => { - if (typeof node.url === 'string' && node.url.startsWith('/docs/')) { - console.log(`[assertNoDocsPrefix] ${p} url=${node.url}`); - } - }); - - visit(tree, 'jsx', (node: any) => { - if (typeof node.value === 'string' && node.value.includes('"/docs/')) { - console.log(`[assertNoDocsPrefix] ${p} jsx contains "/docs/`); - } - }); - }; -} - - -const config: Config = { - title: 'NetFoundry Documentation', - tagline: 'Documentation for NetFoundry products and projects', - favicon: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/png/icon/netfoundry-icon-color.png', - - // Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future - future: { - v4: true, // Improve compatibility with the upcoming Docusaurus v4 - }, - - // Set the production url of your site here - url: cfg.docusaurus.url, - // Set the // pathname under which your site is served - // For GitHub pages deployment, it is often '//' - baseUrl: docsBase, - // trailingSlash: false, leave as is - - // GitHub pages deployment config. - // If you aren't using GitHub pages, you don't need these. - organizationName: 'netfoundry', // Usually your GitHub org/user name. - projectName: 'netfoundry', // Usually your repo name. - - onBrokenLinks: 'throw', - - // Even if you don't use internationalization, you can use this field to set - // useful metadata like html lang. For example, if your site is Chinese, you - // may want to replace "en" with "zh-Hans". - i18n: { - defaultLocale: 'en', - locales: ['en'], - }, - markdown: { - hooks: { - onBrokenMarkdownLinks: "throw" - }, - mermaid: true, - }, - staticDirectories: [ - 'static', - '_remotes/frontdoor/docusaurus/static/', - '_remotes/onprem/docusaurus/static/', - '_remotes/openziti/docusaurus/static/', - '_remotes/zlan/docusaurus/static/', - `${zrokRoot}/static/`, - `${zrokRoot}/docs/images` - ], - customFields: { - DOCUSAURUS_BASE_PATH: docsBase, - DOCUSAURUS_DOCS_PATH: docsBase, - OPENZITI_DOCS_BASE: `${docsBase}openziti`, - UNIFIED_DOC_PATH: true, - ALGOLIA_APPID: cfg.algolia.appId, - ALGOLIA_APIKEY: cfg.algolia.apiKey, - ALGOLIA_INDEXNAME: cfg.algolia.indexName, - }, - themes: [ - ['@docusaurus/theme-classic', { - customCss: require.resolve('./src/css/custom.css'), - }], - '@docusaurus/theme-mermaid', - '@docusaurus/theme-search-algolia', - ], - plugins: [ - '@docusaurus/plugin-debug', - function webpackAliases() { - return { - name: 'unified-doc-webpack-aliases', - configureWebpack(config:any, isServer:any) { - return { - resolve: { - alias: { - '@openziti': path.resolve(__dirname, `${openziti}/docusaurus`), - '@frontdoor': path.resolve(__dirname, `${frontdoor}/docusaurus`), - '@onprem': path.resolve(__dirname, `${onprem}/docusaurus`), - '@zlan': path.resolve(__dirname, `${zlan}/docusaurus`), - '@zrok': path.resolve(__dirname, `${zrokRoot}`), - '@zrokroot': path.resolve(__dirname, `${zrokRoot}`), - '@staticdir': path.resolve(__dirname, `docusaurus/static`), - }, - }, - module: { - rules: [ - { - test: /\.ya?ml$/, - use: 'yaml-loader', - }, - ], - }, - }; - }, - }; - }, - - ['@docusaurus/plugin-content-pages',{path: 'src/pages',routeBasePath: '/'}], - build(BUILD_FLAGS.FRONTDOOR) && ['@docusaurus/plugin-content-pages',{id: `frontdoor-pages`, path: `${frontdoor}/docusaurus/src/pages`, routeBasePath: '/frontdoor'}], - build(BUILD_FLAGS.ONPREM) && ['@docusaurus/plugin-content-pages',{id: `onprem-pages`, path: `${onprem}/docusaurus/src/pages`, routeBasePath: '/onprem'}], - build(BUILD_FLAGS.OPENZITI) && ['@docusaurus/plugin-content-pages',{id: `openziti-pages`, path: `${openziti}/docusaurus/src/pages`, routeBasePath: '/openziti'}], - build(BUILD_FLAGS.ZLAN) && ['@docusaurus/plugin-content-pages',{id: `zlan-pages`, path: `${zlan}/docusaurus/src/pages`, routeBasePath: '/zlan'}], - build(BUILD_FLAGS.ZROK) && ['@docusaurus/plugin-content-pages',{id: `zrok-pages`, path: `${zrokRoot}/src/pages`, routeBasePath: '/zrok'}], - build(BUILD_FLAGS.ONPREM) && [ - '@docusaurus/plugin-content-docs', - { - id: 'onprem', // do not change - affects algolia search - path: `${onprem}/docusaurus/docs`, - routeBasePath: 'onprem', - sidebarPath: `${onprem}/docusaurus/sidebars.ts`, - includeCurrentVersion: true, - beforeDefaultRemarkPlugins: [ - remarkGithubAdmonitionsToDirectives, - ], - remarkPlugins: [ - [remarkScopedPath, { mappings: REMARK_MAPPINGS, debug: false }], - [remarkCodeSections, { logLevel: LogLevel.Silent }], - ], - }, - ], - build(BUILD_FLAGS.FRONTDOOR) && [ - '@docusaurus/plugin-content-docs', - { - id: 'frontdoor', // do not change - affects algolia search - path: `${frontdoor}/docusaurus/docs`, - routeBasePath: 'frontdoor', - sidebarPath: `${frontdoor}/docusaurus/sidebars.ts`, - includeCurrentVersion: true, - beforeDefaultRemarkPlugins: [ - remarkGithubAdmonitionsToDirectives, - ], - remarkPlugins: [ - [remarkScopedPath, { mappings: REMARK_MAPPINGS, logLevel: LogLevel.Silent}], - [remarkCodeSections, { logLevel: LogLevel.Silent }], - ], - }, - ], - build(BUILD_FLAGS.OPENZITI) && [ - '@docusaurus/plugin-content-docs', - { - id: 'openziti', // do not change - affects algolia search - path: `${openziti}/docusaurus/docs`, - routeBasePath: 'openziti', - sidebarPath: `${openziti}/docusaurus/sidebars.ts`, - includeCurrentVersion: true, - beforeDefaultRemarkPlugins: [ - remarkGithubAdmonitionsToDirectives, - ], - remarkPlugins: [ - [remarkReplaceMetaUrl, {from: '@staticoz', to: `${docsBase}openziti`, logLevel: LogLevel.Silent}], - [remarkScopedPath, { mappings: REMARK_MAPPINGS, logLevel: LogLevel.Silent }], - [remarkCodeSections, { logLevel: LogLevel.Debug }], - ], - }, - ], - build(BUILD_FLAGS.ZLAN) && [ - '@docusaurus/plugin-content-docs', - { - id: 'zlan', // do not change - affects algolia search - path: `${zlan}/docusaurus/docs`, - routeBasePath: 'zlan', - sidebarPath: `${zlan}/docusaurus/sidebars.ts`, - includeCurrentVersion: true, - beforeDefaultRemarkPlugins: [ - remarkGithubAdmonitionsToDirectives, - ], - remarkPlugins: [ - [remarkScopedPath, { mappings: REMARK_MAPPINGS, logLevel: LogLevel.Silent }], - [remarkCodeSections, { logLevel: LogLevel.Silent }], - ], - }, - ], - build(BUILD_FLAGS.OPENZITI) && [ - '@docusaurus/plugin-content-blog', - { - showReadingTime: true, - routeBasePath: 'openziti/blog', - tagsBasePath: 'tags', - include: ['**/*.{md,mdx}'], - path: '_remotes/openziti/docusaurus/blog', - remarkPlugins: [ - remarkYouTube, - [remarkReplaceMetaUrl, {from: '@staticoz', to: `${docsBase}openziti`, logLevel: LogLevel.Silent}], - [remarkScopedPath, { mappings: REMARK_MAPPINGS, logLevel: LogLevel.Silent }], - [remarkCodeSections, { logLevel: LogLevel.Silent }], - ], - blogSidebarCount: 'ALL', - blogSidebarTitle: 'All posts', - }, - ], - build(BUILD_FLAGS.ZROK) && extendDocsPlugins(zrokDocsPluginConfig(zrokRoot, REMARK_MAPPINGS)), - // Fallback redirects for JSX pages with hardcoded /docs/ paths (from upstream repos) - isVercel && [ - '@docusaurus/plugin-client-redirects', - { - createRedirects(existingPath: string) { - // Redirect /docs/X → /X for all doc paths - return existingPath.match(/^\/(onprem|frontdoor|openziti|zrok|zlan)/) - ? [`/docs${existingPath}`] - : undefined; - }, - }, - ], - ['@docusaurus/plugin-sitemap', { changefreq: "daily", priority: 0.8 }], - [pluginHotjar, {}], - ['@docusaurus/plugin-google-tag-manager', {id: `openziti-gtm`, containerId: cfg.google.tag}], - ].filter(Boolean), - themeConfig: { - docs: { - sidebar: { - hideable: false, - autoCollapseCategories: true - } - }, - mermaid: { - theme: {light: 'neutral', dark: 'forest'}, - }, - // Replace with your project's social card - image: 'https://netfoundry.io/wp-content/uploads/2024/07/netfoundry-logo-tag-color-stacked-1.svg', - navbar: { - hideOnScroll: false, - title: 'NetFoundry Documentation', - logo: { - alt: 'NetFoundry Logo', - src: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg', - }, - items: [ - { - label: 'Docs', - position: 'left', - items: [ - { to: '/onprem/intro', label: 'On-Prem' }, - { to: '/frontdoor/intro', label: 'Frontdoor' }, - { to: '/openziti/learn/introduction', label: 'OpenZiti' }, - ], - }, - ], - }, - prism: { - theme: prismThemes.github, - darkTheme: prismThemes.dracula, - }, - algolia: { - appId: cfg.algolia.appId, - apiKey: cfg.algolia.apiKey, - indexName: cfg.algolia.indexName, - contextualSearch: true, - searchParameters: { - typoTolerance: "min", - hitsPerPage: 25, - attributesToRetrieve: ["content", "hierarchy", "url"], - attributesToHighlight: ["content", "hierarchy"], - restrictSearchableAttributes: ["content", "hierarchy"] - }, - searchPagePath: 'search' - }, - hotjar: { - applicationId: cfg.hotjar.id - }, - } satisfies Preset.ThemeConfig, - presets: [ - [ 'redocusaurus', - { - specs: [ - { - id: 'openapi', - spec: `${frontdoor}/docusaurus/static/frontdoor-api-spec.yaml`, - }, - { - id: 'edge-client', - spec: 'https://get.openziti.io/spec/client.yml', - }, - { - id: 'edge-management', - spec: 'https://get.openziti.io/spec/management.yml', - }, - ], - // Theme Options for modifying how redoc renders them - theme: { - // Change with your site colors - primaryColor: '#1890ff', - } - }, - ], - ], -}; - -export default config; diff --git a/unified-doc/src/pages/index.tsx b/unified-doc/src/pages/index.tsx index 755ad38..ca6cb1f 100644 --- a/unified-doc/src/pages/index.tsx +++ b/unified-doc/src/pages/index.tsx @@ -1,8 +1,8 @@ import React, {JSX} from 'react'; import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; import clsx from 'clsx'; +import {DOCS_BASE} from '../generated/docsBase'; import styles from './index.module.css'; const CYAN = '#22d3ee'; @@ -27,7 +27,7 @@ const products = [ logo: `${IMG}/openziti-sm-logo.svg`, tag: 'Open Source', accent: GREEN, - link: '/openziti/learn/introduction', + link: `${DOCS_BASE}openziti/learn/introduction`, description: 'The open-source zero-trust networking framework at the heart of the NetFoundry platform. Embed dark, app-native security directly in your code — no VPN, no perimeter.' }, { @@ -36,7 +36,7 @@ const products = [ logo: `${IMG}/frontdoor-sm-logo.svg`, tag: 'Managed', accent: CYAN, - link: '/frontdoor/intro', + link: `${DOCS_BASE}frontdoor/intro`, features: ['No agent or VPN required', 'Zero firewall rules', 'Identity-based access', 'Any app, any browser'], description: 'Secure, clientless access to any application — without a VPN or firewall rule. Expose nothing to the internet while giving authorized users instant access.' }, @@ -46,7 +46,7 @@ const products = [ logo: `${IMG}/zrok-1.0.0-rocket-purple.svg`, tag: 'Open Source', accent: GREEN, - link: '/zrok', + link: `${DOCS_BASE}zrok/getting-started`, description: 'Geo-scale secure sharing built on the OpenZiti mesh. Share services, files, or HTTP endpoints peer-to-peer — no open ports, no NAT traversal tricks.' }, { @@ -56,7 +56,7 @@ const products = [ logo: `${IMG}/onprem-sm-logo.svg`, tag: 'Self-Hosted', accent: CYAN, - link: '/selfhosted/intro', + link: `${DOCS_BASE}selfhosted/intro`, features: ['Full infrastructure control', 'Air-gap compatible', 'On-prem or any cloud', 'Enterprise SLA'], description: 'Deploy the full NetFoundry control plane and fabric in your own environment. Full sovereignty over your zero-trust infrastructure — on-prem, air-gapped, or any cloud.' }, @@ -66,7 +66,7 @@ const products = [ logo: `${IMG}/zlan-logo.svg`, tag: 'OT Security', accent: CYAN, - link: '/zlan/intro', + link: `${DOCS_BASE}zlan/intro`, features: ['Deep OT/IT traffic visibility', 'Identity-aware micro-segmentation', 'Centralized zero-trust policy', 'Built on NetFoundry Self-Hosted'], description: 'Identity-aware micro-segmentation firewall for operational technology networks. Deep traffic visibility, centralized policy, and zero-trust access control for OT environments.' }, @@ -77,10 +77,9 @@ const byId = Object.fromEntries(products.map(p => [p.id, p])) as Record - + {product.tag}
{product.logo && {product.title}} @@ -99,7 +98,6 @@ function BentoCard({product, featured = false}: {product: Product; featured?: bo } export default function Home(): JSX.Element { - const getStartedUrl = useBaseUrl('/frontdoor/intro'); return (
@@ -107,7 +105,7 @@ export default function Home(): JSX.Element {

NetFoundry Docs

Secure, high-performance networking for the modern era.

- Get Started + Get Started Request Demo
From 28978f9cb72c52438d0f797bf7a925b65114e302 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:03:08 -0500 Subject: [PATCH 18/21] always use /docs --- unified-doc/docusaurus.config.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index 887d275..46be8a1 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -385,7 +385,7 @@ const config: Config = { }, { label: 'Frontdoor', - to: '/frontdoor/intro', + to: '/docs/frontdoor/intro', logo: 'https://netfoundry.io/docs/img/frontdoor-sm-logo.svg', description: 'Secure application access gateway.', }, @@ -396,13 +396,13 @@ const config: Config = { links: [ { label: 'OpenZiti', - to: '/openziti/learn/introduction', + to: '/docs/openziti/learn/introduction', logo: 'https://netfoundry.io/docs/img/openziti-sm-logo.svg', description: 'Programmable zero-trust mesh infrastructure.', }, { label: 'zrok', - to: '/zrok', + to: '/docs/zrok/getting-started', logo: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-purple.svg', logoDark: 'https://netfoundry.io/docs/img/zrok-1.0.0-rocket-green.svg', description: 'Secure peer-to-peer sharing built on OpenZiti.', @@ -414,13 +414,13 @@ const config: Config = { links: [ { label: 'Self-Hosted', - to: '/selfhosted/intro', + to: '/docs/selfhosted/intro', logo: 'https://netfoundry.io/docs/img/onprem-sm-logo.svg', description: 'Deploy the full stack in your own environment.', }, { label: 'zLAN', - to: '/zlan/intro', + to: '/docs/zlan/intro', logo: 'https://netfoundry.io/docs/img/zlan-logo.svg', description: 'Zero-trust access for OT networks.', }, From e1a5dbf6a2dbf9bfb006d0aa101d6d0320c48876 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:17:30 -0500 Subject: [PATCH 19/21] fix pages paths --- unified-doc/docusaurus.config.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index 46be8a1..0781a75 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -273,11 +273,11 @@ const config: Config = { }, ['@docusaurus/plugin-content-pages',{path: 'src/pages',routeBasePath: '/'}], - build(BUILD_FLAGS.FRONTDOOR) && ['@docusaurus/plugin-content-pages',{id: `frontdoor-pages`, path: `${frontdoor}/docusaurus/src/pages`, routeBasePath: '/frontdoor'}], - build(BUILD_FLAGS.SELFHOSTED) && ['@docusaurus/plugin-content-pages',{id: `selfhosted-pages`, path: `${selfhosted}/docusaurus/src/pages`, routeBasePath: '/selfhosted'}], - build(BUILD_FLAGS.OPENZITI) && ['@docusaurus/plugin-content-pages',{id: `openziti-pages`, path: `${openziti}/docusaurus/src/pages`, routeBasePath: '/openziti'}], - build(BUILD_FLAGS.ZLAN) && ['@docusaurus/plugin-content-pages',{id: `zlan-pages`, path: `${zlan}/docusaurus/src/pages`, routeBasePath: '/zlan'}], - build(BUILD_FLAGS.ZROK) && ['@docusaurus/plugin-content-pages',{id: `zrok-pages`, path: `${zrokRoot}/src/pages`, routeBasePath: '/zrok'}], + build(BUILD_FLAGS.FRONTDOOR) && ['@docusaurus/plugin-content-pages',{id: `frontdoor-pages`, path: `${frontdoor}/docusaurus/src/pages`, routeBasePath: `/${routeBase('frontdoor')}`}], + build(BUILD_FLAGS.SELFHOSTED) && ['@docusaurus/plugin-content-pages',{id: `selfhosted-pages`, path: `${selfhosted}/docusaurus/src/pages`, routeBasePath: `/${routeBase('selfhosted')}`}], + build(BUILD_FLAGS.OPENZITI) && ['@docusaurus/plugin-content-pages',{id: `openziti-pages`, path: `${openziti}/docusaurus/src/pages`, routeBasePath: `/${routeBase('openziti')}`}], + build(BUILD_FLAGS.ZLAN) && ['@docusaurus/plugin-content-pages',{id: `zlan-pages`, path: `${zlan}/docusaurus/src/pages`, routeBasePath: `/${routeBase('zlan')}`}], + build(BUILD_FLAGS.ZROK) && ['@docusaurus/plugin-content-pages',{id: `zrok-pages`, path: `${zrokRoot}/src/pages`, routeBasePath: `/${routeBase('zrok')}`}], build(BUILD_FLAGS.ZROK) && extendDocsPlugins(zrokDocsPluginConfig(zrokRoot, REMARK_MAPPINGS, routeBase('zrok'))), build(BUILD_FLAGS.SELFHOSTED) && [ '@docusaurus/plugin-content-docs', From 1ad09dbad4ddf00965ce5e94b97bcd80c11d3b52 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:31:39 -0500 Subject: [PATCH 20/21] try dropping the last slash to fix vercel build --- unified-doc/docusaurus.config.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index 0781a75..3b0fb0b 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -87,11 +87,11 @@ const REMARK_MAPPINGS = [ { from: '@openzitidocs', to: `${docsBase}openziti`}, { from: '@zrokdocs', to: `${docsBase}zrok`}, { from: '@static', to: docsBase}, - { from: '/openziti/', to: `${docsBase}${routeBase('openziti')}/` }, - { from: '/frontdoor/', to: `${docsBase}${routeBase('frontdoor')}/` }, - { from: '/selfhosted/', to: `${docsBase}${routeBase('selfhosted')}/` }, - { from: '/zrok/', to: `${docsBase}${routeBase('zrok')}/` }, - { from: '/zlan/', to: `${docsBase}${routeBase('zlan')}/` }, + { from: '/openziti', to: `${docsBase}${routeBase('openziti')}` }, + { from: '/frontdoor', to: `${docsBase}${routeBase('frontdoor')}` }, + { from: '/selfhosted', to: `${docsBase}${routeBase('selfhosted')}` }, + { from: '/zrok', to: `${docsBase}${routeBase('zrok')}` }, + { from: '/zlan', to: `${docsBase}${routeBase('zlan')}` }, ]; console.log("CANONICAL URL : " + cfg.docusaurus.url); From 8cbb1cf1437d5249f7f435fcfd4738b4d5331d72 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:38:57 -0500 Subject: [PATCH 21/21] still trying to fix vercel build again.... --- unified-doc/src/theme/Navbar/Logo/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unified-doc/src/theme/Navbar/Logo/index.tsx b/unified-doc/src/theme/Navbar/Logo/index.tsx index 7d5c641..9aa7cf6 100644 --- a/unified-doc/src/theme/Navbar/Logo/index.tsx +++ b/unified-doc/src/theme/Navbar/Logo/index.tsx @@ -12,11 +12,11 @@ const mapTitle = (p: string) => { const rootSegment = segments[1] === 'docs' ? segments[2] : segments[1]; const checkPath = (segment: string) => rootSegment === segment; - if (checkPath('frontdoor')) return {includeNFLogo: true, to: '/frontdoor', alt:'Frontdoor', logoLight: `/img/frontdoor-sm-logo.svg`, logoDark: `/img/frontdoor-sm-logo.svg`}; - if (checkPath('selfhosted')) return {includeNFLogo: true, to: '/selfhosted',alt:'Self-Hosted', logoLight: `/img/onprem-sm-logo.svg`, logoDark: `/img/onprem-sm-logo.svg`}; - if (checkPath('openziti')) return {includeNFLogo: true, to: '/openziti',alt:'OpenZiti', logoLight: `/img/openziti-sm-logo.svg`, logoDark: `/img/openziti-sm-logo.svg`}; - if (checkPath('zlan')) return {includeNFLogo: true, to: '/zlan', alt:'zlan', logoLight: `/img/zlan-logo.svg`, logoDark: `/img/zlan-logo.svg`}; - if (checkPath('zrok')) return {text: '', includeNFLogo: true, to: '/zrok', alt:'zrok', logoLight: `/img/zrok-1.0.0-rocket-purple.svg`, logoDark: `/img/zrok-1.0.0-rocket-green.svg`}; + if (checkPath('frontdoor')) return {includeNFLogo: true, to: '/docs/frontdoor', alt:'Frontdoor', logoLight: `/img/frontdoor-sm-logo.svg`, logoDark: `/img/frontdoor-sm-logo.svg`}; + if (checkPath('selfhosted')) return {includeNFLogo: true, to: '/docs/selfhosted',alt:'Self-Hosted', logoLight: `/img/onprem-sm-logo.svg`, logoDark: `/img/onprem-sm-logo.svg`}; + if (checkPath('openziti')) return {includeNFLogo: true, to: '/docs/openziti',alt:'OpenZiti', logoLight: `/img/openziti-sm-logo.svg`, logoDark: `/img/openziti-sm-logo.svg`}; + if (checkPath('zlan')) return {includeNFLogo: true, to: '/docs/zlan', alt:'zlan', logoLight: `/img/zlan-logo.svg`, logoDark: `/img/zlan-logo.svg`}; + if (checkPath('zrok')) return {text: '', includeNFLogo: true, to: '/docs/zrok', alt:'zrok', logoLight: `/img/zrok-1.0.0-rocket-purple.svg`, logoDark: `/img/zrok-1.0.0-rocket-green.svg`}; return { includeNFLogo: false, to: '/',