diff --git a/assets/css/header.css b/assets/css/header.css index 7a1e804..1640a01 100644 --- a/assets/css/header.css +++ b/assets/css/header.css @@ -165,8 +165,8 @@ } .utci-search-toggle[aria-expanded="true"] { - color: #fff; - background: #c8922a; + color: #c8922a; + background: #fef3dc; border-color: #c8922a; } @@ -486,6 +486,38 @@ body.tl-scrubbing * { transition: none !important; } left: 0; width: 100%; z-index: 60; + /* "Eyes opening": unfurl from the top edge while fading in, so the drop + shadow grows with the box instead of flashing in fully-formed. */ + transform-origin: center center; + animation: utci-search-open 0.26s cubic-bezier(0.22, 1, 0.36, 1); +} + +@keyframes utci-search-open { + from { + opacity: 0; + transform: scaleY(0.05); + } + to { + opacity: 1; + transform: scaleY(1); + } +} + +/* Reverse the reveal when dismissing — close from the vertical middle and + fade out rather than vanishing instantly. */ +.utci-search-overlay.is-closing { + animation: utci-search-close 0.2s cubic-bezier(0.4, 0, 1, 1) forwards; +} + +@keyframes utci-search-close { + from { + opacity: 1; + transform: scaleY(1); + } + to { + opacity: 0; + transform: scaleY(0.05); + } } .utci-search-overlay .utci-search { diff --git a/assets/js/app.js b/assets/js/app.js index f466f71..76524a8 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -219,6 +219,11 @@ export function UTCIForecast() { // Search is hidden by default and revealed via the magnifier next to the // location. The input then overlays the location line to save space. const [searchOpen, setSearchOpen] = useState(false); + // While closing, keep the overlay mounted so it can fade/close out before + // unmounting; cleared when the exit animation ends. + const [searchClosing, setSearchClosing] = useState(false); + const openSearch = () => { setSearchClosing(false); setSearchOpen(true); }; + const closeSearch = () => { setSearchOpen(false); setSearchClosing(true); }; const searchWrapRef = useRef(null); const [colTogglesOpen, setColTogglesOpen] = useState(false); const searchInputRef = useRef(null); @@ -286,12 +291,11 @@ export function UTCIForecast() { if (!searchOpen) return; const onDown = (e) => { if (searchWrapRef.current && !searchWrapRef.current.contains(e.target)) { - setSearchOpen(false); - setSearchQuery(''); + closeSearch(); } }; const onKey = (e) => { - if (e.key === 'Escape') { setSearchOpen(false); setSearchQuery(''); } + if (e.key === 'Escape') { closeSearch(); } }; document.addEventListener('mousedown', onDown); document.addEventListener('keydown', onKey); @@ -625,7 +629,7 @@ export function UTCIForecast() { class="utci-loc-name" aria-label="Search for a town or city" aria-expanded=${searchOpen} - onClick=${() => setSearchOpen((v) => !v)} + onClick=${() => (searchOpen ? closeSearch() : openSearch())} >
${isStale ? html`` : ''}Last update: ${fetchedAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
`} - ${searchOpen && html` -
+ ${(searchOpen || searchClosing) && html` +
{ + if (e.target === e.currentTarget && !searchOpen) { + setSearchClosing(false); + setSearchQuery(''); + } + }} + >
${r.name}${r.admin1 ? `, ${r.admin1}` : ''}