diff --git a/packages/docusaurus-theme/css/hero-background.css b/packages/docusaurus-theme/css/hero-background.css
new file mode 100644
index 00000000..5b75434e
--- /dev/null
+++ b/packages/docusaurus-theme/css/hero-background.css
@@ -0,0 +1 @@
+/* HeroBackground — reserved for future animation styles */
diff --git a/packages/docusaurus-theme/css/product-picker.css b/packages/docusaurus-theme/css/product-picker.css
index 3270882f..1be6a429 100644
--- a/packages/docusaurus-theme/css/product-picker.css
+++ b/packages/docusaurus-theme/css/product-picker.css
@@ -25,12 +25,14 @@
position: relative;
padding-right: 1.2rem;
transition: color 0.3s ease;
+ font-weight: var(--ifm-font-weight-bold);
}
.nf-picker-trigger:hover,
.nf-resources-dropdown:hover,
.navbar__item.dropdown--show .nf-picker-trigger,
-.navbar__item.dropdown--show .nf-resources-dropdown {
+.navbar__item.dropdown--show .nf-resources-dropdown,
+.navbar__item.nf-picker--open .nf-picker-trigger {
color: var(--ifm-color-primary);
text-decoration: none;
}
@@ -59,17 +61,17 @@
opacity: 1;
}
-/* Open: chevron rotates 180° */
+/* Open: chevron holds the hover drop position */
.nf-picker--open .nf-picker-trigger::after,
.navbar__item.dropdown--show .nf-resources-dropdown::after {
- transform: translateY(-50%) rotate(180deg);
+ transform: translateY(-20%);
opacity: 1;
}
/* Dark mode: cyan accent on hover */
[data-theme='dark'] .nf-picker-trigger:hover,
[data-theme='dark'] .nf-resources-dropdown:hover,
-[data-theme='dark'] .nf-picker--open .nf-picker-trigger,
+[data-theme='dark'] .navbar__item.nf-picker--open .nf-picker-trigger,
[data-theme='dark'] .navbar__item.dropdown--show .nf-resources-dropdown {
color: #22d3ee;
}
@@ -138,6 +140,7 @@
/* Narrower panel for the 2-column Resources menu */
.dropdown__menu:has(.picker-resources) { max-width: 700px; }
+.nf-picker-panel--narrow { max-width: 680px; }
/* Ensure visibility when open */
.dropdown--show > .dropdown__menu,
@@ -179,9 +182,32 @@
transition: all 0.2s ease;
border-radius: 8px;
}
-.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; }
+.picker-link:hover { background: rgba(0, 118, 255, 0.06); transform: translateX(3px); text-decoration: none; }
+.picker-link strong { color: var(--ifm-font-color-base); font-size: 0.95rem; font-weight: 900; letter-spacing: -0.02em; display: block; text-decoration: none; }
+.picker-link span { color: #64748b; font-size: 0.82rem; display: block; margin-top: 2px; line-height: 1.35; text-decoration: none; }
+
+/* ── Navbar icon links (GitHub, Discourse) ──────────────────────────────── */
+.nf-icon-links {
+ display: flex;
+ align-items: center;
+ gap: 0.1rem;
+}
+
+.nf-icon-link,
+.nf-icon-link:visited {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 2rem;
+ height: 2rem;
+ color: var(--ifm-navbar-link-color);
+ text-decoration: none;
+}
+
+.nf-icon-link:hover {
+ color: var(--ifm-color-primary);
+ text-decoration: none;
+}
/* ── Mobile (<= 996 px) ─────────────────────────────────────────────────── */
@media (max-width: 996px) {
diff --git a/packages/docusaurus-theme/css/theme.css b/packages/docusaurus-theme/css/theme.css
index 0b06560f..276aa224 100644
--- a/packages/docusaurus-theme/css/theme.css
+++ b/packages/docusaurus-theme/css/theme.css
@@ -1,20 +1,51 @@
-/**
- * NetFoundry Docusaurus Theme - Combined Styles
- *
- * This file is automatically loaded by the theme via getClientModules().
- *
- * Consuming projects no longer need to manually add these imports
- * to their custom.css files.
- */
-
-/* CSS variables for light mode */
-@import "./vars.css";
-
-/* CSS variables for dark mode */
-@import "./vars-dark.css";
-
-/* Layout styles */
-@import "./layout.css";
-
-/* Legacy design system variables and comprehensive styling */
-@import "./legacy.css";
+/**
+ * NetFoundry Docusaurus Theme - Combined Styles
+ *
+ * This file is automatically loaded by the theme via getClientModules().
+ *
+ * Consuming projects no longer need to manually add these imports
+ * to their custom.css files.
+ */
+
+/* CSS variables for light mode */
+@import "./vars.css";
+
+/* CSS variables for dark mode */
+@import "./vars-dark.css";
+
+/* Layout styles */
+@import "./layout.css";
+
+/* Legacy design system variables and comprehensive styling */
+@import "./legacy.css";
+
+/* ── Footer social link hover ───────────────────────────────────────────── */
+footer a[class*="footerSocialLink"] {
+ transition: all 0.2s ease;
+}
+
+[data-theme='dark'] footer a[class*="footerSocialLink"] {
+ background-color: #1a2640;
+ color: #64748b;
+ border: 1px solid rgba(148, 163, 184, 0.1);
+}
+[data-theme='dark'] footer a[class*="footerSocialLink"]:hover {
+ background-color: #22d3ee;
+ color: #020617;
+ border-color: transparent;
+ transform: translateY(-3px);
+ box-shadow: 0 6px 16px rgba(34, 211, 238, 0.35);
+}
+
+[data-theme='light'] footer a[class*="footerSocialLink"] {
+ background-color: #e2e8f0;
+ color: #475569;
+ border: 1px solid rgba(0, 0, 0, 0.06);
+}
+[data-theme='light'] footer a[class*="footerSocialLink"]:hover {
+ background-color: #0891b2;
+ color: #ffffff;
+ border-color: transparent;
+ transform: translateY(-3px);
+ box-shadow: 0 6px 16px rgba(8, 145, 178, 0.3);
+}
diff --git a/packages/docusaurus-theme/package.json b/packages/docusaurus-theme/package.json
index f59c7104..6fe46948 100644
--- a/packages/docusaurus-theme/package.json
+++ b/packages/docusaurus-theme/package.json
@@ -51,13 +51,17 @@
"react-dom": "^18 || ^19"
},
"dependencies": {
+ "@docsearch/css": "^3",
"@docsearch/react": "^3",
+ "@types/three": "^0.183.1",
"algoliasearch": "^5",
"clsx": "^2.0.0",
"instantsearch.js": "^4",
"react-device-detect": "^2.2.3",
"react-github-btn": "^1.4.0",
- "react-instantsearch": "^7"
+ "react-instantsearch": "^7",
+ "three": "^0.134.0",
+ "vanta": "^0.5.24"
},
"devDependencies": {
"@docusaurus/core": "^3",
@@ -67,6 +71,7 @@
"@types/js-yaml": "^4.0.9",
"@types/react": "^18",
"@types/react-dom": "^18",
+ "@types/three": "^0.183.1",
"jest": "^30.0.4",
"react": "^18",
"react-dom": "^18",
diff --git a/packages/docusaurus-theme/src/components/HeroBackground.module.css b/packages/docusaurus-theme/src/components/HeroBackground.module.css
new file mode 100644
index 00000000..20ee16d1
--- /dev/null
+++ b/packages/docusaurus-theme/src/components/HeroBackground.module.css
@@ -0,0 +1,57 @@
+.bg {
+ position: absolute;
+ inset: 0;
+ background-color: #020617;
+ background-image:
+ repeating-linear-gradient(0deg, transparent, transparent 59px, rgba(34, 211, 238, 0.04) 59px, rgba(34, 211, 238, 0.04) 60px),
+ repeating-linear-gradient(90deg, transparent, transparent 59px, rgba(34, 211, 238, 0.04) 59px, rgba(34, 211, 238, 0.04) 60px);
+ overflow: hidden;
+}
+
+.orb {
+ position: absolute;
+ border-radius: 50%;
+ filter: blur(80px);
+}
+
+.orb1 {
+ width: 55%;
+ height: 75%;
+ background: radial-gradient(circle, rgba(34, 211, 238, 0.35) 0%, transparent 70%);
+ top: -15%;
+ left: -10%;
+ animation: drift1 14s ease-in-out infinite alternate;
+}
+
+.orb2 {
+ width: 45%;
+ height: 65%;
+ background: radial-gradient(circle, rgba(0, 104, 249, 0.28) 0%, transparent 70%);
+ top: 20%;
+ right: -10%;
+ animation: drift2 18s ease-in-out infinite alternate;
+}
+
+.orb3 {
+ width: 40%;
+ height: 55%;
+ background: radial-gradient(circle, rgba(34, 197, 94, 0.18) 0%, transparent 70%);
+ bottom: -15%;
+ left: 25%;
+ animation: drift3 16s ease-in-out infinite alternate;
+}
+
+@keyframes drift1 {
+ from { transform: translate(0, 0); }
+ to { transform: translate(18%, 22%); }
+}
+
+@keyframes drift2 {
+ from { transform: translate(0, 0); }
+ to { transform: translate(-22%, 18%); }
+}
+
+@keyframes drift3 {
+ from { transform: translate(0, 0); }
+ to { transform: translate(12%, -28%); }
+}
diff --git a/packages/docusaurus-theme/src/components/HeroBackground.tsx b/packages/docusaurus-theme/src/components/HeroBackground.tsx
new file mode 100644
index 00000000..40b7361b
--- /dev/null
+++ b/packages/docusaurus-theme/src/components/HeroBackground.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import styles from './HeroBackground.module.css';
+
+export default function HeroBackground(): React.ReactElement {
+ return (
+
+ );
+}
diff --git a/packages/docusaurus-theme/src/components/NetFoundryFooter/NetFoundryFooter.tsx b/packages/docusaurus-theme/src/components/NetFoundryFooter/NetFoundryFooter.tsx
index 102de0cc..badb6991 100644
--- a/packages/docusaurus-theme/src/components/NetFoundryFooter/NetFoundryFooter.tsx
+++ b/packages/docusaurus-theme/src/components/NetFoundryFooter/NetFoundryFooter.tsx
@@ -122,8 +122,8 @@ export function NetFoundryFooter(props: NetFoundryFooterProps) {
)}
{sp.twitterUrl && (
-
)}
@@ -167,7 +167,7 @@ export function NetFoundryFooter(props: NetFoundryFooterProps) {
-
© 2025 NetFoundry Inc. OpenZiti is an open source project sponsored by NetFoundry. All rights reserved.
+
© 2026 NetFoundry Inc. OpenZiti is an open source project sponsored by NetFoundry. All rights reserved.
diff --git a/packages/docusaurus-theme/src/components/NetFoundryFooter/styles.module.css b/packages/docusaurus-theme/src/components/NetFoundryFooter/styles.module.css
index d4192e01..b4eec6b5 100644
--- a/packages/docusaurus-theme/src/components/NetFoundryFooter/styles.module.css
+++ b/packages/docusaurus-theme/src/components/NetFoundryFooter/styles.module.css
@@ -86,7 +86,7 @@
}
.footerSocialLink:hover {
- background-color: var(--primary);
+ background-color: var(--ifm-color-primary);
transform: translateY(-3px);
}
diff --git a/packages/docusaurus-theme/src/components/index.ts b/packages/docusaurus-theme/src/components/index.ts
index 744424ac..b66dafd4 100644
--- a/packages/docusaurus-theme/src/components/index.ts
+++ b/packages/docusaurus-theme/src/components/index.ts
@@ -1,4 +1,5 @@
export * from './Alert'
+export {default as HeroBackground} from './HeroBackground'
export * from './CodeBlock';
export * from './Common';
export * from './NetFoundry'
diff --git a/packages/docusaurus-theme/src/index.ts b/packages/docusaurus-theme/src/index.ts
index b3d7eeb5..359dce66 100644
--- a/packages/docusaurus-theme/src/index.ts
+++ b/packages/docusaurus-theme/src/index.ts
@@ -22,7 +22,9 @@ export default function themeNetFoundry(
// Automatically inject CSS
getClientModules() {
const modules: string[] = [
+ require.resolve('@docsearch/css'),
require.resolve('../css/theme.css'),
+ require.resolve('../css/hero-background.css'),
];
// Add custom CSS if specified in options
diff --git a/packages/docusaurus-theme/src/vanta.d.ts b/packages/docusaurus-theme/src/vanta.d.ts
new file mode 100644
index 00000000..d5f2f923
--- /dev/null
+++ b/packages/docusaurus-theme/src/vanta.d.ts
@@ -0,0 +1 @@
+declare module 'vanta/dist/vanta.net.min';
diff --git a/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx b/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx
index 0053ff05..d14d85a0 100644
--- a/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx
+++ b/packages/docusaurus-theme/theme/NavbarItem/ComponentTypes.tsx
@@ -1,4 +1,6 @@
import ProductPicker from './types/ProductPicker';
+import ResourcesPicker from './types/ResourcesPicker';
+import IconLinks from './types/IconLinks';
// @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
@@ -11,4 +13,6 @@ const ComponentTypesOrig = require('@theme-init/NavbarItem/ComponentTypes').defa
export default {
...ComponentTypesOrig,
'custom-productPicker': ProductPicker,
+ 'custom-resourcesPicker': ResourcesPicker,
+ 'custom-iconLinks': IconLinks,
};
diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/IconLinks/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/IconLinks/index.tsx
new file mode 100644
index 00000000..1ac55989
--- /dev/null
+++ b/packages/docusaurus-theme/theme/NavbarItem/types/IconLinks/index.tsx
@@ -0,0 +1,50 @@
+import React, {useState, useEffect} from 'react';
+
+const GITHUB_ROUTES: Record = {
+ '/docs/openziti': 'https://github.com/openziti/ziti',
+ '/docs/zrok': 'https://github.com/openziti/zrok',
+};
+
+export default function IconLinks(_props: {position?: 'left' | 'right'}) {
+ const [githubUrl, setGithubUrl] = useState(null);
+
+ useEffect(() => {
+ const check = () => {
+ const {pathname} = window.location;
+ const entry = Object.entries(GITHUB_ROUTES).find(([p]) => pathname.startsWith(p));
+ setGithubUrl(entry ? entry[1] : null);
+ };
+ check();
+ window.addEventListener('popstate', check);
+ return () => window.removeEventListener('popstate', check);
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx
index 0671509d..6c54c2de 100644
--- a/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx
+++ b/packages/docusaurus-theme/theme/NavbarItem/types/ProductPicker/index.tsx
@@ -1,156 +1,156 @@
-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;
- to: string;
- logo?: string;
- logoDark?: string;
- description?: string;
-};
-
-export type PickerColumn = {
- header: string;
- headerClass?: string;
- links: PickerLink[];
-};
-
-type Props = {
- label?: string;
- position?: 'left' | 'right';
- className?: string;
-};
-
-const HEADER_CLASSES = ['picker-header--nf-primary', 'picker-header--nf-secondary', 'picker-header--nf-tertiary'];
-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: 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.' },
- ],
- },
- {
- header: 'Open Source',
- 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.' },
- ],
- },
- {
- header: 'Your own infrastructure',
- 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/zlan-logo.svg`, description: 'Zero-trust access for OT networks.' },
- ],
- },
-];
-
-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 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);
-
- 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 product picker opens
- useEffect(() => {
- const onOtherOpen = (e: any) => {
- if (e.detail.label !== label) close();
- };
- 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-picker: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 (
-
- );
-}
+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;
+ to: string;
+ logo?: string;
+ logoDark?: string;
+ description?: string;
+};
+
+export type PickerColumn = {
+ header: string;
+ headerClass?: string;
+ links: PickerLink[];
+};
+
+type Props = {
+ label?: string;
+ position?: 'left' | 'right';
+ className?: string;
+};
+
+const HEADER_CLASSES = ['picker-header--nf-tertiary', 'picker-header--nf-secondary', 'picker-header--nf-primary'];
+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: 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.' },
+ ],
+ },
+ {
+ header: 'Open Source',
+ 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.' },
+ ],
+ },
+ {
+ header: 'Your own infrastructure',
+ 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/zlan-logo.svg`, description: 'Zero-trust access for OT networks.' },
+ ],
+ },
+];
+
+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 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);
+
+ 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 product picker opens
+ useEffect(() => {
+ const onOtherOpen = (e: any) => {
+ if (e.detail.label !== label) close();
+ };
+ 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-picker: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 (
+
+ );
+}
diff --git a/packages/docusaurus-theme/theme/NavbarItem/types/ResourcesPicker/index.tsx b/packages/docusaurus-theme/theme/NavbarItem/types/ResourcesPicker/index.tsx
new file mode 100644
index 00000000..264baae6
--- /dev/null
+++ b/packages/docusaurus-theme/theme/NavbarItem/types/ResourcesPicker/index.tsx
@@ -0,0 +1,132 @@
+import React, {useState, useRef, useEffect, useCallback} from 'react';
+import Link from '@docusaurus/Link';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import {useThemeConfig} from '@docusaurus/theme-common';
+import clsx from 'clsx';
+
+const NF_LOGO_DEFAULT = 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg';
+
+const YOUTUBE_ICON = ``;
+const DISCOURSE_ICON = ``;
+
+type Props = {
+ label?: string;
+ position?: 'left' | 'right';
+ className?: string;
+};
+
+export default function ResourcesPicker({label = 'Resources', className}: Props) {
+ const themeConfig = useThemeConfig() as any;
+ const consoleLogo = themeConfig?.netfoundry?.consoleLogo ?? NF_LOGO_DEFAULT;
+ const openzitiLogo = useBaseUrl('/img/openziti-sm-logo.svg');
+
+ 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]);
+
+ // Close when another picker opens
+ useEffect(() => {
+ const onOtherOpen = (e: any) => {
+ if (e.detail.label !== label) close();
+ };
+ 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-picker:open', {detail: {label}}));
+ setOpen(true);
+ }, [label]);
+
+ const handlePanelEnter = useCallback(() => {
+ hasEnteredPanel.current = true;
+ }, []);
+
+ const handlePanelLeave = useCallback(() => {
+ if (hasEnteredPanel.current) close();
+ }, [close]);
+
+ const columns = [
+ {
+ header: 'Learn & Engage',
+ headerClass: 'picker-header--nf-tertiary',
+ links: [
+ { label: 'NetFoundry Blog', description: 'Latest news, updates, and insights from NetFoundry.', href: 'https://netfoundry.io/blog/', logoSrc: consoleLogo },
+ { label: 'OpenZiti Tech Blog', description: 'Technical articles and community updates.', href: 'https://blog.openziti.io/', logoSrc: openzitiLogo },
+ ],
+ },
+ {
+ header: 'Community & Support',
+ headerClass: 'picker-header--nf-secondary',
+ links: [
+ { label: 'NetFoundry YouTube', description: 'Video tutorials, demos, and technical deep dives.', href: 'https://www.youtube.com/c/NetFoundry', svgIcon: YOUTUBE_ICON },
+ { label: 'OpenZiti Discourse', description: 'Ask questions and connect with the community.', href: 'https://openziti.discourse.group/', svgIcon: DISCOURSE_ICON },
+ ],
+ },
+ ];
+
+ return (
+
+ );
+}
diff --git a/packages/test-site/docusaurus.config.ts b/packages/test-site/docusaurus.config.ts
index 46610321..e042c020 100644
--- a/packages/test-site/docusaurus.config.ts
+++ b/packages/test-site/docusaurus.config.ts
@@ -176,38 +176,51 @@ export default {
{
label: 'zLAN',
to: '/docs/zlan',
- logo: 'https://netfoundry.io/docs/img/zlan-logo.svg',
+ logo: '/img/zlan-logo.svg',
description: 'Zero-trust access for OT networks.',
},
],
},
],
footer: {
- description: 'This is just a test site for the NetFoundry Docusaurus theme.',
+ description: 'Secure, high-performance networking for the modern era.',
+ copyright: `Copyright © 2026 NetFoundry Inc.`,
socialProps: {
githubUrl: 'https://github.com/netfoundry/',
youtubeUrl: 'https://youtube.com/netfoundry/',
linkedInUrl: 'https://www.linkedin.com/company/netfoundry/',
- twitterUrl: 'https://twitter.com/netfoundry/',
+ twitterUrl: 'https://x.com/netfoundry/',
},
+ documentationLinks: [
+ {href: '/docs/learn/quickstarts/services/ztha', label: 'Get started'},
+ {href: '/docs/reference/developer/api/', label: 'API reference'},
+ {href: '/docs/reference/developer/sdk/', label: 'SDK integration'},
+ ],
+ communityLinks: [
+ {href: 'https://github.com/openziti/ziti', label: 'GitHub'},
+ {href: 'https://openziti.discourse.group/', label: 'OpenZiti Discourse'},
+ {href: '/docs/openziti/policies/CONTRIBUTING', label: 'Contribute'},
+ ],
+ resourceLinks: [
+ {href: 'https://netfoundry.io/', label: 'NetFoundry'},
+ {href: 'https://netfoundry.io/blog/', label: 'NetFoundry Tech Blog'},
+ {href: 'https://blog.openziti.io', label: 'OpenZiti Tech Blog'},
+ ],
},
},
// 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',
+ title: 'NetFoundry Docs',
logo: {
alt: 'NetFoundry Logo',
src: 'https://raw.githubusercontent.com/netfoundry/branding/refs/heads/main/images/svg/icon/netfoundry-icon-color.svg',
},
items: [
- { type: 'custom-productPicker', position: 'left' },
- {
- to: '/docs',
- label: 'Main Docs',
- position: 'left',
- },
+ { type: 'custom-productPicker', position: 'left' },
+ { type: 'custom-resourcesPicker', position: 'left' },
+ { type: 'custom-iconLinks', position: 'right' },
],
},
prism: {
diff --git a/packages/test-site/package.json b/packages/test-site/package.json
index 815e9be9..79b28b49 100644
--- a/packages/test-site/package.json
+++ b/packages/test-site/package.json
@@ -18,6 +18,7 @@
"@docusaurus/preset-classic": "3.9.2",
"@docusaurus/theme-common": "3.9.2",
"@docusaurus/theme-mermaid": "^3.9.2",
+ "@docusaurus/theme-search-algolia": "3.9.2",
"@docusaurus/utils": "3.9.2",
"@mdx-js/react": "^3.0.0",
"@netfoundry/docusaurus-theme": "^0.1.2",
diff --git a/packages/test-site/remotes/openziti/docusaurus/static/img/openziti-sm-logo.svg b/packages/test-site/remotes/openziti/docusaurus/static/img/openziti-sm-logo.svg
new file mode 100644
index 00000000..d038e674
--- /dev/null
+++ b/packages/test-site/remotes/openziti/docusaurus/static/img/openziti-sm-logo.svg
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+ Sheet.181
+
+ Page-1
+
+ Sheet.183
+
+ Sheet.184
+
+
+
+
+ Lightning_Crashes
+
+ Sheet.186
+
+
+
+
+ Sheet.187
+
+ Sheet.188
+
+ Sheet.189
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/test-site/remotes/zlan/docusaurus/static/img/zlan-logo.svg b/packages/test-site/remotes/zlan/docusaurus/static/img/zlan-logo.svg
new file mode 100644
index 00000000..462d42d0
--- /dev/null
+++ b/packages/test-site/remotes/zlan/docusaurus/static/img/zlan-logo.svg
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/test-site/src/custom/custom.css b/packages/test-site/src/custom/custom.css
index ca8a8153..85fea511 100644
--- a/packages/test-site/src/custom/custom.css
+++ b/packages/test-site/src/custom/custom.css
@@ -1,6 +1,5 @@
@import '@netfoundry/docusaurus-theme/css/tabs-v8-float.css';
-@import '@netfoundry/docusaurus-theme/css/layout.css';
:root {
--ifm-navbar-height: 60px;
@@ -18,6 +17,18 @@ body,
flex-direction: column;
}
+/* Separator between navbar logo and nav items */
+.navbar__brand::after {
+ content: '';
+ display: inline-block;
+ width: 1px;
+ height: 1.2em;
+ background: currentColor;
+ opacity: 0.2;
+ margin-left: 1rem;
+ vertical-align: middle;
+}
+
/* Docusaurus-specific utility class */
:global(.docusaurus-mt-lg) {
margin-top: 3rem;
@@ -59,6 +70,9 @@ body,
display: flex;
flex-direction: column;
+ article {
+ padding-bottom: 2em;
+ }
> div {
max-width: var(--ziti-landing-max-width);
width: 100%;
diff --git a/packages/test-site/src/pages/index.tsx b/packages/test-site/src/pages/index.tsx
index 8c4367f0..1cfa17cd 100644
--- a/packages/test-site/src/pages/index.tsx
+++ b/packages/test-site/src/pages/index.tsx
@@ -3,6 +3,7 @@ import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import clsx from 'clsx';
import styles from './landing.module.css';
+import {HeroBackground} from '@netfoundry/docusaurus-theme/ui';
const CYAN = '#22d3ee';
const GREEN = '#22c55e';
@@ -15,7 +16,7 @@ const products = [
{ 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: '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];
@@ -47,12 +48,13 @@ export default function Home(): JSX.Element {
return (
diff --git a/packages/test-site/src/pages/landing.module.css b/packages/test-site/src/pages/landing.module.css
index f7a749bf..8b5828f4 100644
--- a/packages/test-site/src/pages/landing.module.css
+++ b/packages/test-site/src/pages/landing.module.css
@@ -1,160 +1,166 @@
-.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; }
-}
+.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.75), 0 2px 12px rgba(0, 0, 0, 0.9);
+}
+.nf-green-text {
+ color: #22c55e;
+ font-family: inherit; font-weight: inherit; font-size: inherit;
+ text-shadow: 0 0 20px rgba(34, 197, 94, 0.75), 0 2px 12px rgba(0, 0, 0, 0.9);
+}
+.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; text-decoration: none;
+}
+.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; text-decoration: none;
+}
+
+:global([data-theme='dark']) .nf-btn-primary,
+:global([data-theme='dark']) .nf-btn-primary:hover,
+:global([data-theme='dark']) .nf-btn-ghost,
+:global([data-theme='dark']) .nf-btn-ghost:hover { 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; text-decoration: none;
+ 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);
+}
+.nf-bento-card:hover * { text-decoration: none; }
+: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; }
+}