## Summary - apply public website launch readiness updates across the Astro site shell, content, and navigation - refine website components, metadata, and localization-related structure for launch prep - update docs/content paths and smoke coverage to match the launch-ready public site state ## Scope - touches the website app and related spec artifacts for feature 403 - does not modify `apps/platform` ## Testing - not run in this step Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #394
214 lines
7.0 KiB
Plaintext
214 lines
7.0 KiB
Plaintext
---
|
|
//Import relevant dependencies
|
|
import ThemeIcon from '@components/ThemeIcon.astro';
|
|
import NavLink from '@components/ui/links/NavLink.astro';
|
|
import Authentication from '../misc/Authentication.astro';
|
|
import BrandLogo from '@components/BrandLogo.astro';
|
|
import LanguagePicker from '@components/ui/LanguagePicker.astro';
|
|
import { getLocaleFromPath, localizeHref } from '@/i18n';
|
|
import { siteCopy } from '@data/site-copy';
|
|
|
|
const locale = getLocaleFromPath(Astro.url.pathname);
|
|
const strings = siteCopy[locale];
|
|
const homeUrl = localizeHref('/', locale);
|
|
type NavItem = {
|
|
name: string;
|
|
url: string;
|
|
};
|
|
---
|
|
|
|
{/* Main header component */}
|
|
<header
|
|
class="sticky inset-x-0 top-4 z-50 flex w-full flex-wrap text-sm md:flex-nowrap md:justify-start"
|
|
>
|
|
{/* Navigation container */}
|
|
<nav
|
|
class="relative mx-2 w-full rounded-[36px] border border-yellow-100/40 bg-yellow-50/60 px-4 py-3 backdrop-blur-md md:flex md:items-center md:justify-between md:px-6 md:py-0 lg:px-8 xl:mx-auto dark:border-neutral-700/40 dark:bg-neutral-800/80 dark:backdrop-blur-md"
|
|
aria-label="Global"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
{/* Brand logo */}
|
|
<a
|
|
class="flex-none rounded-lg text-xl font-bold ring-zinc-500 outline-hidden focus-visible:ring-3 dark:ring-zinc-200 dark:focus:outline-hidden"
|
|
href={homeUrl}
|
|
aria-label="Brand"
|
|
>
|
|
<BrandLogo class="h-auto w-36 sm:w-40" />
|
|
</a>
|
|
{/* Collapse toggle for smaller screens */}
|
|
<div class="mr-5 ml-auto md:hidden">
|
|
<button
|
|
type="button"
|
|
class="hs-collapse-toggle flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold text-neutral-600 transition duration-300 hover:bg-neutral-200 disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:focus:outline-hidden"
|
|
data-hs-collapse="#navbar-collapse-with-animation"
|
|
aria-controls="navbar-collapse-with-animation"
|
|
aria-label="Toggle navigation"
|
|
>
|
|
<svg
|
|
class="hs-collapse-open:hidden h-[1.25rem] w-[1.25rem] shrink-0"
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<line x1="3" x2="21" y1="6" y2="6"></line>
|
|
<line x1="3" x2="21" y1="12" y2="12"></line>
|
|
<line x1="3" x2="21" y1="18" y2="18"></line>
|
|
</svg>
|
|
<svg
|
|
class="hs-collapse-open:block hidden h-[1.25rem] w-[1.25rem] shrink-0"
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path d="M18 6 6 18"></path>
|
|
<path d="m6 6 12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
{/* ThemeIcon component specifically for smaller screens */}
|
|
<span class="inline-block md:hidden">
|
|
<ThemeIcon />
|
|
</span>
|
|
</div>
|
|
{/* Contains navigation links */}
|
|
<div
|
|
id="navbar-collapse-with-animation"
|
|
class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block"
|
|
>
|
|
{/* Navigation links container */}
|
|
<div
|
|
class="mt-5 flex flex-col gap-x-0 gap-y-4 md:mt-0 md:flex-row md:items-center md:justify-end md:gap-x-4 md:gap-y-0 md:ps-7 lg:gap-x-7"
|
|
>
|
|
{/* Navigation links and Authentication component */}
|
|
{
|
|
strings.nav.map((link: NavItem) => (
|
|
<NavLink url={localizeHref(link.url, locale)} name={link.name} />
|
|
))
|
|
}
|
|
|
|
<Authentication locale={locale} />
|
|
<LanguagePicker />
|
|
{/* ThemeIcon component specifically for larger screens */}
|
|
<span class="hidden md:inline-block">
|
|
<ThemeIcon />
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
{/* Theme Appearance script to manage light/dark modes */}
|
|
<script is:inline>
|
|
const HSThemeAppearance = {
|
|
init() {
|
|
const defaultTheme = 'default';
|
|
let theme = localStorage.getItem('hs_theme') || defaultTheme;
|
|
|
|
if (document.querySelector('html').classList.contains('dark')) return;
|
|
this.setAppearance(theme);
|
|
},
|
|
_resetStylesOnLoad() {
|
|
const $resetStyles = document.createElement('style');
|
|
$resetStyles.innerText = `*{transition: unset !important;}`;
|
|
$resetStyles.setAttribute('data-hs-appearance-onload-styles', '');
|
|
document.head.appendChild($resetStyles);
|
|
return $resetStyles;
|
|
},
|
|
setAppearance(theme, saveInStore = true, dispatchEvent = true) {
|
|
const $resetStylesEl = this._resetStylesOnLoad();
|
|
|
|
if (saveInStore) {
|
|
localStorage.setItem('hs_theme', theme);
|
|
}
|
|
|
|
if (theme === 'auto') {
|
|
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
? 'dark'
|
|
: 'default';
|
|
}
|
|
|
|
document.querySelector('html').classList.remove('dark');
|
|
document.querySelector('html').classList.remove('default');
|
|
document.querySelector('html').classList.remove('auto');
|
|
|
|
document
|
|
.querySelector('html')
|
|
.classList.add(this.getOriginalAppearance());
|
|
|
|
setTimeout(() => {
|
|
$resetStylesEl.remove();
|
|
});
|
|
|
|
if (dispatchEvent) {
|
|
window.dispatchEvent(
|
|
new CustomEvent('on-hs-appearance-change', { detail: theme })
|
|
);
|
|
}
|
|
},
|
|
getAppearance() {
|
|
let theme = this.getOriginalAppearance();
|
|
if (theme === 'auto') {
|
|
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
? 'dark'
|
|
: 'default';
|
|
}
|
|
return theme;
|
|
},
|
|
getOriginalAppearance() {
|
|
const defaultTheme = 'default';
|
|
return localStorage.getItem('hs_theme') || defaultTheme;
|
|
},
|
|
};
|
|
HSThemeAppearance.init();
|
|
|
|
window
|
|
.matchMedia('(prefers-color-scheme: dark)')
|
|
.addEventListener('change', () => {
|
|
if (HSThemeAppearance.getOriginalAppearance() === 'auto') {
|
|
HSThemeAppearance.setAppearance('auto', false);
|
|
}
|
|
});
|
|
|
|
window.addEventListener('load', () => {
|
|
const $clickableThemes = document.querySelectorAll(
|
|
'[data-hs-theme-click-value]'
|
|
);
|
|
const $switchableThemes = document.querySelectorAll(
|
|
'[data-hs-theme-switch]'
|
|
);
|
|
|
|
$clickableThemes.forEach($item => {
|
|
$item.addEventListener('click', () =>
|
|
HSThemeAppearance.setAppearance(
|
|
$item.getAttribute('data-hs-theme-click-value'),
|
|
true,
|
|
$item
|
|
)
|
|
);
|
|
});
|
|
|
|
$switchableThemes.forEach($item => {
|
|
$item.addEventListener('change', e => {
|
|
HSThemeAppearance.setAppearance(e.target.checked ? 'dark' : 'default');
|
|
});
|
|
|
|
$item.checked = HSThemeAppearance.getAppearance() === 'dark';
|
|
});
|
|
|
|
window.addEventListener('on-hs-appearance-change', e => {
|
|
$switchableThemes.forEach($item => {
|
|
$item.checked = e.detail === 'dark';
|
|
});
|
|
});
|
|
});
|
|
</script>
|