diff --git a/public/assets/css/modal.css b/public/assets/css/modal.css deleted file mode 100644 index 1abd25b..0000000 --- a/public/assets/css/modal.css +++ /dev/null @@ -1,179 +0,0 @@ -/* Victor Westerlund */ - -/* -- Transition overrides -- */ - -body main .screen { - transition: var(--transition) transform, var(--transition) filter; - transition-delay: calc(var(--transition) / 2); -} - -.modal.active + .modal:nth-child(n+2), -body .modal.active ~ main .screen { - transition: var(--transition); - transition-delay: 1ms; - transform: scale(.95); - pointer-events: none; - filter: blur(2px); -} - -.modal.active + .modal { - z-index: 10; -} - -.modal:first-child { - z-index: 15; -} - -.modal.active + .modal:nth-child(n+2) { - filter: blur(2px) brightness(.5); - z-index: 5; -} - -/* -- Boilerplate -- */ - -.modal { - transition: var(--transition) transform, var(--transition) filter; - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100%; - z-index: 10; - pointer-events: none; - box-sizing: border-box; - padding: var(--padding); -} - -.modal.active { - pointer-events: all; -} - -.modal .button { - align-self: stretch; -} - -.modal .inner { - transition: var(--transition) transform, var(--transition) opacity; - position: relative; - background-color: var(--swatch-background); - width: calc(100vw - var(--padding)); - max-width: 500px; - max-height: 100%; - overflow-y: auto; - word-break: break-word; - box-sizing: border-box; - padding: var(--padding); - border-radius: var(--border-radius); - box-shadow: 0 3px 30px 0 rgba(33,33,33,.2); - opacity: 0; - display: flex; - flex-direction: column; - align-items: center; - gap: var(--padding); -} - -.modal.active .inner { - opacity: 1; -} - -.modal .inner > h1, -.modal .inner > h2, -.modal .inner > p { - text-align: center; -} - -/* ---- */ - -.spinner.logo { - --size: clamp(30px,5vw,60px); - --anim-speed: 1s; - align-self: center; - margin-top: var(--padding); - margin-left: calc((var(--size) / 2) * -1); - animation: logoSpinner var(--anim-speed) infinite alternate linear; -} - -.error { - text-align: center; - font-size: 20px; -} - -.error:first-line { - font-size: 50px; -} - -@keyframes logoSpinner { - to { - opacity: .1; - } -} - -/* ---- */ - -.modal h1 { - font-size: clamp(20px,2vw,20px); -} - -.modal pre { - align-self: stretch; - overflow: scroll; - background-color: black; - color: white; - padding: 10px 15px; - border-radius: 6px; -} - -/* -- Cards -- */ - -.modal.card .inner { - align-self: flex-end; - transform: scale(.99) translateY(1vh); -} - -.modal.card.active .inner { - transform: scale(1) translateY(0); -} - -.modal.card .button[data-action="close"] { - margin-top: auto; -} - -/* -- Dialogs -- */ - -.modal.dialog .inner { - transform: scale(.95); -} - -.modal.dialog.active .inner { - transform: scale(1); -} - -@media (min-aspect-ratio: 14/9) { - /* -- Transition overrides -- */ - - body .modal { - transition-delay: calc(var(--transition) / 2); - } - - body .modal.active { - transition-delay: 1ms; - } - - /* -- Boilerplate -- */ - - .modal.card .inner { - align-self: unset; - transform: scale(.99) translateY(10px); - } - - .modal.dialog .inner { - width: unset; - min-width: 100px; - max-width: 50vw; - } - - .modal.dialog .button { - align-self: unset; - width: clamp(100px,100%,500px); - } -} \ No newline at end of file diff --git a/public/assets/css/style.css b/public/assets/css/style.css index ac30e44..4aca975 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -1,39 +1,10 @@ -/* Victor Westerlund */ - +/* Victor Westerlund - www.victorwesterlund.com */ :root { - /* Component colors */ --palette-background: 255,255,255; - --palette-inverted: 0,0,0; --palette-contrast: 33,33,33; - --palette-accent: 22,183,255; - - /* Compiled colors */ - --swatch-background: rgb(var(--palette-background)); - --swatch-inverted: rgb(var(--palette-inverted)); - --swatch-contrast: rgb(var(--palette-contrast)); - --swatch-accent: rgb(var(--palette-accent)); - - /* Default styles */ - --padding: 20px; - --border-radius: 10px; - --header-height: 100px; - --transition: 300ms; -} - -.dark { - --palette-background: 33,33,33; - --palette-inverted: 255,255,255; - --palette-contrast: 255,255,255; - --palette-accent: 255,255,255; --swatch-background: rgb(var(--palette-background)); - --swatch-inverted: rgb(var(--palette-inverted)); --swatch-contrast: rgb(var(--palette-contrast)); - --swatch-accent: rgb(var(--palette-accent)); -} - -.wide { - display: none; /* Hide wide-screen elements */ } @font-face { @@ -64,433 +35,65 @@ *::selection { background-color: var(--swatch-contrast); - color: var(--swatch-accent); -} - -a, -picture { - text-decoration: none; - display: contents; + color: var(--swatch-background); } html, body { + display: flex; + justify-content: center; + align-items: center; width: 100%; height: 100%; overflow: hidden; background-color: var(--swatch-background); } -main { - width: 200vw; - height: 100%; - overflow: hidden; - display: flex; -} - -body.menuActive { - background-color: var(--swatch-contrast); -} - -body.dark.menuActive { - background-color: black; -} - -body.menuActive main { - transform: translateX(-100vw); -} - -/* ---- */ - -.screen { - --background-pattern: - linear-gradient(90deg, var(--swatch-background) calc(var(--padding) + 1px), transparent 1%) center, - linear-gradient(var(--swatch-background) calc(var(--padding) + 1px), transparent 1%) center, var(--swatch-contrast); - --background-pattern-size: calc(var(--padding) + 2px) calc(var(--padding) + 2px); - - width: 100vw; - background-color: var(--swatch-background); - display: flex; - flex-direction: column; -} - -body.dark .screen.dark { - background-color: black; -} - -.screen .inner { - display: contents; -} - -.screen .content { - position: relative; - box-sizing: border-box; - padding: calc(var(--padding) * 1.5); - padding-top: 0; - flex-grow: 1; -} - -/* -- Positioning -- */ - -.center { - display: flex; - justify-content: center; - align-items: center; -} - -/* ---- */ - -.logo { - --size: 1em; - --skew: calc(var(--size) / 1.7); - - width: 0; - height: 0; - border: var(--skew) solid transparent; - border-top: var(--size) solid var(--swatch-accent); -} - -.logo::after { - content: ""; - border: var(--skew) solid transparent; - border-top: var(--size) solid rgba(var(--palette-accent),.3); -} - -/* ---- */ - -.button { - text-align: center; - padding: 25px; - border-radius: var(--border-radius); - font-size: clamp(16px,5vw,22px); - display: flex; - justify-content: center; - align-items: center; - gap: var(--padding); -} - -a.button, -p.button { - padding: unset; - display: inline; - font-size: inherit; - color: var(--swatch-accent); - text-decoration: underline; -} - -.button.solid { - background-color: var(--swatch-contrast); - color: var(--swatch-background); - fill: var(--swatch-background); -} - -.button.phantom { - background-color: rgba(var(--palette-inverted),.05); - color: var(--swatch-contrast); - fill: var(--swatch-contrast); -} - -.button svg { - pointer-events: none; - fill: inherit; - transform: scale(1.2); -} - -.button p { - pointer-events: none; - font-size: inherit; - color: inherit; -} - -.button.loading p { - opacity: 0; -} - -.button.loading::after { - position: absolute; - content: "loading..."; - opacity: 1; -} - -/* -- Screens -- */ - -header { - --size: var(--header-height,100px); - box-sizing: border-box; - padding: var(--padding); - height: var(--size); - display: flex; - align-items: center; +a { + display: content; + text-decoration: none; font-weight: bold; } -header > *:nth-child(2) { - margin-left: 10px; +a::after { + content: " →"; } -header > *:nth-child(n+3) { - margin-left: var(--padding); -} - -header .hamburger { - width: calc(var(--size) - (var(--padding) * 2)); - height: calc(var(--size) - (var(--padding) * 2)); - box-sizing: border-box; - flex-shrink: 0; - padding: 15px; -} - -header .hamburger div { - width: 100%; - height: 2px; - background: var(--swatch-contrast); - box-shadow: 0 -10px 0 0 var(--swatch-contrast), 0 10px 0 0 var(--swatch-contrast); -} - -header .hamburger svg { - fill: none; - stroke: var(--swatch-contrast); - stroke-linecap: round; - stroke-width: 2; -} - -header .spacer { - width: 1px; - height: 80%; - background-color: rgba(var(--palette-contrast),.2); -} - -.dark header .spacer { - background-color: black; -} - -body.dark .dark header .spacer { - background-color: rgba(var(--palette-contrast),.2); -} - -header .logo { - --size: 25px; - margin-top: calc(var(--size) / 2); - margin-right: calc(var(--size) / 2); -} - -/* -- Screen > Landingpage -- */ - -.screen.landingpage { - background: var(--background-pattern); - background-size: var(--background-pattern-size); -} - -.screen.landingpage .content { - padding-bottom: 0; -} - -.screen.landingpage img { - position: relative; - width: clamp(100px,80vw,40vh); - align-self: flex-end; - z-index: 1; -} - -.screen.landingpage .pattern { - position: absolute; - top: var(--header-height); - width: 100vw; - height: calc(100% - var(--header-height)); - overflow: hidden; -} - -.screen.landingpage .pattern div { - --size: clamp(100px,100vw,35vh); - position: relative; - top: calc((var(--size) - var(--header-height)) * -1); - width: 0; - height: 0; - border: solid var(--size) transparent; - border-bottom: solid calc(var(--size) * 2) rgba(var(--palette-accent),.1); - transform-origin: 50% 75%; - transform: rotate(20deg); -} - -/* -- Screen > Menu -- */ - -.screen.menu .content { +main { display: flex; flex-direction: column; - align-items: center; - gap: 20px; + gap: 30px; + font-size: 20px; + transform: translateY(0); } -.screen.menu .button { - width: calc(100% - (var(--padding) * 2)); - max-width: 400px; - flex: 0; -} +/* -- Media Queries -- */ -.screen.menu .button[data-value="contact"] { - margin-top: auto; -} - -/* WIP */ -.screen.menu .content > .narrow { - align-items: flex-start; - max-width: 900px; - gap: var(--padding); -} - -/* -- Media queries -- */ - -@media (max-width: 570px) { - .screen.menu .content > .narrow { - flex-direction: column; - } -} - -/* Wide-screen */ -@media (min-aspect-ratio: 14/9) and (min-height: 300px) { - /* -- Cornerstones -- */ - - .narrow, - header { - display: none; - } - - main { - width: 100vw; - flex-direction: row-reverse; - } - - /* -- Cornerstones > State overrides -- */ - - body.menuActive { - background-color: inherit; - } - - body.menuActive main { - transform: unset; - } - - /* -- Screens -- */ - - .screen.menu, - .screen.landingpage { - width: 50vw; - flex-direction: row; - background: var(--background-pattern); - background-size: var(--background-pattern-size); - } - - body.dark .screen { - --swatch-background: black; - } - - /* -- Screens > Menu -- */ - - body:not(.dark) .screen.menu { - /* Component colors */ - --palette-background: 255,255,255; - --palette-inverted: 0,0,0; - --palette-contrast: 33,33,33; - --palette-accent: 33,33,33; - - /* Compiled colors */ - --swatch-background: rgb(var(--palette-background)); - --swatch-inverted: rgb(var(--palette-inverted)); - --swatch-contrast: rgb(var(--palette-contrast)); - --swatch-accent: rgb(var(--palette-accent)); - } - - .screen.menu .content { - padding-top: calc(var(--padding) * 1.5); - } - - .screen.menu .wide { - display: flex; - flex-direction: column; - justify-content: space-between; - box-sizing: border-box; - padding: clamp(var(--padding),5vw,5vh); - width: 100%; - height: 100%; - overflow-y: auto; - } - - .screen.menu .wide .group { - display: flex; - flex-direction: column; - gap: var(--padding); - } - - .screen.menu .wide .logo { - --size: clamp(20px,3.5vw,5vh); - } - - .screen.menu .wide h1 { - margin: 0; - font-size: clamp(20px,3vw,5vh); - } - - .screen.menu .wide h1 span { - background: var(--swatch-contrast); - color: var(--swatch-background); - padding: 0 var(--padding); - } - - .screen.menu .wide p { - margin: 0; - font-size: clamp(16px,5vw,2vh); - } - - .screen.menu .wide nav { - display: flex; - margin-top: calc(var(--padding) * 2); - gap: clamp(var(--padding),3vw,500px); - } - - .screen.menu .wide nav p { - display: inline-block; - padding: var(--padding); - } - - .screen.menu .button[data-value="contact"] { - display: none; - } -} - -/* Narrow display */ @media (max-width: 300px) { - .button svg:not(.hidden) ~ p, - header .logo { - display: none; + main { + text-align: center; + align-items: center; } } -/* Super-narrow display */ -@media (max-width: 230px) { - header { - justify-content: center; - } - - header .spacer, - header p { - display: none; +@media print { + a::after { + content: ": " attr(href); } } -/* -- Media queries > Media features -- */ - @media (pointer: fine) { - .button { - cursor: pointer; + a:hover { + background: rgba(var(--palette-contrast),.1); } } -@media (any-hover: hover) { - .button { - transition: var(--transition) background-color; - } +@media (prefers-color-scheme: dark) { + :root { + --palette-background: 0,0,0; + --palette-contrast: 255,255,255; - .button.phantom:hover { - background-color: rgba(var(--palette-inverted),.2); + --swatch-background: rgb(var(--palette-background)); + --swatch-contrast: rgb(var(--palette-contrast)); } } \ No newline at end of file diff --git a/public/assets/img/icons/email.svg b/public/assets/img/icons/email.svg deleted file mode 100644 index 3e44b87..0000000 --- a/public/assets/img/icons/email.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/img/icons/signal.svg b/public/assets/img/icons/signal.svg deleted file mode 100644 index fa9c720..0000000 --- a/public/assets/img/icons/signal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/img/myface/highres.avif b/public/assets/img/myface/highres.avif deleted file mode 100644 index 85c50e9..0000000 Binary files a/public/assets/img/myface/highres.avif and /dev/null differ diff --git a/public/assets/img/myface/highres.webp b/public/assets/img/myface/highres.webp deleted file mode 100644 index 11e709b..0000000 Binary files a/public/assets/img/myface/highres.webp and /dev/null differ diff --git a/public/assets/img/myface/mediumres.avif b/public/assets/img/myface/mediumres.avif deleted file mode 100644 index 190a9b0..0000000 Binary files a/public/assets/img/myface/mediumres.avif and /dev/null differ diff --git a/public/assets/img/myface/mediumres.png b/public/assets/img/myface/mediumres.png deleted file mode 100644 index c8ca5cc..0000000 Binary files a/public/assets/img/myface/mediumres.png and /dev/null differ diff --git a/public/assets/img/myface/mediumres.webp b/public/assets/img/myface/mediumres.webp deleted file mode 100644 index 8b7cc86..0000000 Binary files a/public/assets/img/myface/mediumres.webp and /dev/null differ diff --git a/public/assets/img/pattern.gif b/public/assets/img/pattern.gif deleted file mode 100644 index b85a387..0000000 Binary files a/public/assets/img/pattern.gif and /dev/null differ diff --git a/public/assets/img/pattern.php b/public/assets/img/pattern.php deleted file mode 100644 index 93cfa1f..0000000 --- a/public/assets/img/pattern.php +++ /dev/null @@ -1,8 +0,0 @@ - `window._debug.${f}();`)); - } - - toggleMenu() { - document.getElementsByClassName("hamburger")[0].click(); - } - - openContactsModal() { - document.getElementsByClassName("hamburger")[0].click(); - document.querySelector("div[data-action='openContactCard']").click(); - } - - demoCard() { - const module = import("./Modals.mjs"); - const interactions = { - hello: () => { - console.log("Hello world"); - } - }; - - module.then(modals => { - const card = new modals.Card(interactions); - card.inner.style.height = "80vh"; - card.inner.insertAdjacentHTML("afterbegin","

Hello world

"); - card.open(); - }); - } - - invalidCard() { - const module = import("./Modals.mjs"); - const interactions = { - hello: () => { - console.log("Hello world"); - } - }; - - module.then(modals => { - const card = new modals.Card(interactions); - card.openPage("invalid_card"); - }); - } - - infiniteLoadingCard() { - const module = import("./Modals.mjs"); - const spinner = document.createElement("div"); - spinner.classList = "logo spinner"; - - module.then(modals => { - const card = new modals.Card(new Object()); - card.insertElement(spinner); - card.open(); - }); - } -} - -export default window._debug = new Debug(); \ No newline at end of file diff --git a/public/assets/js/modules/Logging.mjs b/public/assets/js/modules/Logging.mjs deleted file mode 100644 index e59e49e..0000000 --- a/public/assets/js/modules/Logging.mjs +++ /dev/null @@ -1,34 +0,0 @@ -// Victor Westerlund - www.victorwesterlund.com - -class Logging { - constructor() { - this.endpoint = "/log/"; - this.data = new URLSearchParams(); - - document.addEventListener("visibilitychange",() => { - if(document.visibilityState === "hidden") { - this.send(); - } - }); - - this.log("foo","bar"); - } - - log(key,value) { - this.data.append(key,value); - } - - send() { - const send = navigator.sendBeacon(this.endpoint,this.data); - if(send !== true) { - const url = this.endpoint + this.data.toString(); - fetch(url).catch(response => console.log(response)); - } - } -} - -export default class Log { - constructor(value,key = "u") { - // WIP - } -} \ No newline at end of file diff --git a/public/assets/js/modules/Modals.mjs b/public/assets/js/modules/Modals.mjs deleted file mode 100644 index fdb0c83..0000000 --- a/public/assets/js/modules/Modals.mjs +++ /dev/null @@ -1,191 +0,0 @@ -// Victor Westerlund - www.victorwesterlund.com - -import { default as Interaction, destroy } from "./UI.mjs"; -import { Button } from "./Components.mjs"; - -// Boilerplate for creating element overlays -class Modal extends Interaction { - constructor(extendedInteractions = {}) { - const element = document.createElement("div"); - let interactions = { - close: () => { - this.close(); - }, - openPage: (event) => { - let modal = undefined; - switch(event.target.dataset.type) { - case "card": - modal = new Card({}); - break; - case "dialog": - default: - modal = new Dialog({}); - break; - } - modal.openPage(event.target.dataset.value); - } - }; - // Combine template and incoming interactions into one object - interactions = Object.assign(interactions,extendedInteractions); - super(interactions,element); - - this.transition = 300; - - this.element = this.applyTemplate(element); - this.element.close = () => this.close(); // Bind modal close to element prototype - document.body.insertAdjacentElement("afterbegin",this.element); - } - - // Fetch page html from "assets/pages" - async getPage(page) { - const url = `assets/pages/${page}`; - const response = await fetch(url); - if(!response.ok) { - const report = { - "self": "Modal.getPage()", - "self_page": page, - "resp_status": response.status, - "resp_statusText": response.statusText, - "resp_url": response.url, - "rqst_ua": navigator.userAgent - }; - throw new Error(JSON.stringify(report,null,2)); - } - return response.text(); - } - - insertHTML(element) { - this.inner.insertAdjacentHTML("afterbegin",element); - } - - insertElement(element) { - this.inner.insertAdjacentElement("afterbegin",element); - } - - // Apply a modal template to the provided element - applyTemplate(element) { - // The inner div will contain modal content - this.inner = document.createElement("div"); - this.inner.classList.add("inner"); - element.appendChild(this.inner); - element.classList.add("modal"); - - // PointerEvents on the outer div will close the modal - element.addEventListener("click",event => { - if(event.target == element) { - this.close(); - } - }); - - return element; - } - - error(message) { - const oops = document.createElement("p"); - const infoButton = document.createElement("p"); - - oops.classList.add("error"); - oops.innerText = "🤯\nSomething went wrong"; - - infoButton.innerText = "more info.."; - infoButton.addEventListener("click",() => { - const details = new Dialog(); - - details.insertHTML(`

📄 Error report

${message}
`); - details.open(); - - this.close(); - }); - - this.insertElement(infoButton); - this.insertElement(oops); - } - - // Open page from "assets/pages" - openPage(page) { - // Show a spinner while fetching - const spinner = document.createElement("div"); - spinner.classList = "logo spinner"; - this.element.setAttribute("data-page",page); - this.insertElement(spinner); - this.open(); - - // Fetch the requested page - this.getPage(page).then(html => { - this.insertHTML(html); - this.bindAll(this.inner); - }) - .catch(error => { - const tryAgain = new Button({ - text: "try again", - type: "solid" - }); - tryAgain.element.addEventListener("click",() => { - // Clear and recreate modal structure - destroy(this.inner); - this.applyTemplate(this.element); - this.init(); - this.insertElement(spinner); - // Attempt to fetch the requested url again (with soft rate-limiting) - setTimeout(() => { - this.openPage(page); - destroy(spinner); - },500); - }); - this.insertElement(tryAgain.element); - this.error(error); - }) - .finally(() => destroy(spinner)); - } - - open() { - setTimeout(() => this.element.classList.add("active"),this.transition / 2); - } - - // Close the modal and remove it from the DOM - close() { - this.element.classList.remove("active"); - setTimeout(() => destroy(this.element),this.transition + 1); // Wait for transition - } -} - -export class Dialog extends Modal { - constructor(interactions = {}) { - super(interactions); - this.init(); - } - - init() { - this.element.classList.add("dialog"); - this.element.classList.add("center"); - const closeButton = new Button({ - text: "close", - action: "close", - type: "phantom" - }); - - this.bind(closeButton.element); - this.inner.appendChild(closeButton.element); - } -} - -// Overlay with a slide-in animation from the bottom of the viewport -export class Card extends Modal { - constructor(interactions = {}) { - super(interactions); - this.init(); - } - - init() { - this.element.classList.add("card"); - this.element.classList.add("center"); - const closeButton = new Button({ - text: "close", - action: "close", - type: "phantom" - }); - - this.bind(closeButton.element); - this.inner.appendChild(closeButton.element); - } -} \ No newline at end of file diff --git a/public/assets/js/modules/Preload.mjs b/public/assets/js/modules/Preload.mjs deleted file mode 100644 index 48e32bd..0000000 --- a/public/assets/js/modules/Preload.mjs +++ /dev/null @@ -1,42 +0,0 @@ -// Victor Westerlund - www.victorwesterlund.com - -// Load assets for later use on this page. -// This implements a hybrid of the link types "preload" and "prefetch" -export default class Preload { - constructor(assets) { - this.scripts = []; - this.stylesheets = []; - - // Get the type of asset from the file extension - assets.forEach(asset => { - const components = asset.split("."); - const extension = components[components.length - 1]; - switch(extension) { - case "mjs": - this.scripts.push(asset); - break; - case "css": - this.stylesheets.push(asset); - break; - } - }); - - // Append tags when DOM is ready - window.addEventListener("DOMContentLoaded",() => this.import()); - } - - import() { - this.scripts.forEach(script => { - const element = document.createElement("script"); - element.setAttribute("type","module"); - element.src = "assets/js/" + script; - document.body.appendChild(element); - }); - this.stylesheets.forEach(sheet => { - const element = document.createElement("link"); - element.setAttribute("rel","stylesheet"); - element.href = "assets/css/" + sheet; - document.head.appendChild(element); - }); - } -} \ No newline at end of file diff --git a/public/assets/js/modules/UI.mjs b/public/assets/js/modules/UI.mjs deleted file mode 100644 index 09fffe8..0000000 --- a/public/assets/js/modules/UI.mjs +++ /dev/null @@ -1,57 +0,0 @@ -// Victor Westerlund - www.victorwesterlund.com - -import { default as Logging } from "./Logging.mjs"; - -// Remove an element and its subtree -export function destroy(family) { - while(family.firstChild) { - family.removeChild(family.lastChild); - } - family.parentNode.removeChild(family); -} - -// General-purpose scoped event handler -export default class Interaction extends Logging { - constructor(interactions,scope) { - super(); - this.interactions = interactions; - this.attribute = "data-action"; // Target elements with this attribute - - this.bindAll(scope); - } - - // Bind event listeners to this element - bind(element) { - if(element.hasAttribute("data-bound") || !element.hasAttribute(this.attribute)) { - return false; - } - element.addEventListener("click",event => this.pointerEvent(event)); - element.setAttribute("data-bound",""); - } - - // Get all elements with the target attribute in scope - getAll(scope) { - return scope.querySelectorAll(`[${this.attribute}]`); - } - - // Bind listeners to all attributed elements within scope - bindAll(scope) { - const elements = this.getAll(scope); - for(const element of elements) { - this.bind(element); - } - } - - // Handle click/touch interactions - pointerEvent(event) { - const target = event.target.closest(`[${this.attribute}]`); - const action = target?.getAttribute(this.attribute) ?? null; - - if(!target || !action || !Object.keys(this.interactions).includes(action)) { - // Exit if the interaction is invalid or action doesn't exist - return false; - } - // Execute the function from the data-action attribute - this.interactions[action](event); - } -} \ No newline at end of file diff --git a/public/assets/js/nomodule.js b/public/assets/js/nomodule.js deleted file mode 100644 index 2e68d64..0000000 --- a/public/assets/js/nomodule.js +++ /dev/null @@ -1,7 +0,0 @@ -// Promote IE to Edge -if(/MSIE \d|Trident.*rv:/.test(navigator.userAgent)) { - window.location = 'microsoft-edge:' + window.location; - setTimeout(function() { - window.location = 'https://go.microsoft.com/fwlink/?linkid=2135547'; - },1); -} diff --git a/public/assets/js/script.js b/public/assets/js/script.js deleted file mode 100644 index b7ce617..0000000 --- a/public/assets/js/script.js +++ /dev/null @@ -1,85 +0,0 @@ -// Victor Westerlund - www.victorwesterlund.com -import { default as Preload } from "./modules/Preload.mjs"; -import { default as Interaction, destroy } from "./modules/UI.mjs"; - -// Load these assets when the DOM is ready (not needed right away) -new Preload([ - "modules/Modals.mjs", - "modules/Components.mjs", - "modal.css" -]); - -function updateTheme() { - const media = window.matchMedia("(prefers-color-scheme: dark)"); - document.body.classList.remove("dark"); - - // Force dark theme on all pages - if(media.matches) { - document.body.classList.add("dark"); - return; - } -} - -// All default interactions -const interactions = { - toggleMenu: () => { - const transition = 200; - const menu = document.getElementsByTagName("main")[0]; - - // Animate menu state change - menu.style.setProperty("transition",`${transition}ms`); - document.body.classList.toggle("menuActive"); - // Remove transition CSS when finished. Wonky resize effects otherwise - setTimeout(() => menu.style.removeProperty("transition"),transition + 1); - }, - // Open page defined with data-value as a card - newCard: (event) => { - const module = import("./modules/Modals.mjs"); - const interactions = { - // Like newCard() except it closes the previous card - getContact: (event) => { - const service = event.target.dataset.value; - module.then(modals => { - event.target.closest(".modal").close(); - const card = new modals.Card(interactions); - card.openPage(service); - }); - }, - // Copy text defined in data-value to clipboard and play animation - copyText: (event) => { - const copy = navigator.clipboard.writeText(event.target.dataset.value); - copy.then(() => { - event.target.classList.add("copied"); - const copied = document.createElement("p"); - copied.innerText = "copied!"; - event.target.appendChild(copied); - - // Reset button state - setTimeout(() => { - event.target.classList.remove("copied"); - destroy(copied); - },1000); - }); - } - }; - - // Create card and open the specified page asynchronously - module.then(modals => { - const card = new modals.Card(interactions); - card.openPage(event.target.dataset.value); - }); - } -} - -navigator.serviceWorker.getRegistrations().then(serviceWorkers => { - for(const serviceWorker of serviceWorkers) { - serviceWorker.unregister(); - } -}); - -// Set the current page theme, and listen for changes -const theme = window.matchMedia("(prefers-color-scheme: dark)"); -theme.addEventListener("change",updateTheme); - -new Interaction(interactions,document.body); // Initialize default interactions -updateTheme(); diff --git a/public/assets/pages/contact.html b/public/assets/pages/contact.html deleted file mode 100644 index 104487e..0000000 --- a/public/assets/pages/contact.html +++ /dev/null @@ -1,45 +0,0 @@ - - -
-
- -

Signal

-
-
- -

E-Mail

-
-
\ No newline at end of file diff --git a/public/assets/pages/contact_email.html b/public/assets/pages/contact_email.html deleted file mode 100644 index a0be5ff..0000000 --- a/public/assets/pages/contact_email.html +++ /dev/null @@ -1,65 +0,0 @@ - - -
- -

PGP key

-
-

-

hello@victorwesterlund.com

-

You can also here to send a mail directly from your mail app

-

-
- -

copy email

-
\ No newline at end of file diff --git a/public/assets/pages/contact_email_pgp.html b/public/assets/pages/contact_email_pgp.html deleted file mode 100644 index e9b2895..0000000 --- a/public/assets/pages/contact_email_pgp.html +++ /dev/null @@ -1,9 +0,0 @@ - -

🔑 PGP Public Key

-

-

5466 B1EB 2F44 6D3D DC34 E9F7 5BE0 CB0B E3BB 69DA

-

show key

-
- -

download .gpg

-
\ No newline at end of file diff --git a/public/assets/pages/contact_email_pgp_view.html b/public/assets/pages/contact_email_pgp_view.html deleted file mode 100644 index 36144b4..0000000 --- a/public/assets/pages/contact_email_pgp_view.html +++ /dev/null @@ -1,31 +0,0 @@ - -
------BEGIN PGP PUBLIC KEY BLOCK-----
-
-mQENBF/K6MkBCACkRMhMfYdeNP+M3XQoZHQVJgippQvYZ4QqH6F6brWD5989Xy5W
-kDCvLbmPJ66boqB0dHExswOvMlhfFha65pRmfP6lIoIxZlZKwll1XASP2osS8f6r
-63T7hAbL3V2Dkm49tiH1tk578xGomDrxOrd4izpH4mn9AyBIL4M+5j34bKFVZKQ+
-QfMu7tduF/1oQHfDaXJeLXSfn5cNTy8DlLcLJKUSk4cjabf1D88gMVszqAAC5o1a
-fI0YxoyZ+Fv+CmyrQm2iIZ3+MyDU9JAvoImtlp1h5aNgbFRDi2vKcSlv158Hq97Z
-XlH1ttRZuFZiJzb8iukgUUFi4RORoXWt2rtNABEBAAG0LlZpY3RvciBXZXN0ZXJs
-dW5kIDxoZWxsb0B2aWN0b3J3ZXN0ZXJsdW5kLmNvbT6JATUEEAEIACkFAl/K6MkG
-CwkIBwMCCRBb4MsL47tp2gQVCAIKAxYCAQIZAQIbAwIeAQAAJ5MIAKDl9yHjwTO7
-20sDrPa6ECsSBU/FwkvkWecuauvY19/OqtacNk8dEeiITLeUeBXkvNzN+P0y8hoF
-ABZeir59dsY00iIp8gm03eLalhcblR5jYe3c08HssJH8PksczP3kitRNLvPAf2nU
-BYg3zca5Ka21/4BPRLFb9SAQGxfHyZdy3Poug+o+pokbeK2wLqqfSMtH+waBB6Lg
-2dRXuEnaZorUpNBpsahxastvNehv31Ke41Brvft15VKpO25GKZDPhm0odXMth1/J
-pzWRQtndazY2guB0Ft+5wujv28HFCgVgZn2fKiQVytAetO+/wzPijBkGRvdIE+Zb
-VRd3Nc0mHI65AQ0EX8royQEIALcoWEurmyXD2LoGvR+sYW+YPAPM6KG8KF4cWUn8
-8+kZ6F4FH9OW64di2npYe3x+zR7DgQ1yHXcmalAsP0nN4JWTavLwsSO+JAv8NpL5
-bgDs6fGaEQFl+X4fYOpkBkBmb1JrbnBk1a2u3qsEw8t7+wW1LG9z/Si5+G1KQko8
-x/PEaZ2ZVv7L51ZfIQRnMtl4vL5X23BPVsDywotvuFqlTiSjGP4CR0lVa5CRv3DJ
-FSmHxAxeI0vMMlwbIIUTrtwJR320sZvh2cRiwAXHQXm6l0ojzRnl46mmXnB3N6q9
-PyWOaUgPrMFjT24wtgopIOwbFAT3xTr1Un0FbdeaG9JhdJ8AEQEAAYkBHwQYAQgA
-EwUCX8royQkQW+DLC+O7adoCGwwAAIV/B/9OLYeQOxbXh1/hvW7/oTvN1py8wfFq
-buvQSrb/MZKm6lZgG+kQy3DWjGTi/xvNqDHfBiObFSGso8RHSbHFldzEuMgrgoWW
-/4JH1GDiKOp+rmBxfG30/DzOoFSfVcUfP5r8xNQby4Bh6zJhKPKVB3sZjO8cHNZD
-HcNAqT3Gh5yFzsUna+ZjvPF7iU5RF1YP46dsIdvuo4xFbHpEPoZs7wgZijf+vmKO
-lP61UFvKuXzwcLiI6s919EBJ9+7je8ZAxe6BCaazk+AhxXeokVvDgwQ150DNk4up
-1ftWZI0LHqEpVGNejQ09uu+TdC/ISy/Ti0XKlJDER1eUL577YRUl876Y
-=2qWm
------END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file diff --git a/public/assets/pages/contact_signal.html b/public/assets/pages/contact_signal.html deleted file mode 100644 index 9e9191f..0000000 --- a/public/assets/pages/contact_signal.html +++ /dev/null @@ -1,76 +0,0 @@ - - - -

+4670-245-2459

-

Signal is a free and encrypted message platform with apps for all major platforms.

-
- -

copy number

-
\ No newline at end of file diff --git a/public/index.html b/public/index.html index a754867..5759bed 100644 --- a/public/index.html +++ b/public/index.html @@ -4,71 +4,27 @@ Victor Westerlund - - -
-
-
-
-
-
-
- -

victor westerlund

-
-
-
-
-
- - - - - - - -
-
- +
+

victor westerlund

+

github

- - +