Files
armarium-website/src/components/layout/Header.astro
T
Daniel Krähenbühl d668aa0fdf feat: Armarium full customization and 4-language i18n (v0.8.0)
Replaces Astro Rocket demo content with Armarium branding and adds
complete DE/FR/IT/EN translations across all pages.

Branding & content (v0.7.0):
- Add horizontal SVG logo to navbar with currentColor dark mode support
- Rewrite homepage with Armarium hero, 6 feature cards, trust bar,
  Zürich coat of arms SVG, and CTA; shared HomePage.astro component
- Add privacy page (/datenschutz) with 6 Infomaniak certification cards
  and 8-section policy (ISO 27001:2022, Swiss Hosting, nDSG/GDPR, etc.)
- Add legal notice page (/impressum)
- Rewrite about, contact, 404 pages with Armarium content
- Add features page (/projects) from projects content collection
- Add language switcher dropdown (LanguageSwitcherDropdown.astro)
- Add single launch blog post; remove all demo blog/project content
- Set up i18n foundation: astro.config.mjs, ui.ts, utils.ts

Full i18n (v0.8.0):
- Add all pages in FR/IT/EN: about, contact, blog, features, privacy,
  legal notice — 28 locale variants total
- Language switcher visible in every layout (PageLayout, BlogLayout,
  ProjectLayout, LandingLayout) with translated nav items
- Locale-aware nav and footer hrefs via nav.*.href keys in ui.ts
- Shared page components (AboutPage, ContactPage, FeaturesIndexPage,
  BlogIndexPage) accept locale prop; locale pages are 4-line wrappers
- Extend content.config.ts locale enum with de and it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 21:51:21 +02:00

772 lines
26 KiB
Plaintext

