Compare commits

..

1 Commits

Author SHA1 Message Date
0fc891dbb4 add initial Glaube section 2024-02-01 17:14:17 +01:00
87 changed files with 3270 additions and 2375 deletions

View File

@@ -1,33 +0,0 @@
name: CI
# Controls when the action will run.
on:
# Triggers the workflow on push to master (including merged PRs)
push:
branches: [ master ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
update:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Updating website.
uses: appleboy/ssh-action@master
with:
host: bocken.org
username: homepage
key: ${{ secrets.homepage_ssh }}
passphrase: ${{ secrets.homepage_pass }}
port: 22
script: |
cd /usr/share/webapps/homepage
git pull --force https://Alexander:${{ secrets.homepage_gitea_token }}@git.bocken.org/Alexander/homepage
npm run build
sudo systemctl restart homepage.service

View File

@@ -5,15 +5,12 @@ My own homepage, bocken.org, built with svelte-kit.
## TODO
### General
- [ ] Admin user management -> move to authentik via oIDC
- [x] login to authentik
- [x] only let rezepte_users edit recipes -> currently only letting them log in, should be changed
- [x] get user info from authentik (more than email and name)
- [ ] upload pfp
- [ ] upload/change pfp
- [x] registration only with minimal permissions
- [ ] logout without /logout page
- [ ] preferences page
- [x] change password
- [ ] fail2ban integration
- [x] css dark mode `@media (prefers-color-scheme: dark) {}`
- [ ] dark mode toggle
@@ -23,7 +20,7 @@ My own homepage, bocken.org, built with svelte-kit.
- [x] verify randomize arrays based on day
- [x] notes for next time
- [ ] refactor, like, a lot
- [ ] expose json-ld for recipes https://json-ld.org/ https://schema.org/Recipe
- [ ] expose json-ld for recipes https://json-ld.org/ https://github.com/flauschtrud/broccoli
- [ ] reference other recipes in recipe
- [ ] add a link to the recipe
- [ ] add ingredients to the ingredients list
@@ -63,7 +60,7 @@ My own homepage, bocken.org, built with svelte-kit.
#### Gitea
- [ ] consistent theming
- [x] OpenID Connect
- [x] sane landing page
- [ ] sane landing page
#### Jellyfin
- [x] connect to LDAP
@@ -73,7 +70,7 @@ My own homepage, bocken.org, built with svelte-kit.
- [x] setup Oauth2proxy -> not necessary, authentik has proxy integrated
- [x] connect to OIDC using Oauth2proxy (using authentik)
- [ ] consistent theming
- [x] auto-login if not logged in
- [ ] auto-login if not logged in
#### Jitsi
- [ ] consistent theming
@@ -89,8 +86,8 @@ My own homepage, bocken.org, built with svelte-kit.
- [ ] OIDC integration (waiting on upstream)
#### Nextcloud
- [x] consistent theming
- [x] collabora integration
- [ ] consistent theming
- [ ] collabora integration
#### Transmission
- [x] move behind authentik
- [ ] move behind authentik

2266
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,21 +11,23 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^4.0.0",
"@sveltejs/adapter-auto": "^2.1.0",
"@sveltejs/kit": "^1.22.3",
"svelte": "^3.59.2",
"svelte-check": "^3.4.6",
"svelte-preprocess-import-assets": "^1.0.1",
"tslib": "^2.6.0",
"typescript": "^5.1.6",
"vite": "^5.0.0"
"vite": "^4.4.4"
},
"dependencies": {
"@auth/sveltekit": "^0.14.0",
"@sveltejs/adapter-node": "^2.0.0",
"@sveltejs/adapter-node": "^1.3.1",
"argon2": "^0.30.3",
"cheerio": "1.0.0-rc.12",
"jsonwebtoken": "^9.0.1",
"mongoose": "^7.4.0",
"sharp": "^0.32.3"
"sharp": "^0.32.3",
"svelte-cookie": "^1.0.1",
"sveltekit-oidc": "^0.0.8"
}
}

View File

@@ -1,29 +0,0 @@
import { SvelteKitAuth } from "@auth/sveltekit"
import Authentik from "@auth/core/providers/authentik"
import { AUTHENTIK_ID, AUTHENTIK_SECRET, AUTHENTIK_ISSUER } from "$env/static/private";
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Authentik({
clientId: AUTHENTIK_ID,
clientSecret: AUTHENTIK_SECRET,
issuer: AUTHENTIK_ISSUER,
})],
callbacks: {
// this feels like an extremely hacky way to get nickname and groups into the session object
// TODO: investigate if there's a better way to do this
jwt: async ({token, profile}) => {
if(profile){
token.nickname = profile.nickname;
token.groups = profile.groups;
}
return token;
},
session: async ({session, token}) => {
session.user.nickname = token.nickname;
session.user.groups = token.groups;
return session;
},
}
})

View File

@@ -1,32 +1,28 @@
import { authenticateUser } from "$lib/js/authenticate"
import type { Handle } from "@sveltejs/kit"
import { redirect } from "@sveltejs/kit"
import { error } from "@sveltejs/kit"
import { SvelteKitAuth } from "@auth/sveltekit"
import Authentik from "@auth/core/providers/authentik"
import { AUTHENTIK_ID, AUTHENTIK_SECRET, AUTHENTIK_ISSUER } from "$env/static/private";
import { sequence } from "@sveltejs/kit/hooks"
import * as auth from "./auth"
async function authorization({ event, resolve }) {
// Protect any routes under /authenticated
if (event.url.pathname.startsWith('/rezepte/edit') || event.url.pathname.startsWith('/rezepte/add')) {
const session = await event.locals.getSession();
if (!session) {
redirect(303, '/auth/signin');
export const handle : Handle = async({event, resolve}) => {
if(event.url.pathname.startsWith('/rezepte/edit') || event.url.pathname.startsWith('/rezepte/add')){
event.locals.user = await authenticateUser(event.cookies)
if(!event.locals.user){
throw redirect(303, "/login")
}
else if (! session.user.groups.includes('rezepte_users')) {
// strip last dir from url
// TODO: give indication of why access failed
const new_url = event.url.pathname.split('/').slice(0, -1).join('/');
redirect(303, new_url);
else if(!event.locals.user.access.includes("rezepte")){
throw error(401, "Your user does not have access to this page")
}
}
else if(event.url.pathname.startsWith('/abrechnung')){
event.locals.user = await authenticateUser(event.cookies)
if(!event.locals.user){
throw redirect(303, "/login")
}
else if(!event.locals.user.access.includes("abrechnung")){
throw error(401, "Your User does not have access to this page")
}
}
// If the request is still here, just proceed as normally
return resolve(event);
const response = await resolve(event)
return response
}
export const handle: Handle = sequence(
auth.handle,
authorization
);

View File

@@ -7,11 +7,6 @@ import "$lib/css/nordtheme.css";
import "$lib/css/shake.css";
import "$lib/css/icon.css";
export let do_margin_right = false;
// to manually override lazy loading for top cards
export let loading_strat : "lazy" | "eager" | undefined;
if(loading_strat === undefined){
loading_strat = "lazy"
}
if(icon_override){
current_month = recipe.season[0]
@@ -25,7 +20,6 @@ onMount(() => {
isloaded = document.querySelector("img")?.complete ? true : false
})
const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
</script>
<style>
.card_anchor{
@@ -50,9 +44,6 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
background-color: var(--blue);
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.3);
}
.icon{
font-family: "Noto Color Emoji", emoji, sans-serif;
}
#image{
width: 300px;
height: 255px;
@@ -185,11 +176,11 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
<a class=card_anchor href="/rezepte/{recipe.short_name}" class:search_me={search} data-tags=[{recipe.tags}] >
<div class="card" class:margin_right={do_margin_right}>
<div class=div_div_image >
<div class=div_image style="background-image:url(https://bocken.org/static/rezepte/placeholder/{img_name})">
<div class=div_image style="background-image:url({'https://bocken.org/static/rezepte/placeholder/' + recipe.short_name + '.webp'})">
<noscript>
<img id=image class="backdrop_blur" src="https://bocken.org/static/rezepte/thumb/{img_name}" loading={loading_strat} alt="{recipe.alt}"/>
<img id=image class="backdrop_blur" src={'https://bocken.org/static/rezepte/thumb/' + recipe.short_name + '.webp'} loading=lazy alt="{recipe.alt}"/>
</noscript>
<img class:blur={!isloaded} id=image class="backdrop_blur" src={'https://bocken.org/static/rezepte/thumb/' + recipe.short_name + '.webp'} loading={loading_strat} alt="{recipe.alt}" on:load={() => isloaded=true}/>
<img class:blur={!isloaded} id=image class="backdrop_blur" src={'https://bocken.org/static/rezepte/thumb/' + recipe.short_name + '.webp'} loading=lazy alt="{recipe.alt}" on:load={() => isloaded=true}/>
</div>
</div>
{#if icon_override || recipe.season.includes(current_month)}
@@ -199,7 +190,7 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
<a class=category href="/rezepte/category/{recipe.category}" >{recipe.category}</a>
<div>
<div class=name>{@html recipe.name}</div>
<div class=description>{@html recipe.description}</div>
<div class=description>{recipe.description}</div>
</div>
<div class=tags>
{#each recipe.tags as tag}

View File

@@ -3,21 +3,11 @@
import Cross from '$lib/assets/icons/Cross.svelte'
import "$lib/css/shake.css"
import "$lib/css/icon.css"
import { onMount } from 'svelte'
// all data shared with rest of page in card_data
export let card_data
export let image_preview_url
onMount( () => {
fetch(image_preview_url, { method: 'HEAD' })
.then(response => {
if(response.redirected){
image_preview_url = ""
}
})
})
import { img } from '$lib/js/img_store';
if(!card_data.tags){

View File

@@ -413,7 +413,7 @@ h3{
}
</style>
<div class=list_wrapper >
<div class=list_wrapper>
<h4>Portionen:</h4>
<p contenteditable type="text" bind:innerText={portions_local} on:blur={set_portions}></p>
@@ -455,13 +455,9 @@ h3{
</button>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)} >
{ingredient.amount} {ingredient.unit}
</div>
<div on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)} >{ingredient.amount} {ingredient.unit}</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class=force_wrap on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)} >
{@html ingredient.name}
</div>
<div class=force_wrap on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)} >{ingredient.name}</div>
<div class=mod_icons><button class="action_button button_subtle" on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)}>
<Pen fill=var(--nord1) height=1em width=1em></Pen></button>
<button class="action_button button_subtle" on:click="{() => remove_ingredient(list_index, ingredient_index)}"><Cross fill=var(--nord1) height=1em width=1em></Cross></button></div>

View File

