diff --git a/404.html b/404.html index 0e5f223..68bddf2 100644 --- a/404.html +++ b/404.html @@ -3,9 +3,10 @@ - + Page Not Found + @@ -20,7 +21,7 @@ - + + + + + + + +

4💀4

@@ -50,6 +85,9 @@

Sorry, but the page you were trying to view does not exist.

+ + + diff --git a/css/a11y.css b/css/a11y.css new file mode 100644 index 0000000..40a4dc8 --- /dev/null +++ b/css/a11y.css @@ -0,0 +1,69 @@ +*, a, span, li { + line-height: var(--line-loose); + letter-spacing: 0.12em; + word-spacing: 0.16em; +} + +a { + line-height: var(--line-loose); + letter-spacing: 0.12em; + word-spacing: 0.16em; +} + +p, ul, ol, li { + margin: 1.6em 0 3.2em 0; +} + +button, .button-primary, .button-secondary, .menu-button, .logo { + line-height: var(--line-loose); + letter-spacing: 0.12em; + word-spacing: 0.16em; + text-decoration: underline; + text-underline-offset: 0.175rem; + text-decoration-thickness: 0.075rem; + transition: 0.2s; +} + +button:hover, .button-primary:hover, .button-secondary:hover, .logo:hover { + text-decoration: underline; + text-underline-offset: 0.175rem; + text-decoration-thickness: 0.075rem; + transition: 0.2s; +} + +button:active, .button-primary:active, .button-secondary:active, .logo:active { + text-decoration: underline; + text-underline-offset: 0.175rem; + text-decoration-thickness: 0.075rem; + transition: 0.2s; +} + +.menu-button:hover, +.desktop-nav a[aria-current="page"]:hover, +.mobile-menu a[aria-current="page"]:hover { + text-decoration: underline; + text-underline-offset: 0.175rem; + text-decoration-thickness: 0.075rem; + transition: 0.2s; +} + +.menu-button:active, +.desktop-nav a[aria-current="page"]:active, +.mobile-menu a[aria-current="page"]:active, +.desktop-nav a[aria-current="page"], +.mobile-menu a[aria-current="page"] { + text-decoration: underline; + text-underline-offset: 0.175rem; + text-decoration-thickness: 0.075rem; + transition: 0.2s; +} + +.menu-button:active, +.desktop-nav a[aria-current="page"]:active, +.mobile-menu a[aria-current="page"]:active { + text-decoration: underline; + text-underline-offset: 0.175rem; + text-decoration-thickness: 0.075rem; + transition: 0.2s; +} + diff --git a/css/style.css b/css/style.css index 79c301b..bbd0524 100644 --- a/css/style.css +++ b/css/style.css @@ -55,6 +55,7 @@ :root { color-scheme: light dark; + background-color: light-dark(#F7F7F7, #121212); /* Primitive tokens: raw design values */ --color-neutral-100: #F7F7F7; @@ -87,6 +88,21 @@ --color-orange-800: #933D00; --color-orange-900: #7A2C00; + --shadow-light: 0px 2px 8px rgba(0, 0, 0, 0.3), 0px 1px 4px rgba(0, 0, 0, 0.2); + --shadow-dark: 0px 2px 8px rgba(0, 0, 0, 0.5), 0px 1px 4px rgba(0, 0, 0, 0.2); + + --gradient-stop-1-light: #f7f7f7; + --gradient-stop-1-dark: #121212; + + --gradient-stop-2-light: #f7f7f7; + --gradient-stop-2-dark: #121212; + + --gradient-stop-3-light: #f7f7f780; + --gradient-stop-3-dark: #12121280; + + --gradient-stop-4-light: #f7f7f700; + --gradient-stop-4-dark: #12121200; + --text-h1: clamp(2.25rem, 1.9rem + 1.1vw, 3rem); --text-h2: clamp(1.875rem, 1.6rem + 0.9vw, 2.5rem); --text-h3: clamp(1.625rem, 1.4rem + 0.7vw, 2rem); @@ -94,6 +110,8 @@ --text-h5: clamp(1.25rem, 1.15rem + 0.25vw, 1.5rem); --text-h6: clamp(1.125rem, 1.05rem + 0.25vw, 1.25rem); + --round-interval: 2px; + --text-body: clamp(1.125rem, 1.05rem + 0.25vw, 1.25rem); --text-small: 0.875rem; --text-button: clamp(1.25rem, 1.15rem + 0.25vw, 1.5rem); @@ -126,8 +144,6 @@ --radius-xl: 2rem; --radius-xxl: 2.5rem; - --shadow-light: 0px 2px 8px rgba(0, 0, 0, 0.3), 0px 1px 4px rgba(0, 0, 0, 0.2); - --shadow-dark: 0px 2px 8px rgba(0, 0, 0, 0.5), 0px 1px 4px rgba(0, 0, 0, 0.2); /* Semantic tokens: contextual usage */ --color-background: light-dark(var(--color-neutral-100), var(--color-neutral-900)); @@ -149,13 +165,24 @@ --color-text-primary: light-dark(var(--color-neutral-900), var(--color-neutral-100)); --color-text-secondary: light-dark(var(--color-neutral-700), var(--color-neutral-400)); + --color-text-primary-inverted: light-dark(var(--color-neutral-100), var(--color-neutral-900)); + --color-text-secondary-inverted: light-dark(var(--color-neutral-400), var(--color-neutral-700)); - --color-primary-button: var(--color-orange-red-500); - --color-primary-button-hover: var(--color-orange-500); - --color-primary-button-active: var(--color-orange-red-600); + --color-button-primary: var(--color-orange-red-500); + --color-button-primary-hover: var(--color-orange-500); + --color-button-primary-active: var(--color-orange-red-600); - --color-text-button: var(--color-neutral-900); - --color-text-button-active: var(--color-neutral-100); + --color-button-secondary: light-dark(var(--color-neutral-200), var(--color-neutral-700)); + --color-button-secondary-hover: light-dark(var(--color-neutral-100), var(--color-neutral-600)); + --color-button-secondary-active: light-dark(var(--color-neutral-300), var(--color-neutral-800)); + + --color-text-button-primary: var(--color-neutral-900); + --color-text-button-primary-hover: var(--color-neutral-100); + --color-text-button-primary-active: var(--color-neutral-900); + + --color-text-button-secondary: light-dark(var(--color-neutral-800), var(--color-neutral-200)); + --color-text-button-seconary-hover: light-dark(var(--color-neutral-900), var(--color-neutral-100)); + --color-text-button-seconary-active: light-dark(var(--color-neutral-900), var(--color-neutral-100)); --shadow-box: var(--shadow-light); @@ -177,14 +204,18 @@ -webkit-font-smoothing: antialiased; } +html, +body { + margin: 0; + background: var(--color-background); +} + html { scroll-behavior: smooth; } body { - margin: 0; - background: var(--color-background); - color: var(--color-text); + color: var(--color-text-primary); font-family: "MD UI", Helvetica, sans; font-style: normal; font-weight: 400; @@ -203,8 +234,7 @@ h3, h4, h5, h6, -p, -li { +p { scroll-margin-top: 80px; width: clamp(min(100%, 40ch), 50ch, min(100%, 60ch)); } @@ -294,27 +324,27 @@ h1, h2, h3, h4, h5, h6 { } h1, .size-h1 { - font-size: var(--text-h1); + font-size: round(down, var(--text-h1), var(--round-interval)); } h2, .size-h2 { - font-size: var(--text-h2); + font-size: round(down, var(--text-h2), var(--round-interval)); } h3, .size-h3 { - font-size: var(--text-h3); + font-size: round(down, var(--text-h3), var(--round-interval)); } h4, .size-h4 { - font-size: var(--text-h4); + font-size: round(down, var(--text-h4), var(--round-interval)); } h5, .size-h5 { - font-size: var(--text-h5); + font-size: round(down, var(--text-h5), var(--round-interval)); } h6, .size-h6 { - font-size: var(--text-h6); + font-size: round(down, var(--text-h6), var(--round-interval)); } p, ul, ol, li { @@ -322,7 +352,7 @@ p, ul, ol, li { font-style: normal; font-weight: 400; font-feature-settings: "ss01" 1, "ss02" 1; - font-size: var(--text-body); + font-size: round(down, var(--text-body), var(--round-interval)); line-height: var(--line-relaxed); margin: var(--spacing-3) 0 var(--spacing-6) 0; text-align: left; @@ -346,7 +376,7 @@ a:visited { text-decoration-thickness: 0.175rem; } -button, .link-button { +button, .button-primary, .button-secondary, .menu-button, .logo { appearance: none; -webkit-appearance: none; display: inline-flex; @@ -359,32 +389,86 @@ button, .link-button { line-height: var(--line-normal); letter-spacing: 0.025rem; padding: 0.875rem 1rem; - background: var(--color-primary-button); - font-size: var(--text-button); - color: var(--color-text-button); + font-size: round(down, var(--text-button), var(--round-interval)); border: none; border-radius: var(--radius-xxl); + text-decoration: none; + transition: transform 0.2s ease; + cursor: pointer; +} + +button, .button-primary, .logo { + background: var(--color-button-primary); + color: var(--color-text-button-primary); box-shadow: var(--shadow-button); +} + +.button-secondary { + background: var(--color-button-secondary); + color: var(--color-text-button-secondary); + box-shadow: var(--shadow-button); +} + +.logo, .button-secondary { + padding: 1rem; + aspect-ratio: 1/1; +} + +.logo svg { + height: 2em; + width: 2em; + flex: none; + display: block; +} + +.logo svg path { + fill: currentColor; +} + +button:hover, .button-primary:hover, .button-secondary:hover, .logo:hover { text-decoration: none; + transform: scale(1.05); } -button:hover, .link-button:hover { - background: var(--color-primary-button-hover); - color: var(--color-text-button); +button:hover, .button-primary:hover, .logo:hover { + background: var(--color-button-primary-hover); + color: var(--color-text-button-primary); } -button:active, .link-button:active { - background: var(--color-primary-button-active); +.button-secondary:hover { + background: var(--color-button-secondary-hover); + color: var(--color-text-button-secondary-hover); +} + +button:active, .button-primary:active, .button-secondary:active, .logo:active { box-shadow: var(--shadow-button-active); - color: var(--color-text-button-active); + text-decoration: none; + transform: scale(1.05); } -button:visited, .link-button:visited { - color: var(--color-text-button); +button:active, .button-primary:active, .logo:active { + background: var(--color-button-primary-active); + color: var(--color-text-button-primary-active); +} + +.button-secondary:active { + background: var(--color-button-secondary-active); + color: var(--color-text-button-secondary-active); +} + +button:visited, .button-primary:visited, .logo:visited { + background: var(--color-button-primary); + color: var(--color-text-button-primary); text-decoration: none; } -.link-button span { +.button-secondary:visited { + background: var(--color-button-secondary); + color: var(--color-text-button-secondary); + text-decoration: none; +} + +.button-primary span { margin-right: 0.3em; } @@ -392,12 +476,141 @@ button:visited, .link-button:visited { Layout styles ======================= */ +/* Site header */ + +.site-header { + display: flex; + align-items: center; + justify-content: center; + position: sticky; + top: 0; + padding-top: max(1px, env(safe-area-inset-top)); + z-index: 100; + background-image: linear-gradient( + 180deg, + light-dark(var(--gradient-stop-1-light), var(--gradient-stop-1-dark)) 0%, + 6%, + light-dark(var(--gradient-stop-2-light), var(--gradient-stop-2-dark)) 12%, + 44%, + light-dark(var(--gradient-stop-3-light), var(--gradient-stop-3-dark)) 60%, + 76%, + light-dark(var(--gradient-stop-4-light), var(--gradient-stop-4-dark)) 100% + ); +} + +.menu-bar { + width: 900px; + padding: var(--spacing-5) var(--spacing-6) var(--spacing-4) var(--spacing-6); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-4); +} + +.menu-left, .menu-right { + display: flex; + align-items: stretch; + gap: var(--spacing-4); +} + +.desktop-nav { + flex: 1 1 auto; + display: flex; + align-self: stretch; + background: var(--color-button-secondary); + border-radius: var(--radius-xxl); + box-shadow: var(--shadow-button); +} + +.desktop-nav ul { + display: flex; + align-items: stretch; + width: 100%; + margin: 0; + padding: var(--spacing-2); + list-style: none; + gap: var(--spacing-2); +} + +.desktop-nav li { + flex: 1 1 0; + display: flex; + align-items: stretch; + margin: 0; + padding: 0; + list-style: none; +} + +.desktop-nav .menu-button { + flex: 1 1 auto; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + white-space: nowrap; +} + +.menu-button { + background: none; + color: var(--color-text-button-secondary); + transition: transform 0.2s ease; +} + +.menu-button:hover, +.desktop-nav a[aria-current="page"]:hover, +.mobile-menu a[aria-current="page"]:hover { + background: var(--color-button-secondary-hover); + color: var(--color-text-button-seconary-hover); + text-decoration: none; + transform: scale(1.03); +} + +.menu-button:active, +.desktop-nav a[aria-current="page"]:active, +.mobile-menu a[aria-current="page"]:active, +.desktop-nav a[aria-current="page"], +.mobile-menu a[aria-current="page"] { + background: light-dark(var(--color-neutral-400), var(--color-neutral-800)); + color: var(--color-text-button-seconary-active); +} + +.menu-button:active, +.desktop-nav a[aria-current="page"]:active, +.mobile-menu a[aria-current="page"]:active { + text-decoration: none; +} + +.desktop-nav-secondary ul { + display: flex; + align-items: stretch; + gap: var(--spacing-4); + margin: 0; + padding: 0; + list-style: none; +} + +.desktop-nav-secondary li { + display: flex; + margin: 0; + padding: 0; +} + +.desktop-nav-secondary svg { + color: currentcolor; + height: 1em; + width: 1em; + flex: none; + display: block; +} + +/* Maint content */ + main, footer { max-width: 900px; border: none; margin-left:auto; margin-right:auto; - padding: var(--spacing-4) var(--spacing-6); + padding: 0 var(--spacing-6) var(--spacing-4) var(--spacing-6); display: flex; flex-direction: column; align-items: center; @@ -420,7 +633,7 @@ footer { font-family: "MD IO", monospace; font-style: normal; font-weight: 400; - font-size: var(--text-small); + font-size: round(down, var(--text-small), var(--round-interval)); line-height: var(--line-relaxed); color: var(--color-text-secondary); } @@ -537,6 +750,8 @@ footer a:visited { color: var(--color-neutral-900); } +/* Footer */ + .footer-content { display: flex; flex-direction: row; @@ -549,40 +764,28 @@ footer a:visited { column-gap: var(--spacing-4); } -/* .footer-fade { - position: fixed; - left: 0; - right: 0; - bottom: 0; - height: 320px; - background: linear-gradient( - to bottom, - hsla(0, 0%, 6%, 0) 0%, - hsla(0, 0%, 6%, 0.1) 13.6%, - hsla(0, 0%, 6%, 0.199) 24.8%, - hsla(0, 0%, 6%, 0.296) 33.8%, - hsla(0, 0%, 6%, 0.391) 41%, - hsla(0, 0%, 6%, 0.481) 46.8%, - hsla(0, 0%, 6%, 0.568) 51.5%, - hsla(0, 0%, 6%, 0.649) 55.4%, - hsla(0, 0%, 6%, 0.724) 58.8%, - hsla(0, 0%, 6%, 0.792) 62.2%, - hsla(0, 0%, 6%, 0.852) 65.9%, - hsla(0, 0%, 6%, 0.903) 70.1%, - hsla(0, 0%, 6%, 0.944) 75.3%, - hsla(0, 0%, 6%, 0.975) 81.8%, - hsla(0, 0%, 6%, 0.993) 89.9%, - hsl(0, 0%, 6%) 100% - ); - pointer-events: none; - z-index: 10; -} */ - - /* ======================= Helper classes ======================= */ +/* + * Adding skipLink styling for screen readers + */ + +#skipLink a { + display: block; + position: absolute; + left: -999px; + top: -999px; +} + +#skipLink a:focus { + left: 0; + top: 0; + padding: var(--spacing-2); + background: var(--color-background-content-box); +} + /* * Hide visually and from screen readers */ @@ -667,8 +870,12 @@ footer a:visited { } @media screen and (max-width:768px) { + .menu-bar { + padding: var(--spacing-5) var(--spacing-5) var(--spacing-4) var(--spacing-5); + } + main { - padding: var(--spacing-4) var(--spacing-5); + padding: 0 var(--spacing-5) var(--spacing-4) var(--spacing-5); } .footer-content { flex-direction: column; @@ -680,13 +887,9 @@ footer a:visited { } @media screen and (max-width:440px) { - /* .footer-fixed { - position: unset; - left: unset; - right: unset; - bottom: unset; - z-index: unset; - } */ + .menu-bar { + padding: var(--spacing-4) var(--spacing-5) var(--spacing-4) var(--spacing-5); + } .footer-links { flex-direction: column; row-gap: var(--spacing-4); @@ -698,7 +901,19 @@ footer a:visited { @media screen and (max-width:375px) { main { - padding: var(--spacing-4); + padding: 0 var(--spacing-4) var(--spacing-4) var(--spacing-4); + } + .menu-bar { + padding: var(--spacing-4); + } + .menu-left, .menu-right { + gap: var(--spacing-3); + } + .desktop-nav-secondary ul { + gap: var(--spacing-3); + } + .menu-bar { + gap: var(--spacing-3); } } diff --git a/index.html b/index.html index 882d38f..2abc7dc 100644 --- a/index.html +++ b/index.html @@ -3,9 +3,10 @@ - + Lars Winter + @@ -20,12 +21,44 @@ - + + + + -
+ + + + +
@@ -71,7 +104,7 @@ - + diff --git a/js/app.js b/js/app.js index 2aa8b89..26f65f0 100644 --- a/js/app.js +++ b/js/app.js @@ -1,57 +1,98 @@ - const body = document.body; - const menuToggle = document.getElementById('menuToggle'); - const menuClose = document.getElementById('menuClose'); - const menuOverlay = document.getElementById('menuOverlay'); - const mobileMenu = document.getElementById('mobileMenu'); - const siteContent = document.getElementById('siteContent'); +// accessibility toggle - function openMenu() { - body.classList.add('menu-open'); - menuOverlay.hidden = false; - menuToggle.classList.add('is-open'); - menuToggle.setAttribute('aria-expanded', 'true'); - menuToggle.setAttribute('aria-label', 'Close menu'); - mobileMenu.setAttribute('aria-hidden', 'false'); - siteContent.setAttribute('inert', ''); - menuClose.focus(); +{ + const STORAGE_KEY = 'extra-css-enabled'; + + function applyOverrideState(enabled) { + const link = document.getElementById('a11y-css'); + const button = document.getElementById('a11y-toggle'); + if (!link) return; + + link.disabled = !enabled; + button?.setAttribute('aria-pressed', String(enabled)); } - - function closeMenu() { - body.classList.remove('menu-open'); - menuToggle.classList.remove('is-open'); - menuToggle.setAttribute('aria-expanded', 'false'); - menuToggle.setAttribute('aria-label', 'Open menu'); - mobileMenu.setAttribute('aria-hidden', 'true'); - siteContent.removeAttribute('inert'); - menuToggle.focus(); - - window.setTimeout(() => { - if (!body.classList.contains('menu-open')) { - menuOverlay.hidden = true; - } - }, 250); - } - - function toggleMenu() { - if (body.classList.contains('menu-open')) { - closeMenu(); - } else { - openMenu(); - } - } - - menuToggle.addEventListener('click', toggleMenu); - menuClose.addEventListener('click', closeMenu); - menuOverlay.addEventListener('click', closeMenu); - - document.addEventListener('keydown', (event) => { - if (event.key === 'Escape' && body.classList.contains('menu-open')) { - closeMenu(); - } + + const enabled = localStorage.getItem(STORAGE_KEY) === 'true'; + applyOverrideState(enabled); + + document.getElementById('a11y-toggle')?.addEventListener('click', () => { + const nextState = localStorage.getItem(STORAGE_KEY) !== 'true'; + localStorage.setItem(STORAGE_KEY, String(nextState)); + applyOverrideState(nextState); }); - - window.addEventListener('resize', () => { - if (window.innerWidth >= 768 && body.classList.contains('menu-open')) { - closeMenu(); +} + + +// light-dark toggle + +{ + const STORAGE_KEY = 'theme-preference'; + const root = document.documentElement; + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + + function getSystemTheme() { + return mq.matches ? 'dark' : 'light'; + } + + function getSavedTheme() { + try { + return localStorage.getItem(STORAGE_KEY) || 'system'; + } catch { + return 'system'; } - }); \ No newline at end of file + } + + function setSavedTheme(value) { + try { + localStorage.setItem(STORAGE_KEY, value); + } catch {} + } + + function applyTheme(theme = getSavedTheme()) { + root.style.colorScheme = theme === 'system' ? 'light dark' : theme; + } + + function getNextTheme() { + return getSavedTheme() === 'system' + ? (getSystemTheme() === 'dark' ? 'light' : 'dark') + : 'system'; + } + + function updateButton() { + const btn = document.getElementById('theme-toggle'); + if (!btn) return; + + const theme = getSavedTheme(); + // btn.textContent = + // theme === 'system' + // ? `Use ${getSystemTheme() === 'dark' ? 'light' : 'dark'} theme` + // : 'Use browser/OS theme'; + } + + function toggleTheme() { + setSavedTheme(getNextTheme()); + applyTheme(); + updateButton(); + } + + function initThemeToggle() { + applyTheme(); + updateButton(); + + const btn = document.getElementById('theme-toggle'); + if (btn) btn.addEventListener('click', toggleTheme); + + mq.addEventListener('change', () => { + if (getSavedTheme() === 'system') { + applyTheme('system'); + updateButton(); + } + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initThemeToggle); + } else { + initThemeToggle(); + } +} \ No newline at end of file diff --git a/legal-notice.html b/legal-notice.html index 33f1f4e..87142fa 100644 --- a/legal-notice.html +++ b/legal-notice.html @@ -3,9 +3,10 @@ - - Lars Winter + + Legal notice – Lars Winter + @@ -18,19 +19,52 @@ - + - + + + + -
+ + + + + +
- Home - - +

EnglishDeutsch @@ -110,7 +144,7 @@ - + diff --git a/privacy.html b/privacy.html index 6f388a6..016cb64 100644 --- a/privacy.html +++ b/privacy.html @@ -3,9 +3,10 @@ - - Lars Winter + + Privacy – Lars Winter + @@ -20,17 +21,50 @@ - + + + + + +

+ + +
- Home - - +

EnglishDeutsch @@ -144,7 +178,7 @@ - +