the index page, footer and navigation and other things have been made initially, first versionpull/2/head
@ -0,0 +1 @@
|
||||
VITE_APP_URL=
|
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
@ -1 +1,4 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
interface ImportMetaEnv {
|
||||
VITE_APP_URL: string;
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { getCookie } from "./helpers/cookies";
|
||||
|
||||
function close() {
|
||||
document.cookie = "closedConstruction=true";
|
||||
let classes = document.querySelector("#item").classList;
|
||||
classes.add("hidden");
|
||||
}
|
||||
|
||||
function shouldShow() {
|
||||
let cookie = getCookie("closedConstruction");
|
||||
let classes = document.querySelector("#item").classList;
|
||||
|
||||
console.log(cookie);
|
||||
|
||||
if (cookie != "true") {
|
||||
classes.add("block");
|
||||
classes.remove("hidden");
|
||||
} else {
|
||||
classes.add("hidden");
|
||||
classes.adremoved("block");
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
shouldShow();
|
||||
});
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="relative py-2 text-center bg-amber-500 text-white hidden"
|
||||
id="item"
|
||||
>
|
||||
<div
|
||||
class="container flex lg:flex-row flex-col justify-between items-center"
|
||||
>
|
||||
<h2 class="text-xl font-bold text-left">🚧 Under Construction 🏗️</h2>
|
||||
|
||||
<p class="lg:text-right text-sm px-2 lg:px-0">
|
||||
I started making this a few days ago after I realized I haven't had
|
||||
a good personal website in a while. Please bear with me as I fill
|
||||
this up and make it look better!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute inset-0 z-10 h-full flex items-start lg:items-center justify-end pt-2 lg:pt-0 pr-3"
|
||||
>
|
||||
<button type="none" on:click={close}> ❌ </button>
|
||||
</div>
|
||||
</section>
|
@ -1,97 +0,0 @@
|
||||
<script>
|
||||
import { spring } from 'svelte/motion';
|
||||
|
||||
let count = 0;
|
||||
|
||||
const displayed_count = spring();
|
||||
$: displayed_count.set(count);
|
||||
$: offset = modulo($displayed_count, 1);
|
||||
|
||||
function modulo(n, m) {
|
||||
// handle negative numbers
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="counter">
|
||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="counter-viewport">
|
||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||
<strong style="top: -100%" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.counter {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.counter button {
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.counter button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
|
||||
.counter-viewport {
|
||||
width: 8em;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.counter-viewport strong {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 400;
|
||||
color: var(--accent-color);
|
||||
font-size: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.counter-digits {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import Social from "./footer/Social.svelte";
|
||||
</script>
|
||||
|
||||
<span class="flex gap-10">
|
||||
<Social
|
||||
url="https://twitter.com/midblep"
|
||||
title="The Bird App"
|
||||
icon="🐦"
|
||||
text="Twitter"
|
||||
/>
|
||||
<Social
|
||||
url="https://gitlab.com/midblep"
|
||||
title="I don't use GitHub"
|
||||
icon="🦊"
|
||||
text="GitLab"
|
||||
/>
|
||||
<Social
|
||||
url="https://discord.com/users/191525900880183296"
|
||||
title="Mid#0001"
|
||||
icon="💬"
|
||||
text="Discord"
|
||||
/>
|
||||
<Social
|
||||
url="mailto:mrrmiddynight@gmail.com"
|
||||
title="mrrmiddynight@gmail.com"
|
||||
icon="✉️"
|
||||
text="E-Mail"
|
||||
/>
|
||||
</span>
|
@ -0,0 +1,42 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { getCookie } from "./helpers/cookies";
|
||||
|
||||
let classes =
|
||||
"w-full h-full py-1 px-5 flex justify-center items-center hover:bg-orange-200 dark:hover:bg-orange-700 duration-300";
|
||||
|
||||
function switchTheme() {
|
||||
if (document.documentElement.classList.contains("dark")) {
|
||||
setLight();
|
||||
} else {
|
||||
setDark();
|
||||
}
|
||||
}
|
||||
|
||||
function setLight() {
|
||||
document.documentElement.classList.remove("dark");
|
||||
document.querySelector("#theme").innerHTML = "🌙";
|
||||
document.cookie = "theme=light";
|
||||
}
|
||||
|
||||
function setDark() {
|
||||
document.documentElement.classList.add("dark");
|
||||
document.querySelector("#theme").innerHTML = "☀️";
|
||||
document.cookie = "theme=dark";
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
let theme = getCookie("theme");
|
||||
|
||||
if (!theme) {
|
||||
document.cookie = "theme=light";
|
||||
} else {
|
||||
if (theme == "light") setLight();
|
||||
else if (theme == "dark") setDark();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<button type="none" class={classes} on:click={switchTheme} id="theme">
|
||||
☀️ <span class="text-xs text-gray-400 px-2">/</span> 🌙
|
||||
</button>
|
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
export let href;
|
||||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
class="py-2 px-5 bg-white dark:bg-black bg-opacity-70 dark:bg-opacity-50 rounded-lg hover:bg-opacity-100 dark:hover:bg-opacity-100 duration-150 text-gray-800 hover:text-black dark:text-gray-200 dark:hover:text-white"
|
||||
>
|
||||
<slot />
|
||||
</a>
|
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
import Socials from "$lib/Socials.svelte";
|
||||
import ThemeSwitcher from "$lib/ThemeSwitcher.svelte";
|
||||
import Social from "./Social.svelte";
|
||||
</script>
|
||||
|
||||
<main
|
||||
class="py-10 px-2 lg:px-10 bg-white dark:bg-black dark:bg-opacity-50 text-black dark:text-white shadow"
|
||||
>
|
||||
<section
|
||||
class="container flex flex-col-reverse lg:flex-row justify-between gap-10"
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
<h4 class="font-sm font-bold">Bart Industries</h4>
|
||||
<hr />
|
||||
<p class="text-xs">Copyright 2022 Mid</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-5 lg:items-end">
|
||||
<Socials />
|
||||
<div class="text-amber-500 flex gap-2 items-center font-bold">
|
||||
<span class="text-xs">Theme:</span>
|
||||
<span class="bg-gray-100 dark:bg-gray-800"
|
||||
><ThemeSwitcher /></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
export let text;
|
||||
export let icon;
|
||||
export let url;
|
||||
export let title;
|
||||
|
||||
if (title == null) title = text;
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={url}
|
||||
{title}
|
||||
class="text-xs flex flex-col justify-center hover:scale-110 duration-150"
|
||||
>
|
||||
<span class="text-4xl ">{icon}</span>
|
||||
<span class="text-gray-600 dark:text-gray-300 font-light">{text}</span>
|
||||
</a>
|
@ -1,49 +0,0 @@
|
||||
// this action (https://svelte.dev/tutorial/actions) allows us to
|
||||
// progressively enhance a <form> that already works without JS
|
||||
export function enhance(form, { pending, error, result }) {
|
||||
let current_token;
|
||||
|
||||
async function handle_submit(e) {
|
||||
const token = (current_token = {});
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const body = new FormData(form);
|
||||
|
||||
if (pending) pending(body, form);
|
||||
|
||||
try {
|
||||
const res = await fetch(form.action, {
|
||||
method: form.method,
|
||||
headers: {
|
||||
accept: 'application/json'
|
||||
},
|
||||
body
|
||||
});
|
||||
|
||||
if (token !== current_token) return;
|
||||
|
||||
if (res.ok) {
|
||||
result(res, form);
|
||||
} else if (error) {
|
||||
error(res, null, form);
|
||||
} else {
|
||||
console.error(await res.text());
|
||||
}
|
||||
} catch (e) {
|
||||
if (error) {
|
||||
error(null, e, form);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.addEventListener('submit', handle_submit);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
form.removeEventListener('submit', handle_submit);
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
export let text;
|
||||
export let url;
|
||||
export let type;
|
||||
export let disabled;
|
||||
|
||||
let classes = "h-full py-2 px-5 flex justify-center items-center lowercase";
|
||||
|
||||
switch (type) {
|
||||
case "title":
|
||||
classes +=
|
||||
" text-xl font-bold text-white bg-amber-500 duration-300";
|
||||
break;
|
||||
|
||||
default:
|
||||
classes +=
|
||||
" hover:bg-orange-200 dark:hover:bg-orange-700 duration-300";
|
||||
break;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
url = null;
|
||||
classes += " cursor-not-allowed";
|
||||
}
|
||||
</script>
|
||||
|
||||
<a href={url} class={classes}>
|
||||
{text}
|
||||
</a>
|
@ -0,0 +1,41 @@
|
||||
<script>
|
||||
import MenuIcon from "./MenuIcon.svelte";
|
||||
|
||||
export let text;
|
||||
export let type;
|
||||
|
||||
let classes = "h-full px-5 flex justify-center items-center";
|
||||
|
||||
switch (type) {
|
||||
case "title":
|
||||
classes +=
|
||||
" text-xl font-bold text-white bg-amber-500 duration-300";
|
||||
break;
|
||||
|
||||
default:
|
||||
classes +=
|
||||
" hover:bg-orange-200 dark:hover:bg-orange-700 duration-300";
|
||||
break;
|
||||
}
|
||||
|
||||
function openMenu() {
|
||||
let dropdown = document.querySelector("#dropdown");
|
||||
console.log(dropdown);
|
||||
dropdown.classList.toggle("block");
|
||||
dropdown.classList.toggle("hidden");
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="relative">
|
||||
<!-- <MenuIcon /> -->
|
||||
<button on:click={openMenu} type="none" class={classes} id="trigger">
|
||||
{text}
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="absolute w-screen right-0 bg-white dark:bg-black hidden z-50"
|
||||
id="dropdown"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
@ -0,0 +1,23 @@
|
||||
<script>
|
||||
export let text;
|
||||
export let url;
|
||||
export let type;
|
||||
|
||||
let classes = "h-full px-5 flex justify-center items-center";
|
||||
|
||||
switch (type) {
|
||||
case "title":
|
||||
classes +=
|
||||
" text-xl font-bold text-white bg-amber-500 duration-300";
|
||||
break;
|
||||
|
||||
default:
|
||||
classes +=
|
||||
" hover:bg-orange-200 dark:hover:bg-orange-700 duration-300";
|
||||
break;
|
||||
}
|
||||
</script>
|
||||
|
||||
<a href={url} class={classes}>
|
||||
{text}
|
||||
</a>
|
@ -1,124 +1,11 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import logo from './svelte-logo.svg';
|
||||
import Nav from "./Nav.svelte";
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="corner">
|
||||
<a href="https://kit.svelte.dev">
|
||||
<img src={logo} alt="SvelteKit" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||
</svg>
|
||||
<ul>
|
||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||
<li class:active={$page.url.pathname === '/about'}>
|
||||
<a sveltekit:prefetch href="/about">About</a>
|
||||
</li>
|
||||
<li class:active={$page.url.pathname === '/todos'}>
|
||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||
</li>
|
||||
</ul>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||
</svg>
|
||||
</nav>
|
||||
|
||||
<div class="corner">
|
||||
<!-- TODO put something else here? github link? -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.corner {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.corner a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.corner img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 2em;
|
||||
height: 3em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: var(--background);
|
||||
}
|
||||
|
||||
ul {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
background: var(--background);
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
li.active::before {
|
||||
--size: 6px;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - var(--size));
|
||||
border: var(--size) solid transparent;
|
||||
border-top: var(--size) solid var(--accent-color);
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
<main
|
||||
class="bg-white dark:bg-black dark:bg-opacity-50 text-black dark:text-white shadow z-50"
|
||||
>
|
||||
<section class="container">
|
||||
<Nav />
|
||||
</section>
|
||||
</main>
|
||||
|
@ -0,0 +1,45 @@
|
||||
<script>
|
||||
function myFunction(x) {
|
||||
document.querySelector("#icon").classList.toggle("change");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container" id="icon" on:click={() => myFunction(this)}>
|
||||
<div class="bar1" />
|
||||
<div class="bar2" />
|
||||
<div class="bar3" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bar1,
|
||||
.bar2,
|
||||
.bar3 {
|
||||
width: 35px;
|
||||
height: 5px;
|
||||
background-color: #333;
|
||||
margin: 6px 0;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
/* Rotate first bar */
|
||||
.change .bar1 {
|
||||
-webkit-transform: rotate(-45deg) translate(-9px, 6px);
|
||||
transform: rotate(-45deg) translate(-9px, 6px);
|
||||
}
|
||||
|
||||
/* Fade out the second bar */
|
||||
.change .bar2 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Rotate last bar */
|
||||
.change .bar3 {
|
||||
-webkit-transform: rotate(45deg) translate(-8px, -8px);
|
||||
transform: rotate(45deg) translate(-8px, -8px);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
import ThemeSwitcher from "$lib/ThemeSwitcher.svelte";
|
||||
|
||||
import Button from "./Button.svelte";
|
||||
import Dropdown from "./Dropdown.svelte";
|
||||
import DropdownButton from "./DropdownButton.svelte";
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<a class="flex h-14" href="/">
|
||||
<!-- <Button url="/" text="<img src='/bart.png' />" type="title" /> -->
|
||||
<img src="/bart.png" alt="Logo" class="h-full px-5 py-2 bg-amber-500" />
|
||||
</a>
|
||||
<div class="hidden lg:flex h-14">
|
||||
<Button url="/" text="🏠 Home" />
|
||||
<Button disabled="true" url="/esu" text="📒 ESU" />
|
||||
<Button disabled="true" url="/portfolio" text="🧰 works" />
|
||||
<Button disabled="true" url="/furry" text="🐈⬛ Furry" />
|
||||
<Button disabled="true" url="http://ad.localhost" text="🔞 AD Site" />
|
||||
<span><ThemeSwitcher /></span>
|
||||
</div>
|
||||
<div class="flex lg:hidden h-14">
|
||||
<Dropdown text="🍔">
|
||||
<Button url="/" text="🏠 Home" />
|
||||
<Button disabled="true" url="/esu" text="📒 ESU" />
|
||||
<Button disabled="true" url="/portfolio" text="🧰 works" />
|
||||
<Button disabled="true" url="/furry" text="🐈⬛ Furry" />
|
||||
<Button
|
||||
disabled="true"
|
||||
url="http://ad.localhost"
|
||||
text="🔞 AD Site"
|
||||
/>
|
||||
<ThemeSwitcher />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
Before Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,15 @@
|
||||
export let getCookie = (cname) => {
|
||||
let name = cname + "=";
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(";");
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == " ") {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
export const variables = {
|
||||
appUrl: import.meta.env.VITE_APP_URL,
|
||||
};
|
@ -0,0 +1,108 @@
|
||||
<script>
|
||||
import Socials from "$lib/Socials.svelte";
|
||||
|
||||
const array = [
|
||||
"/banner1.png",
|
||||
"/banner2.png",
|
||||
"/banner3.png",
|
||||
"/banner4.png",
|
||||
"/banner5.jpeg",
|
||||
"/banner6.png",
|
||||
"/banner7.png",
|
||||
"/banner8.png",
|
||||
];
|
||||
|
||||
const randomBannerPic = array[Math.floor(Math.random() * array.length)];
|
||||
</script>
|
||||
|
||||
<main class="flex gap-10 items-center justify-between px-6 lg:px-20">
|
||||
<div class="flex flex-col gap-5 justify-center w-full">
|
||||
<span>
|
||||
<img src="/hello.png" alt="Hello!" class="h-20 popout" />
|
||||
</span>
|
||||
|
||||
<span class="flex flex-wrap gap-2 items-end">
|
||||
<h2 class="text-4xl font-extrabold">I'm Midnight,</h2>
|
||||
<span class="text-3xl font-extralight">
|
||||
i like to make things on the interwebs
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<hr class="w-1/3 my-2" />
|
||||
|
||||
<span class="my-2">
|
||||
<p class="text-gray-800 dark:text-gray-300 lg:w-3/4">
|
||||
Bart Industries is a collection of my works, projects and
|
||||
ambitions <br /> including my furry characters, stories and other
|
||||
things i feel like sharing online.
|
||||
</p>
|
||||
</span>
|
||||
|
||||
<Socials />
|
||||
</div>
|
||||
|
||||
<div class="hidden lg:block float h-full w-2/3">
|
||||
<img
|
||||
src={randomBannerPic}
|
||||
alt="mid"
|
||||
class="rounded-xl shadow-xl w-full"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.popout {
|
||||
animation: popout;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes popout {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2) rotate(-5deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
50% {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
100% {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.float {
|
||||
animation: float;
|
||||
animation-duration: 3s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
50% {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
100% {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,40 @@
|
||||
<script>
|
||||
export let color;
|
||||
export let rotation;
|
||||
export let buttonText;
|
||||
export let buttonHref;
|
||||
let skew;
|
||||
|
||||
switch (rotation) {
|
||||
case "left":
|
||||
skew = "skew-x-1 lg:skew-x-6";
|
||||
break;
|
||||
|
||||
case "right":
|
||||
skew = "-skew-x-1 lg:-skew-x-6";
|
||||
break;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 w-full relative py-8 px-8 lg:px-12 lg:py-10">
|
||||
<div
|
||||
class="{skew} {color} absolute inset-0 z-0 w-full shadow-xl rounded-xl flex flex-col justify-end"
|
||||
>
|
||||
{#if buttonText != null && buttonHref != null}
|
||||
<a
|
||||
href={buttonHref}
|
||||
class="rounded-b-xl bg-white bg-opacity-50 p-5 hover:bg-opacity-100 duration-150 text-base lg:text-lg font-bold dark:text-black"
|
||||
>
|
||||
<span class="p-5 z-10">{buttonText}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="z-10 flex flex-col gap-5 flex-grow">
|
||||
<slot />
|
||||
|
||||
{#if buttonText != null && buttonHref != null}
|
||||
<span class="h-16 lg:h-10" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,96 @@
|
||||
<script>
|
||||
import Socials from "$lib/Socials.svelte";
|
||||
|
||||
import { variables } from "$lib/helpers/variables";
|
||||
|
||||
let email;
|
||||
let message;
|
||||
let notify;
|
||||
|
||||
function send() {
|
||||
email = "";
|
||||
message = "";
|
||||
notify = "Email sent!";
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="flex flex-col gap-5">
|
||||
<span class="p-5 flex justify-center">
|
||||
<Socials />
|
||||
</span>
|
||||
|
||||
<section
|
||||
class="bg-gray-200 dark:bg-gray-800 p-10 rounded-xl shadow w-full flex flex-col gap-5"
|
||||
>
|
||||
<h3 class="text-xl font-bold">Quick compose email</h3>
|
||||
|
||||
<form
|
||||
class="flex flex-col gap-5"
|
||||
action="https://api.staticforms.xyz/submit"
|
||||
method="post"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="accessKey"
|
||||
value="f237579b-85d1-414f-846c-409b8c8f57f0"
|
||||
/>
|
||||
<input type="text" name="honeypot" style="display: none;" />
|
||||
<input type="hidden" name="redirectTo" value={variables.appUrl} />
|
||||
|
||||
<div class="flex flex-col">
|
||||
<label for="email" value="E-Mail" class="text-lg font-bold"
|
||||
>E-Mail</label
|
||||
>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
class="text-lg py-1 px-2 rounded-lg shadow"
|
||||
bind:value={email}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<label for="message" value="E-Mail" class="text-lg font-bold"
|
||||
>Message</label
|
||||
>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
class="text-lg py-1 px-2 rounded-lg shadow"
|
||||
bind:value={message}
|
||||
rows="5"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
type="submit"
|
||||
on:click={send}
|
||||
class="bg-white py-2 px-3 rounded-lg font-bold shadow"
|
||||
>Send</button
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if notify}
|
||||
<p class="text-green-500 font-bold text-lg">{notify}</p>
|
||||
{/if}
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
button {
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transition-duration: 0.3s;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transition-duration: 0.3s;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,3 @@
|
||||
<main>
|
||||
<h1>Error</h1>
|
||||
</main>
|
@ -1,45 +1,19 @@
|
||||
<script>
|
||||
import Header from '$lib/header/Header.svelte';
|
||||
import '../app.css';
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
import Construction from "$lib/Construction.svelte";
|
||||
import Footer from "$lib/footer/Footer.svelte";
|
||||
import Header from "$lib/header/Header.svelte";
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||
</footer>
|
||||
import "../app.css";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
<div class="bg-gray-100 dark:bg-gray-900 text-black dark:text-white">
|
||||
<Construction />
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
<div class="min-h-screen">
|
||||
<Header />
|
||||
|
||||
footer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@media (min-width: 480px) {
|
||||
footer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<Footer />
|
||||
</div>
|
||||
|
@ -1,50 +0,0 @@
|
||||
<script context="module">
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const hydrate = dev;
|
||||
|
||||
// ...but if the client-side router is already loaded
|
||||
// (i.e. we came here from elsewhere in the app), use it
|
||||
export const router = browser;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="content">
|
||||
<h1>About this app</h1>
|
||||
|
||||
<p>
|
||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||
following into your command line and following the prompts:
|
||||
</p>
|
||||
|
||||
<!-- TODO lose the @next! -->
|
||||
<pre>npm init svelte@next</pre>
|
||||
|
||||
<p>
|
||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||
the devtools network panel and reloading.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||
it with JavaScript disabled!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import Socials from "$lib/Socials.svelte";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
</svelte:head>
|
||||
|
||||
<main />
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import Socials from "$lib/Socials.svelte";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
</svelte:head>
|
||||
|
||||
<main />
|
@ -1,59 +1,163 @@
|
||||
<script context="module">
|
||||
export const prerender = true;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import Counter from '$lib/Counter.svelte';
|
||||
import AnchorButton from "$lib/elements/AnchorButton.svelte";
|
||||
|
||||
import Banner from "$lib/home/Banner.svelte";
|
||||
import Card from "$lib/home/Card.svelte";
|
||||
import Form from "$lib/home/Form.svelte";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
<title>Home | Bart Industries</title>
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<h1>
|
||||
<div class="welcome">
|
||||
<picture>
|
||||
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||
<img src="svelte-welcome.png" alt="Welcome" />
|
||||
</picture>
|
||||
<main class="relative">
|
||||
<section class="container py-20">
|
||||
<Banner />
|
||||
</section>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
|
||||
<path
|
||||
class="block dark:hidden"
|
||||
fill="rgba(229, 231, 235, 1)"
|
||||
fill-opacity="1"
|
||||
d="M0,64L34.3,85.3C68.6,107,137,149,206,165.3C274.3,181,343,171,411,160C480,149,549,139,617,133.3C685.7,128,754,128,823,154.7C891.4,181,960,235,1029,240C1097.1,245,1166,203,1234,160C1302.9,117,1371,75,1406,53.3L1440,32L1440,320L1405.7,320C1371.4,320,1303,320,1234,320C1165.7,320,1097,320,1029,320C960,320,891,320,823,320C754.3,320,686,320,617,320C548.6,320,480,320,411,320C342.9,320,274,320,206,320C137.1,320,69,320,34,320L0,320Z"
|
||||
/>
|
||||
<path
|
||||
class="hidden dark:block"
|
||||
fill="rgba(31, 41, 55, 1)"
|
||||
fill-opacity="1"
|
||||
d="M0,64L34.3,85.3C68.6,107,137,149,206,165.3C274.3,181,343,171,411,160C480,149,549,139,617,133.3C685.7,128,754,128,823,154.7C891.4,181,960,235,1029,240C1097.1,245,1166,203,1234,160C1302.9,117,1371,75,1406,53.3L1440,32L1440,320L1405.7,320C1371.4,320,1303,320,1234,320C1165.7,320,1097,320,1029,320C960,320,891,320,823,320C754.3,320,686,320,617,320C548.6,320,480,320,411,320C342.9,320,274,320,206,320C137.1,320,69,320,34,320L0,320Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<section class="bg-gray-200 dark:bg-gray-800 -mt-1">
|
||||
<div class="container px-5 flex flex-col gap-10 justify-center">
|
||||
<h2 class="text-2xl font-bold italic">Places I'm a part of</h2>
|
||||
|
||||
<div class="flex flex-col lg:flex-row gap-5 justify-between">
|
||||
<Card
|
||||
buttonHref="/works#puppypride"
|
||||
buttonText="🌐 More details and pictures on my Works page"
|
||||
rotation="left"
|
||||
color="bg-blue-300 dark:bg-blue-700"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-bold inline-flex items-center gap-3"
|
||||
>
|
||||
<img
|
||||
src="/puppypride.png"
|
||||
class="h-8"
|
||||
alt="puppypride"
|
||||
/> Puppy Pride
|
||||
</h3>
|
||||
<p class="text-sm text-gray-800 dark:text-gray-100">
|
||||
I was the sole developer of the new Puppy Pride social
|
||||
network over the course of 2021, and continuing on to
|
||||
this day. The whole site was built from the ground up
|
||||
using the TALL stack in PHP.
|
||||
<br /><br />
|
||||
The network sports a whole array of social features including
|
||||
blogging, picture albums, statuses, extensive user profiles,
|
||||
events, customizable groups with roles and custom pages,
|
||||
instant messaging, various networking tools like friending
|
||||
and blocking, notifications through channels like push and
|
||||
mail, live feed, subscriptions and views, extensive comment
|
||||
sections, discussions, and much more.
|
||||
<br /><br />
|
||||
The project is nearing completion and will be released soon.
|
||||
Puppy Pride is the world's leading pup play community and
|
||||
has tens of thousands of members across the globe.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
buttonHref="https://puppypride.social"
|
||||
buttonText="🚪 Join the Discord"
|
||||
rotation="left"
|
||||
color="bg-orange-300 dark:bg-orange-800"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-bold inline-flex items-center gap-3"
|
||||
>
|
||||
<img
|
||||
src="/moonlitden.png"
|
||||
class="h-8"
|
||||
alt="moonlitden"
|
||||
/> The Moonlit Den
|
||||
</h3>
|
||||
<p class="text-sm text-gray-800 dark:text-gray-100">
|
||||
Successor to the wildly popular Wumpus' Universe, a
|
||||
cornerstone of Discord's early fanatic community, The
|
||||
Moonlit Den is a small piece of what it once was. Today,
|
||||
it continues to do much of the same it always did,
|
||||
becoming more sophisticated and professional as it's
|
||||
main sponsor DubbelNull improves.
|
||||
<br /><br />
|
||||
Moonlit Den is a generalistic Discord community aimed at
|
||||
being a friendly place to make your home online. We run various
|
||||
gameservers, have game nights together, and overall just
|
||||
like to chat about whatever.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
buttonHref="https://dubbelnull.com"
|
||||
buttonText="➡️ Go to DubbelNull.com"
|
||||
rotation="left"
|
||||
color="bg-gray-300 dark:bg-gray-700"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-bold inline-flex items-center gap-3"
|
||||
>
|
||||
<img
|
||||
src="/dubbelnull.png"
|
||||
class="h-10 -my-2"
|
||||
alt="dubbelnull"
|
||||
/> DubbelNull
|
||||
</h3>
|
||||
<p class="text-sm text-gray-800 dark:text-gray-100">
|
||||
I am the founder and CTO of DubbelNull, and do most of
|
||||
the programming and technological work there today. We
|
||||
run our own server architecture and sponsor several
|
||||
communities to do what they want, such as run their
|
||||
gameservers, websites, scripts and services.
|
||||
<br /><br />
|
||||
DubbelNull currently offers specialized website design and
|
||||
construction of any type, be it a webapp or simple portfolio.
|
||||
We focus on efficiency in communication and satisfaction
|
||||
of the end result.
|
||||
<br /><br />
|
||||
We are also looking into expanding into the SaaS business,
|
||||
notably with our own Cloud Storage solution built on top
|
||||
of Nextcloud, and a chatting app in the works called Flame.
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
|
||||
<path
|
||||
class="block dark:hidden"
|
||||
fill="rgba(229, 231, 235, 1)"
|
||||
fill-opacity="1"
|
||||
d="M0,320L34.3,288C68.6,256,137,192,206,186.7C274.3,181,343,235,411,229.3C480,224,549,160,617,128C685.7,96,754,96,823,112C891.4,128,960,160,1029,170.7C1097.1,181,1166,171,1234,160C1302.9,149,1371,139,1406,133.3L1440,128L1440,0L1405.7,0C1371.4,0,1303,0,1234,0C1165.7,0,1097,0,1029,0C960,0,891,0,823,0C754.3,0,686,0,617,0C548.6,0,480,0,411,0C342.9,0,274,0,206,0C137.1,0,69,0,34,0L0,0Z"
|
||||
/>
|
||||
<path
|
||||
class="hidden dark:block"
|
||||
fill="rgba(31, 41, 55, 1)"
|
||||
fill-opacity="1"
|
||||
d="M0,320L34.3,288C68.6,256,137,192,206,186.7C274.3,181,343,235,411,229.3C480,224,549,160,617,128C685.7,96,754,96,823,112C891.4,128,960,160,1029,170.7C1097.1,181,1166,171,1234,160C1302.9,149,1371,139,1406,133.3L1440,128L1440,0L1405.7,0C1371.4,0,1303,0,1234,0C1165.7,0,1097,0,1029,0C960,0,891,0,823,0C754.3,0,686,0,617,0C548.6,0,480,0,411,0C342.9,0,274,0,206,0C137.1,0,69,0,34,0L0,0Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
to your new<br />SvelteKit app
|
||||
</h1>
|
||||
|
||||
<h2>
|
||||
try editing <strong>src/routes/index.svelte</strong>
|
||||
</h2>
|
||||
|
||||
<Counter />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||
}
|
||||
|
||||
.welcome img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<section
|
||||
class="container px-5 lg:px-0 pb-20 flex flex-col lg:flex-row gap-10 justify-between items-center lg:items-start"
|
||||
>
|
||||
<img src="/contact.png" class="lg:w-1/3 h-full" alt="contact" />
|
||||
|
||||
<div class="lg:w-1/3">
|
||||
<Form />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import Socials from "$lib/Socials.svelte";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
</svelte:head>
|
||||
|
||||
<main />
|
@ -1,14 +0,0 @@
|
||||
import { api } from './_api';
|
||||
|
||||
// PATCH /todos/:uid.json
|
||||
export const patch = async (request) => {
|
||||
return api(request, `todos/${request.locals.userid}/${request.params.uid}`, {
|
||||
text: request.body.get('text'),
|
||||
done: request.body.has('done') ? !!request.body.get('done') : undefined
|
||||
});
|
||||
};
|
||||
|
||||
// DELETE /todos/:uid.json
|
||||
export const del = async (request) => {
|
||||
return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
|
||||
};
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
This module is used by the /todos.json and /todos/[uid].json
|
||||
endpoints to make calls to api.svelte.dev, which stores todos
|
||||
for each user. The leading underscore indicates that this is
|
||||
a private module, _not_ an endpoint — visiting /todos/_api
|
||||
will net you a 404 response.
|
||||
|
||||
(The data on the todo app will expire periodically; no
|
||||
guarantees are made. Don't use it to organise your life.)
|
||||
*/
|
||||
|
||||
const base = 'https://api.svelte.dev';
|
||||
|
||||
export async function api(request, resource, data) {
|
||||
// user must have a cookie set
|
||||
if (!request.locals.userid) {
|
||||
return { status: 401 };
|
||||
}
|
||||
|
||||
const res = await fetch(`${base}/${resource}`, {
|
||||
method: request.method,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: data && JSON.stringify(data)
|
||||
});
|
||||
|
||||
// if the request came from a <form> submission, the browser's default
|
||||
// behaviour is to show the URL corresponding to the form's "action"
|
||||
// attribute. in those cases, we want to redirect them back to the
|
||||
// /todos page, rather than showing the response
|
||||
if (res.ok && request.method !== 'GET' && request.headers.accept !== 'application/json') {
|
||||
return {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: '/todos'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
body: await res.json()
|
||||
};
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { api } from './_api';
|
||||
|
||||
// GET /todos.json
|
||||
export const get = async (request) => {
|
||||
// request.locals.userid comes from src/hooks.js
|
||||
const response = await api(request, `todos/${request.locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return { body: [] };
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
// POST /todos.json
|
||||
export const post = async (request) => {
|
||||
const response = await api(request, `todos/${request.locals.userid}`, {
|
||||
// because index.svelte posts a FormData object,
|
||||
// request.body is _also_ a (readonly) FormData
|
||||
// object, which allows us to get form data
|
||||
// with the `body.get(key)` method
|
||||
text: request.body.get('text')
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
@ -1,220 +0,0 @@
|
||||
<script context="module">
|
||||
import { enhance } from '$lib/form';
|
||||
|
||||
// see https://kit.svelte.dev/docs#loading
|
||||
export const load = async ({ fetch }) => {
|
||||
const res = await fetch('/todos.json');
|
||||
|
||||
if (res.ok) {
|
||||
const todos = await res.json();
|
||||
|
||||
return {
|
||||
props: { todos }
|
||||
};
|
||||
}
|
||||
|
||||
const { message } = await res.json();
|
||||
|
||||
return {
|
||||
error: new Error(message)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
export let todos;
|
||||
|
||||
async function patch(res) {
|
||||
const todo = await res.json();
|
||||
|
||||
todos = todos.map((t) => {
|
||||
if (t.uid === todo.uid) return todo;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todos</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="todos">
|
||||
<h1>Todos</h1>
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos.json"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: async (res, form) => {
|
||||
const created = await res.json();
|
||||
todos = [...todos, created];
|
||||
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||
</form>
|
||||
|
||||
{#each todos as todo (todo.uid)}
|
||||
<div
|
||||
class="todo"
|
||||
class:done={todo.done}
|
||||
transition:scale|local={{ start: 0.7 }}
|
||||
animate:flip={{ duration: 200 }}
|
||||
>
|
||||
<form
|
||||
action="/todos/{todo.uid}.json?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: (data) => {
|
||||
todo.done = !!data.get('done');
|
||||
},
|
||||
result: patch
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
class="text"
|
||||
action="/todos/{todo.uid}.json?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: patch
|
||||
}}
|
||||
>
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos/{todo.uid}.json?_method=DELETE"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: () => (todo.pending_delete = true),
|
||||
result: () => {
|
||||
todos = todos.filter((t) => t.uid !== todo.uid);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.todos {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.new {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ff3e00 !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.new input {
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
padding: 0.5em 1em 0.3em 1em;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.todo {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr 2rem;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||
transform: translate(-1px, -1px);
|
||||
transition: filter 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.done {
|
||||
transform: none;
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
form.text {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo input {
|
||||
flex: 1;
|
||||
padding: 0.5em 2em 0.5em 0.8em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.todo button {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
button.toggle {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
background-size: 1em auto;
|
||||
}
|
||||
|
||||
.done .toggle {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.delete:hover,
|
||||
.delete:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.save {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.todo input:focus + .save,
|
||||
.save:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import Socials from "$lib/Socials.svelte";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
</svelte:head>
|
||||
|
||||
<main />
|
After Width: | Height: | Size: 206 KiB |
After Width: | Height: | Size: 6.3 MiB |
After Width: | Height: | Size: 5.7 MiB |
After Width: | Height: | Size: 204 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 229 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 90 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 253 KiB |
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 206 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 6.8 KiB |
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
darkMode: "class",
|
||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|