diff --git a/assets/css/document.css b/assets/css/document.css index ebd5489..c7372c7 100755 --- a/assets/css/document.css +++ b/assets/css/document.css @@ -5,6 +5,7 @@ --padding: 20px; --running-size: 80px; + --header-search-size: var(--running-size); } /* # Cornerstones */ @@ -94,15 +95,23 @@ h3 { /* ## Buttons */ button { + border: none; + background-color: transparent; + color: inherit; + fill: inherit; + cursor: pointer; +} + +/* ### Inline */ + +button.inline { padding: calc(var(--padding) / 2) var(--padding); color: white; border: solid 2px white; border-radius: 6px; - background-color: transparent; - cursor: pointer; } -button.solid { +button.inline.solid { color: black; border-color: var(--color-accent); background-color: var(--color-accent); @@ -112,6 +121,8 @@ a > button::after { content: " ➜"; } +/* ### Text links */ + a[target="_blank"] > button::after, :is(h1, h2, h3, p, li) > a[target="_blank"]::after { content: " ↑"; @@ -135,13 +146,31 @@ header { border-bottom: var(--border-style); display: grid; align-items: stretch; - justify-items: end; - grid-template-columns: 1fr var(--running-size); + grid-template-columns: 1fr var(--header-search-size) var(--running-size); grid-template-rows: var(--running-size); background-color: rgba(0, 0, 0, .8); z-index: 100; + perspective: 3000px; -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px); + overflow: hidden; +} + +header > * { + --anim-3d-depth: 5px; + --anim-3d-peek: 25deg; + + transition: 300ms background-color; + transform: rotateX(0deg); + backface-visibility: hidden; + box-shadow: 0 var(--anim-3d-depth) 0 0 rgba(255, 255, 255, .2); +} + +/* enable 3d flip animation */ +@media not (prefers-reduced-motion: reduce) { + header > * { + transition: 600ms transform, 300ms background-color; + } } header nav { @@ -150,27 +179,90 @@ header nav { padding: var(--padding); } -header .logo { - width: calc(var(--running-size) - 1px); - height: calc(var(--running-size) - 1px); - display: grid; - align-items: center; - justify-items: center; - border-left: var(--border-style); -} - header .logo path.stroke { fill: var(--color-accent); } -header searchbox { +header header .search { display: none; } +/* ### Buttons */ + +header button { + --icon-size: 25px; + + display: grid; + width: 100%; + border-left: var(--border-style); + grid-template-columns: 1fr; + align-items: center; + justify-items: center; + padding: var(--padding); + gap: var(--padding); + fill: var(--color-accent); + font-size: 13px; + color: rgba(255, 255, 255, .5); + cursor: pointer; +} + +header button:not(.logo) svg { + width: var(--icon-size); +} + +header button.search p { + display: none; +} + +/* ### Searchbox */ + +header searchbox { + position: absolute; + right: 0; + width: 100%; + height: var(--running-size); + background-color: var(--color-accent); + display: grid; + align-items: stretch; + grid-template-columns: 1fr var(--running-size); + grid-template-rows: var(--running-size); + box-shadow: none; + transform: rotateX(180deg); +} + +header searchbox > * { + box-shadow: 0 calc(var(--anim-3d-depth) * -1) 0 0 rgba(var(--primer-color-accent), .8); +} + +header searchbox button { + transition: 300ms background-color, 300ms border-color; + border-color: rgba(0, 0, 0, .1); + fill: black; +} + +header searchbox input { + padding: 0 var(--padding); + background-color: transparent; + outline: none; + color: black; + border: none; +} + +/* #### Active */ + +header.searchboxActive > * { + transform: rotateX(-180deg); + pointer-events: none; +} + +header.searchboxActive searchbox { + transform: rotateX(0); + pointer-events: all; +} + /* ## Main */ main { - transition: 400ms transform; position: relative; padding: calc(var(--padding) * 1.5); width: 100%; @@ -186,105 +278,49 @@ main.loading > * { opacity: 0; } -/* ## Search */ +/* ## Search results */ -/* ### Box */ - -searchbox { - --icon-size: 25px; - - display: grid; +search-results { + transition: 500ms opacity, 300ms transform; + position: fixed; + top: var(--running-size); + right: 0; width: 100%; - border-left: var(--border-style); - grid-template-columns: var(--icon-size) 1fr; - align-items: center; padding: var(--padding); - gap: var(--padding); - fill: var(--color-accent); - font-size: 13px; - color: rgba(255, 255, 255, .5); - cursor: pointer; -} - -searchbox > svg { - width: var(--icon-size); -} - -/* ### Dialog */ - -body.search-dialog-open main { - transform: scale(.94); -} - -dialog.search { - transition: 200ms height cubic-bezier(.41,0,.34,.99); - margin: auto; - width: 100%; - max-width: 1000px; - height: calc(var(--running-size) + (var(--padding) * 5)); - max-height: 1000px; - border-color: transparent; - background-color: transparent; - overflow: visible; - outline: none; -} - -dialog.search.active { - height: 70vh; -} - -dialog.search search { - transition: 400ms transform, 200ms opacity; - width: 100%; - height: 100%; - display: grid; - grid-template-rows: var(--running-size) 1fr; - gap: calc(var(--padding) * 2); - transform: scale(1.1); - overflow: hidden; - background-color: rgba(255, 255, 255, .05); - -webkit-backdrop-filter: blur(20px); - backdrop-filter: brightness(.3) blur(20px); - border-radius: 12px; - box-shadow: 0 10px 30px 10px black; + height: calc(100svh - var(--running-size)); + background-color: black; + pointer-events: none; opacity: 0; + transform: scale(.99); + transform-origin: 100% 0; + overflow-y: scroll; } -body.search-dialog-open dialog.search search { - transform: scale(1); - padding: calc(var(--padding) * 1.5); +search-results:not([vv-page]) { + display: grid; + align-items: center; + justify-items: center; +} + +header.searchboxActive ~ search-results { opacity: 1; + pointer-events: all; + transform: scale(1); } -search input { - transition: 200ms background-color, 200ms box-shadow, 200ms color; - border-radius: 6px; - border: none; - outline: none; - color: black; - font-size: 16px; - padding: var(--padding) calc(var(--padding) * 1.5); - background-color: rgba(255, 255, 255, .05); - box-shadow: 0 5px 70px 10px rgba(0, 0, 0, .3); - color: white; -} +/* ### "Start typing" prompt */ -search input:focus { - background-color: rgba(255, 255, 255, .9); - box-shadow: 0 10px 30px 10px black; - color: black; -} - -/* ### Search results */ - -dialog.search search search-results { - overflow-y: auto; -} - -dialog.search search search-results > svg { +search-results .info { + display: flex; + align-items: center; + flex-direction: column; margin: auto; - width: 150px; - fill: rgba(255, 255, 255, .05); + gap: 3svh; +} + +search-results .info :is(svg, img) { + width: 128px; + fill: var(--color-accent); } /* # Feature queries */ @@ -298,7 +334,7 @@ dialog.search search search-results > svg { /* # Components */ - button { + button.inline { transition: 200ms background-color, 200ms border-color, 200ms color; } @@ -320,18 +356,31 @@ dialog.search search search-results > svg { fill: var(--color-accent); } - searchbox { - transition: 200ms background-color; + header searchbox button:hover { + background-color: rgba(0, 0, 0, .08); } - searchbox:hover { - background-color: rgba(255, 255, 255, .07); + /* ### Search */ + + @media not (prefers-reduced-motion: reduce) { + header:not(.searchboxActive) button.search:hover, + header:not(.searchboxActive) button.search:hover + button.logo { + transform: rotateX(calc(var(--anim-3d-peek) * -1)); + } + + header:not(.searchboxActive) button.search:hover ~ searchbox { + transform: rotateX(calc(180deg - var(--anim-3d-peek))); + } } } /* # Size queries */ @media (min-width: 700px) { + :root { + --header-search-size: 250px; + } + /* # Cornerstones */ body::before { @@ -346,19 +395,49 @@ dialog.search search search-results > svg { /* ## Header */ - header { - grid-template-columns: 1fr 250px var(--running-size); + header nav { + margin: 0 calc(var(--padding) / 2); } - header nav { - justify-self: start; - margin: 0 calc(var(--padding) / 2); + header > button.search { + grid-template-columns: var(--icon-size) 1fr; + } + + header > button.search p { + display: initial; + } + + header.searchboxActive > nav { + transform: rotateX(0deg); + pointer-events: all; + } + + /* ### Searchbox */ + + header searchbox { + width: calc(var(--header-search-size) + var(--running-size)); } /* ### Menu */ /* Move the search box to the header */ - header searchbox { + header > button.search { display: grid; + justify-items: baseline; + } + + @media (min-height: 600px) { + search-results { + top: calc(var(--running-size) + var(--padding)); + width: 50%; + height: calc(100svh - 100px); + background-color: rgba(0, 0, 0, .8); + box-shadow: + inset 0 0 100px 200px rgba(0, 0, 0, 1), + 0 0 100px 200px rgba(0, 0, 0, 1) + ; + --webkit-backdrop-filter: blur(15px); + backdrop-filter: blur(15px); + } } } \ No newline at end of file diff --git a/assets/css/pages/index.css b/assets/css/pages/index.css index 9c2c4e3..7529a07 100755 --- a/assets/css/pages/index.css +++ b/assets/css/pages/index.css @@ -154,6 +154,10 @@ splash::after { opacity: 1; text-shadow: 0 0 10px rgba(var(--primer-color-accent), .4); } + + button.email:hover { + background-color: transparent; + } } /* # Size quries */ @@ -169,8 +173,4 @@ splash::after { main img { width: 35vh; } - - button:hover { - background-color: transparent; - } } diff --git a/assets/css/pages/search.css b/assets/css/pages/search.css index e81316d..86fedee 100755 --- a/assets/css/pages/search.css +++ b/assets/css/pages/search.css @@ -12,7 +12,7 @@ section.search { width: 100%; - display: flex; + display: none; flex-direction: column; align-items: center; gap: var(--padding); @@ -21,6 +21,10 @@ section.search { margin-bottom: calc(var(--padding) * 2); } +main[vv-page="/search"] > section.search { + display: flex; +} + section.search form { display: contents; } @@ -31,6 +35,11 @@ section.search search { section.search input { width: 100%; + border: none; + color: black; + outline: none; + padding: var(--padding); + background-color: var(--color-accent); } section.search button[type="submit"] { @@ -42,10 +51,6 @@ section.search > svg { width: 100%; } -body:not([vv-page="/search"]) section.search { - display: none; -} - /* # Search results */ section.results .result { @@ -54,19 +59,6 @@ section.results .result { gap: calc(var(--padding) / 2); } -/* ---- */ - -main > svg, -dialog.search search search-results > svg { - margin: auto; - width: 150px; - fill: rgba(255, 255, 255, .05); -} - -dialog.search search search-results .empty { - text-align: center; -} - /* ## Titles */ section.title a h2 { diff --git a/assets/js/document.js b/assets/js/document.js index 8405b26..0c9c39a 100755 --- a/assets/js/document.js +++ b/assets/js/document.js @@ -1,72 +1,40 @@ -new vv.Interactions("document"); - -const mainElement = document.querySelector(vv._env.MAIN); +new vv.Interactions("document", { + navigateHome: () => new vv.Navigation("/").navigate(), + closeSearchbox: () => document.querySelector("header").classList.remove("searchboxActive"), + openSearchbox: () => { + document.querySelector("header").classList.add("searchboxActive"); + // Select searchbox inner input element + document.querySelector("searchbox input").focus(); + } +}); // Crossfade pages on navigation -// Or maybe I shouldn't... hmmm -mainElement.addEventListener(vv.Navigation.events.LOADING, () => { - mainElement.classList.add("loading"); - - // Clean up modified transform-origin if set after search dialog animation - mainElement.style.removeProperty("transform-origin"); -}); - -mainElement.addEventListener(vv.Navigation.events.LOADED, () => { - [...document.querySelectorAll("dialog")].forEach(element => element.close()) - - // Wait 200ms for the page fade-in animation to finish - setTimeout(() => mainElement.classList.remove("loading"), 200); -}); - -// Search dialog open/close logic { - const CLASNAME_DIALOG_OPEN = "search-dialog-open"; - // Offset in pixels from scroll position when scaling the main element - const TRANSFORM_ORIGIN_Y_PADDING = 350; + const mainElement = document.querySelector(vv._env.MAIN); - const dialog = document.querySelector("dialog.search"); - - // "Polyfill" for HTMLDialogELement open and close events - (new MutationObserver((mutations) => { - // There is only one search dialog elemenet - const target = mutations[0].target; - - // Set or unset dialog open class on body depending on dialog visibility - target.hasAttribute("open") - ? target.dispatchEvent(new Event("open")) - : target.dispatchEvent(new Event("close")); - - }).observe(dialog, { attributes: true })); - - dialog.addEventListener("open", () => { - // Scale main element from the current scroll position - mainElement.style.setProperty("transform-origin", `50% calc(${window.scrollY}px + ${TRANSFORM_ORIGIN_Y_PADDING}px)`); - - document.body.classList.add(CLASNAME_DIALOG_OPEN); + mainElement.addEventListener(vv.Navigation.events.LOADING, () => { + mainElement.classList.add("loading"); }); - dialog.addEventListener("close", () => document.body.classList.remove(CLASNAME_DIALOG_OPEN)); - - // Close search dialog if dialog is clicked outside inner content - dialog.addEventListener("click", (event) => event.target === dialog ? dialog.close() : null); - // Open search dialog when searchbox is clicked - document.querySelector("searchbox").addEventListener("click", () => dialog.showModal()); + mainElement.addEventListener(vv.Navigation.events.LOADED, () => { + // Close searchbox on main page navigation + document.querySelector("header").classList.remove("searchboxActive"); + + // Wait 200ms for the page fade-in animation to finish + setTimeout(() => mainElement.classList.remove("loading"), 200); + }); } -// Search logic +// Handle search logic { const searchResultsElement = document.querySelector("search-results"); - const search = (query) => { - new vv.Navigation(`/search?q=${query}`, { - carrySearchParams: true - }).navigate(searchResultsElement); - }; - // Run search on keyup - document.querySelector("search input").addEventListener("keyup", (event) => search(event.target.value)); - - // Trigger expand search box animation - document.querySelector("search input").addEventListener("keydown", () => { - searchResultsElement.closest("dialog").classList.add("active"); - }, { once: true }); + document.querySelector("header input[type='search']").addEventListener("input", (event) => { + // Debounce user input + clearTimeout(event.target._throttle); + event.target._throttle = setTimeout(() => { + // Navigate search-results element on user input + new vv.Navigation(`/search?q=${event.target.value}`).navigate(searchResultsElement); + }, 100); + }); } \ No newline at end of file diff --git a/assets/js/pages/search.js b/assets/js/pages/search.js index 6964dbc..14f4494 100755 --- a/assets/js/pages/search.js +++ b/assets/js/pages/search.js @@ -1,25 +1 @@ -// Don't open the search dialog overlay if search page is open stand-alone -{ - const searchBox = document.querySelector("body:not(.search-dialog-open) searchbox"); - - // Page is stand-alone - if (searchBox) { - // Shift focus to the on-page search box instead of opening search dialog on click - const shiftSearchboxFocus = () => { - // Override normal "open search dialog" behavior - document.querySelector("dialog.search").close(); - - // Shift focus to the on-page search input instead - } - - // Bind event listener to searchbox element - document.querySelector("body:not(.search-dialog-open) searchbox").addEventListener("click", shiftSearchboxFocus, true); - - // Remove event listener from searchbox element on page navigation - mainElement.addEventListener(vv.Navigation.events.LOADING, () => { - searchBox.removeEventListener("click", shiftSearchboxFocus); - }); - } -} - new vv.Interactions("search"); \ No newline at end of file diff --git a/assets/media/travolta.gif b/assets/media/travolta.gif new file mode 100644 index 0000000..3c74c0d Binary files /dev/null and b/assets/media/travolta.gif differ diff --git a/pages/contact.php b/pages/contact.php index 6f206c3..dfb7a22 100755 --- a/pages/contact.php +++ b/pages/contact.php @@ -49,8 +49,8 @@
my key is also listed on the openPGP key server for victor@vlw.se so your e-mail client can automatically retreive it if supported.
= VV::media("line.svg") ?> @@ -77,7 +77,7 @@ - + diff --git a/pages/document.php b/pages/document.php index 6cedad1..f879f59 100755 --- a/pages/document.php +++ b/pages/document.php @@ -2,7 +2,7 @@ - + diff --git a/pages/search.php b/pages/search.php index 64f6776..068d8a5 100755 --- a/pages/search.php +++ b/pages/search.php @@ -18,12 +18,12 @@Something went wrong
No results for search term "= $_GET["q"] ?>"
Connection to VLW API was successful but lacking permission to search
Unknown request validation error
Type at least = $error_msg ?> characters to search!
+type at least = $error_msg ?> characters to search!
Most of my free open-source software is available on GitHub and it's also mirrored on my server