---
/**
* Header Component
* Flexible navigation header with variant-based configuration
*
* Variants:
* - layout: 'default' | 'centered' | 'minimal'
* - position: 'fixed' | 'sticky' | 'static'
* - size: 'sm' | 'md' | 'lg'
* - variant: 'default' | 'solid' | 'transparent'
* - colorScheme: 'default' | 'invert' (use 'invert' for dark backgrounds)
* - shape: 'bar' | 'floating' (use 'floating' for capsule header)
*
* Features:
* - Dynamic navigation from nav.config.ts (default) or custom nav prop
* - Optional CTA button with customization
* - Mobile menu with Escape key support
* - Theme toggle
* - GitHub/action buttons
* - Full slot support for customization
* - Inverted color scheme for use on dark/image backgrounds
* - Floating capsule shape with scroll-reactive bg + color flip
*/
import type { HTMLAttributes } from 'astro/types';
import { cn } from '@/lib/cn';
import { getNavItems, type NavItem as NavConfigItem } from '@/config/nav.config';
import { headerVariants, headerInnerVariants } from './header.variants';
import Button from '@/components/ui/form/Button/Button.astro';
import Icon from '@/components/ui/primitives/Icon/Icon.astro';
import Logo from '@/components/ui/marketing/Logo/Logo.astro';
import ThemeToggle from '@/components/layout/ThemeToggle.astro';
import ThemeSelector from '@/components/layout/ThemeSelector.astro';
import ThemeSelectorDropdown from '@/components/layout/ThemeSelectorDropdown.astro';
import LanguageSwitcherDropdown from '@/components/layout/LanguageSwitcherDropdown.astro';
import siteConfig from '@/config/site.config';
import type { Locale } from '@/i18n/ui';
export interface NavItem {
label: string;
href: string;
}
export interface HeaderAction {
icon: string;
href: string;
label: string;
iconOnly?: boolean;
target?: string;
}
interface Props extends HTMLAttributes<'header'> {
/** Layout style: default (logo left, nav right), centered (logo center), minimal (logo + cta only) */
layout?: 'default' | 'centered' | 'minimal';
/** Position behavior */
position?: 'fixed' | 'sticky' | 'static';
/** Header height */
size?: 'sm' | 'md' | 'lg';
/** Background variant */
variant?: 'default' | 'solid' | 'transparent';
/** Color scheme for text/icons - use 'invert' for dark backgrounds */
colorScheme?: 'default' | 'invert';
/** Shape: 'bar' (full-width, default) or 'floating' (centered capsule) */
shape?: 'bar' | 'floating';
/** Override default navigation (replaces getNavRoutes()) */
nav?: NavItem[];
/** Additional navigation items (e.g., #features for landing pages) */
extraNav?: NavItem[];
/** Show CTA button */
showCta?: boolean;
/** CTA button configuration */
cta?: { label?: string; href?: string; icon?: string };
/** Action buttons (GitHub, etc.) */
actions?: HeaderAction[];
/** Show theme toggle (default: true) */
showThemeToggle?: boolean;
/** Show colour-theme selector swatches */
showThemeSelector?: boolean;
/** Show mobile menu (default: true) */
showMobileMenu?: boolean;
/** Show active state for current page (default: true) */
showActiveState?: boolean;
/** Logo text override */
logoText?: string;
/** Hide logo entirely */
hideLogo?: boolean;
/** Show language switcher */
showLanguageSwitcher?: boolean;
/** Current locale for language switcher */
currentLocale?: Locale;
/** Show social icon links (desktop/tablet only, reads from siteConfig.socialLinks) */
showSocialLinks?: boolean;
/** Show scroll progress bar at the bottom of the header */
showScrollProgress?: boolean;
/** Position of the scroll progress bar: 'top' (above header) or 'bottom' (below header, default) */
scrollProgressPosition?: 'top' | 'bottom';
}
const {
layout = 'default',
position = 'fixed',
size = 'lg',
variant = 'solid',
colorScheme = 'default',
shape = 'bar',
nav,
extraNav = [],
showCta = false,
cta = { label: 'Start a project', href: '/contact' },
actions = [],
showThemeToggle = true,
showThemeSelector = false,
showMobileMenu = true,
showSocialLinks = false,
showActiveState = true,
showScrollProgress = false,
scrollProgressPosition = 'bottom',
showLanguageSwitcher = false,
currentLocale = 'de',
logoText,
hideLogo = false,
class: className,
...attrs
} = Astro.props;
// Shape + color scheme helpers
const isFloating = shape === 'floating';
const isInvert = colorScheme === 'invert';
// Get navigation items
const defaultNav = getNavItems().map((item: NavConfigItem) => ({
label: item.label,
href: item.href,
}));
const navItems: NavItem[] = nav || [...extraNav, ...defaultNav];
// Current path for active state
const currentPath = Astro.url.pathname;
// Check if we're on the landing page
const isLandingPage = currentPath === '/';
// Process CTA href for landing page anchor links
const ctaHref = cta.href?.startsWith('#') && !isLandingPage ? `/${cta.href}` : cta.href;
// Check slots
const hasLogoSlot = Astro.slots.has('logo');
const hasNavSlot = Astro.slots.has('nav');
const hasActionsSlot = Astro.slots.has('actions');
const hasMobileMenuSlot = Astro.slots.has('mobile-menu');
// Compute header classes
const headerClasses = cn(
headerVariants({ position, variant, shape }),
isInvert && !isFloating && 'invert-section',
className
);
// Compute inner container classes
const innerClasses = headerInnerVariants({ size, shape });
// Check if a nav item is active
function isActive(href: string): boolean {
if (!showActiveState) return false;
if (href.startsWith('#')) return false;
return currentPath === href || currentPath.startsWith(href + '/');
}
// Map a social URL to its icon name + accessible label
function getSocialIconData(url: string): { icon: string; label: string } {
if (url.includes('github.com')) return { icon: 'github', label: 'GitHub' };
if (url.includes('instagram.com')) return { icon: 'instagram', label: 'Instagram' };
if (url.includes('x.com') || url.includes('twitter.com')) return { icon: 'x-twitter', label: 'X' };
if (url.includes('linkedin.com')) return { icon: 'linkedin', label: 'LinkedIn' };
if (url.includes('bsky.app')) return { icon: 'bluesky', label: 'Bluesky' };
return { icon: 'link', label: 'Social' };
}
// Generate unique ID for this header instance
const menuId = `mobile-menu-${Math.random().toString(36).slice(2, 9)}`;
const buttonId = `${menuId}-button`;
---
<header
class={headerClasses}
data-menu-id={menuId}
data-button-id={buttonId}
data-header-shape={shape}
data-header-variant={variant}
data-header-color-scheme={colorScheme}
{...attrs}
>
<div class={innerClasses}>
{/* Logo */}
{
!hideLogo &&
(hasLogoSlot ? (
<slot name="logo" />
) : (
<a href="/" class="flex items-center">
<Logo
variant="full"
size={size === 'lg' ? 'lg' : 'md'}
forceDark={isInvert}
class={cn(isFloating ? 'hdr-logo-text' : (isInvert ? 'text-on-invert' : 'text-foreground'))}
/>
</a>
))
}
{/* Desktop Navigation */}
{
layout !== 'minimal' &&
(hasNavSlot ? (
<nav class="hidden items-center gap-1 md:flex" aria-label="Main navigation">
<slot name="nav" />
</nav>
) : (
<nav class="hidden items-center gap-1 md:flex" aria-label="Main navigation">
{navItems.map(({ label, href }) => (
<a
href={href.startsWith('#') && !isLandingPage ? `/${href}` : href}
class={cn(
'nav-link relative rounded-md px-3 py-2 text-sm',
'transition-all duration-(--transition-fast)',
isFloating && 'hdr-invert-text',
isFloating
? (isActive(href)
? 'hdr-nav-active font-semibold'
: 'font-medium opacity-80 hover:opacity-100')
: (isActive(href)
? 'nav-link-active font-semibold text-foreground bg-secondary'
: 'nav-link-inactive font-medium text-foreground-muted hover:text-foreground hover:bg-secondary/70')
)}
aria-current={isActive(href) ? 'page' : undefined}
>
{label}
</a>
))}
</nav>
))
}
{/* Actions Area */}
<div class="flex items-center gap-2 justify-self-end">
{
hasActionsSlot ? (
<slot name="actions" />
) : (
<>
{showThemeToggle && (
<ThemeToggle class={isFloating ? 'hdr-invert-text' : undefined} />
)}
{showThemeSelector && (
<div class="hidden md:flex">
<ThemeSelectorDropdown class={isFloating ? 'hdr-invert-text' : undefined} />
</div>
)}
{showLanguageSwitcher && (
<div class="hidden md:flex">
<LanguageSwitcherDropdown currentLocale={currentLocale as Locale} />
</div>
)}
{showSocialLinks && siteConfig.socialLinks.length > 0 && (
<div class="hidden md:flex items-center gap-0.5">
{siteConfig.socialLinks.map((url) => {
const { icon, label } = getSocialIconData(url);
return (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
class={cn(
'rounded-md p-2 transition-colors duration-(--transition-fast)',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
isFloating
? 'hdr-invert-text'
: 'text-foreground-muted hover:text-foreground hover:bg-secondary/70'
)}
>
<Icon name={icon} size="md" />
</a>
);
})}
</div>
)}
{actions.map((action) => (
<Button
variant="ghost"
size="sm"
icon={action.iconOnly}
href={action.href}
target={action.target}
aria-label={action.label}
class={isFloating ? 'hdr-invert-text' : undefined}
>
<Icon name={action.icon} size="sm" />
{!action.iconOnly && action.label}
</Button>
))}
{showCta && (
<div class="hidden md:flex">
<Button
size="sm"
href={ctaHref}
target={ctaHref?.startsWith('http') ? '_blank' : undefined}
class={cn('hdr-cta-brand', isFloating ? 'hdr-invert-cta' : undefined)}
>
{cta.icon && <Icon name={cta.icon} size="sm" />}
{cta.label}
</Button>
</div>
)}
</>
)
}
{/* Mobile Menu Toggle */}
{
showMobileMenu && layout !== 'minimal' && (
<button
type="button"
id={buttonId}
class={cn(
'inline-flex items-center justify-center rounded-md p-2 md:hidden',
'transition-colors',
'focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none',
isFloating
? 'hdr-invert-text'
: 'text-foreground-muted hover:text-foreground hover:bg-secondary'
)}
aria-expanded="false"
aria-controls={menuId}
aria-label="Toggle menu"
>
<span class="menu-icon">
<Icon name="menu" size="md" />
</span>
<span class="close-icon hidden">
<Icon name="x" size="md" />
</span>
</button>
)
}
</div>
</div>
{/* Scroll Progress Bar */}
{showScrollProgress && (
<div
id="scroll-progress-bar"
class={`absolute left-0 h-[2px] w-0 bg-brand-500 transition-none ${scrollProgressPosition === 'top' ? 'top-0' : 'bottom-0'}`}
aria-hidden="true"
/>
)}
{/* Mobile Menu */}
{
showMobileMenu &&
layout !== 'minimal' &&
(hasMobileMenuSlot ? (
<div
id={menuId}
class={cn(
'hidden origin-top scale-y-0 opacity-0 shadow-[0_8px_24px_-12px_rgba(0,0,0,0.12)] md:hidden',
isFloating
? 'rounded-b-2xl bg-background/95 backdrop-blur-xl'
: 'border-border bg-background border-t'
)}
role="navigation"
aria-label="Mobile navigation"
>
<slot name="mobile-menu" />
</div>
) : (
<div
id={menuId}
class={cn(
'hidden origin-top scale-y-0 opacity-0 shadow-[0_8px_24px_-12px_rgba(0,0,0,0.12)] md:hidden',
isFloating
? 'rounded-b-2xl bg-background/95 backdrop-blur-xl'
: 'border-border bg-background border-t'
)}
role="navigation"
aria-label="Mobile navigation"
>
<div class={cn(
'space-y-1 py-4',
isFloating ? 'px-4' : 'mx-auto max-w-6xl px-6'
)}>
{navItems.map(({ label, href }) => (
<a
href={href.startsWith('#') && !isLandingPage ? `/${href}` : href}
class={cn(
'mobile-nav-link block rounded-md px-3 py-2 text-sm',
'transition-all duration-(--transition-fast)',
isActive(href)
? 'mobile-nav-link-active bg-secondary text-foreground font-semibold'
: 'mobile-nav-link-inactive text-foreground-muted hover:bg-secondary/70 hover:text-foreground font-medium'
)}
aria-current={isActive(href) ? 'page' : undefined}
>
{label}
</a>
))}
{showCta && (
<div class="border-border mt-3 border-t pt-3">
<Button fullWidth href={ctaHref} target={ctaHref?.startsWith('http') ? '_blank' : undefined}>
{cta.label}
</Button>
</div>
)}
{showThemeSelector && (
<div class="border-border mt-3 border-t pt-3">
<div class="flex items-center justify-between px-1">
<span class="text-sm text-foreground-muted">Colour theme</span>
<ThemeSelector />
</div>
</div>
)}
{showLanguageSwitcher && (
<div class="border-border mt-3 border-t pt-3">
<div class="flex items-center justify-between px-1">
<span class="text-sm text-foreground-muted">Language</span>
<LanguageSwitcherDropdown currentLocale={currentLocale as Locale} />
</div>
</div>
)}
</div>
</div>
))
}
</header>
{/* Mobile Menu Backdrop - positioned outside header to blur page content */}
{
showMobileMenu && layout !== 'minimal' && (
<div
id={`${menuId}-backdrop`}
class="pointer-events-none fixed inset-0 z-40 opacity-0 transition-opacity duration-200 md:hidden"
aria-hidden="true"
/>
)
}
<script>
function initMobileMenu() {
const menuHeaders = document.querySelectorAll<HTMLElement>('header[data-menu-id]');
menuHeaders.forEach((header) => {
const menuId = header.dataset.menuId!;
const buttonId = header.dataset.buttonId!;
const isFloating = header.dataset.headerShape === 'floating';
const button = document.getElementById(buttonId);
const menu = document.getElementById(menuId);
const backdrop = document.getElementById(`${menuId}-backdrop`);
const menuIcon = button?.querySelector('.menu-icon');
const closeIcon = button?.querySelector('.close-icon');
if (!button || !menu || !menuIcon || !closeIcon) return;
if (button.dataset.menuInit) return;
button.dataset.menuInit = 'true';
let isOpen = false;
let isAnimating = false;
function open() {
if (isOpen || isAnimating) return;
isAnimating = true;
isOpen = true;
button!.setAttribute('aria-expanded', 'true');
menuIcon!.classList.add('hidden');
closeIcon!.classList.remove('hidden');
if (isFloating) {
// Force scrolled state + flatten bottom corners
header.setAttribute('data-scrolled', '');
header.classList.remove('rounded-2xl');
header.classList.add('rounded-t-2xl');
} else {
header.classList.add('!bg-background');
}
// Fade out and blur the page content
const mainContent = document.querySelector('main');
const footer = document.querySelector('footer');
if (mainContent) mainContent.classList.add('mobile-menu-blur');
if (footer) footer.classList.add('mobile-menu-blur');
// Show menu and backdrop with animations
menu!.classList.remove('hidden', 'animate-menu-up', 'opacity-0', 'scale-y-0');
menu!.classList.add('animate-menu-down');
if (backdrop) {
backdrop.classList.remove('pointer-events-none', 'animate-backdrop-out');
backdrop.classList.add('animate-backdrop');
}
isAnimating = false;
}
function close() {
if (!isOpen || isAnimating) return;
isAnimating = true;
button!.setAttribute('aria-expanded', 'false');
menuIcon!.classList.remove('hidden');
closeIcon!.classList.add('hidden');
// Start closing animation
menu!.classList.remove('animate-menu-down');
menu!.classList.add('animate-menu-up');
if (backdrop) {
backdrop.classList.remove('animate-backdrop');
backdrop.classList.add('animate-backdrop-out');
}
// Restore page content
const mainContent = document.querySelector('main');
const footer = document.querySelector('footer');
if (mainContent) mainContent.classList.remove('mobile-menu-blur');
if (footer) footer.classList.remove('mobile-menu-blur');
// Wait for animation to complete before hiding
setTimeout(() => {
menu!.classList.add('hidden', 'opacity-0', 'scale-y-0');
if (backdrop) {
backdrop.classList.add('pointer-events-none');
}
if (isFloating) {
// Restore rounded corners
header.classList.remove('rounded-t-2xl');
header.classList.add('rounded-2xl');
// Only remove scrolled if actually at top
if (window.scrollY <= 60) {
header.removeAttribute('data-scrolled');
}
} else {
header.classList.remove('!bg-background');
}
isOpen = false;
isAnimating = false;
}, 200);
}
function toggle() {
if (isOpen) {
close();
} else {
open();
}
}
button.addEventListener('click', toggle);
// Close on backdrop click
if (backdrop) {
backdrop.addEventListener('click', close);
}
// Close on Escape key
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && isOpen) {
close();
}
});
// Close when clicking on mobile menu links
menu.querySelectorAll('a').forEach((link) => {
link.addEventListener('click', close);
});
});
}
initMobileMenu();
document.addEventListener('astro:page-load', initMobileMenu);
document.addEventListener('astro:after-swap', initMobileMenu);
</script>
<script>
const SCROLL_THRESHOLD = 60;
const BAR_SCROLLED_CLASSES = ['bg-background/80', 'backdrop-blur-lg', 'border-b', 'border-border/50'];
function initScrollWatcher() {
const scrollHeaders = document.querySelectorAll<HTMLElement>('header[data-header-shape="floating"], header[data-header-shape="bar"]');
scrollHeaders.forEach((header) => {
if (header.dataset.scrollInit) return;
header.dataset.scrollInit = 'true';
const isBar = header.dataset.headerShape === 'bar';
const isTransparentBar = isBar && header.dataset.headerVariant === 'transparent';
let ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
if (window.scrollY > SCROLL_THRESHOLD) {
header.setAttribute('data-scrolled', '');
if (isTransparentBar) {
header.classList.add(...BAR_SCROLLED_CLASSES);
header.classList.remove('bg-transparent');
}
} else {
// Don't remove if mobile menu is open
const menuId = header.dataset.menuId;
const menu = menuId ? document.getElementById(menuId) : null;
const menuOpen = menu && !menu.classList.contains('hidden');
if (!menuOpen) {
header.removeAttribute('data-scrolled');
if (isTransparentBar) {
header.classList.remove(...BAR_SCROLLED_CLASSES);
header.classList.add('bg-transparent');
}
}
}
ticking = false;
});
}
window.addEventListener('scroll', onScroll, { passive: true });
// Set initial state
onScroll();
});
}
initScrollWatcher();
document.addEventListener('astro:page-load', initScrollWatcher);
document.addEventListener('astro:after-swap', initScrollWatcher);
</script>
<script>
function initScrollProgress() {
const bar = document.getElementById('scroll-progress-bar');
if (!bar) return;
if (bar.dataset.progressInit) return;
bar.dataset.progressInit = 'true';
let ticking = false;
function update() {
if (!bar) return;
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const pct = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
bar!.style.width = `${pct}%`;
ticking = false;
}
window.addEventListener('scroll', () => {
if (!ticking) {
ticking = true;
requestAnimationFrame(update);
}
}, { passive: true });
update();
}
initScrollProgress();
document.addEventListener('astro:page-load', initScrollProgress);
document.addEventListener('astro:after-swap', initScrollProgress);
</script>
<style is:global>
.mobile-menu-blur {
opacity: 0.3;
filter: blur(4px);
transition: opacity 200ms, filter 200ms;
}
/* ===== Floating header: scroll state ===== */
[data-header-shape="floating"][data-scrolled] {
background: color-mix(in oklch, var(--color-background) 92%, transparent);
backdrop-filter: blur(24px);
border-color: var(--color-border);
box-shadow: 0 4px 20px -6px rgba(0, 0, 0, 0.1);
}
/* ===== Floating header: color flip (invert → normal on scroll) ===== */
/* Text elements: on-invert → foreground */
[data-header-shape="floating"][data-header-color-scheme="invert"] .hdr-invert-text {
color: var(--color-on-invert);
transition: color 300ms;
}
[data-header-shape="floating"][data-header-color-scheme="invert"][data-scrolled] .hdr-invert-text {
color: var(--color-foreground);
}
/* Logo text: on-invert → brand-500 */
[data-header-shape="floating"][data-header-color-scheme="invert"] .hdr-logo-text {
color: var(--color-on-invert);
transition: color 300ms;
}
[data-header-shape="floating"][data-header-color-scheme="invert"][data-scrolled] .hdr-logo-text {
color: var(--color-brand-500);
}
/* Non-invert floating: use normal colors */
[data-header-shape="floating"][data-header-color-scheme="default"] .hdr-logo-text {
color: var(--color-brand-500);
}
[data-header-shape="floating"][data-header-color-scheme="default"] .hdr-invert-text {
color: var(--color-foreground-muted);
transition: color 300ms;
}
[data-header-shape="floating"][data-header-color-scheme="default"] .hdr-invert-text:hover {
color: var(--color-foreground);
}
/* ===== Floating nav link underline indicators ===== */
[data-header-shape="floating"] .nav-link::after {
content: '';
position: absolute;
bottom: 2px;
left: 50%;
right: 50%;
height: 2px;
background: currentColor;
border-radius: 1px;
transition: left 200ms, right 200ms;
}
[data-header-shape="floating"] .nav-link:hover::after,
[data-header-shape="floating"] .nav-link.hdr-nav-active::after {
left: 12px;
right: 12px;
}
/* ===== CTA: invert color flip ===== */
[data-header-shape="floating"][data-header-color-scheme="invert"] .hdr-invert-cta {
background: white;
color: #111;
border: 1px solid rgba(0, 0, 0, 0.15);
transition: background 300ms, color 300ms, border-color 300ms;
}
[data-header-shape="floating"][data-header-color-scheme="invert"] .hdr-invert-cta:hover {
background: rgba(255, 255, 255, 0.9);
}
[data-header-shape="floating"][data-header-color-scheme="invert"][data-scrolled] .hdr-invert-cta {
background: var(--color-primary);
color: var(--color-primary-foreground);
}
[data-header-shape="floating"][data-header-color-scheme="invert"][data-scrolled] .hdr-invert-cta:hover {
opacity: 0.9;
}
/* ===== Reduced motion ===== */
@media (prefers-reduced-motion: reduce) {
[data-header-shape="floating"],
[data-header-shape="floating"] .hdr-invert-text,
[data-header-shape="floating"] .hdr-logo-text,
[data-header-shape="floating"] .hdr-invert-cta,
[data-header-shape="floating"] .nav-link::after {
transition: none !important;
}
}
</style>