feat: redesign — one message, one audience, light pages
The site felt old, unfocused and bloated: 11 pages, a features page of 171 bullets, a homepage of 830 words in card grids around seven mockup-style screenshots, and no single thing it wanted a visitor to understand. - Message: control instead of drift. Audience: IT departments in small and mid-sized companies. Goal: buy a licence — netOrk itself is free, the licence adds vulnerability data and image updates. - Home is under 400 words: the drift comparison of a real access point, three steps, the hardware it runs on, vulnerabilities with a licence, what else is in the box, one closing band. Features, Drivers and Roadmap are gone; their URLs redirect (router and nginx 301). - New Pricing page: the free core, Starter / Pro / Enterprise on request with the plan differences from the licence server, four questions; buttons go to the licence portal. The unit-less KB request limit is left out. - Persona pages are one template; NIS2 and Plugins are cut to half or less. Impressum and Datenschutz exist as marked placeholders; the unsupported "MIT licence" claim is gone from the footer. - Look: light paper and ink, the dark product on a stage, Inter self-hosted, split sections and ruled lists instead of cards. Four real, cropped screenshots replace eight full-window ones. - Language follows the browser until someone chooses; <html lang> is set. Scroll-to-top on navigation, a catch-all route, no dead /docs/architecture. - CLAUDE.md, DESIGN.md, PAGES.md and PRODUCT.md describe the new rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
369f66afdc
commit
f70fec496a
@@ -9,18 +9,34 @@ interface LangContextValue {
|
||||
|
||||
const LangContext = createContext<LangContextValue | null>(null)
|
||||
|
||||
export function LangProvider({ children }: { children: ReactNode }) {
|
||||
const [lang, setLangState] = useState<Lang>(() => {
|
||||
// A stored choice wins; otherwise the browser decides: German for de-*, English for everything else.
|
||||
function initialLang(): Lang {
|
||||
try {
|
||||
const stored = localStorage.getItem('lang')
|
||||
return stored === 'de' || stored === 'en' ? stored : 'de'
|
||||
})
|
||||
if (stored === 'de' || stored === 'en') return stored
|
||||
} catch {
|
||||
// storage blocked: fall through to the browser language
|
||||
}
|
||||
const preferred = navigator.languages?.[0] ?? navigator.language ?? 'en'
|
||||
return preferred.toLowerCase().startsWith('de') ? 'de' : 'en'
|
||||
}
|
||||
|
||||
export function LangProvider({ children }: { children: ReactNode }) {
|
||||
const [lang, setLangState] = useState<Lang>(initialLang)
|
||||
|
||||
// <html lang> drives hyphenation and screen readers.
|
||||
useEffect(() => {
|
||||
localStorage.setItem('lang', lang)
|
||||
document.documentElement.lang = lang
|
||||
}, [lang])
|
||||
|
||||
// Only an explicit switch is remembered, so the browser keeps deciding until someone chooses.
|
||||
function setLang(l: Lang) {
|
||||
setLangState(l)
|
||||
try {
|
||||
localStorage.setItem('lang', l)
|
||||
} catch {
|
||||
// storage blocked: the choice lasts for this visit
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user