@@ -509,9 +509,7 @@ h3{
</button>
</div>
<div>
<div on:click={() => show_modal_edit_step(list_index, step_index)}>
{@html step}
</div>
<div on:click={() => show_modal_edit_step(list_index, step_index)}>{step}</div>
<div><button class="action_button button_subtle" on:click={() => show_modal_edit_step(list_index, step_index)}>
<Pen fill=var(--nord1)></Pen>
</button>

View File

@@ -19,6 +19,23 @@ link_els.forEach((el) => {
</script>
<style>
:global(*){
box-sizing: border-box;
font-family: sans-serif;
}
:global(body){
margin:0;
padding:0;
background-color: #fbf9f3;
overflow-x: hidden;
}
@media (prefers-color-scheme: dark) {
:global(body){
color: white;
background-color: var(--background-dark);
}
}
nav{
position: sticky;
background-color: var(--nord0);
@@ -29,7 +46,6 @@ nav{
justify-content: space-between !important;
align-items: center;
box-shadow: 0 1em 1rem 0rem rgba(0,0,0,0.4);
height: 4rem;
}
nav[hidden]{
display:block;
@@ -81,16 +97,16 @@ nav[hidden]{
padding-inline: 0.5rem;
}
:global(svg.symbol){
height: 4rem;
width: 4rem;
height: 3.5rem;
width: 3.5rem;
border-radius: 10000px;
}
/*:global(a:has(svg.symbol)){
:global(a:has(svg.symbol)){
padding: 0 !important;
width: 4rem;
height: 4rem;
width: 3.5rem;
height: 3.5rem;
margin-left: 1rem;
}*/
}
.wrapper{
display:flex;
flex-direction: column;
@@ -120,8 +136,8 @@ footer{
background-color: unset;
display: block;
fill: white;
margin-inline: 0.5rem;
width: 2rem;
margin-inline: 1rem;
width: 2.5rem;
aspect-ratio: 1;
}
.nav_button svg{
@@ -181,7 +197,7 @@ footer{
<div>
<div class=button_wrapper>
<a href="/"><Symbol></Symbol></a>
<button class=nav_button on:click={() => {toggle_sidebar()}}><svg xmlns="http://www.w3.org/2000/svg" height="0.5em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></button>
<button class=nav_button on:click={() => {toggle_sidebar()}}><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></button>
</div>
<nav hidden class=nav_site>
<a class=entry href="/"><Symbol></Symbol></a>

View File

@@ -5,7 +5,6 @@
</script>
<style>
a{
font-family: "Noto Color Emoji", emoji;
font-size: 2rem;
text-decoration: none;
padding: 0.5em;

View File

@@ -8,7 +8,6 @@
<style>
a{
font-family: "Noto Color Emoji", emoji, sans-serif;
font-size: 2rem;
text-decoration: none;
padding: 0.5em;

View File

@@ -1,8 +1,7 @@
<script>
import { onMount } from 'svelte';
import { onNavigate } from "$app/navigation";
export let data
let multiplier;
let multiplier = 1
let custom_mul = "…"
onMount(() => {
@@ -10,10 +9,6 @@ onMount(() => {
const urlParams = new URLSearchParams(window.location.search);
multiplier = urlParams.get('multiplier') || 1;
})
onNavigate(() => {
const urlParams = new URLSearchParams(window.location.search);
multiplier = urlParams.get('multiplier') || 1;
})
function convertFloatsToFractions(inputString) {
// Split the input string into individual words
@@ -232,7 +227,7 @@ span
{/if}
<div class=ingredients_grid>
{#each list.list as item}
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div><div class=name>{@html item.name.replace("{{multiplier}}", multiplier * item.amount)}</div>
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div><div class=name>{@html item.name.replace("{{multiplier}}", multiplier)}</div>
{/each}
</div>
{/each}

View File

@@ -1,91 +0,0 @@
<style>
:global(.links_grid a:nth-child(4n)),
:global(.links_grid a:nth-child(4n) svg){
background-color: var(--nord4);
fill: var(--nord11);
}
:global(.links_grid a:nth-child(4n+1)),
:global(.links_grid a:nth-child(4n+1) svg){
background-color: var(--nord6);
fill: var(--nord10);
}
:global(.links_grid a:nth-child(4n+2)){
background-color: var(--nord5);
}
:global(.links_grid a:nth-child(4n+3)){
background-color: var(--nord5);
}
:global(a){
text-decoration: unset;
color: var(--nord0);
transition: 200ms;
}
:global(.links_grid a:hover){
box-shadow: 1em 1em 2em 1em rgba(0,0,0, 0.3);
}
:global(.links_grid a){
box-shadow: 0.2em 0.2em 1em 1em rgba(0,0,0, 0.1);
}
.links_grid{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 1000px;
margin-inline: auto;
padding: 2rem 1rem;
}
:global(.links_grid a){
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-decoration: unset;
color: var(--nord0);
transition: 200ms;
width: 100%;
padding: 1rem;
}
:global(.links_grid a:hover){
scale: 1.02;
}
:global(.links_grid a :is(svg, img)){
height: 120px;
fill: var(--nord0);
}
:global(.links_grid h3){
font-size: 1.5rem;
}
@media (prefers-color-scheme: dark){
:global(.links_grid h3){
color: white;
}
:global(.links_grid a:nth-child(4n)),
:global(.links_grid a:nth-child(4n) svg){
background-color: var(--nord6-dark);
fill: var(--nord11);
}
:global(.links_grid a:nth-child(4n+1)),
:global(.links_grid a:nth-child(4n+1) svg){
background-color: var(--accent-dark);
fill: var(--nord9);
}
:global(.links_grid a:nth-child(4n+2)),
:global(.links_grid a:nth-child(4n+2) svg){
background-color: var(--nord1);
fill: var(--nord8);
}
:global(.links_grid a:nth-child(4n+3)),
:global(.links_grid a:nth-child(4n+3) svg){
background-color: var(--background-dark);
fill: var(--nord7);
}
}
</style>
<div class=links_grid>
<slot></slot>
</div>

View File

@@ -7,7 +7,6 @@
}
svg{
transition: 100ms;
height: 3em;
}
svg:hover,
svg:focus-visible
@@ -30,9 +29,14 @@
</style>
<svg
width="calc(45.742325px/ 1.3)"
height="calc(80.310539px / 1.3)"
viewBox="0 0 45.742326 80.310541"
version="1.1"
xmlns="http://www.w3.org/2000/svg">
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<g class=stroke
id="branches"
transform="translate(-42.033271,-37.145192)" >

View File

@@ -2,23 +2,15 @@
export let src
export let placeholder_src
let isloaded=false
let isredirected=false
import { onMount } from "svelte";
onMount(() => {
const el = document.querySelector("img")
if(el.complete){
isloaded = true
}
fetch(src, { method: 'HEAD' })
.then(response => {
isredirected = response.redirected
})
})
function show_dialog_img(){
if(isredirected){
return
}
if(document.querySelector("img").complete){
document.querySelector("#img_carousel").showModal();
}
@@ -168,12 +160,11 @@ dialog button{
.zoom-in{
cursor: zoom-in;
}
</style>
<section class="section">
<figure class="image-container">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class:zoom-in={isloaded && !isredirected} on:click={show_dialog_img}>
<div class:zoom-in={isloaded} on:click={show_dialog_img}>
<div class=placeholder style="background-image:url({placeholder_src})" >
<div class=placeholder_blur>
<img class:unblur={isloaded} id=image {src} on:load={() => {isloaded=true}} alt=""/>

View File

@@ -1,12 +1,11 @@
<script lang="ts">
import { onMount } from "svelte";
export let user;
export let username;
function toggle_options(){
const el = document.querySelector("#options")
el.hidden = !el.hidden
}
let src="https://bocken.org/static/user/thumb/" + username + ".webp"
onMount( () => {
document.addEventListener("click", (e) => {
const el = document.querySelector("#button")
@@ -102,12 +101,6 @@
/* (B2) BOTTOM "CALLOUT TAIL" */
h2{
margin-block: 0;
font-size: 1.2rem;
}
h2 + p{
padding-top: 0;
margin-top: 0;
font-size: 1.2rem;
}
@media screen and (max-width: 800px){
#options{
@@ -132,17 +125,16 @@ h2 + p{
}
</style>
{#if user}
<button on:click={toggle_options} style="background-image: url(https://bocken.org/static/user/thumb/{user.nickname}.webp)" id=button>
{#if username}
<button on:click={toggle_options} style="background-image: url({src})" id=button>
<div id=options class="speech top" hidden>
<h2>{user.name}</h2>
<p>({user.nickname})</p>
<h2>{username}</h2>
<ul>
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
<li><a href="/auth/signout" >Log Out</a></li>
<li><a href="/settings" >Einstellungen</a></li>
<li><a href="/logout" >Log Out</a></li>
</ul>
</div>
</button>
{:else}
<a class=entry href=/auth/signin>Log In</a>
<a class=entry href=/login>Log In</a>
{/if}

View File

@@ -1,32 +0,0 @@
div.gebet{
text-align: center;
font-size: 1.25em;
}
ul {
font-size: 120%;
}
.gebet v{
margin:0;
}
.gebet v:lang(la) {
color: var(--nord6);
}
.gebet.bilingue v:lang(de){
color: grey;
}
i{
font-style: normal;
color: var(--nord11);
font-weight: 900;
}
i.txt {
font-size: 70%;
font-weight: normal;
}
v{
display: block;
}
.mobile audio{
width:70%;
}

View File

@@ -27,28 +27,3 @@
--background-dark: #21201b;
--font-default-dark: #ffffff;
}
a:not(:visited){
color: var(--red);
}
a:visited{
color: var(--purple);
}
*{
box-sizing: border-box;
font-family: Helvetica, Arial, "Noto Sans", sans-serif
}
body{
margin:0;
padding:0;
background-color: #fbf9f3;
overflow-x: hidden;
}
@media (prefers-color-scheme: dark) {
body{
color: white;
background-color: var(--background-dark);
}
}

View File

@@ -1,65 +0,0 @@
@font-face {
font-family: 'UnifrakturMaguntia';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/UnifrakturMaguntia20.ttf');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
.bibel{
font-family: 'UnifrakturMaguntia', cursive;
-moz-font-feature-settings: "cv11";
-webkit-font-feature-settings: "cv11";
-ms-font-feature-settings: "cv11";
font-feature-settings: "cv11";
font-size: 1.2rem;
}
li::marker, i, li:lang(la){
font-family: serif;
}
ol{
list-style-position: inside;
}
a {
text-decoration: underline;
}
h1{
font-size: 4rem;
}
h2{
font-size: 2.5rem;
}
h3{
font-size: 2rem;
}
h4{
font-size: 1.5rem;
}
.quote p.title{
font-weight: bold;
}
/*.quote .bibel{
margin-bottom: 1em;
}*/
.quote q{
display: block;
width: 90%;
margin-left: auto;
margin-right: auto;
}
.tod, .grund{
font-size: 1.5rem;
display: block;
text-align: center;
margin-bottom: 1rem;
margin-top: -1rem;
}
.schott q{
quotes: "«" "»";
}
.predigt video {
display: block;
width: 80%;
margin: auto;
}

View File

@@ -1,204 +0,0 @@
@font-face {
font-family: 'crosses';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/crosses.ttf);
}
@font-face {
font-family: 'LibertineMinimal';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/LinLibertine_minimal.ttf);
}
.sbeads{
fill: var(--nord10);
}
.chain{
stroke:black;
stroke-width: 0.7;
stroke-miterlimit: 4;
stroke: gray;
fill: none;
}
.sbeads circle.hitbox{
r: 3.2px;
stroke-width:0;
}
#start1 circle{
cx:15.559271px;
cy: 20.881956px;
}
#start2 circle{
cx:21.633902px;
cy:20.367514px;
}
#start3 circle{
cx:27.96961px;
cy:21.178484px;
}
#lbead5 circle{
cx:118.50725px;
cy:59.477211px;
}
#lbead4 circle{
cx:126.81134px;
cy:15.751753px;
}
#lbead1 circle{
cx:7.6719489px;
cy:25.364584px;
}
#lbead2 circle{
cx:36.798512px;
cy:23.486462px;
}
#lbead3 circle{
cx:84.105789px;
cy:3.0456686px;
}
#lbead6 circle{
cx:72.185097px;
cy:64.006859px;
}
#start1:hover .msg,
#start2:hover .msg,
#start3:hover .msg,
#secret1:hover .msg,
#secret2:hover .msg,
#secret3:hover .msg,
#secret4:hover .msg,
#secret5:hover .msg,
#lbeads .beforedecades:hover .msg,
#lbeads .afterdecade:hover .msg,
#cross:hover .msg
{
display:block;
}
#start1:hover .sbeads circle:not(.hitbox),
#start2:hover .sbeads circle:not(.hitbox),
#start3:hover .sbeads circle:not(.hitbox),
#secret1:hover .sbeads circle,
#secret2:hover .sbeads circle,
#secret3:hover .sbeads circle,
#secret4:hover .sbeads circle,
#secret5:hover .sbeads circle
{
fill: var(--nord11);
r: 1.5px;
}
#lbead1:hover .lbead,
#lbead2:hover .lbead,
#lbead3:hover .lbead,
#lbead4:hover .lbead,
#lbead5:hover .lbead,
#lbead6:hover .lbead{
r: 2.8px;
fill: var(--nord11);
}
#cross:hover .symbol{
fill: var(--nord11);
stroke: var(--nord11);
stroke-width: 0.25;
}
#lbeads.msg{
display:block;
}
.sbeads circle{
r: 1.25px;
}
.msg .diff{
fill: var(--nord11);
}
.msg .b{
font-family: crosses;
font-weight: bold;
}
.msg .title{
fill: var(--nord10);
font-weight: bold;
font-size: 5px;
}
.msg{
font-size: 4px;
stroke: none;
fill: var(--nord4);
display:none;
}
text{
font-family: LibertineMinimal;
}
#lbeads circle.hitbox{
r:5px;
stroke:none;
stroke-width:0;
}
.lbead{
fill: var(--nord12);
r: 2.65px;
}
.hitbox{
opacity:0;
stroke-width: 2;
fill: red;
stroke: red;
}
#coin circle{
r: 2.7px;
fill:darkgray;
}
#coin text{
fill:var(--nord0);
font-size: 4.259px;
line-height:1.25;
font-family: crosses;
}
#cross .symbol{
font-family: crosses;
fill: var(--nord4);
font-size: 17.3637px;
line-height: 1.25;
stroke-width:0.434093
}
table{
width: 100%;
border-collapse: collapse;
}
td{
text-align:center;
border-left: 1px solid;
border-right: 1px solid;
border-color: var(--nord2);
padding-left: 5px;
padding-right: 5px;
}
tr :last-child{
border-right: none;
}
tr :first-child{
border-left: 0px solid;
}
thead td{
color: var(--nord4);
border-bottom-width: 3px;
border-bottom-color: var(--nord10);
border-bottom-style: dotted;
font-size: 110%;
font-weight: bold;
}
.table{
width:100%;
overflow-x: auto;
}

View File

@@ -1,37 +0,0 @@
import mongoose from 'mongoose';
import { MONGO_URL } from '$env/static/private';
/*
0 - disconnected
1 - connected
2 - connecting
3 - disconnecting
4 - uninitialized
*/
const mongoConnection = {
isConnected: 0,
};
export const dbConnect = async () => {
if (mongoConnection.isConnected === 1) {
return;
}
if (mongoose.connections.length > 0) {
mongoConnection.isConnected = mongoose.connections[0].readyState;
if (mongoConnection.isConnected === 1) {
return;
}
await mongoose.disconnect();
}
await mongoose.connect(MONGO_URL ?? '');
mongoConnection.isConnected = 1;
};
export const dbDisconnect = async () => {
if (process.env.NODE_ENV === 'development') return;
if (mongoConnection.isConnected === 0) return;
await mongoose.disconnect();
mongoConnection.isConnected = 0;
};

View File

@@ -0,0 +1,41 @@
import type { RequestEvent } from "@sveltejs/kit";
import { COOKIE_SECRET } from "$env/static/private";
import pkg from 'jsonwebtoken';
const { verify } = pkg;
import { error } from "@sveltejs/kit";
import { dbConnect, dbDisconnect } from "../../utils/db";
import { User } from "../../models/User";;
export async function authenticateUser(cookies){
// Set your master secret key (replace with your own secret)
const masterSecret = COOKIE_SECRET;
const secretKey = masterSecret
let decoded
try{
const cookie : string = cookies.get("UserSession")
if(cookie){
decoded = await verify(cookie, secretKey);
}
}
catch(e){
return null
}
if(decoded){
await dbConnect()
let res = await User.findOne({username: decoded.username}, 'access').lean();
await dbDisconnect()
if(!res){
throw error(404, "User for this Cookie does no longer exist")
}
return {
username: decoded.username,
access: res.access,
_id: res._id.toString(),
}
}
else{
return null
}
}

View File

@@ -0,0 +1,33 @@
import type { RequestEvent } from "@sveltejs/kit";
import { COOKIE_SECRET } from "$env/static/private";
import pkg from 'jsonwebtoken';
const { verify } = pkg;
import { error } from "@sveltejs/kit";
import { dbConnect, dbDisconnect } from "../../utils/db";
import { User } from "../../models/User";;
export async function get_username(cookies){
// Set your master secret key (replace with your own secret)
const masterSecret = COOKIE_SECRET;
const secretKey = masterSecret
let decoded
try{
const cookie : string = cookies.get("UserSession")
if(cookie){
decoded = await verify(cookie, secretKey);
}
}
catch(e){
return null
}
if(decoded){
return {
username: decoded.username,
}
}
else{
return null
}
}

View File

@@ -0,0 +1,10 @@
import { hash } from 'argon2'
export async function hashPassword(password, salt) {
try {
const hashedPassword = await hash(password, salt); // Hash the password with the salt and pepper
return hashedPassword;
} catch (error) {
console.error('Error hashing password:', error);
}
}

View File

@@ -1,4 +1,6 @@
const time = new Date()
const MS_PER_DAY = 86400000
let seed = Math.floor(time.getTime()/MS_PER_DAY)
function mulberry32(a) {
return function() {
var t = a += 0x6D2B79F5;
@@ -9,8 +11,6 @@ function mulberry32(a) {
}
export function rand_array(array){
let time = new Date()
const seed = Math.floor(time.getTime()/MS_PER_DAY)
let rand = mulberry32(seed)
array.sort((a,b) => 0.5 - rand())
return array

View File

@@ -1,25 +0,0 @@
import mongoose from 'mongoose';
const PaymentSchema = new mongoose.Schema(
{
type: {type: String, required: true, enum: ['payment', 'reimbursement']},
name: {type: String, required: true},
category : {type: String, required: false,},
date: {type: Date, default: Date.now},
images: [ {
mediapath: {type: String, required: false},
}],
description: {type: String, required: false},
note: {type: String, required: false},
tags : [String],
original_amount: {type: Number, required: true},
total_amount: {type: Number, required: true},
personal_amounts: [{
user: {type:String, required: true},
amount: {type: Number, required: true, default:0}
}],
currency: {type: String, required: true, default: 'CHF'},
}, {timestamps: true}
);
export const Payment= mongoose.model("Payment", PaymentSchema);

15
src/models/Payment.ts Normal file
View File

@@ -0,0 +1,15 @@
import mongoose from 'mongoose';
const PaymentSchema= new mongoose.Schema(
{
payee: {type: String, required: true},
amount: {type: Number, required: true},
for_self: {type: Number},
for_other: {type: Number},
description: {type: String},
added_by: {type: String},
date: {type: Date, required: true, default: Date.now},
}, {timestamps: true}
);
export const Payment = mongoose.model("Payment", PaymentSchema);

12
src/models/User.ts Normal file
View File

@@ -0,0 +1,12 @@
import mongoose from 'mongoose';
const UserSchema = new mongoose.Schema(
{
username: {type: String, required: true, unique: true},
pass_hash: {type: String, required: true},
salt : {type: String, required: true},
access: [String], //rezepte, flims, abrechnung, ...
}, {timestamps: true}
);
export const User = mongoose.model("User", UserSchema);

View File

@@ -1,7 +1,7 @@
import type { PageServerLoad } from "./$types"
import { get_username } from '$lib/js/get_username';;
import type { Actions, PageServerLoad } from "./$types"
import { error } from "@sveltejs/kit"
export const load : PageServerLoad = (async ({locals}) => {
return {
session: await locals.auth(),
}
export const load = (async ({cookies}) => {
return { user: await get_username(cookies) }
});

View File

@@ -2,15 +2,22 @@
import Header from '$lib/components/Header.svelte'
import UserHeader from '$lib/components/UserHeader.svelte';
export let data
let user;
if(data.session){
user = data.session.user
let username = ""
if(data.user){
username = data.user.username
}
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/rezepte">Rezepte</a></li>
<li><a href="/git">Git</a></li>
<li><a href="https://stream.bocken.org">Streaming</a></li>
<li><a href="/bilder">Bilder</a></li>
<li><a href="https://meet.bocken.org">Jitsi</a></li>
<li><a href="https://cloud.bocken.org">Cloud</a></li>
<li><a href="/searx">Searx</a></li>
</ul>
<UserHeader {user} slot=right_side></UserHeader>
<UserHeader {username} slot=right_side></UserHeader>
<slot></slot>
</Header>

View File

@@ -1,4 +1,4 @@
<script lang="ts">
<script>
import "$lib/css/nordtheme.css";
import LinksGrid from "$lib/components/LinksGrid.svelte";
export let data;
@@ -48,18 +48,10 @@ section h2{
}
}
</style>
<svelte:head>
<title>Bocken</title>
<meta name="description" content="Die persönliche Website von Alexander Bocken" />
<meta property="og:image" content="https://bocken.org/static/favicon.png" />
<meta property="og:image:secure_url" content="https://bocken.org/favicon.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:alt" content="Das Familienwappen simplifiziert" />
</svelte:head>
{#if ! data.session}
{#if ! data.user}
<section class=hero>
<img src="https://bocken.org/static/user/full/alexander.webp" alt="Smiling Alexander Bocken">
<img src="https://bocken.org/static/user/full/Alexander.webp" alt="Smiling Alexander Bocken">
<div>
<h1><q>Willkommen auf bocken.org</q></h1>
<p>
@@ -67,7 +59,7 @@ section h2{
Alles ist selbst gehostet bei mir daheim auf einem kleinen Mini-Server (Arch, btw).
</p>
<p>
Zu empfehlen ist meine stetig wachsende Rezeptsammlung. Dort findest du viele leckere Rezepte, die ich selbst ausprobiert habe und ständig weiterfeilsche.
Zu empfeheln ist meine stetig wachsende Rezeptsammlung. Dort findest du viele leckere Rezepte, die ich selbst ausprobiert habe und ständig weiterfeilsche.
Zudem kannst du gerne meine Suchmaschine oder auch Jitsi-instanz für Videokonferenzen nutzen.
Einiges ist hinter einem Login versteckt, anderes ist öffentlich zugänglich.
Wer sich ein bisschen mit Programmieren auskennt, kann auch gerne in meinen Git-Repositories stöbern.
@@ -77,14 +69,13 @@ section h2{
{/if}
<section>
<h2>Seiten</h2>
<LinksGrid>
<a href="rezepte">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M240 144A96 96 0 1 0 48 144a96 96 0 1 0 192 0zm44.4 32C269.9 240.1 212.5 288 144 288C64.5 288 0 223.5 0 144S64.5 0 144 0c68.5 0 125.9 47.9 140.4 112h71.8c8.8-9.8 21.6-16 35.8-16H496c26.5 0 48 21.5 48 48s-21.5 48-48 48H392c-14.2 0-27-6.2-35.8-16H284.4zM144 80a64 64 0 1 1 0 128 64 64 0 1 1 0-128zM400 240c13.3 0 24 10.7 24 24v8h96c13.3 0 24 10.7 24 24s-10.7 24-24 24H280c-13.3 0-24-10.7-24-24s10.7-24 24-24h96v-8c0-13.3 10.7-24 24-24zM288 464V352H512V464c0 26.5-21.5 48-48 48H336c-26.5 0-48-21.5-48-48zM48 320h80 16 32c26.5 0 48 21.5 48 48s-21.5 48-48 48H160c0 17.7-14.3 32-32 32H64c-17.7 0-32-14.3-32-32V336c0-8.8 7.2-16 16-16zm128 64c8.8 0 16-7.2 16-16s-7.2-16-16-16H160v32h16zM24 464H200c13.3 0 24 10.7 24 24s-10.7 24-24 24H24c-13.3 0-24-10.7-24-24s10.7-24 24-24z"/></svg>
<h3>Rezepte</h3>
</a>
<a href=https://git.bocken.org>
<a href=git>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"/></svg>
<h3>Git</h3>
</a>
@@ -94,7 +85,7 @@ section h2{
<h3>Streaming</h3>
</a>
<a href="https://bilder.bocken.org">
<a href="/bilder">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>
<h3>Familienbilder</h3>
</a>
@@ -109,7 +100,7 @@ section h2{
<h3>Videokonferenzen</h3>
</a>
<a href="https://searx.bocken.org">
<a href="/searx">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
<h3>Suchmaschine</h3>
</a>
@@ -131,27 +122,5 @@ section h2{
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg>
<h3>Transmission</h3>
</a>
<!-- instead of redirect_to_docs(), use a normal link with internal checks for data.session -->
{#if !data.session}
<a href="/auth/signin">
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m106 512h300c24.814 0 45-20.186 45-45v-317h-105c-24.814 0-45-20.186-45-45v-105h-195c-24.814 0-45 20.186-45 45v422c0 24.814 20.186 45 45 45zm60-301h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h120c8.291 0 15 6.709 15 15s-6.709 15-15 15h-120c-8.291 0-15-6.709-15-15s6.709-15 15-15z"/><path d="m346 120h96.211l-111.211-111.211v96.211c0 8.276 6.724 15 15 15z"/></svg>
<h3>Dokumente</h3>
</a>
{:else if data.session.user.groups.includes("paperless_users")}
<a href="https://docs.bocken.org">
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m106 512h300c24.814 0 45-20.186 45-45v-317h-105c-24.814 0-45-20.186-45-45v-105h-195c-24.814 0-45 20.186-45 45v422c0 24.814 20.186 45 45 45zm60-301h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h120c8.291 0 15 6.709 15 15s-6.709 15-15 15h-120c-8.291 0-15-6.709-15-15s6.709-15 15-15z"/><path d="m346 120h96.211l-111.211-111.211v96.211c0 8.276 6.724 15 15 15z"/></svg>
<h3>Dokumente</h3>
</a>
{:else if data.session.user.groups.includes("paperless_eltern_users")}
<a href="https://dokumente.bocken.org">
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m106 512h300c24.814 0 45-20.186 45-45v-317h-105c-24.814 0-45-20.186-45-45v-105h-195c-24.814 0-45 20.186-45 45v422c0 24.814 20.186 45 45 45zm60-301h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h120c8.291 0 15 6.709 15 15s-6.709 15-15 15h-120c-8.291 0-15-6.709-15-15s6.709-15 15-15z"/><path d="m346 120h96.211l-111.211-111.211v96.211c0 8.276 6.724 15 15 15z"/></svg>
<h3>Dokumente</h3>
</a>
{/if}
<a href=https://audio.bocken.org>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>
<h3>Hörbücher & Podcasts</h3>
</a>
</LinksGrid>
</section>

View File

@@ -0,0 +1,35 @@
import { redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types"
import { error } from "@sveltejs/kit"
export const actions: Actions = {
login: async (event) => {
const data = await event.request.formData()
const res = await event.fetch('/api/user/login',
{method: 'POST',
body: JSON.stringify({
username: data.get('username'),
password: data.get('password'),
})
}
)
const jwt = await res.json()
if(res.ok){
event.cookies.set("UserSession", jwt, {
path: "/",
httpOnly: true,
sameSite: "strict",
secure: process.env.NODE_ENV === "production",
maxAge: 60 * 60 * 24 * 7, // 1 week
})
throw redirect(303, "/")
}
else{
throw error(401, jwt.message)
}
},
logout: async () => {
throw redirect(303, "/logout")
},
}

View File

@@ -0,0 +1,18 @@
<script>
import "$lib/css/form.css"
import "$lib/css/nordtheme.css"
</script>
<form action="?/login" method=POST>
<h1>Log In</h1>
<label>
Benutzername
<input type="text" name="username" required>
</label>
<label>
Passwort
<input name="password" type="password" required>
</label>
<button type="submit">Log In</button>
<p>Noch keinen Account? <a href=/register>Hier registrieren</a>.</p>
</form>

View File

@@ -0,0 +1 @@
{"terminal": "nvimterm"}

View File

@@ -0,0 +1,8 @@
import redirect from "@sveltejs/kit"
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({cookies}) => {
cookies.delete("UserSession")
}
} satisfies Actions;

View File

@@ -0,0 +1,7 @@
<script>
import "$lib/css/form.css"
</script>
<form method='POST'>
<h1>Log out</h1>
<button type='submit'>Log Out</button>
</form>

View File

@@ -1,96 +0,0 @@
import type { RequestHandler } from '@sveltejs/kit';
import fs from 'fs';
import path from 'path';
import { mkdir } from 'fs/promises';
import { Payment } from '$lib/models/Payment'; // adjust path as needed
import { dbConnect, dbDisconnect } from '$lib/db/db';
const UPLOAD_DIR = '/var/lib/www/static/test';
const BASE_CURRENCY = 'CHF'; // Default currency
export const POST: RequestHandler = async ({ request, locals }) => {
const formData = await request.formData();
try {
const name = formData.get('name') as string;
const category = formData.get('category') as string;
const date= new Date(formData.get('date') as string);
const description = formData.get('description') as string;
const note = formData.get('note') as string;
const tags = JSON.parse(formData.get('tags') as string) as string[];
let currency = formData.get('currency') as string;
let original_amount = parseFloat(formData.get('original_amount') as string);
let total_amount = NaN;
// if currency is not BASE_CURRENCY, fetch current conversion rate using frankfurter API and date in YYYY-MM-DD format
if (!currency || currency === BASE_CURRENCY) {
currency = BASE_CURRENCY;
total_amount = parseFloat(formData.get('total_amount') as string);
} else {
const date_fmt = date.toISOString().split('T')[0]; // Convert date to YYYY-MM-DD format
// Fetch conversion rate logic here (not implemented in this example)
const res = await fetch(`https://api.frankfurter.app/${date_fmt}?from=${currency}&to=${BASE_CURRENCY}`)
const { result } = await res.json();
if (!result || !result[BASE_CURRENCY]) {
return new Response(JSON.stringify({ message: 'Currency conversion failed.' }), { status: 400 });
}
// Assuming you want to convert the total amount to BASE_CURRENCY
const conversionRate = parseFloat(result.rates[BASE_CURRENCY]);
alert(`Conversion rate from ${currency} to ${BASE_CURRENCY} on ${date_fmt}: ${conversionRate}`);
total_amount = original_amount * conversionRate;
}
const personal_amounts = JSON.parse(formData.get('personal_amounts') as string) as { user: string, amount: number }[];
if (!name || isNaN(total_amount)) {
return new Response(JSON.stringify({ message: 'Invalid required fields.' }), { status: 400 });
}
// await mkdir(UPLOAD_DIR, { recursive: true });
const images: { mediapath: string }[] = [];
const imageFiles = formData.getAll('images') as File[];
for (const file of imageFiles) {
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const safeName = `${Date.now()}_${file.name.replace(/[^a-zA-Z0-9_.-]/g, '_')}`;
const fullPath = path.join(UPLOAD_DIR, safeName);
//fs.writeFileSync(fullPath, buffer);
images.push({ mediapath: `/static/test/${safeName}` });
}
await dbConnect();
const payment = new Payment({
name,
category,
date,
description,
note,
tags,
total_amount,
original_amount,
currency,
personal_amounts,
images
});
// let auth = await locals.auth();
// // if(!auth){
// throw error(401, "Not logged in")
// }
try{
await Payment.create(payment);
} catch(e){
throw error(400, e)
}
await dbDisconnect();
return new Response(JSON.stringify({ message: 'Payment event created successfully.' }), { status: 201 });
} catch (err) {
console.error(err);
return new Response(JSON.stringify({ message: 'Error processing request.' }), { status: 500 });
}
};

View File

@@ -0,0 +1,61 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Payment } from '../../../../models/Payment';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';;
import sharp from 'sharp';
import path from 'path';
import {IMAGE_DIR} from '$env/static/private';
export const POST: RequestHandler = async ({request, cookies}) => {
const user = await authenticateUser(cookies)
if(!user){
throw error(401, "Not logged in")
}
if(!user.access.includes("abrechnung")){
throw error(401, "This user does not have permissions to add payments")
}
else{
const formData = await request.formData();
const json = {
amount: formData.get("amount"),
for_self: formData.get("for_self"),
for_other: formData.get("for_other"),
payee: formData.get("payee"),
added_by: user._id
}
await dbConnect();
let id;
try{
id = (await Payment.create(json))._id.toString();
} catch(e){
await dbDisconnect();
throw error(400, e)
}
await dbDisconnect();
const img = formData.get("file")
if(img){
//this feels stupid, is there a smarter way directly to Buffer?
const full_res = Buffer.from(await img.arrayBuffer())
await sharp(full_res)
.toFormat('webp')
.toFile(path.join(IMAGE_DIR,
"abrechnung",
"full",
id + '.webp'))
await sharp(full_res)
.resize({width: 20})
.toFormat('webp')
.toFile(path.join(IMAGE_DIR,
"abrechnung",
"placeholder",
id + '.webp'))
}
return new Response(JSON.stringify({message: "Added payment successfully"}),{
status: 200,
});
}
};

View File

@@ -0,0 +1,24 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Payment } from '../../../../models/Payment';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, cookies}) => {
let json = await request.json()
const user = await authenticateUser(cookies)
if(!user) throw error(401, "Need to be logged in")
if(!user.access.includes("abrechnung")){
throw error(401, "Insufficient permissions")
}
else{
await dbConnect();
await Payment.findOneAndDelete({_id: json.id});
await dbDisconnect();
return new Response(JSON.stringify({msg: "Deleted payment successfully"}),{
status: 200,
});
}
}

View File

@@ -0,0 +1,27 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Payment } from '../../../../models/Payment';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json()
const json = message.payment
const user = await authenticateUser(cookies)
if(!user){
throw error(403, "Not logged in")
}
else if(!user.access.includes("abrechnung")){
throw error(403, "This user does not have edit permissions for payments")
}
else{
await dbConnect();
await Payment.findOneAndUpdate({_id: json.id}, json);
await dbDisconnect();
return new Response(JSON.stringify({msg: "Edited payment successfully"}),{
status: 200,
});
}
};

View File

@@ -0,0 +1,27 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Payment } from '../../../../../models/Payment';
import { dbConnect, dbDisconnect } from '../../../../../utils/db';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json()
const json = message.payment
const user = await authenticateUser(cookies)
if(!user){
throw error(403, "Not logged in")
}
else if(!user.access.includes("abrechnung")){
throw error(403, "This user does not have edit permissions for payments")
}
else{
await dbConnect();
const payment = await Payment.findOne({_id: json.id}).lean();
await dbDisconnect();
return new Response(JSON.stringify({payment}),{
status: 200,
});
}
};

View File

@@ -0,0 +1,28 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Payment } from '../../../../../models/Payment';
import { dbConnect, dbDisconnect } from '../../../../../utils/db';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, cookies, params}) => {
let message = await request.json()
const n = params.range
const start = message?.start ?? 0;
const user = await authenticateUser(cookies)
if(!user){
throw error(403, "Not logged in")
}
else if(!user.access.includes("abrechnung")){
throw error(403, "This user does not have viewing permissions for payments")
}
else{
await dbConnect();
const payments = await Payment.find({}).sort({ date: -1 }).skip(start).limit(n).lean()
await dbDisconnect();
return new Response(JSON.stringify({payments}),{
status: 200,
});
}
};

View File

@@ -0,0 +1,26 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Payment } from '../../../../models/Payment';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
import { User } from '../../../../models/User';
// header: use for bearer token for now
// recipe json in body
export const GET: RequestHandler = async ({request, cookies}) => {
const user = await authenticateUser(cookies)
if(!user){
throw error(403, "Not logged in")
}
else if(!user.access.includes("abrechnung")){
throw error(403, "This user does not have edit permissions for payments")
}
else{
await dbConnect();
const users = await User.find({access: "abrechnung"}, 'username').lean()
await dbDisconnect();
return new Response(JSON.stringify({users}),{
status: 200,
});
}
};

View File

@@ -2,28 +2,29 @@ import type { RequestHandler } from '@sveltejs/kit';
import { Recipe } from '../../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';;
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, cookies, locals}) => {
export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json()
const recipe_json = message.recipe
let auth = await locals.auth();
/*const user = session.user;*/
console.log(auth)
if(!auth){
const user = await authenticateUser(cookies)
if(!user){
throw error(401, "Not logged in")
}
/*if(!user.access.includes("rezepte")){
if(!user.access.includes("rezepte")){
throw error(401, "This user does not have permissions to add recipes")
}*/
await dbConnect();
try{
await Recipe.create(recipe_json);
} catch(e){
throw error(400, e)
}
await dbDisconnect();
return new Response(JSON.stringify({msg: "Added recipe successfully"}),{
status: 200,
});
}
else{
await dbConnect();
try{
await Recipe.create(recipe_json);
} catch(e){
throw error(400, e)
}
await dbDisconnect();
return new Response(JSON.stringify({msg: "Added recipe successfully"}),{
status: 200,
});
}
};

View File

@@ -3,13 +3,15 @@ import { Recipe } from '../../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import type {RecipeModelType} from '../../../../types/types';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, locals}) => {
export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json()
const auth = await locals.auth();
if(!auth) throw error(401, "Need to be logged in")
const user = await authenticateUser(cookies)
if(!user) throw error(401, "Need to be logged in")
if(!user.access.includes("rezepte")) throw error(401, "Insufficient permissions")
const short_name = message.old_short_name
await dbConnect();

View File

@@ -3,15 +3,20 @@ import { Recipe } from '../../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import type {RecipeModelType} from '../../../../types/types';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, locals}) => {
export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json()
const recipe_json = message.recipe
const auth = await locals.auth();
if(!auth){
const user = await authenticateUser(cookies)
console.log(user)
if(!user){
throw error(403, "Not logged in")
}
else if(!user.access.includes("rezepte")){
throw error(403, "This user does not have edit permissions for recipes")
}
else{
await dbConnect();
await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json);

View File

@@ -3,11 +3,13 @@ import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { IMAGE_DIR } from '$env/static/private'
import sharp from 'sharp';
import { authenticateUser } from '$lib/js/authenticate';
export const POST = (async ({ request, locals}) => {
export const POST = (async ({ request, cookies }) => {
const data = await request.json();
const auth = await locals.auth();
if (!auth) throw error(401, "Need to be logged in")
const user = await authenticateUser(cookies)
if (!user) throw error(401, "Need to be logged in")
if (!user.access.includes("rezepte")) throw error(401, "You don't have sufficient permissions for this")
let full_res = new Buffer.from(data.image, 'base64')
// reduce image size if over 500KB
const MAX_SIZE_KB = 500

View File

@@ -3,12 +3,13 @@ import type { RequestHandler } from '@sveltejs/kit';
import { IMAGE_DIR } from '$env/static/private'
import { unlink } from 'node:fs';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';;
export const POST = (async ({ request, locals}) => {
export const POST = (async ({ request, cookies }) => {
const data = await request.json();
const auth = await locals.auth()
if(!auth) throw error(401, "You need to be logged in")
const user = await authenticateUser(cookies)
if(!user) throw error(401, "You need to be logged in")
if(!user.access.includes("rezepte")) throw error(401, "Your don't have the required permission for this")
[ "full", "thumb", "placeholder"].forEach((folder) => {
unlink(path.join(IMAGE_DIR, "rezepte", folder, data.name + ".webp"), (e) => {
if(e) error(404, "could not delete: " + folder + "/" + data.name + ".webp" + e)

View File

@@ -3,11 +3,13 @@ import type { RequestHandler } from '@sveltejs/kit';
import { IMAGE_DIR } from '$env/static/private'
import { rename } from 'node:fs';
import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
export const POST = (async ({ request, locals}) => {
export const POST = (async ({ request, cookies }) => {
const data = await request.json();
const auth = await locals.auth();
if(!auth ) throw error(401, "need to be logged in")
const user = await authenticateUser(cookies)
if(!user) throw error(401, "need to be logged in")
if(!user.access.includes("rezepte")) throw error(401, "You don't have the required permission to do this")
[ "full", "thumb", "placeholder"].forEach((folder) => {
const old_path = path.join(IMAGE_DIR, "rezepte", folder, data.old_name + ".webp")

View File

@@ -6,7 +6,7 @@ import { rand_array } from '$lib/js/randomize';
export const GET: RequestHandler = async ({params}) => {
await dbConnect();
let found_brief = rand_array(await Recipe.find({}, 'name short_name tags category icon description season dateModified').lean()) as BriefRecipeType[];
let found_brief = rand_array(await Recipe.find({}, 'name short_name tags category icon description season').lean()) as BriefRecipeType[];
await dbDisconnect();
return json(JSON.parse(JSON.stringify(found_brief)));
};

View File

@@ -6,7 +6,7 @@ import { rand_array } from '$lib/js/randomize';
export const GET: RequestHandler = async ({params}) => {
await dbConnect();
let recipes = rand_array(await Recipe.find({category: params.category}, 'name short_name images tags category icon description season dateModified').lean()) as BriefRecipeType[];
let recipes = rand_array(await Recipe.find({category: params.category}, 'name short_name images tags category icon description season').lean()) as BriefRecipeType[];
await dbDisconnect();
recipes = JSON.parse(JSON.stringify(recipes));

View File

@@ -6,7 +6,7 @@ import { rand_array } from '$lib/js/randomize';
export const GET: RequestHandler = async ({params}) => {
await dbConnect();
let recipes = rand_array(await Recipe.find({icon: params.icon}, 'name short_name images tags category icon description season dateModified').lean()) as BriefRecipeType[];
let recipes = rand_array(await Recipe.find({icon: params.icon}, 'name short_name images tags category icon description season').lean()) as BriefRecipeType[];
await dbDisconnect();
recipes = JSON.parse(JSON.stringify(recipes));

View File

@@ -6,7 +6,7 @@ import { rand_array } from '$lib/js/randomize';
export const GET: RequestHandler = async ({params}) => {
await dbConnect();
let found_in_season = rand_array(await Recipe.find({season: params.month, icon: {$ne: "🍽️"}}, 'name short_name images tags category icon description season dateModified').lean());
let found_in_season = rand_array(await Recipe.find({season: params.month, icon: {$ne: "🍽️"}}, 'name short_name images tags category icon description season').lean());
await dbDisconnect();
found_in_season = JSON.parse(JSON.stringify(found_in_season));
return json(found_in_season);

View File

@@ -6,7 +6,7 @@ import { rand_array } from '$lib/js/randomize';
export const GET: RequestHandler = async ({params}) => {
await dbConnect();
let recipes = rand_array(await Recipe.find({tags: params.tag}, 'name short_name images tags category icon description season dateModified').lean()) as BriefRecipeType[];
let recipes = rand_array(await Recipe.find({tags: params.tag}, 'name short_name images tags category icon description season').lean()) as BriefRecipeType[];
await dbDisconnect();
recipes = JSON.parse(JSON.stringify(recipes));

View File

@@ -0,0 +1,23 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { dbConnect, dbDisconnect } from '../../../../../utils/db';
import { User } from '../../../../../models/User';
import { get_username } from '$lib/js/get_username';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({cookies}) => {
const requesting_user = await get_username(cookies)
await dbConnect()
let res = await User.findOne({username: requesting_user}, 'access').lean()
if(!res.access.contains("admin")){
await dbDisconnect()
throw error(401, {message: "Your user does not have the permissions to do this"})
}
else{
let res = await User.find({}, 'username access').lean()
await dbDisconnect()
return { res }
}
};

View File

@@ -0,0 +1,36 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { verify } from 'argon2';
import { hashPassword } from '$lib/js/hashPassword'
import {randomBytes} from 'crypto'
import { PEPPER } from '$env/static/private';
import { User } from '../../../../models/User';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request}) => {
const {username, old_password, new_password, new_password_rep} = await request.json()
if(new_password != new_password_rep){
throw error(400, 'new passwords do not match!')
}
await dbConnect();
const user = await User.findOne({username: username});
console.log("Found user:", user)
const isMatch = await verify(user.pass_hash, old_password + PEPPER, {salt: user.salt})
console.log("isMatch:", isMatch)
if(isMatch){
const salt = randomBytes(32).toString('hex'); // Generate a random salt
const pass_hash = await hashPassword(new_password + PEPPER, salt)
await User.findOneAndUpdate({username: username}, {pass_hash: pass_hash, salt: salt})
await dbDisconnect()
return new Response(JSON.stringify({message: "Password updated successfully"}),
{status: 200})
}
else{
await dbDisconnect();
throw error(401, "Wrong old password")
}
};

View File

@@ -0,0 +1,46 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import pkg from 'jsonwebtoken';
const { sign } = pkg;
import { verify} from 'argon2';
import { COOKIE_SECRET } from '$env/static/private'
import { PEPPER } from '$env/static/private'
import { dbConnect, dbDisconnect } from '../../../../utils/db';
import { User } from '../../../../models/User';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request}) => {
const {username, password} = await request.json()
await dbConnect()
let res = await User.findOne({username: username}, 'pass_hash salt').lean()
await dbDisconnect()
if(!res){
console.log("NOT FOUND")
throw error(401, {message: "wrong password or user does not exist"})
}
const stored_pw = res.pass_hash
const salt = res.salt
const isMatch = await verify(stored_pw, password + PEPPER, {salt})
if(!isMatch){
throw error(401, {message: "wrong password or user does not exist"})
}
res = await createJWT(username)
return new Response(JSON.stringify(res))
};
async function createJWT(username) {
const payload = {
username: username,
};
const masterSecret = COOKIE_SECRET;
const secretKey = masterSecret;
const jwt = sign(payload, secretKey);
console.log(jwt)
return jwt
}

View File

@@ -0,0 +1,41 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { randomBytes } from 'crypto';
import { ALLOW_REGISTRATION } from '$env/static/private';
import { PEPPER } from '$env/static/private';
import {hashPassword} from '$lib/js/hashPassword'
import { User } from '../../../../models/User';
import { dbConnect, dbDisconnect } from '../../../../utils/db';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request}) => {
if(ALLOW_REGISTRATION){
const {username, password} = await request.json()
const salt = randomBytes(32).toString('hex'); // Generate a random salt
const pass_hash = await hashPassword(password + PEPPER, salt)
await dbConnect();
try{
await User.create({
username: username,
pass_hash: pass_hash,
salt: salt,
access: [],
})
}catch(e){
await dbDisconnect();
throw error(400, e);
}
await dbDisconnect();
return new Response(JSON.stringify({message: "User added successfully"}),
{status: 200}
);
}
else{
throw error(401, "user registration currently closed")
}
};

View File

@@ -1,7 +0,0 @@
import type { PageServerLoad } from "./$types"
export const load : PageServerLoad = (async ({locals}) => {
return {
session: await locals.auth(),
}
});

View File

@@ -1,18 +0,0 @@
<script>
import Header from '$lib/components/Header.svelte'
import UserHeader from '$lib/components/UserHeader.svelte';
export let data
let username = ""
if(data.user){
username = data.user.username
}
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/glaube/gebete">Gebete</a></li>
<li><a href="/glaube/rosenkranz">Rosenkranz</a></li>
<li><a href="/glaube/predigten">Predigten</a></li>
</ul>
<UserHeader {username} slot=right_side></UserHeader>
<slot></slot>
</Header>

View File

@@ -1,133 +0,0 @@
<style>
h1{
text-align: center;
font-size: 3em;
}
p{
max-width: 600px;
margin: 0 auto;
font-size: 1.1em;
}
</style>
<h1>Settlement Plan</h1>
<script lang="ts">
import { onMount } from 'svelte';
let name = '';
let category = '';
let date = new Date().toISOString().split('T')[0]; // format as yyyy-mm-dd
let images: File[] = [];
let description = '';
let note = '';
let tags = '';
let original_amount = 0;
let currency = 'CHF';
let payment_method = '';
let personal_amounts = [
{ user: 'alexander', amount: 0 },
{ user: 'anna', amount: 0 }
];
const handleSubmit = async () => {
const formData = new FormData();
formData.append('name', name);
formData.append('category', category);
formData.append('dateCreated', date);
formData.append('description', description);
formData.append('note', note);
formData.append('tags', JSON.stringify(tags.split(',').map(tag => tag.trim())));
formData.append('total_amount', total_amount.toString());
formData.append('currency', currency);
formData.append('payment_method', payment_method);
formData.append('personal_amounts', JSON.stringify(personal_amounts));
images.forEach((file, index) => {
formData.append('images', file);
});
const res = await fetch('/api/cospend/add', {
method: 'POST',
body: formData
});
const result = await res.json();
alert(result.message);
};
</script>
<form on:submit|preventDefault={handleSubmit} class="flex flex-col gap-4 max-w-xl">
<label>
Name:
<input type="text" bind:value={name} required />
</label>
<label>
Category:
<input type="text" bind:value={category} />
</label>
<label>
Date Created:
<input type="date" bind:value={date} />
</label>
<label>
Images:
<input type="file" multiple accept="image/*" on:change={(e) => images = Array.from(e.target.files)} />
</label>
<label>
Description:
<textarea bind:value={description}></textarea>
</label>
<label>
Note:
<textarea bind:value={note}></textarea>
</label>
<label>
Tags (comma separated):
<input type="text" bind:value={tags} />
</label>
<label>
Total Amount:
<input type="number" bind:value={original_amount} step="0.01" required />
</label>
<fieldset>
<legend>Personal Amounts</legend>
{#each personal_amounts as entry, i}
<div class="flex gap-2 items-center">
<label>{entry.user}</label>
<input type="number" bind:value={personal_amounts[i].amount} step="0.01" required />
</div>
{/each}
</fieldset>
<label>
Currency:
<select bind:value={currency}>
<option value="CHF">CHF</option>
<option value="EUR">EUR</option>
<option value="USD">USD</option>
</select>
</label>
<label>
Payment Method:
<select bind:value={payment_method}>
<option value="">-- Select --</option>
<option value="cash">Cash</option>
<option value="bank_transfer">Bank Transfer</option>
<option value="credit_card">Credit Card</option>
<option value="twint">Twint</option>
</select>
</label>
<button type="submit">Save Payment</button>
</form>

View File

@@ -1,7 +1,7 @@
import type { PageServerLoad } from "./$types"
import { get_username } from '$lib/js/get_username';;
import type { Actions, PageServerLoad } from "./$types"
import { error } from "@sveltejs/kit"
export const load : PageServerLoad = (async ({locals}) => {
return {
session: await locals.auth(),
}
export const load = (async ({cookies}) => {
return { user: await get_username(cookies) }
});

View File

@@ -56,20 +56,47 @@
<h3>Rosenkranz</h3>
</a>
<a href="/glaube/predigten">
<svg
enable-background="new 0 0 512 512"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg">
<g>
<path
d="m134.057 149.979v-69.942h-74.41c-8.284 0-15 6.716-15 15v39.94c0 8.284 6.716 15 15 15z"/>
<path d="m437.947 391.026v-211.047h-60.004v150.343c0 4.694-2.197 9.118-5.938 11.954-2.637 1.999-5.826 3.046-9.062 3.046-1.354 0-2.717-.184-4.051-.558l-102.892-28.865-102.892 28.865c-4.521 1.267-9.374.346-13.113-2.489-3.741-2.836-5.938-7.259-5.938-11.954v-150.342h-60.004v211.047z"/>
<path d="m377.943 149.979 74.409-.002c8.284 0 15-6.716 15-15v-39.94c0-8.284-6.716-15-15-15h-74.409z"/>
<path d="m164.057 310.535 87.892-24.657c1.325-.372 2.688-.558 4.052-.558s2.727.186 4.052.558l87.892 24.657v-230.5h-183.888zm106.943-175.06v17.394h15.34c8.284 0 15 6.716 15 15s-6.716 15-15 15h-15.34v57.793c0 8.284-6.716 15-15 15s-15-6.716-15-15v-57.793h-15.34c-8.284 0-15-6.716-15-15s6.716-15 15-15h15.34v-17.394c0-8.284 6.716-15 15-15s15 6.715 15 15z"/>
<path d="m497 482h-18.397v-35.972c0-13.785-11.215-25-25-25h-395.206c-13.785 0-25 11.215-25 25v35.972h-18.397c-8.284 0-15 6.716-15 15s6.716 15 15 15h482c8.284 0 15-6.716 15-15s-6.716-15-15-15z"/>
<path d="m377.943 50.035v-35.035c0-8.284-6.716-15-15-15h-76.926c-11.523 0-22.046 4.357-30.017 11.505-7.971-7.148-18.494-11.505-30.018-11.505h-76.926c-8.284 0-15 6.716-15 15v35.035z"/>
</g>
</svg>
<h3>Predigten</h3>
<svg viewBox="0 0 56.664422 57.373692">
<g
transform="translate(-346.55989,-393.80378)">
<g>
<path
style="color:#000000;stroke-linecap:round;-inkscape-stroke:none"
d="m 456.23047,509.13672 a 1.5,1.5 0 0 0 -1.5,1.5 1.5,1.5 0 0 0 1.5,1.5 h 53.66406 a 1.5,1.5 0 0 0 1.5,-1.5 1.5,1.5 0 0 0 -1.5,-1.5 z"
transform="translate(-108.17112,-60.95933)" />
<g
transform="translate(-108.17112,-60.959331)">
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 459.49805,499.79492 a 1.50015,1.50015 0 0 0 -1.5,1.5 v 9.24219 a 1.5,1.5 0 0 0 1.5,1.5 1.5,1.5 0 0 0 1.5,-1.5 v -7.74219 H 504.875 v 7.16602 a 1.5,1.5 0 0 0 1.5,1.5 1.5,1.5 0 0 0 1.5,-1.5 v -8.66602 a 1.50015,1.50015 0 0 0 -1.5,-1.5 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 464.54883,468.27344 a 1.5,1.5 0 0 0 -1.5,1.5 v 31.46289 a 1.5,1.5 0 0 0 1.5,1.5 1.5,1.5 0 0 0 1.5,-1.5 v -31.46289 a 1.5,1.5 0 0 0 -1.5,-1.5 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 501.99023,467.93359 a 1.5,1.5 0 0 0 -1.49804,1.5 l 0.01,31.32813 a 1.5,1.5 0 0 0 1.50196,1.5 1.5,1.5 0 0 0 1.49804,-1.50195 l -0.01,-31.32618 a 1.5,1.5 0 0 0 -1.50196,-1.5 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 460.71289,461.20898 a 1.50015,1.50015 0 0 0 -1.5,1.5 v 6.88672 a 1.50015,1.50015 0 0 0 1.5,1.5 h 10.29688 a 1.5,1.5 0 0 0 1.5,-1.5 1.5,1.5 0 0 0 -1.5,-1.5 h -8.79688 v -3.88672 h 41.50391 v 3.69141 h -8.31641 a 1.5,1.5 0 0 0 -1.5,1.5 1.5,1.5 0 0 0 1.5,1.5 h 9.81641 a 1.50015,1.50015 0 0 0 1.5,-1.5 v -6.69141 a 1.50015,1.50015 0 0 0 -1.5,-1.5 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 471.07227,461.29883 a 1.5,1.5 0 0 0 -1.5,1.5 v 28.28711 a 1.50015,1.50015 0 0 0 1.88867,1.44922 l 11.50781,-3.08399 11.95508,3.20313 a 1.50015,1.50015 0 0 0 1.88867,-1.44727 v -28.27344 a 1.5,1.5 0 0 0 -1.5,-1.5 1.5,1.5 0 0 0 -1.5,1.5 v 26.31836 l -10.45508,-2.80273 a 1.50015,1.50015 0 0 0 -0.77734,0 l -10.00781,2.68359 v -26.33398 a 1.5,1.5 0 0 0 -1.5,-1.5 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 480.62109,454.93555 -9.63672,0.043 a 1.50015,1.50015 0 0 0 -1.49414,1.5 v 6.28125 a 1.5,1.5 0 0 0 1.5,1.5 1.5,1.5 0 0 0 1.5,-1.5 v -4.78711 l 8.13672,-0.0371 c 0.77001,0 1.02148,0.56256 1.10157,0.77148 l 0.0449,3.13867 a 1.5,1.5 0 0 0 1.52148,1.47852 1.5,1.5 0 0 0 1.47852,-1.52149 l -0.0508,-3.46875 a 1.50015,1.50015 0 0 0 -0.084,-0.47851 c 0,0 -1.09277,-2.91992 -4.01172,-2.91992 a 1.50015,1.50015 0 0 0 -0.006,0 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 495.15625,454.76367 -8.86523,0.008 c -2.5527,0.001 -4.04883,2.05469 -4.04883,2.05469 a 1.5,1.5 0 0 0 0.14648,2.11719 1.5,1.5 0 0 0 2.11719,-0.14649 c 0,0 1.18681,-1.02539 1.78711,-1.02539 a 1.50015,1.50015 0 0 0 0.002,0 l 7.35156,-0.006 -0.0215,3.83594 a 1.5,1.5 0 0 0 1.49023,1.50977 1.5,1.5 0 0 0 1.50977,-1.49219 l 0.0312,-5.34766 a 1.50015,1.50015 0 0 0 -1.5,-1.50781 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 483.12891,468.0293 a 1.5,1.5 0 0 0 -1.5,1.5 v 12.26953 a 1.5,1.5 0 0 0 1.5,1.5 1.5,1.5 0 0 0 1.5,-1.5 V 469.5293 a 1.5,1.5 0 0 0 -1.5,-1.5 z"/>
<path
style="color:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="m 479.31641,471.66797 a 1.5,1.5 0 0 0 -1.5,1.5 1.5,1.5 0 0 0 1.5,1.5 h 7.97265 a 1.5,1.5 0 0 0 1.5,-1.5 1.5,1.5 0 0 0 -1.5,-1.5 z"/>
</g>
</g>
</g>
</svg>
<h3>Predigten<h3>
</a>
</LinksGrid>

View File

@@ -463,19 +463,19 @@ Der unten abgebildete Rosenkranz zeigt die aktuellen Gehmeinisse des Tages nach
</g>
<g id=lbead5>
<circle class=lbead />
<circle class=hitbox onclick="{true}" />
<circle class=hitbox onclick="" />
</g>
<g id=lbead4>
<circle class=lbead />
<circle class=hitbox onclick="{true}" />
<circle class=hitbox onclick="" />
</g>
<g id=lbead3>
<circle class=lbead />
<circle class=hitbox onclick="{true}" />
<circle class=hitbox onclick="" />
</g>
<g id=lbead6>
<circle class=lbead />
<circle class=hitbox onclick="{true}" />
<circle class=hitbox onclick="" />
</g>
</g>
<g class=beforedecades>
@@ -490,11 +490,11 @@ Der unten abgebildete Rosenkranz zeigt die aktuellen Gehmeinisse des Tages nach
</g>
<g id=lbead1>
<circle class=lbead />
<circle class=hitbox onclick="{true}" />
<circle class=hitbox onclick="" />
</g>
<g id=lbead2>
<circle class=lbead />
<circle class=hitbox onclick="{true}" />
<circle class=hitbox onclick="" />
</g>
</g>
</g>
@@ -511,7 +511,7 @@ Dieser Plan ist wie folgt:
<div class=table >
<table>
<tbody>
<tr>
<thead>
<td>Mo</td>
<td>Di</td>
<td>Mi</td>
@@ -519,7 +519,7 @@ Dieser Plan ist wie folgt:
<td>Fr</td>
<td>Sa</td>
<td>So</td>
</tr>
</thead>
<tr>
<td>freudenreich</td>
<td>schmerzhaft</td>
@@ -546,7 +546,7 @@ Der Plan ohne lichtreiche Geheimnisse ist wie folgt:
<div class=table>
<table>
<tbody>
<tr>
<thead>
<td>Mo</td>
<td>Di</td>
<td>Mi</td>
@@ -554,7 +554,7 @@ Der Plan ohne lichtreiche Geheimnisse ist wie folgt:
<td>Fr</td>
<td>Sa</td>
<td>So</td>
</tr>
</thead>
<tr>
<td>freudenreich</td>
<td>schmerzhaft</td>

View File

@@ -1,7 +1,5 @@
import type { PageServerLoad } from "./$types"
import { get_username } from '$lib/js/get_username';;
export const load : PageServerLoad = async ({locals}) => {
return {
session: await locals.auth()
}
};
export const load = (async ({cookies}) => {
return { user: await get_username(cookies) }
});

View File

@@ -2,9 +2,9 @@
import Header from '$lib/components/Header.svelte'
import UserHeader from '$lib/components/UserHeader.svelte';
export let data
let user;
if(data.session){
user = data.session.user
let username = ""
if(data.user){
username = data.user.username
}
</script>
@@ -15,8 +15,7 @@ if(data.session){
<li><a href="/rezepte/category">Kategorie</a></li>
<li><a href="/rezepte/icon">Icon</a></li>
<li><a href="/rezepte/tag">Stichwörter</a></li>
<li><a href="/rezepte/tips-and-tricks">Tipps</a></li>
</ul>
<UserHeader slot=right_side {user}></UserHeader>
<UserHeader slot=right_side {username}></UserHeader>
<slot></slot>
</Header>

View File

@@ -6,7 +6,7 @@
import Search from '$lib/components/Search.svelte';
export let data: PageData;
export let current_month = new Date().getMonth() + 1
const categories = ["Hauptspeise", "Nudel", "Brot", "Dessert", "Suppe", "Beilage", "Salat", "Kuchen", "Frühstück", "Sauce", "Zutat", "Getränk", "Aufstrich", "Guetzli", "Snack"]
const categories = ["Hauptspeise", "Nudel", "Brot", "Dessert", "Suppe", "Beilage", "Salat", "Kuchen", "Frühstück", "Sauce", "Zutat", "Getränk", "Aufstrich", "Guetzli", "Unterwegs"]
</script>
<style>
h1{
@@ -14,11 +14,6 @@ h1{
margin-bottom: 0;
font-size: 4rem;
}
.subheading{
text-align: center;
margin-top: 0;
font-size: 1.5rem;
}
</style>
<svelte:head>
<title>Bocken Rezepte</title>
@@ -30,13 +25,12 @@ h1{
</svelte:head>
<h1>Rezepte</h1>
<p class=subheading>{data.all_brief.length} Rezepte und stetig wachsend...</p>
<Search></Search>
<MediaScroller title="In Saison">
{#each data.season as recipe}
<Card {recipe} {current_month} loading_strat={"eager"} do_margin_right={true}></Card>
<Card {recipe} {current_month} do_margin_right={true}></Card>
{/each}
</MediaScroller>

View File

@@ -15,8 +15,8 @@
export let data: PageData;
let hero_img_src = "https://bocken.org/static/rezepte/full/" + data.short_name + ".webp?v=" + data.dateModified
let placeholder_src = "https://bocken.org/static/rezepte/placeholder/" + data.short_name + ".webp?v=" + data.dateModified
let hero_img_src = "https://bocken.org/static/rezepte/full/" + data.short_name + ".webp"
let placeholder_src = "https://bocken.org/static/rezepte/placeholder/" + data.short_name + ".webp"
export let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]
function season_intervals() {
let interval_arr = []
@@ -185,7 +185,6 @@ h1{
}
.icon{
font-family: "Noto Color Emoji", emoji;
position: absolute;
top: -1em;
right: -0.75em;
@@ -293,9 +292,7 @@ h4{
<h4>Saison:</h4>
{#each season_iv as season}
<a class=tag href="/rezepte/season/{season[0]}">
{#if season[0]}
{months[season[0] - 1]}
{/if}
{months[season[0] - 1]}
{#if season[1]}
- {months[season[1] - 1]}
{/if}

View File

@@ -85,7 +85,7 @@
console.log(img_local)
const data = {
image: img_local,
name: short_name.trim(),
name: short_name,
}
await fetch(`/api/rezepte/img/add`, {
method: 'POST',
@@ -108,9 +108,9 @@
recipe: {
...card_data,
...add_info,
images: {mediapath: short_name.trim() + '.webp', alt: "", caption: ""}, // TODO
images: {mediapath: short_name + '.webp', alt: "", caption: ""}, // TODO
season: season_local,
short_name : short_name.trim(),
short_name,
portions: portions_local,
datecreated,
datemodified,

View File

@@ -10,7 +10,7 @@
export let data: PageData;
let preamble = data.recipe.preamble
let addendum = data.recipe.addendum
let image_preview_url="https://bocken.org/static/rezepte/thumb/" + data.recipe.short_name + ".webp?v=" + data.recipe.dateModified;
let image_preview_url="https://bocken.org/static/rezepte/thumb/" + data.recipe.short_name + ".webp"
let note = data.recipe.note
import { season } from '$lib/js/season_store';
@@ -160,7 +160,7 @@
async function upload_img(){
const data = {
image: img_local,
name: short_name.trim(),
name: short_name,
}
const res = await fetch(`/api/rezepte/img/add`, {
method: 'POST',
@@ -191,7 +191,7 @@
},
body: JSON.stringify({
old_name: old_short_name,
new_name: short_name.trim(),
new_name: short_name,
})
})
if(!res_img.ok){
@@ -208,7 +208,7 @@
...add_info,
images, // TODO
season: season_local,
short_name: short_name.trim(),
short_name,
datecreated,
portions: portions_local,
datemodified,
@@ -228,7 +228,7 @@
if(res.ok){
const url = location.href.split('/');
url.splice(url.length -2, 2);
url.push(short_name.trim());
url.push(short_name);
location.assign(url.join('/'))
}
else{

View File

@@ -10,7 +10,6 @@
</script>
<style>
a{
font-family: "Noto Color Emoji", emoji, sans-serif;
--padding: 0.5em;
font-size: 3rem;
text-decoration: none;

View File

@@ -1,62 +0,0 @@
<script lang="ts">
import type { PageData } from './$types';
import AddButton from '$lib/components/AddButton.svelte';
import Converter from './Converter.svelte';
</script>
<style>
h1{
text-align: center;
margin-bottom: 0;
font-size: 4rem;
}
.subheading{
text-align: center;
margin-top: 0;
font-size: 1.5rem;
}
.content{
max-width: 800px;
margin: 0 auto;
background-color: var(--nord0);
padding: 1rem;
margin-block: 1rem;
}
</style>
<svelte:head>
<title>Bocken Rezepte</title>
<meta name="description" content="Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche." />
<meta property="og:image" content="https://bocken.org/static/rezepte/thumb/ragu_aus_rindsrippen.webp" />
<meta property="og:image:secure_url" content="https://bocken.org/static/rezepte/thumb/ragu_aus_rindsrippen.webp" />
<meta property="og:image:type" content="image/webp" />
<meta property="og:image:alt" content="Pasta al Ragu mit Linguine" />
</svelte:head>
<h1>Tipps & Tricks</h1>
<div class=content>
<h2>Trockenhefe vs. Frischhefe</h2>
<Converter></Converter>
<p>
Frischhefe ist mit Trockenhefe ersetzbar, jedoch muss man ein paar Kleinigkeiten beachten:
</p>
<ol>
<li>Nur ein Drittel der Menge verwenden.</li>
<li>Falls ein kalter Teig zubereitet wird, die Trockenhefe umbedingt zuerst zur Gärprobe in warmer Flüssigkeit (je nach Rezept z.B. Milch oder Wasser) mit einem TL Zucker für ~10 Minuten <q>aufwachen</q> lassen.</li>
<li>Generell ist die Meinung das Trockenhefe etwas <q>energischer</q> ist am Anfang der Gärung und etwas langsamer am Ende der Gare. Dementsprechend eventuell die Stock- und Stückgare verkürzen bzw. verlängern.</li>
</ol>
</div>
<div class=content>
<h2>Fensterprobe</h2>
<p>
Die Fensterprobe ist eine Methode um den optimalen Knetzustand eines Teiges zu bestimmen.
Dazu wird ein kleines, ca. Walnussgrosses Stück Teig zwischen den Fingern auseinandergezogen. Ist der Teig elastisch und reißt nicht bis der Teig so dünn ist, dass man leicht licht durchsehen kann, so ist der Teig optimal verknetet.
</p>
<p>
Teig lässt sich leichter verkneten wenn er noch trockener ist. Daher lohnt es sich zunächst etwa 10% der Flüssigkeit zurückzuhalten und erst nach und nach zuzugeben nachdem der Teig bereits für einige Minuten geknetet wurde.
</p>
</div>
<AddButton></AddButton>

View File

@@ -1,68 +0,0 @@
<script>
class HefeConverter {
constructor(trockenhefe = 1) {
this._trockenhefe = trockenhefe;
this._frischhefe = this._trockenhefe * 3;
}
get trockenhefe() {
return Math.round(this._trockenhefe * 100) / 100 + "g";
}
set trockenhefe(value) {
this._trockenhefe = value.replace(/\D/g, '');
this._frischhefe = this._trockenhefe * 3;
}
get frischhefe() {
return this._frischhefe+"g";
}
set frischhefe(value) {
this._frischhefe = value.replace(/\D/g, '');
this._trockenhefe = this._frischhefe / 3;
}
}
const hefeConverter = new HefeConverter();
</script>
<style>
.converter_container {
width: fit-content;
display: flex;
flex-direction: row;
justify-content: center;
gap: 2rem;
background-color: var(--blue);
padding: 2rem;
margin-inline: auto;
align-items: center;
}
input {
width: 5rem;
height: 2rem;
font-size: 1rem;
text-align: center;
}
.flex_column {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 0.25rem;
}
</style>
<div class=converter_container>
<div class="flex_column">
<label for="trockenhefe">Trockenhefe</label>
<input type="text" bind:value={hefeConverter.trockenhefe} min="0" />
</div>
<div>
=
</div>
<div class="flex_column">
<label for="frischhefe">Frischhefe</label>
<input type="text" bind:value={hefeConverter.frischhefe} min="0"/>
</div>
</div>

1
src/utils/seed-data.ts Normal file
View File

@@ -0,0 +1 @@
export {};

BIN
static/flims.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

1
static/icons/Git.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92pt" height="92pt" viewBox="0 0 92 92"><defs><clipPath id="a"><path d="M0 .113h91.887V92H0Zm0 0"/></clipPath></defs><g clip-path="url(#a)"><path style="stroke:none;fill-rule:nonzero;fill:#f03c2e;fill-opacity:1" d="M90.156 41.965 50.036 1.848a5.918 5.918 0 0 0-8.372 0l-8.328 8.332 10.566 10.566a7.03 7.03 0 0 1 7.23 1.684 7.034 7.034 0 0 1 1.669 7.277l10.187 10.184a7.028 7.028 0 0 1 7.278 1.672 7.04 7.04 0 0 1 0 9.957 7.05 7.05 0 0 1-9.965 0 7.044 7.044 0 0 1-1.528-7.66l-9.5-9.497V59.36a7.04 7.04 0 0 1 1.86 11.29 7.04 7.04 0 0 1-9.957 0 7.04 7.04 0 0 1 0-9.958 7.06 7.06 0 0 1 2.304-1.539V33.926a7.049 7.049 0 0 1-3.82-9.234L29.242 14.272 1.73 41.777a5.925 5.925 0 0 0 0 8.371L41.852 90.27a5.925 5.925 0 0 0 8.37 0l39.934-39.934a5.925 5.925 0 0 0 0-8.371"/></g></svg>

After

Width:  |  Height:  |  Size: 819 B

97
static/icons/Jellyfin.svg Normal file
View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Part of the Jellyfin project (https://jellyfin.media)
-
- All copyright belongs to the Jellyfin contributors; a full list can
- be found in the file CONTRIBUTORS.md
-
- This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
- ***** END LICENSE BLOCK ***** -->
<svg
id="svg30"
viewBox="0 0 465.40958 465.39288"
version="1.1"
sodipodi:docname="Jelly-banner-light.svg"
width="465.40958"
height="465.39288"
inkscape:export-filename="src/homepage/jellyfin_logo.svg"
inkscape:export-xdpi="300"
inkscape:export-ydpi="300"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview32"
pagecolor="#ffffff"
bordercolor="#111111"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
showgrid="false" />
<defs
id="defs7">
<linearGradient
id="linear-gradient"
x1="110.25"
y1="213.3"
x2="496.14001"
y2="436.09"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
stop-color="#aa5cc3"
id="stop2" />
<stop
offset="1"
stop-color="#00a4dc"
id="stop4" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linear-gradient"
id="linearGradient679"
gradientUnits="userSpaceOnUse"
x1="110.25"
y1="213.3"
x2="496.14001"
y2="436.09" />
<linearGradient
inkscape:collect="always"
xlink:href="#linear-gradient"
id="linearGradient681"
gradientUnits="userSpaceOnUse"
x1="110.25"
y1="213.3"
x2="496.14001"
y2="436.09" />
</defs>
<title
id="title9">banner-light</title>
<g
id="banner-light"
transform="translate(-28.68525,-23.3)">
<g
id="banner-light-icon">
<path
id="inner-shape"
d="m 261.42,201.62 c -20.44,0 -86.24,119.29 -76.2,139.43 10.04,20.14 142.48,19.92 152.4,0 9.92,-19.92 -55.76,-139.42 -76.2,-139.43 z"
fill="url(#linear-gradient)"
style="fill:url(#linearGradient679)" />
<path
id="outer-shape"
d="M 261.42,23.3 C 199.83,23.3 1.57,382.73 31.8,443.43 c 30.23,60.7 429.34,60 459.24,0 C 520.94,383.43 323,23.3 261.42,23.3 Z M 411.9,390.76 c -19.59,39.33 -281.08,39.77 -300.9,0 -19.82,-39.77 110.1,-275.28 150.45,-275.28 40.35,0 170.04,235.94 150.45,275.28 z"
fill="url(#linear-gradient)"
style="fill:url(#linearGradient681)" />
</g>
<g
id="jellyfin-dark-outlines"
style="isolation:isolate"
transform="translate(43.8)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

650
static/icons/Jitsi.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1 @@
<svg data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 266 266"><defs><linearGradient id="a" x1="45.04" y1="231.72" x2="231.72" y2="45.04" gradientUnits="userSpaceOnUse" gradientTransform="translate(-5.38 -5.38)"><stop offset="0" stop-color="#fff"/><stop offset="0" stop-color="#b8edff"/><stop offset="1" stop-color="#d4b8ff"/></linearGradient></defs><circle cx="133" cy="133" r="132" style="fill:url(#a)"/><path data-name="Logo Pfad" d="m224.19 176.51-4 24.19M41.91 177.5l14.81 14m95.76-137.65L56.62 191.31a.09.09 0 0 0 .07.15l163.41 9.37a.09.09 0 0 0 .09-.13L152.62 53.87a.1.1 0 0 0-.14-.02zm-19.74-13.29L41.8 177.31a.13.13 0 0 0 .11.19l182.18-.8a.12.12 0 0 0 .1-.19L132.95 40.56a.12.12 0 0 0-.21 0zm.11-.16 19.77 13.32" style="fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:6px"/></svg>

After

Width:  |  Height:  |  Size: 819 B

198
static/icons/Searx.svg Normal file
View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="330.93661"
height="303.22852"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="searx_logo.svg"
inkscape:export-filename="/home/a/magnif.png"
inkscape:export-xdpi="23.1774"
inkscape:export-ydpi="203.1774">
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient3857">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3859" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3861" />
</linearGradient>
<linearGradient
id="linearGradient3790">
<stop
style="stop-color:#a9a9a9;stop-opacity:1;"
offset="0"
id="stop3792" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop3794" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3790"
id="radialGradient3798"
cx="294.45947"
cy="208.37973"
fx="294.45947"
fy="208.37973"
r="107.58125"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3857"
id="linearGradient3865"
x1="120.68947"
y1="239.61774"
x2="120.68947"
y2="602.17517"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3790"
id="linearGradient3912"
x1="186.74416"
y1="354.42426"
x2="255.84358"
y2="254.35953"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2227304,0,0,0.89945099,-289.31433,113.40259)" />
<filter
inkscape:collect="always"
id="filter4024"
x="-0.12996517"
width="1.2599303"
y="-0.14709377"
height="1.2941875"
color-interpolation-filters="sRGB">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="6.4759344"
id="feGaussianBlur4026" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="-11.542922"
inkscape:cy="142.31651"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1855"
inkscape:window-height="1056"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:window-maximized="1"
showguides="true"
inkscape:guide-bbox="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="-24"
fit-margin-bottom="-6" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-61.719803,-34.870671)">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 70.523181,34.870671 c -7.11959,15.242893 -10.17798,31.779192 -8.22563,48.814566 5.01677,43.774133 41.675309,79.324503 91.536109,95.162893 -6.62576,-22.40752 -5.34093,-44.9362 2.6395,-65.84431 C 108.73618,98.821131 74.828141,70.195435 70.523181,34.870671 z"
id="path3814-0-7"
inkscape:connector-curvature="0" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 303.77876,36.21406 c 7.11959,15.242893 10.17798,31.779192 8.22563,48.814566 -5.01677,43.774134 -41.67531,79.324504 -91.53611,95.162894 6.62576,-22.40752 5.34093,-44.9362 -2.6395,-65.84431 47.73698,-14.18269 81.64502,-42.808386 85.94998,-78.13315 z"
id="path3814-0"
inkscape:connector-curvature="0" />
<path
transform="matrix(0.6556593,-0.75505688,0.75505688,0.6556593,0,0)"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m -5.0905523,259.06055 18.4167573,0 c 6.220455,0 11.228257,16.68196 11.228257,37.40349 l 0,172.83701 c 0,20.72153 -5.007802,37.40349 -11.228257,37.40349 l -18.4167573,0 c -6.2204547,0 -11.2282577,-16.68196 -11.2282577,-37.40349 l 0,-172.83701 c 0,-20.72153 5.007803,-37.40349 11.2282577,-37.40349 z"
id="rect3804"
inkscape:connector-curvature="0" />
<path
sodipodi:type="arc"
style="fill:url(#radialGradient3798);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path2987"
sodipodi:cx="294.45947"
sodipodi:cy="208.37973"
sodipodi:rx="107.58125"
sodipodi:ry="107.58125"
d="m 402.04073,208.37973 a 107.58125,107.58125 0 1 1 -215.16251,0 107.58125,107.58125 0 1 1 215.16251,0 z"
transform="translate(-107.07617,-60.609153)" />
<path
sodipodi:type="arc"
style="fill:url(#linearGradient3865);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path3757"
sodipodi:cx="131.82491"
sodipodi:cy="299.29346"
sodipodi:rx="101.52033"
sodipodi:ry="101.52033"
d="m 233.34524,299.29346 a 101.52033,101.52033 0 1 1 -203.040667,0 101.52033,101.52033 0 1 1 203.040667,0 z"
transform="matrix(0.76865672,0,0,0.76865672,85.80266,-82.535889)" />
<path
sodipodi:type="arc"
style="fill:#1a1a1a;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path3800"
sodipodi:cx="183.34268"
sodipodi:cy="156.35687"
sodipodi:rx="27.274118"
sodipodi:ry="27.274118"
d="m 210.6168,156.35687 a 27.274118,27.274118 0 1 1 -54.54824,0 27.274118,27.274118 0 1 1 54.54824,0 z"
transform="translate(5,-7.1428572)" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path3802"
sodipodi:cx="197.9899"
sodipodi:cy="203.32896"
sodipodi:rx="5.5558391"
sodipodi:ry="5.5558391"
d="m 203.54574,203.32896 a 5.5558391,5.5558391 0 1 1 -11.11168,0 5.5558391,5.5558391 0 1 1 11.11168,0 z"
transform="translate(1.4847712,-63.564549)" />
<rect
style="fill:#ffffff;fill-opacity:0.82211531;fill-rule:nonzero;stroke:none;filter:url(#filter4024)"
id="rect3916"
width="2.2392972"
height="159.43797"
x="19.525793"
y="337.8396"
rx="2.8666623"
ry="9.0007057"
transform="matrix(0.74466525,-0.84318084,0.84318084,0.74466525,-35.543204,-26.349917)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,445 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg5186"
sodipodi:version="0.32"
inkscape:version="0.45+devel"
sodipodi:docname="transmission.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-filename="/home/andreas/project/application icons/48x48/transmission.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs5188">
<linearGradient
inkscape:collect="always"
id="linearGradient9795">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop9797" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop9799" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient9783">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop9785" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop9787" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient9775">
<stop
style="stop-color:#f9f9f9;stop-opacity:1"
offset="0"
id="stop9777" />
<stop
style="stop-color:#eeeeec;stop-opacity:0.62037037"
offset="1"
id="stop9779" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5948">
<stop
style="stop-color:#787b76;stop-opacity:1;"
offset="0"
id="stop5950" />
<stop
id="stop5956"
offset="0.87125719"
style="stop-color:#babcb9;stop-opacity:1" />
<stop
style="stop-color:#787b76;stop-opacity:1"
offset="1"
id="stop5952" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5908">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop5910" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop5912" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5898">
<stop
style="stop-color:#cc0000;stop-opacity:1;"
offset="0"
id="stop5900" />
<stop
id="stop5906"
offset="0.36509839"
style="stop-color:#ef0000;stop-opacity:1" />
<stop
style="stop-color:#aa0000;stop-opacity:1"
offset="1"
id="stop5902" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5871">
<stop
style="stop-color:#f0f2ef;stop-opacity:1"
offset="0"
id="stop5873" />
<stop
style="stop-color:#cdd1c8;stop-opacity:1"
offset="1"
id="stop5875" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5843">
<stop
style="stop-color:#888a85;stop-opacity:1"
offset="0"
id="stop5845" />
<stop
style="stop-color:#2e3436;stop-opacity:1"
offset="1"
id="stop5847" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5835">
<stop
style="stop-color:#555753;stop-opacity:1;"
offset="0"
id="stop5837" />
<stop
style="stop-color:#2e3436;stop-opacity:1"
offset="1"
id="stop5839" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5823">
<stop
style="stop-color:#2e3436;stop-opacity:1;"
offset="0"
id="stop5825" />
<stop
style="stop-color:#2e3436;stop-opacity:0;"
offset="1"
id="stop5827" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5234">
<stop
style="stop-color:#babdb6;stop-opacity:1;"
offset="0"
id="stop5236" />
<stop
id="stop5242"
offset="0.13299191"
style="stop-color:#eeeeec;stop-opacity:1" />
<stop
style="stop-color:#babdb6;stop-opacity:1"
offset="1"
id="stop5238" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5234"
id="linearGradient5240"
x1="23.738585"
y1="4.156569"
x2="23.738585"
y2="19.46567"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5823"
id="linearGradient5829"
x1="23.732271"
y1="30.057167"
x2="23.688078"
y2="22.632544"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5835"
id="linearGradient5841"
x1="23.9375"
y1="30.616879"
x2="23.9375"
y2="36.357994"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5843"
id="linearGradient5849"
x1="20.771132"
y1="32.248005"
x2="20.563131"
y2="23.939499"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5898"
id="linearGradient5904"
x1="14.8125"
y1="5.6244211"
x2="14.8125"
y2="9"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5908"
id="linearGradient5914"
x1="24.040522"
y1="5.0690055"
x2="24.040522"
y2="10.0086"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5871"
id="linearGradient5928"
x1="13.625"
y1="33.125"
x2="14.125"
y2="24"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5948"
id="linearGradient5954"
x1="10.1875"
y1="20.25"
x2="10.1875"
y2="42.5"
gradientUnits="userSpaceOnUse" />
<filter
inkscape:collect="always"
id="filter9771"
x="-0.02976581"
width="1.0595316"
y="-0.13995509"
height="1.2799102">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.5196773"
id="feGaussianBlur9773" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9775"
id="linearGradient9781"
x1="24.71875"
y1="35.958694"
x2="23.936657"
y2="17.070877"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9783"
id="linearGradient9789"
x1="18.3125"
y1="20.743757"
x2="18.3125"
y2="21.814325"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9795"
id="linearGradient9801"
x1="30.4375"
y1="31.82852"
x2="29.742416"
y2="27.45352"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6568542"
inkscape:cx="30.372474"
inkscape:cy="21.423534"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1091"
inkscape:window-height="777"
inkscape:window-x="557"
inkscape:window-y="164">
<inkscape:grid
type="xygrid"
id="grid5195" />
</sodipodi:namedview>
<metadata
id="metadata5191">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<rect
style="opacity:0.28240741;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter9771)"
id="rect9761"
width="41.901279"
height="8.9116125"
x="3"
y="39"
rx="2.2980971"
ry="2.2980971" />
<path
style="fill:url(#linearGradient5954);fill-rule:evenodd;stroke:#555753;stroke-width:1.00000011999999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="M 10,16.59375 C 8.8196081,16.548814 7.6402135,17.571722 7.53125,18.8125 C 6.643292,26.100083 5.3269606,33.403527 4.65625,40.6875 L 4.65625,43.75 C 4.6900093,45.329492 5.7271791,46.392039 6.875,46.59375 L 41.5,46.59375 C 42.479024,46.569246 43.565009,45.89005 43.53125,44.59375 L 43.53125,40.65625 L 40.40625,19.4375 C 40.152431,18.135677 39.039534,16.752716 37.5,16.59375 L 10,16.59375 z"
id="path5232"
sodipodi:nodetypes="ccccccccccc" />
<path
style="fill:url(#linearGradient5928);fill-opacity:1;fill-rule:evenodd;stroke:#555753;stroke-width:0.99999994000000003px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 10.601853,39.624614 C 9.47224,39.502143 8.6733861,38.760954 8.7014295,37.401046 L 10.601853,21.407733 C 10.893931,20.339398 11.586949,19.485349 12.680909,19.488442 L 34.605501,19.488442 C 35.691818,19.455762 36.778134,20.208796 37.062569,21.104687 L 39.478435,37.237611 C 39.535481,38.706714 38.931012,39.557098 37.913093,39.523599 L 10.601853,39.624614 z"
id="path5230"
sodipodi:nodetypes="ccccccccc" />
<path
style="fill:url(#linearGradient5841);fill-rule:evenodd;stroke:url(#linearGradient5849);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="M 20.46875,20.4375 L 18.40625,32.46875 L 15.4375,32.46875 L 23.46875,37.625 L 32.4375,32.46875 L 29.46875,32.46875 L 27.59375,20.4375 L 20.46875,20.4375 z"
id="path5197"
sodipodi:nodetypes="cccccccc" />
<rect
style="opacity:1;fill:url(#linearGradient5904);fill-opacity:1;stroke:#930000;stroke-width:1.00000011999999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5224"
width="31.113209"
height="6.0609155"
x="8.4847708"
y="4.5135489"
rx="5.0159144"
ry="1.9854566" />
<rect
style="opacity:0.58333333;fill:none;fill-opacity:1;stroke:url(#linearGradient5914);stroke-width:1.00000011999999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5896"
width="29.080278"
height="3.9395947"
x="9.5003824"
y="5.5690055"
rx="1.8339339"
ry="1.2783499" />
<path
style="opacity:0.24537036000000001;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9781);stroke-width:1.00000011999999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 10.592965,17.57221 C 9.474152,17.53019 8.3562869,18.486727 8.2530054,19.647002 L 5.4687498,39.722803 C 5.4796612,39.847886 5.4997885,39.979699 5.5279893,40.102694 L 5.5279893,42.966491 C 5.559989,44.443503 6.5430497,45.407885 7.6309909,45.596509 L 40.479283,45.596509 C 41.407232,45.573597 42.406944,44.967688 42.374947,43.755497 L 42.374947,40.073472 C 42.382229,40.044972 42.398547,40.013922 42.404566,39.985805 L 42.374947,39.781247 L 42.374947,39.576691 L 42.345327,39.576691 L 39.442592,20.202228 C 39.202015,18.98487 38.147175,17.72086 36.687956,17.57221 L 10.592965,17.57221 z"
id="path5881" />
<path
style="fill:url(#linearGradient9789);fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;opacity:0.20833333000000001"
d="M 10.210155,29.955767 L 12.048004,22 L 36.07815,22.05802 L 37.857941,31.044156 L 36.681164,21.969631 C 36.460193,20.967897 35.929863,20 34.957591,20.025088 L 13.037281,19.980893 C 11.606886,19.936699 11.32554,20.864777 11,21.969631 L 10.210155,29.955767 z"
id="path5926"
sodipodi:nodetypes="ccccccccc" />
<rect
style="opacity:1;fill:url(#linearGradient5240);fill-opacity:1;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5226"
width="7.0964494"
height="25.970053"
x="20.48369"
y="3.6044116"
rx="1.0763195"
ry="1.0763192" />
<rect
style="opacity:1;fill:url(#linearGradient5829);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5244"
width="8.1317272"
height="8.0433397"
x="19.975765"
y="22.013826"
rx="1.0763195"
ry="1.0763192" />
<path
style="opacity:0.43518521;fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
d="M 11.423372,41.486321 L 39.533811,41.486321"
id="path5879"
sodipodi:nodetypes="cc" />
<rect
style="opacity:0.22685185;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5892"
width="5.151906"
height="23.93712"
x="21.428234"
y="4.6321397"
rx="1.0763195"
ry="1.0763192" />
<g
id="g5972"
style="opacity:0.62037037">
<path
sodipodi:nodetypes="cc"
id="path5831"
d="M 20.4375,30.5 L 27.5,30.5"
style="fill:none;fill-rule:evenodd;stroke:#888a85;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:1" />
<path
sodipodi:nodetypes="cc"
id="path5833"
d="M 19.960998,32.5 L 27.976504,32.5"
style="fill:none;fill-rule:evenodd;stroke:#888a85;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:0.68055556" />
<path
sodipodi:nodetypes="cc"
id="path5958"
d="M 20.273498,31.5 L 27.726504,31.5"
style="fill:none;fill-rule:evenodd;stroke:#5d5d5c;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:1" />
<path
sodipodi:nodetypes="cc"
id="path5960"
d="M 19.869986,33.488738 L 28.141277,33.488738"
style="fill:none;fill-rule:evenodd;stroke:#5d5d5c;stroke-width:0.99999994000000003px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:0.68055556" />
</g>
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
d="M 14.381412,31.513733 L 17.519198,31.513733"
id="path9791"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
d="M 30.443912,31.451233 L 33.581698,31.451233"
id="path9803"
sodipodi:nodetypes="cc" />
<path
sodipodi:type="arc"
style="opacity:0.33500001;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path5119"
sodipodi:cx="9.8553009"
sodipodi:cy="42.188465"
sodipodi:rx="1.1932427"
sodipodi:ry="1.0827572"
d="M 11.048544,42.188465 A 1.1932427,1.0827572 0 1 1 8.6620582,42.188465 A 1.1932427,1.0827572 0 1 1 11.048544,42.188465 z"
transform="matrix(0.4216252,0,0,0.4766032,5.3634688,21.39228)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -3,6 +3,12 @@ This file styles jellyfin and can be imported by adding:
@import url("https://bocken.org/other/jellyfin.css");
under Server -> General -> Custom CSS Code
*/
/* jellyscrub default: width: 15vw, annoying for smaller screen sizes */
.chapterThumbContainer{
width: min(80vw, 500px, 50vh);
}
/* Attempt to change accent color */
:root{
--nord0: #2E3440;
--blue: #5E81AC;
@@ -80,10 +86,8 @@ border-color: var(--blue);
.defaultCardBackground3,
.defaultCardBackground4,
.defaultCardBackground5,
.emby-checkbox:checked + span + .checkboxOutline,
.itemProgressBarForeground,
.taskProgressInner,
.sliderMarker.watched
.emby-checkbox:checked + span + .checkboxOutline, .itemProgressBarForeground,
.taskProgressInner
{
background-color: var(--blue) !important;
}
@@ -127,9 +131,6 @@ scale: 1.05;
font-size: initial !important;
}
}
.backgroundContainer{
background-color: #21201b;
}
.pageTitleWithDefaultLogo {
background-image: url(https://bocken.org/static/css/logos/logo_text_light.png);
.emby-scroller{
overflow: visible !important;
}

View File

@@ -1,5 +0,0 @@
User-agent: GPTBot
Disallow: /
User-agent: *
Disallow: /static/

View File

@@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {