diff --git a/src/layouts/AppLayout.astro b/src/layouts/AppLayout.astro index ac6a19d..742bd61 100644 --- a/src/layouts/AppLayout.astro +++ b/src/layouts/AppLayout.astro @@ -26,7 +26,24 @@ const year = new Date().getFullYear(); @@ -134,21 +175,33 @@ const year = new Date().getFullYear(); border-bottom: none; color: var(--on-surface); } + /* The "Fenja AI" lockup reads larger than the "Project · Bifrost" wordmark + beside it: the logo box is 29px tall, the serif text 18px. The dot sits + on the logo's vertical middle but is sized to the smaller text. */ .wordmark { - height: 30px; /* 50% larger than the prior 20px lockup */ + display: inline-flex; + align-items: center; + height: 29px; + color: var(--on-surface); + } + .wordmark-svg { + height: 29px; width: auto; display: block; } .wordmark-sep { - /* Flex-centred against the logo height so the dot sits on the vertical + /* Flex-centred against the lockup height so the dot sits on the vertical middle of the "Fenja AI" logo. */ display: inline-flex; align-items: center; - height: 30px; + height: 29px; color: var(--on-surface-muted); font-family: var(--font-serif); - font-size: 22px; + font-size: 18px; line-height: 1; + /* Nudged down 1px so the dot sits on the optical centre of the lockup. */ + position: relative; + top: 1px; } /* Project (regular) + Bifrost (italic) share a baseline. Italic Newsreader renders a touch taller at the same size, so Bifrost is set 1px smaller so @@ -162,12 +215,16 @@ const year = new Date().getFullYear(); line-height: 1; } .wordmark-project { - font-size: 20px; + font-size: 17px; /* smaller than the Fenja AI logo (29px) */ color: var(--on-surface); + /* Nudge the wordmark down so its optical centre lines up with the taller + logo lockup (the serif sits high in its line box). */ + position: relative; + top: 3px; } .wordmark-bifrost { display: inline-block; - font-size: 19px; + font-size: 17px; /* matched to Project */ font-style: italic; padding: 2px 0; vertical-align: baseline; @@ -217,8 +274,6 @@ const year = new Date().getFullYear(); transform: scaleX(0.5); transform-origin: center; } - .nav-logout-form { display: inline-flex; } - .nav-link { position: relative; font-family: var(--font-sans); @@ -244,36 +299,98 @@ const year = new Date().getFullYear(); font-weight: 500; } - /* ── User zone ──────────────────────────────────────────────────── */ - .nav-user-name { - color: var(--on-surface-variant); - text-decoration: none; - border-bottom: none; - transition: color var(--duration-fast) var(--ease-standard); + /* ── User menu (icon + dropdown) ─────────────────────────────────── */ + .user-menu { + position: relative; + display: inline-flex; } - .nav-user-name:hover { + .user-trigger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; + background: none; + border: none; + border-radius: 50%; + cursor: pointer; + color: var(--on-surface-variant); + /* Slightly faded at rest, fully opaque on hover/open. */ + opacity: 0.6; + transition: opacity var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard), + background var(--duration-fast) var(--ease-standard); + } + .user-trigger:hover, + .user-menu:hover .user-trigger, + .user-menu.open .user-trigger { + opacity: 1; color: var(--on-surface); - border-bottom: none; + background: var(--surface-container-low); + } + /* Invisible bridge across the gap so moving from the icon to the dropdown + doesn't drop the hover and close the menu. */ + .user-menu::after { + content: ''; + position: absolute; + top: 100%; + right: 0; + width: 100%; + height: 14px; } - .logout-btn { + .user-dropdown { + position: absolute; + top: calc(100% + 10px); + right: 0; + min-width: 184px; + display: none; + flex-direction: column; + padding: var(--space-2); + /* Opaque (not glass) so the menu items read clearly over any page + content behind the dropdown. */ + background: var(--surface-container-lowest); + border-radius: var(--radius-md); + box-shadow: var(--shadow-float); + z-index: 200; + } + /* Open on hover/focus (drops down), or on click via the .open class. */ + .user-menu:hover .user-dropdown, + .user-menu:focus-within .user-dropdown, + .user-menu.open .user-dropdown { display: flex; } + + .user-dropdown-name { + font-family: var(--font-sans); + font-size: var(--text-label-sm); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + color: var(--on-surface-muted); + padding: var(--space-2) var(--space-3) var(--space-1); + } + .user-dropdown-form { display: block; } + .user-dropdown-item { + display: block; + width: 100%; + text-align: left; background: none; border: none; cursor: pointer; font-family: var(--font-sans); - font-size: var(--text-label-md); - letter-spacing: var(--tracking-wide); - text-transform: uppercase; - color: var(--on-surface-muted); + font-size: var(--text-body-sm); + color: var(--on-surface-variant); + text-decoration: none; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard); } - .logout-btn:hover { + .user-dropdown-item:hover { color: var(--on-surface); background: var(--surface-container-low); + border-bottom: none; } + .user-dropdown-signout { color: var(--on-surface-muted); } /* ── Content ────────────────────────────────────────────────────── */ .main-content { @@ -364,9 +481,7 @@ const year = new Date().getFullYear(); .nav.open .nav-right { display: flex; } /* Comfortable tap targets in the dropdown. */ - .nav-right .nav-link, - .nav-right .nav-user-name, - .nav-right .logout-btn { + .nav-right .nav-link { display: flex; align-items: center; min-height: 44px; @@ -381,8 +496,27 @@ const year = new Date().getFullYear(); margin: var(--space-2) 0; transform: scaleY(0.5); } - .nav-logout-form { width: 100%; } - .logout-btn { justify-content: flex-start; } + + /* On mobile the icon trigger is dropped and the menu contents render + inline inside the open hamburger as plain full-width items. */ + .user-menu { display: block; width: 100%; } + .user-trigger { display: none; } + .user-dropdown { + position: static; + display: flex; + min-width: 0; + padding: 0; + background: none; + backdrop-filter: none; + -webkit-backdrop-filter: none; + box-shadow: none; + border-radius: 0; + } + .user-dropdown-item { + min-height: 44px; + align-items: center; + font-size: var(--text-body-md); + } /* Footer stacks. */ .footer-inner { @@ -410,4 +544,27 @@ const year = new Date().getFullYear(); toggle?.addEventListener('click', () => setOpen(!nav!.classList.contains('open'))); menu?.querySelectorAll('a').forEach((a) => a.addEventListener('click', () => setOpen(false))); window.addEventListener('resize', () => { if (window.innerWidth > 767) setOpen(false); }); + + // User menu dropdown (desktop): toggle on the icon, close on outside click, + // Escape, or selecting an item. Mobile renders the menu inline so the + // open/closed state is irrelevant there. + const userMenu = document.querySelector('#user-menu'); + const userTrigger = userMenu?.querySelector('#user-trigger'); + + function setUserOpen(open: boolean) { + if (!userMenu || !userTrigger) return; + userMenu.classList.toggle('open', open); + userTrigger.setAttribute('aria-expanded', open ? 'true' : 'false'); + } + + userTrigger?.addEventListener('click', (e) => { + e.stopPropagation(); + setUserOpen(!userMenu!.classList.contains('open')); + }); + document.addEventListener('click', (e) => { + if (userMenu && !userMenu.contains(e.target as Node)) setUserOpen(false); + }); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') setUserOpen(false); + });