mirror of
https://codeberg.org/vlw/victorwesterlund.com.git
synced 2025-09-14 03:23:41 +02:00
dev21w36d
This commit is contained in:
parent
821ed27776
commit
9641533b24
8 changed files with 211 additions and 94 deletions
|
@ -1,9 +1,5 @@
|
|||
/* -- Transition overrides -- */
|
||||
|
||||
body {
|
||||
transition: var(--transition) background-color;
|
||||
}
|
||||
|
||||
body main .screen {
|
||||
transition: var(--transition) transform;
|
||||
transition-delay: calc(var(--transition) / 2);
|
||||
|
@ -39,13 +35,40 @@ body .modal.active ~ main .screen {
|
|||
}
|
||||
|
||||
.modal .button {
|
||||
background-color: rgba(var(--comp-inverted),.05);
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.modal .button p {
|
||||
color: var(--color-inverted);
|
||||
.modal .inner {
|
||||
transition: var(--transition) transform, var(--transition) opacity;
|
||||
position: relative;
|
||||
background-color: var(--color-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);
|
||||
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: 10vw;
|
||||
--anim-speed: 1s;
|
||||
|
@ -70,30 +93,42 @@ body .modal.active ~ main .screen {
|
|||
}
|
||||
}
|
||||
|
||||
/* ---- */
|
||||
|
||||
.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 {
|
||||
transition: var(--transition) transform, var(--transition) opacity;
|
||||
position: relative;
|
||||
background-color: var(--color-background);
|
||||
width: calc(100vw - var(--padding));
|
||||
max-width: 500px;
|
||||
align-self: flex-end;
|
||||
box-sizing: border-box;
|
||||
padding: var(--padding);
|
||||
transform: translateY(1vh);
|
||||
border-radius: var(--border-radius);
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
.modal.card.active .inner {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal.card .button[data-action="close"] {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* -- Dialogs -- */
|
||||
|
||||
.modal.dialog .inner {
|
||||
transform: scale(.95);
|
||||
}
|
||||
|
||||
.modal.dialog.active .inner {
|
||||
transform: scale(1);
|
||||
}
|
|
@ -78,7 +78,15 @@ main {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
main.active {
|
||||
body.menuActive {
|
||||
background-color: var(--color-contrast);
|
||||
}
|
||||
|
||||
body.dark.menuActive {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
body.menuActive main {
|
||||
transform: translateX(-100vw);
|
||||
}
|
||||
|
||||
|
@ -95,6 +103,10 @@ body.dark .screen.dark {
|
|||
background-color: black;
|
||||
}
|
||||
|
||||
.screen .inner {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.screen .content {
|
||||
box-sizing: border-box;
|
||||
padding: calc(var(--padding) * 1.5);
|
||||
|
@ -131,7 +143,6 @@ body.dark .screen.dark {
|
|||
/* ---- */
|
||||
|
||||
.button {
|
||||
background-color: var(--color-contrast);
|
||||
text-align: center;
|
||||
padding: 25px;
|
||||
border-radius: var(--border-radius);
|
||||
|
@ -141,6 +152,16 @@ body.dark .screen.dark {
|
|||
gap: var(--padding);
|
||||
}
|
||||
|
||||
.button.solid {
|
||||
background-color: var(--color-contrast);
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.button.phantom {
|
||||
background-color: rgba(var(--comp-inverted),.05);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.button svg {
|
||||
pointer-events: none;
|
||||
fill: var(--color-contrast);
|
||||
|
@ -150,15 +171,7 @@ body.dark .screen.dark {
|
|||
.button p {
|
||||
pointer-events: none;
|
||||
font-size: clamp(16px,5vw,22px);
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.button.phantom {
|
||||
background-color: rgba(var(--comp-contrast),.1);
|
||||
}
|
||||
|
||||
.button.phantom p {
|
||||
color: var(--color-contrast);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.button.loading p {
|
||||
|
|
|
@ -12,19 +12,40 @@ class Component {
|
|||
export class Button extends Component {
|
||||
constructor(properties) {
|
||||
super("div");
|
||||
this.properties = properties;
|
||||
this.element.classList.add("button");
|
||||
|
||||
this.setText(properties.text);
|
||||
this.setAction(properties.action);
|
||||
this.setText();
|
||||
this.setAction();
|
||||
this.setType();
|
||||
}
|
||||
|
||||
setText(text) {
|
||||
setText() {
|
||||
if(!this.properties.text) {
|
||||
return false;
|
||||
}
|
||||
const textElement = document.createElement("p");
|
||||
textElement.innerText = text;
|
||||
textElement.innerText = this.properties.text;
|
||||
this.element.appendChild(textElement);
|
||||
}
|
||||
|
||||
setAction(action) {
|
||||
this.element.setAttribute("data-action",action);
|
||||
setAction() {
|
||||
if(!this.properties.action) {
|
||||
return false;
|
||||
}
|
||||
this.element.setAttribute("data-action",this.properties.action);
|
||||
}
|
||||
|
||||
setType() {
|
||||
const types = [
|
||||
"solid",
|
||||
"phantom"
|
||||
];
|
||||
const type = types.includes(this.properties.type) ? this.properties.type : false;
|
||||
|
||||
if(!this.properties.type || !type) {
|
||||
return false;
|
||||
}
|
||||
this.element.classList.add(type);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@ class Modal extends Interaction {
|
|||
interactions = Object.assign(interactions,extendedInteractions);
|
||||
super(interactions,element);
|
||||
|
||||
this.transition = 300;
|
||||
|
||||
this.element = this.applyTemplate(element);
|
||||
this.element.close = () => this.close(); // Bind close to element prototype
|
||||
document.body.insertAdjacentElement("afterbegin",this.element);
|
||||
|
@ -26,7 +28,15 @@ class Modal extends Interaction {
|
|||
const url = `assets/pages/${page}`;
|
||||
const response = await fetch(url);
|
||||
if(!response.ok) {
|
||||
throw new Error(`Modal: Failed to fetch page "${page}"`);
|
||||
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();
|
||||
}
|
||||
|
@ -68,21 +78,40 @@ class Modal extends Interaction {
|
|||
}
|
||||
}
|
||||
|
||||
// Overlay with a slide-in animation from the bottom of the viewport
|
||||
export class Card extends Modal {
|
||||
constructor(interactions) {
|
||||
export class Dialog extends Modal {
|
||||
constructor(interactions = {}) {
|
||||
super(interactions);
|
||||
|
||||
this.transition = 300;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init(slim) {
|
||||
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"
|
||||
action: "close",
|
||||
type: "phantom"
|
||||
});
|
||||
|
||||
this.bind(closeButton.element);
|
||||
|
@ -91,19 +120,22 @@ export class Card extends Modal {
|
|||
|
||||
error(message) {
|
||||
const oops = document.createElement("p");
|
||||
const infoButton = new Button({
|
||||
text: "more info"
|
||||
});
|
||||
const infoButton = document.createElement("p");
|
||||
|
||||
oops.classList.add("error");
|
||||
oops.innerText = "🤯\nSomething went wrong";
|
||||
infoButton.innerText = "more info..";
|
||||
|
||||
infoButton.element.addEventListener("click",() => {
|
||||
oops.innerText = "📋\n" + message;
|
||||
destroy(infoButton.element);
|
||||
infoButton.addEventListener("click",() => {
|
||||
const details = new Dialog();
|
||||
|
||||
details.insertHTML(`<h1>📄 Error report</h1><pre>${message}</pre>`);
|
||||
details.open();
|
||||
|
||||
this.close();
|
||||
});
|
||||
|
||||
this.insertElement(infoButton.element);
|
||||
this.insertElement(infoButton);
|
||||
this.insertElement(oops);
|
||||
}
|
||||
|
||||
|
@ -112,6 +144,7 @@ export class Card extends Modal {
|
|||
// 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();
|
||||
|
||||
|
|
|
@ -42,30 +42,6 @@ export default class Interaction extends Logging {
|
|||
}
|
||||
}
|
||||
|
||||
// Set the page theme color (and the theme-color meta tag)
|
||||
setThemeColor(color) {
|
||||
const meta = document.head.querySelector("meta[name='theme-color']");
|
||||
const style = getComputedStyle(document.body);
|
||||
|
||||
if(!meta || !color) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dark mode will always use the background color
|
||||
if(document.body.classList.contains("dark")) {
|
||||
color = "background";
|
||||
}
|
||||
|
||||
if(color[0] !== "#") {
|
||||
// Get CSS variable if color isn't a HEX code
|
||||
color = style.getPropertyValue(`--color-${color}`);
|
||||
color = color.replaceAll(" ","");
|
||||
}
|
||||
|
||||
document.body.style.setProperty("background-color",color);
|
||||
meta.setAttribute("content",color);
|
||||
}
|
||||
|
||||
// Handle click/touch interactions
|
||||
pointerEvent(event) {
|
||||
const target = event.target.closest(`[${this.attribute}]`);
|
||||
|
@ -77,10 +53,5 @@ export default class Interaction extends Logging {
|
|||
}
|
||||
// Execute the function from the data-action attribute
|
||||
this.interactions[action](event);
|
||||
|
||||
// The button has requested a theme-color change
|
||||
if(target.hasAttribute("data-theme-color")) {
|
||||
this.setThemeColor(target.getAttribute("data-theme-color"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ const interactions = {
|
|||
const menu = document.getElementsByTagName("main")[0];
|
||||
|
||||
menu.style.setProperty("transition",`${speed}ms`);
|
||||
menu.classList.toggle("active");
|
||||
document.body.classList.toggle("menuActive");
|
||||
setTimeout(() => menu.style.removeProperty("transition"),speed + 1);
|
||||
},
|
||||
openContactCard: () => {
|
||||
|
@ -30,6 +30,14 @@ const interactions = {
|
|||
const card = new modals.Card(interactions);
|
||||
card.openPage(service);
|
||||
});
|
||||
},
|
||||
copyText: (event) => {
|
||||
const memory = event.target.innerText;
|
||||
event.target.classList.add("bounce");
|
||||
event.target.innerText = "Copied!";
|
||||
setTimeout(() => {
|
||||
event.target.innerText = memory;
|
||||
},this.transition);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -52,11 +60,8 @@ function updateTheme() {
|
|||
document.body.classList.add("dark");
|
||||
return;
|
||||
}
|
||||
|
||||
main.setThemeColor("background");
|
||||
}
|
||||
|
||||
// Set the current page theme, and listen for changes
|
||||
theme.addEventListener("change",updateTheme);
|
||||
updateTheme();
|
||||
window._debug.openContactsModal();
|
|
@ -1,13 +1,15 @@
|
|||
<style>
|
||||
.contact {
|
||||
align-self: stretch;
|
||||
display: grid;
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
.contact .item {
|
||||
height: 140px;
|
||||
--size: 1fr;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: rgba(var(--comp-inverted),.05);
|
||||
flex-direction: column;
|
||||
|
@ -23,6 +25,12 @@
|
|||
.contact .item img {
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
@media (max-width: 258px) {
|
||||
.contact {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="contact">
|
||||
<div class="item center" data-action="getContact" data-value="contact_signal">
|
||||
|
@ -30,11 +38,7 @@
|
|||
<p>Signal</p>
|
||||
</div>
|
||||
<div class="item center" data-action="getContact" data-value="contact_email">
|
||||
<img src="assets/img/icons/email.svg"/>
|
||||
<p>E-Mail</p>
|
||||
</div>
|
||||
<div class="item center" data-action="getContact">
|
||||
<p>
|
||||
</div>
|
||||
<div class="item center" data-action="getContact">
|
||||
</div>
|
||||
</div>
|
|
@ -1,3 +1,38 @@
|
|||
<style>
|
||||
body:not(.dark) .modal[data-page="contact_signal"] .inner {
|
||||
--comp-inverted: 255,255,255;
|
||||
--comp-background: 58,118,240;
|
||||
|
||||
--color-background: rgb(var(--comp-background));
|
||||
--color-inverted: rgb(var(--comp-inverted));
|
||||
}
|
||||
|
||||
body:not(.dark) .modal[data-page="contact_signal"] .button.solid {
|
||||
background-color: var(--color-inverted);
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.modal .inner > h1,
|
||||
.modal .inner > p,
|
||||
.modal .button.phantom {
|
||||
color: var(--color-inverted);
|
||||
}
|
||||
|
||||
/* ---- */
|
||||
|
||||
#logo_signal {
|
||||
width: clamp(100px,50%,200px);
|
||||
}
|
||||
|
||||
#number {
|
||||
background: black;
|
||||
padding: 10px 15px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
<img src="assets/img/icons/signal.svg"/>
|
||||
<img id="logo_signal" src="assets/img/icons/signal.svg"/>
|
||||
<h1 id="number">+4670-2452459</h1>
|
||||
<p>Signal is a free and encrypted message platform with apps for all major platforms.</p>
|
||||
<div class="button solid" data-action="copyText">
|
||||
<p>copy number</p>
|
||||
</div>
|
Loading…
Add table
Reference in a new issue