Compare commits

...

88 Commits

Author SHA1 Message Date
dd71832b10 switch from "Unterwegs" to "Snack"
All checks were successful
CI / update (push) Successful in 49s
2025-03-31 17:57:40 +02:00
ce7a542408 fix copilot autocomplete svg messup
All checks were successful
CI / update (push) Successful in 15s
2025-02-02 13:09:15 +01:00
86225d3237 fix docs autolink
All checks were successful
CI / update (push) Successful in 16s
2025-02-02 13:07:42 +01:00
18a5241c1e fix docs autolink
All checks were successful
CI / update (push) Successful in 15s
2025-02-02 12:58:34 +01:00
17a5d6155d fix for svelte 4
All checks were successful
CI / update (push) Successful in 15s
2025-02-02 12:55:33 +01:00
15bf4fd922 remove unused health.bocken.org and papers.bocken.org
All checks were successful
CI / update (push) Successful in 1m26s
2025-02-02 12:44:21 +01:00
aab1f7da9a added tips-and-tricks route
All checks were successful
CI / update (push) Successful in 19s
2024-10-28 17:00:43 +01:00
367ea7a17e render HTML in Recipe Card description
All checks were successful
CI / update (push) Successful in 17s
2024-10-22 17:29:29 +02:00
154a8f5efe fix typo 2024-10-22 17:28:44 +02:00
87768b539f fix trim()
All checks were successful
CI / update (push) Successful in 14s
2024-08-27 18:13:52 +02:00
470a74099f add https://audio.bocken.org
All checks were successful
CI / update (push) Successful in 8s
2024-08-27 18:10:31 +02:00
1dd47824c7 trim spaces from short_name, otherwise recipes become unnavigatable if ending on spaces
All checks were successful
CI / update (push) Successful in 7s
2024-08-18 21:20:03 +02:00
176ffae32c jellyfin: markers correct color
All checks were successful
CI / update (push) Successful in 16s
2024-08-17 13:16:51 +02:00
2523ca3d31 add forgotten link title for health.bocken.org
All checks were successful
CI / update (push) Successful in 13s
2024-08-11 18:38:46 +02:00
c82b3334c3 add link to health.bocken.org
All checks were successful
CI / update (push) Successful in 13s
2024-08-11 18:35:38 +02:00
b1e05888c9 bump packages
All checks were successful
CI / update (push) Successful in 14s
2024-07-30 14:50:52 +02:00
b6e61caa29 no crawling of /static/
All checks were successful
CI / update (push) Successful in 14s
2024-07-30 14:08:41 +02:00
aeee16078d disallow GPTBot/Openai from crawling website
All checks were successful
CI / update (push) Successful in 1m7s
2024-07-30 14:03:32 +02:00
a1460f5ee3 worker: change to new service name
All checks were successful
CI / update (push) Successful in 31s
2024-06-22 09:27:28 +02:00
c10f622a78 jellyinf: correct theming for progress ticks
Some checks failed
CI / update (push) Failing after 36s
2024-06-22 09:23:57 +02:00
78925d287c fix for not logged in
All checks were successful
CI / update (push) Successful in 16s
2024-03-27 22:09:15 +01:00
a5020be145 conditional redirect for Dokumente
All checks were successful
CI / update (push) Successful in 19s
2024-03-27 22:07:49 +01:00
a1d7420d09 add action to build on push
All checks were successful
CI / update (push) Successful in 14s
2024-03-22 14:54:58 +01:00
8d50e84488 add papers 2024-03-22 13:20:49 +01:00
632be44fe8 update jellyfin css to current state 2024-03-19 10:49:37 +01:00
dda25edd4b add paperless to links grid 2024-03-18 17:40:47 +01:00
3db9f01e1b bump @auth/sveltekit to 0.14.0 2024-03-09 13:52:11 +01:00
b71a7072e7 Render html also in edit panels 2024-03-02 12:38:03 +01:00
5ee48fa733 emoji font for icon 2024-02-26 12:00:38 +01:00
abddf4b201 eager image loading for top recipes on page 2024-02-25 12:54:30 +01:00
b4dc4d194f specify emoji font for icons 2024-02-25 11:15:06 +01:00
687063f216 update multiplier on navgation 2024-02-21 13:47:30 +01:00
0bfbb6da10 apply ingredient amount to multiplier 2024-02-21 13:26:27 +01:00
8b5e089792 attempt to fix rand_array() 2024-02-21 10:29:17 +01:00
bda44e4647 invalidate image cache on /edit properly 2024-02-21 09:38:55 +01:00
68973dbec7 "fix" symbol in header on mobile 2024-02-20 20:27:33 +01:00
5cf21c7d75 finally buildable without jwt 2024-02-19 23:38:08 +01:00
c22a7f0e99 remove user admin routes 2024-02-19 23:24:28 +01:00
4fdfacd7be simplify boolean assignemnt 2024-02-19 23:22:29 +01:00
db391bc383 remove Users from db 2024-02-19 23:18:31 +01:00
2b6499e602 remove unnecesarry deps since moving to authjs 2024-02-19 23:17:16 +01:00
c21fbc7f1e migration to Sveltekit 2 2024-02-19 21:09:39 +01:00
4c5826e1b5 migration to Svelte 4 2024-02-19 21:02:51 +01:00
e3680da1ad move globals out of component into css file 2024-02-18 23:34:14 +01:00
cf79f75a5d do not display placeholder image in edit/adding recipe 2024-02-18 19:58:20 +01:00
794817f69d first attempt in disabling image coursel on redirect 2024-02-18 19:53:47 +01:00
e556e65707 add recipe counter 2024-02-18 17:35:17 +01:00
2b18176310 update links to domain-separated ones 2024-02-18 16:39:12 +01:00
0bf8d50f1e update README to current state 2024-02-18 15:23:14 +01:00
8e9ca091ef add link styling 2024-02-18 15:09:25 +01:00
d5228aab60 add forgotten updated api routes 2024-02-18 14:45:01 +01:00
bd5fdbd7c3 first attempt of img cache invalidation 2024-02-18 14:43:42 +01:00
bb4383f212 fix image url 2024-02-15 09:59:49 +01:00
71aabfb9ba add title metadata to main page, remove link clutter 2024-02-15 04:32:26 +01:00
e7944b9aa0 Correctly display user with pfp if available 2024-02-15 04:28:31 +01:00
022d727394 OIDC can check for groups now to properly secure users 2024-02-15 04:10:06 +01:00
650a6ce1fc re-protect client paths 2024-02-15 03:13:49 +01:00
3a684a5d5a current state of OIDC integration in README 2024-02-14 18:43:29 +01:00
a781be8d00 initial OIDC setup 2024-02-14 16:07:55 +01:00
1929189187 new svgs 2024-02-14 13:44:37 +01:00
d6f8ab9a17 remove unused files 2024-02-14 13:44:22 +01:00
4c92c1c43d add note about recipes login 2024-02-03 13:03:23 +01:00
ee1008eeea add forgotten css 2024-02-01 17:17:20 +01:00
6ccdfd51de add initial Glaube section 2024-02-01 17:15:51 +01:00
29893f931d install OIDC, update packages 2024-01-28 12:37:30 +01:00
08cc4091b1 add cospend link 2024-01-26 15:51:37 +01:00
9d6e160ec8 update to current state 2024-01-24 16:42:58 +01:00
51381c3f3d only margin-right in MediaScroller 2024-01-24 11:09:54 +01:00
6baaefcfe8 more features 2024-01-22 20:14:36 +01:00
5c009d74fe Search: enable click only result 2024-01-22 16:04:58 +01:00
9ddafaacca fix weird shift in Cards due to double insertion of <a> tag on server 2024-01-22 15:09:08 +01:00
1222fe7487 Card.svelte: placeholder image also blurred if JS disabled 2024-01-22 14:53:26 +01:00
61488a8ce9 fix Search 2024-01-22 14:47:22 +01:00
ef78686432 no blurred image if JS disabled 2024-01-22 14:43:52 +01:00
177e2c8fca update to fix vite vulns 2024-01-22 14:16:24 +01:00
7ec94246f0 Card is now fully loaded in itself
No longer do we have this weird shift of the description to the right of
the Card until some magical JS is loaded to fix it.
Not yet perfect: The now wrapping a-tag is for some reason still weirdly
sent to client until some js cleans it up. Currently results in a too
large gap which is fixed by local js.

Still TODO: do not blur images if no js present
2024-01-22 14:13:56 +01:00
0f45145119 Card.svelte: get rid of :has() and Firefox-specific hacks 2024-01-22 12:40:55 +01:00
6fc2755d87 favorite feature layout 2024-01-21 18:57:00 +01:00
b047034731 fix missing nordtheme mvs 2024-01-21 10:36:32 +01:00
18e26790ce slightly improve js-free Card rendering 2024-01-21 10:34:23 +01:00
27c643ef2b fix Login/PFP falling below viewport 2024-01-20 17:49:20 +01:00
735ce5aecc fix typo 2024-01-20 17:41:19 +01:00
37d2265a3b add new goals 2024-01-20 17:35:09 +01:00
b5878390ad fix mobile view messing up startpage 2024-01-20 17:23:12 +01:00
0d180cc4f9 update startpage 2024-01-20 17:09:07 +01:00
c4c72bd8f0 simplify structure by remove (rezepte) 2024-01-20 16:39:27 +01:00
36e0abb26d Header: add box-shadow 2024-01-20 16:29:47 +01:00
65049e49ec add js-free goals 2024-01-20 14:10:03 +01:00
112 changed files with 3926 additions and 3535 deletions

View File

@ -0,0 +1,33 @@
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,12 +5,15 @@ 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
@ -20,15 +23,30 @@ 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://github.com/flauschtrud/broccoli
- [ ] expose json-ld for recipes https://json-ld.org/ https://schema.org/Recipe
- [ ] reference other recipes in recipe
- [ ] add a link to the recipe
- [ ] add ingredients to the ingredients list
- [ ] include steps?
- [ ] add favoriting ability when logged in
- [ ] favorite button on recipe
- [ ] store favorites in DB -> add to user object
- [ ] favorite API endpoint (requires auth of user)
- [ ] set
- [ ] retrieve
- [ ] favorite page/MediaScroller
- [ ] graceful degradation for JS-less browsers
- [ ] use js-only class with display:none and remove it with JS
- [ ] disable search -> use form action instead on submit?
- [x] do not blur images without js
- [x] correct Recipe Card rendering
### Glaube
- [ ] just keep it as MD rendering for now?
- [ ] DB setup
- [ ] just keep it md rendered
- [ ] Google Speech to Text API integration?
- [ ] Gebete
### Outside of this sveltekit project but planned to run on the server as well
- [x] create LDAP and OpenID
@ -37,15 +55,42 @@ My own homepage, bocken.org, built with svelte-kit.
- [x] fail2ban
- [ ] LDAP?
### Dendrite
#### Dendrite
- [x] setup dendrite
- [ ] Connect to LDAP
- [ ] Connect to LDAP/OIDC (waiting on upstream)
- [x] Serve some web-frontend -> Just element?
### Gitea
#### Gitea
- [ ] consistent theming
- [ ] LDAP
- [x] OpenID Connect
- [x] sane landing page
### Jellyfin
#### Jellyfin
- [x] connect to LDAP
- [x] consitent theming
#### Webtrees
- [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
#### Jitsi
- [ ] consistent theming
- [ ] move away from docker
- [ ] find a way to improve max video quality without jitsi becoming unreliable
#### Searx
- [x] investigate SearxNG as more reliable alternative
- [ ] consistent theming
#### Photoprism
- [ ] consistent theming
- [ ] OIDC integration (waiting on upstream)
#### Nextcloud
- [x] consistent theming
- [x] collabora integration
#### Transmission
- [x] move behind authentik

2571
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

29
src/auth.ts Normal file
View File

@ -0,0 +1,29 @@
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,28 +1,32 @@
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"
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")
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');
}
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")
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);
}
}
const response = await resolve(event)
return response
// If the request is still here, just proceed as normally
return resolve(event);
}
export const handle: Handle = sequence(
auth.handle,
authorization
);

View File

@ -1,6 +1,6 @@
<script lang='ts'>
export let href
import "$lib/components/nordtheme.css"
import "$lib/css/nordtheme.css"
import "$lib/css/action_button.css"
</script>

View File

@ -2,9 +2,16 @@
export let recipe
export let current_month
export let icon_override = false;
export let search = "search_me"
export let search = true;
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]
@ -13,25 +20,28 @@ if(icon_override){
let isloaded = false
import { onMount } from "svelte";
onMount(() => {
const el = document.querySelector("img")
if(el.complete){
isloaded = true
}
})
onMount(() => {
isloaded = document.querySelector("img")?.complete ? true : false
})
const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
</script>
<style>
.card_anchor{
border-radius: 20px;
}
.card{
--card-width: 300px;
position: relative;
flex-shrink: 0;
transition: 200ms;
text-decoration: none;
position: relative;
box-sizing: border-box;
font-family: sans-serif;
cursor: pointer;
height: calc(7/4 * var(--card-width)); /* otherwise card is not initialized at correct size and readjusts when populated*/
width: var(--card-width);
height: 525px;
width: 300px;
border-radius: 20px;
background-size: contain;
display: flex;
@ -40,19 +50,24 @@ import { onMount } from "svelte";
background-color: var(--blue);
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.3);
}
.card #image{
width: var(--card-width);
height: calc(var(--card-width)*0.85);
.icon{
font-family: "Noto Color Emoji", emoji, sans-serif;
}
#image{
width: 300px;
height: 255px;
object-fit: cover;
transition: 200ms;
backdrop-filter: blur(10px);
}
.blur{
filter: blur(10px);
}
.unblur{
filter: blur(0px) !important;
.backdrop_blur{
backdrop-filter: blur(10px);
}
div:has(#image){
width: var(--card-width);
.div_image,
.div_div_image{
width: 300px;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
@ -60,33 +75,12 @@ div:has(#image){
border-top-left-radius: inherit;
border-top-right-radius: inherit;
}
div:has(div #image){
height: calc(var(--card-width)*0.85);
.div_div_image{
height: 255px;
position: absolute;
width: var(--card-width);
width: 300px;
top: 0;
}
@supports(-moz-appearance:none){
#image{
}
#image, #div_image{
border-top-left-radius: 20px;
border-top-right-radius: 20px;
height: 100%;
}
#div_image{
background-repeat: no-repeat;
background-size: cover;
}
#div_div_image{
height: calc(var(--card-width)*0.85);
position: absolute;
width: var(--card-width);
top: 0;
}
}
.card:hover,
.card:focus-within{
@ -97,12 +91,12 @@ div:has(div #image){
.card:focus{
scale: 0.95 0.95;
}
.card .title {
position: relative;
box-sizing: border-box;
.card_title {
position: absolute;
padding-top: 0.5em;
height: 50%;
width: 100% ;
height: 262.5px;
width: 300px;
top: 262.5px;
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
display: flex;
@ -110,17 +104,17 @@ div:has(div #image){
justify-content: space-between;
transition: 100ms;
}
.card .name{
.name{
font-size: 2em;
color: white;
padding-inline: 0.5em;
padding-block: 0.2em;
}
.card .description{
.description{
padding-inline: 1em;
color: var(--nord4);
}
.card .tags{
.tags{
display: flex;
flex-wrap: wrap-reverse;
overflow: hidden;
@ -130,7 +124,7 @@ div:has(div #image){
margin-bottom:0.5em;
flex-grow: 0;
}
.card .tag{
.tag{
cursor: pointer;
text-decoration: unset;
background-color: var(--nord4);
@ -142,18 +136,18 @@ div:has(div #image){
transition: 100ms;
box-shadow: 0em 0em 0.2em 0.05em rgba(0, 0, 0, 0.3);
}
.card .tag:hover,
.card .tag:focus-visible
.tag:hover,
.tag:focus-visible
{
transform: scale(1.04, 1.04);
background-color: var(--orange);
box-shadow: 0.2em 0.2em 0.2em 0.1em rgba(0, 0, 0, 0.3);
}
.card .tag:focus{
.tag:focus{
transition: 100ms;
scale: 0.9;
}
.card .title .category{
.card_title .category{
position: absolute;
box-shadow: 0em 0em 1em 0.1em rgba(0, 0, 0, 0.6);
text-decoration: none;
@ -167,14 +161,14 @@ div:has(div #image){
transition: 100ms;
}
.card .title .category:hover,
.card .title .category:focus-within
.card_title .category:hover,
.card_title .category:focus-within
{
box-shadow: -0.2em 0.2em 1em 0.1em rgba(0, 0, 0, 0.6);
background-color: var(--nord3);
transform: scale(1.05, 1.05)
}
.card .category:focus{
.category:focus{
scale: 0.9 0.9;
}
@ -183,23 +177,29 @@ div:has(div #image){
{
animation: shake 0.6s;
}
.margin_right{
margin-right: 2em;
}
</style>
<a class="card {search}" href="/rezepte/{recipe.short_name}" data-tags=[{recipe.tags}]>
<div id=div_div_image >
<div id=div_image style="background-image:url({'https://bocken.org/static/rezepte/placeholder/' + recipe.short_name + '.webp'})">
<img class:unblur={isloaded} id=image src={'https://bocken.org/static/rezepte/thumb/' + recipe.short_name + '.webp'} loading=lazy alt="{recipe.alt}" on:load={() => isloaded=true}/>
</div>
<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})">
<noscript>
<img id=image class="backdrop_blur" src="https://bocken.org/static/rezepte/thumb/{img_name}" loading={loading_strat} 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}/>
</div>
</div>
{#if icon_override || recipe.season.includes(current_month)}
<a class=icon href="/rezepte/icon/{recipe.icon}">{recipe.icon}</a>
{/if}
<div class=title>
<a class=icon href="/rezepte/icon/{recipe.icon}">{recipe.icon}</a>
{/if}
<div class="card_title">
<a class=category href="/rezepte/category/{recipe.category}" >{recipe.category}</a>
<div>
<div class=name>{@html recipe.name}</div>
<div class=description>{recipe.description}</div>
<div class=description>{@html recipe.description}</div>
</div>
<div class=tags>
{#each recipe.tags as tag}
@ -207,4 +207,5 @@ div:has(div #image){
{/each}
</div>
</div>
</div>
</a>

View File

@ -3,11 +3,21 @@
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,9 +455,13 @@ 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)} >{ingredient.name}</div>
<div class=force_wrap on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)} >
{@html 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

@ -5,7 +5,7 @@ import Cross from '$lib/assets/icons/Cross.svelte'
import Plus from '$lib/assets/icons/Plus.svelte'
import Check from '$lib/assets/icons/Check.svelte'
import '$lib/components/nordtheme.css'
import '$lib/css/nordtheme.css'
import "$lib/css/action_button.css"
import { do_on_key } from '$lib/components/do_on_key.js'
@ -509,7 +509,9 @@ h3{
</button>
</div>
<div>
<div on:click={() => show_modal_edit_step(list_index, step_index)}>{step}</div>
<div on:click={() => show_modal_edit_step(list_index, step_index)}>
{@html 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

@ -1,5 +1,5 @@
<script>
import "$lib/components/nordtheme.css"
import "$lib/css/nordtheme.css"
import { onMount } from "svelte";
import Symbol from "./Symbol.svelte"
@ -19,23 +19,6 @@ 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);
@ -45,6 +28,8 @@ nav{
flex-direction: row;
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;
@ -96,18 +81,30 @@ nav[hidden]{
padding-inline: 0.5rem;
}
:global(svg.symbol){
height: 3.5rem;
width: 3.5rem;
height: 4rem;
width: 4rem;
border-radius: 10000px;
}
:global(a:has(svg.symbol)){
/*:global(a:has(svg.symbol)){
padding: 0 !important;
width: 3.5rem;
height: 3.5rem;
width: 4rem;
height: 4rem;
margin-left: 1rem;
}*/
.wrapper{
display:flex;
flex-direction: column;
min-height: 100svh;
}
footer{
padding-block: 1rem;
text-align: center;
margin-top: auto;
}
@media screen and (max-width: 800px) {
.button_wrapper{
box-shadow: 0 1em 1rem 0rem rgba(0,0,0,0.4);
display: flex;
justify-content: space-between;
align-items: center;
@ -123,8 +120,8 @@ nav[hidden]{
background-color: unset;
display: block;
fill: white;
margin-inline: 1rem;
width: 2.5rem;
margin-inline: 0.5rem;
width: 2rem;
aspect-ratio: 1;
}
.nav_button svg{
@ -136,7 +133,7 @@ nav[hidden]{
fill: var(--red);
scale: 0.9;
}
nav{
.nav_site{
position: fixed;
top: 0;
right: 0;
@ -151,16 +148,16 @@ nav[hidden]{
justify-content: space-between!important;
padding-inline: 0.5rem;
}
:global(nav ul){
:global(.nav_site ul){
width: 100% ;
}
nav :first-child{
.nav_site :first-child{
display:none;
}
nav[hidden]{
.nav_site[hidden]{
transform: translateX(100%);
}
nav a:last-child{
:global(.nav_site a:last-child){
margin-bottom: 2rem;
}
@ -179,24 +176,14 @@ nav[hidden]{
transform: unset;
}
}
.wrapper{
display:flex;
flex-direction: column;
min-height: 100svh;
}
footer{
padding-block: 1rem;
text-align: center;
margin-top: auto;
}
</style>
<div class=wrapper lang=de>
<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="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>
<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>
</div>
<nav hidden>
<nav hidden class=nav_site>
<a class=entry href="/"><Symbol></Symbol></a>
<slot name=links></slot>
<slot name=right_side></slot>

View File

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

View File

@ -1,5 +1,5 @@
<script lang="ts">
import '$lib/components/nordtheme.css';
import '$lib/css/nordtheme.css';
import Recipes from '$lib/components/Recipes.svelte';
import Search from './Search.svelte';
export let icons
@ -8,6 +8,7 @@
<style>
a{
font-family: "Noto Color Emoji", emoji, sans-serif;
font-size: 2rem;
text-decoration: none;
padding: 0.5em;

View File

@ -1,7 +1,8 @@
<script>
import { onMount } from 'svelte';
import { onNavigate } from "$app/navigation";
export let data
let multiplier = 1
let multiplier;
let custom_mul = "…"
onMount(() => {
@ -9,6 +10,10 @@ 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
@ -227,7 +232,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)}</div>
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div><div class=name>{@html item.name.replace("{{multiplier}}", multiplier * item.amount)}</div>
{/each}
</div>
{/each}

View File

@ -0,0 +1,91 @@
<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

@ -8,7 +8,7 @@ export let title
flex-direction: row;
flex-wrap:nowrap;
overflow-x: auto;
gap: 2rem;
/*gap: 2rem;*/ /*messes up if js disabled as anchor tag is inserted twice...*/
padding: 3rem;
}
.media_scroller_wrapper{

View File

@ -36,9 +36,12 @@ onMount(() => {
scroller.parentNode.style.display= 'none'
})
scroll
let items = document.querySelectorAll(".matched-recipe");
items = [...new Set(items)] // make unique as seasonal mediascroller can lead to duplicates
// if only one result and click_only_result is true, click it
if(click_only_result && scrollers_with_results.length == 1 && scrollers_with_results[0].querySelector(".matched-recipe").length == 1){
scrollers_with_results[0].querySelector(".matched-recipe").click()
if(click_only_result && scrollers_with_results.length == 1 && items.length == 1){
// add '/rezepte' to history to not force-redirect back to recipe if going back
items[0].click();
}
// if scrollers with results are presenet scroll first result into view
/*if(scrollers_with_results.length > 0){

View File

@ -1,5 +1,5 @@
<script lang="ts">
import '$lib/components/nordtheme.css';
import '$lib/css/nordtheme.css';
import Recipes from '$lib/components/Recipes.svelte';
import Search from './Search.svelte';
let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]

View File

@ -1,5 +1,5 @@
<script lang=ts>
import "$lib/components/nordtheme.css"
import "$lib/css/nordtheme.css"
import { season } from '$lib/js/season_store.js'
import {onMount} from "svelte";
import {do_on_key} from "./do_on_key";

View File

@ -1,9 +1,13 @@
<script>
import "$lib/css/nordtheme.css";
</script>
<style>
:root{
--icon_fill: var(--nord4);
}
svg{
transition: 100ms;
height: 3em;
}
svg:hover,
svg:focus-visible
@ -26,14 +30,9 @@
</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: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">
xmlns="http://www.w3.org/2000/svg">
<g class=stroke
id="branches"
transform="translate(-42.033271,-37.145192)" >

View File

@ -1,7 +1,7 @@
<script lang="ts">
export let tag : string;
export let ref: string;
import '$lib/components/nordtheme.css'
import '$lib/css/nordtheme.css'
</script>
<style>
a{

View File

@ -2,15 +2,23 @@
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();
}
@ -160,16 +168,22 @@ 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} on:click={show_dialog_img}>
<div class:zoom-in={isloaded && !isredirected} 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=""/>
</div>
</div>
<noscript>
<div class=placeholder style="background-image:url({placeholder_src})" >
<img class="unblur" id=image {src} on:load={() => {isloaded=true}} alt=""/>
</div>
</noscript>
</div>
</figure>
<div class=content><slot></slot></div>

View File

@ -1,11 +1,12 @@
<script lang="ts">
import { onMount } from "svelte";
export let username;
export let user;
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")
@ -101,6 +102,12 @@
/* (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{
@ -125,16 +132,17 @@ h2{
}
</style>
{#if username}
<button on:click={toggle_options} style="background-image: url({src})" id=button>
{#if user}
<button on:click={toggle_options} style="background-image: url(https://bocken.org/static/user/thumb/{user.nickname}.webp)" id=button>
<div id=options class="speech top" hidden>
<h2>{username}</h2>
<h2>{user.name}</h2>
<p>({user.nickname})</p>
<ul>
<li><a href="/settings" >Einstellungen</a></li>
<li><a href="/logout" >Log Out</a></li>
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
<li><a href="/auth/signout" >Log Out</a></li>
</ul>
</div>
</button>
{:else}
<a class=entry href=/login>Log In</a>
<a class=entry href=/auth/signin>Log In</a>
{/if}

View File

@ -1,25 +0,0 @@
:root{
--nord0: #2E3440;
--nord1: #3B4252;
--nord2: #434C5E;
--nord3: #4C566A;
--nord4: #D8DEE9;
--nord5: #E5E9F0;
--nord6: #ECEFF4;
--nord7: #8FBCBB;
--nord8: #88C0D0;
--nord9: #81A1C1;
--nord10: #5E81AC;
--nord11: #BF616A;
--nord12: #D08770;
--nord13: #EBCB8B;
--nord14: #A3BE8C;
--nord15: #B48EAD;
--lightblue: var(--nord9);
--blue: var(--nord10);
--red: var(--nord11);
--orange: var(--nord12);
--yellow: var(--nord13);
--green: var(--nord14);
--purple: var(--nord15);
}

32
src/lib/css/christ.css Normal file
View File

@ -0,0 +1,32 @@
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,3 +27,28 @@
--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);
}
}

65
src/lib/css/predigten.css Normal file
View File

@ -0,0 +1,65 @@
@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;
}

204
src/lib/css/rosenkranz.css Normal file
View File

@ -0,0 +1,204 @@
@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,41 +0,0 @@
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

@ -1,33 +0,0 @@
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

@ -1,10 +0,0 @@
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,6 +1,4 @@
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;
@ -11,6 +9,8 @@ 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,15 +0,0 @@
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);

View File

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

View File

@ -2,22 +2,15 @@
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
let user;
if(data.session){
user = data.session.user
}
</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 {username} slot=right_side></UserHeader>
<UserHeader {user} slot=right_side></UserHeader>
<slot></slot>
</Header>

View File

@ -1,164 +1,90 @@
<script>
<script lang="ts">
import "$lib/css/nordtheme.css";
import LinksGrid from "$lib/components/LinksGrid.svelte";
export let data;
</script>
<style>
section{
padding: 2rem 1rem;
min-height: 350px;
overflow: hidden;
transition: 200ms;
}
section:nth-child(3n),
.grid_section a:nth-child(4n),
.grid_section a:nth-child(4n) svg{
background-color: var(--nord4);
fill: var(--nord11);
}
section:nth-child(4n+1),
.grid_section a:nth-child(4n+1),
.grid_section a:nth-child(4n+1) svg{
background-color: var(--nord6);
fill: var(--nord10);
}
section:nth-child(3n+2),
.grid_section a:nth-child(4n+2){
background-color: var(--nord5);
}
.grid_section a:nth-child(4n+3){
background-color: var(--nord5);
}
section:nth-child(2n) .flex{
flex-direction: row-reverse;
}
section:nth-child(2n) img{
object-position: right;
left: -350px;
}
.flex{
.hero{
display: flex;
gap: 2rem;
flex-direction: row;
justify-content: flex-start;
max-width: 1000px;
margin-inline: auto;
min-height: 350px;
}
.flex > *:first-child{
width: 66%;
}
.flex > *:last-child{
position: relative;
max-width: 34%;
}
.flex > *:last-child img{
position:absolute;
}
img{
height: 350px;
object-fit: cover;
}
h2{
font-size: 3rem;
}
a{
text-decoration: unset;
color: var(--nord0);
transition: 200ms;
}
section:hover{
scale: 1.02;
}
section:has(a:hover){
box-shadow: 1em 1em 1em 1em rgba(0,0,0, 0.3);
}
.grid_section a:hover{
box-shadow: 1em 1em 2em 1em rgba(0,0,0, 0.3);
}
.grid_section a{
box-shadow: 0.2em 0.2em 1em 1em rgba(0,0,0, 0.1);
}
.grid_section{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 1000px;
margin-inline: auto;
padding: 2rem 1rem;
}
.grid_section a{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-decoration: unset;
color: var(--nord0);
transition: 200ms;
width: 100%;
padding: 1rem;
max-width: 1400px;
margin-inline: auto;
gap: 2rem;
}
.grid_section a:hover{
scale: 1.02;
.hero img{
border-radius: 1000px;
margin: 1rem;
width: clamp(100px, 300px, 50vw);
object-fit: cover;
background-color: var(--nord4);
box-shadow: 0.2em 0.2em 1em 1em rgba(0, 0, 0, 0.1);
}
.grid_section a :is(svg, img){
height: 120px;
fill: var(--nord0);
}
.grid_section h3{
font-size: 1.5rem;
.hero div{
margin-inline: 1rem;
}
section h2{
font-size: 2rem;
text-align: center;
}
@media (prefers-color-scheme: dark){
*{
color: white;
}
section:nth-child(3n),
.grid_section a:nth-child(4n),
.grid_section a:nth-child(4n) svg{
background-color: var(--nord6-dark);
fill: var(--nord11);
}
section:nth-child(4n+1),
.grid_section a:nth-child(4n+1),
.grid_section a:nth-child(4n+1) svg{
background-color: var(--accent-dark);
fill: var(--nord9);
}
section:nth-child(3n+2),
.grid_section a:nth-child(4n+2),
.grid_section a:nth-child(4n+2) svg{
background-color: var(--nord1);
fill: var(--nord8);
}
.grid_section a:nth-child(4n+3),
.grid_section a:nth-child(4n+3) svg{
background-color: var(--background-dark);
fill: var(--nord7);
.hero img{
box-shadow: 0.1em 0.1em 2em 0.5em rgba(255, 255, 255, 0.1);
}
}
@media (max-width: 600px){
.hero{
flex-direction: column;
gap: 1rem;
}
.hero img{
width: clamp(100px, 200px, 80vw);
}
.hero h1{
text-align: center;
}
}
</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}
<section class=hero>
<img src="https://bocken.org/static/user/full/alexander.webp" alt="Smiling Alexander Bocken">
<div>
<h1><q>Willkommen auf bocken.org</q></h1>
<p>
Hallo, ich bin Alexander Bocken. Auf dieser Seite findest du einige Softwareprojekte für Freunde, Familie und mich.
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.
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.
</p>
</div>
</section>
{/if}
<section>
<a href="/rezepte" class=flex>
<div class=text>
<h2>Re&shy;zep&shy;te</h2>
<p>Die eigenen Rezepte für das ganze Jahr kann man hier finden. Von traditionell Kärntner Küche zu Schweizer Klassikern oder auch das ein oder andere Hipsterrezept findet man für das ganze Jahr Rezepte. An den Rezepten wird kontinuirlich gefeilscht. </p>
</div>
<div>
<img src="https://bocken.org/static/rezepte/thumb/ragu_aus_rindsrippen.webp" alt="">
</div>
</a>
</section>
<div class=grid_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=git>
<a href=https://git.bocken.org>
<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>
@ -168,7 +94,7 @@ section:has(a:hover){
<h3>Streaming</h3>
</a>
<a href="/bilder">
<a href="https://bilder.bocken.org">
<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>
@ -183,19 +109,49 @@ section:has(a:hover){
<h3>Videokonferenzen</h3>
</a>
<a href="/searx">
<a href="https://searx.bocken.org">
<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>
<a href="/transmission">
<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 href="https://cloud.bocken.org/apps/cospend/">
<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="M0 24C0 10.7 10.7 0 24 0H69.5c22 0 41.5 12.8 50.6 32h411c26.3 0 45.5 25 38.6 50.4l-41 152.3c-8.5 31.4-37 53.3-69.5 53.3H170.7l5.4 28.5c2.2 11.3 12.1 19.5 23.6 19.5H488c13.3 0 24 10.7 24 24s-10.7 24-24 24H199.7c-34.6 0-64.3-24.6-70.7-58.5L77.4 54.5c-.7-3.8-4-6.5-7.9-6.5H24C10.7 48 0 37.3 0 24zM128 464a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm336-48a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/></svg>
<h3>Einkauf</h3>
</a>
<a href="https://tree.bocken.org">
<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="M335.5 4l288 160c15.4 8.6 21 28.1 12.4 43.5s-28.1 21-43.5 12.4L320 68.6 47.5 220c-15.4 8.6-34.9 3-43.5-12.4s-3-34.9 12.4-43.5L304.5 4c9.7-5.4 21.4-5.4 31.1 0zM320 160a40 40 0 1 1 0 80 40 40 0 1 1 0-80zM144 256a40 40 0 1 1 0 80 40 40 0 1 1 0-80zm312 40a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zM226.9 491.4L200 441.5V480c0 17.7-14.3 32-32 32H120c-17.7 0-32-14.3-32-32V441.5L61.1 491.4c-6.3 11.7-20.8 16-32.5 9.8s-16-20.8-9.8-32.5l37.9-70.3c15.3-28.5 45.1-46.3 77.5-46.3h19.5c16.3 0 31.9 4.5 45.4 12.6l33.6-62.3c15.3-28.5 45.1-46.3 77.5-46.3h19.5c32.4 0 62.1 17.8 77.5 46.3l33.6 62.3c13.5-8.1 29.1-12.6 45.4-12.6h19.5c32.4 0 62.1 17.8 77.5 46.3l37.9 70.3c6.3 11.7 1.9 26.2-9.8 32.5s-26.2 1.9-32.5-9.8L552 441.5V480c0 17.7-14.3 32-32 32H472c-17.7 0-32-14.3-32-32V441.5l-26.9 49.9c-6.3 11.7-20.8 16-32.5 9.8s-16-20.8-9.8-32.5l36.3-67.5c-1.7-1.7-3.2-3.6-4.3-5.8L376 345.5V400c0 17.7-14.3 32-32 32H296c-17.7 0-32-14.3-32-32V345.5l-26.9 49.9c-1.2 2.2-2.6 4.1-4.3 5.8l36.3 67.5c6.3 11.7 1.9 26.2-9.8 32.5s-26.2 1.9-32.5-9.8z"/></svg>
<h3>Stammbaum</h3>
</a>
</div>
<a href=glaube>
<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="M351.2 4.8c3.2-2 6.6-3.3 10-4.1c4.7-1 9.6-.9 14.1 .1c7.7 1.8 14.8 6.5 19.4 13.6L514.6 194.2c8.8 13.1 13.4 28.6 13.4 44.4v73.5c0 6.9 4.4 13 10.9 15.2l79.2 26.4C631.2 358 640 370.2 640 384v96c0 9.9-4.6 19.3-12.5 25.4s-18.1 8.1-27.7 5.5L431 465.9c-56-14.9-95-65.7-95-123.7V224c0-17.7 14.3-32 32-32s32 14.3 32 32v80c0 8.8 7.2 16 16 16s16-7.2 16-16V219.1c0-7-1.8-13.8-5.3-19.8L340.3 48.1c-1.7-3-2.9-6.1-3.6-9.3c-1-4.7-1-9.6 .1-14.1c1.9-8 6.8-15.2 14.3-19.9zm-62.4 0c7.5 4.6 12.4 11.9 14.3 19.9c1.1 4.6 1.2 9.4 .1 14.1c-.7 3.2-1.9 6.3-3.6 9.3L213.3 199.3c-3.5 6-5.3 12.9-5.3 19.8V304c0 8.8 7.2 16 16 16s16-7.2 16-16V224c0-17.7 14.3-32 32-32s32 14.3 32 32V342.3c0 58-39 108.7-95 123.7l-168.7 45c-9.6 2.6-19.9 .5-27.7-5.5S0 490 0 480V384c0-13.8 8.8-26 21.9-30.4l79.2-26.4c6.5-2.2 10.9-8.3 10.9-15.2V238.5c0-15.8 4.7-31.2 13.4-44.4L245.2 14.5c4.6-7.1 11.7-11.8 19.4-13.6c4.6-1.1 9.4-1.2 14.1-.1c3.5 .8 6.9 2.1 10 4.1z"/></svg>
<h3>Glaube</h3>
</a>
<a href=https://transmission.bocken.org>
<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

@ -1,35 +0,0 @@
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

@ -1,18 +0,0 @@
<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

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
import { get_username } from '$lib/js/get_username';;
export const load = (async ({cookies}) => {
return { user: await get_username(cookies) }
});

View File

@ -1,61 +0,0 @@
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

@ -1,24 +0,0 @@
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

@ -1,27 +0,0 @@
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

@ -1,27 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,26 +0,0 @@
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,29 +2,28 @@ 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}) => {
export const POST: RequestHandler = async ({request, cookies, locals}) => {
let message = await request.json()
const recipe_json = message.recipe
const user = await authenticateUser(cookies)
if(!user){
let auth = await locals.auth();
/*const user = session.user;*/
console.log(auth)
if(!auth){
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")
}
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,
});
}
}*/
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,15 +3,13 @@ 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, cookies}) => {
export const POST: RequestHandler = async ({request, locals}) => {
let message = await request.json()
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 auth = await locals.auth();
if(!auth) throw error(401, "Need to be logged in")
const short_name = message.old_short_name
await dbConnect();

View File

@ -3,20 +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, cookies}) => {
export const POST: RequestHandler = async ({request, locals}) => {
let message = await request.json()
const recipe_json = message.recipe
const user = await authenticateUser(cookies)
console.log(user)
if(!user){
const auth = await locals.auth();
if(!auth){
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,13 +3,11 @@ 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, cookies }) => {
export const POST = (async ({ request, locals}) => {
const data = await request.json();
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")
const auth = await locals.auth();
if (!auth) throw error(401, "Need to be logged in")
let full_res = new Buffer.from(data.image, 'base64')
// reduce image size if over 500KB
const MAX_SIZE_KB = 500

View File

@ -3,13 +3,12 @@ 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, cookies }) => {
export const POST = (async ({ request, locals}) => {
const data = await request.json();
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")
const auth = await locals.auth()
if(!auth) throw error(401, "You need to be logged in")
[ "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,13 +3,11 @@ 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, cookies }) => {
export const POST = (async ({ request, locals}) => {
const data = await request.json();
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")
const auth = await locals.auth();
if(!auth ) throw error(401, "need to be logged in")
[ "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').lean()) as BriefRecipeType[];
let found_brief = rand_array(await Recipe.find({}, 'name short_name tags category icon description season dateModified').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').lean()) as BriefRecipeType[];
let recipes = rand_array(await Recipe.find({category: params.category}, 'name short_name images tags category icon description season dateModified').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').lean()) as BriefRecipeType[];
let recipes = rand_array(await Recipe.find({icon: params.icon}, 'name short_name images tags category icon description season dateModified').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').lean());
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());
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').lean()) as BriefRecipeType[];
let recipes = rand_array(await Recipe.find({tags: params.tag}, 'name short_name images tags category icon description season dateModified').lean()) as BriefRecipeType[];
await dbDisconnect();
recipes = JSON.parse(JSON.stringify(recipes));

View File

@ -1,23 +0,0 @@
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

@ -1,36 +0,0 @@
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

@ -1,46 +0,0 @@
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

@ -1,41 +0,0 @@
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

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

View File

@ -0,0 +1,18 @@
<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

@ -0,0 +1,75 @@
<script>
import LinksGrid from '$lib/components/LinksGrid.svelte';
</script>
<style>
h1{
text-align: center;
font-size: 3em;
}
p{
max-width: 600px;
margin: 0 auto;
font-size: 1.1em;
}
</style>
<h1>Glaube</h1>
<p>
Hier findet man einige Gebete, den Rosenkranz und aufgearbeitete Predigten zum katholischen Glauben.
Ein Fokus auf Latein und den tridentinischen Ritus wird zu bemerken sein.
Diese Seiten sind noch im Aufbau und werden nach und nach erweitert.
Bis jetzt sind nur die Gebete in einem guten Stand.
</p>
<LinksGrid>
<a href="/glaube/gebete">
<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="M351.2 4.8c3.2-2 6.6-3.3 10-4.1c4.7-1 9.6-.9 14.1 .1c7.7 1.8 14.8 6.5 19.4 13.6L514.6 194.2c8.8 13.1 13.4 28.6 13.4 44.4v73.5c0 6.9 4.4 13 10.9 15.2l79.2 26.4C631.2 358 640 370.2 640 384v96c0 9.9-4.6 19.3-12.5 25.4s-18.1 8.1-27.7 5.5L431 465.9c-56-14.9-95-65.7-95-123.7V224c0-17.7 14.3-32 32-32s32 14.3 32 32v80c0 8.8 7.2 16 16 16s16-7.2 16-16V219.1c0-7-1.8-13.8-5.3-19.8L340.3 48.1c-1.7-3-2.9-6.1-3.6-9.3c-1-4.7-1-9.6 .1-14.1c1.9-8 6.8-15.2 14.3-19.9zm-62.4 0c7.5 4.6 12.4 11.9 14.3 19.9c1.1 4.6 1.2 9.4 .1 14.1c-.7 3.2-1.9 6.3-3.6 9.3L213.3 199.3c-3.5 6-5.3 12.9-5.3 19.8V304c0 8.8 7.2 16 16 16s16-7.2 16-16V224c0-17.7 14.3-32 32-32s32 14.3 32 32V342.3c0 58-39 108.7-95 123.7l-168.7 45c-9.6 2.6-19.9 .5-27.7-5.5S0 490 0 480V384c0-13.8 8.8-26 21.9-30.4l79.2-26.4c6.5-2.2 10.9-8.3 10.9-15.2V238.5c0-15.8 4.7-31.2 13.4-44.4L245.2 14.5c4.6-7.1 11.7-11.8 19.4-13.6c4.6-1.1 9.4-1.2 14.1-.1c3.5 .8 6.9 2.1 10 4.1z"/></svg>
<h3>Gebete</h3>
</a>
<a href=/glaube/rosenkranz >
<svg viewBox="0 0 512 512">
<g>
<path class="st0" d="M241.251,145.056c-39.203-17.423-91.472,17.423-104.54,60.982c-13.068,43.558,8.712,117.608,65.337,143.742
c56.626,26.135,108.896-8.712,87.117-39.202c-74.049-8.712-121.963-87.117-100.184-126.319S280.453,162.479,241.251,145.056z"/>
<path class="st0" d="M337.079,271.375c47.914-39.202,21.779-126.319-17.423-135.031c-39.202-8.712-56.626,13.068-26.135,39.202
c39.203,30.491-8.712,91.472-39.202,87.117C254.318,262.663,289.165,310.577,337.079,271.375z"/>
<path class="st0" d="M254.318,119.788c43.558-17.423,74.049-9.579,100.184,16.556c13.068-39.202-30.491-104.54-108.896-113.252
S93.153,118.921,127.999,171.191C136.711,153.767,188.981,106.721,254.318,119.788z"/>
<path class="st0" d="M110.576,245.24C36.527,262.663,28.87,335.248,45.239,380.27c17.423,47.914,4.356,82.761,26.135,91.472
c20.622,8.253,91.472,13.068,152.454,17.423c60.982,4.356,108.896-47.914,91.472-108.896
C141.067,410.761,110.576,284.442,110.576,245.24z"/>
<path class="st0" d="M93.883,235.796c0,0,2.178-28.313,10.89-43.558c-4.356-4.356-8.712-21.779-8.712-21.779
s-4.356-19.601-4.356-34.846c-32.669-6.534-89.295,34.846-91.472,41.38c-2.178,6.534,10.889,80.583,39.202,82.761
C69.927,235.796,93.883,235.796,93.883,235.796z"/>
<path class="st0" d="M489.533,175.546c-39.202-82.761-113.252-65.337-113.252-65.337s4.356,21.779-4.356,34.846
c43.558,47.914,13.067,146.643-24.681,158.265c130.675,56.626,159.712-58.081,164.068-75.504
C515.668,210.393,498.245,197.326,489.533,175.546z"/>
<path class="st0" d="M454.108,332.076c-22.359,15.841-85.663,11.613-121.964-7.265c1.446,14.514-13.067,37.756-20.325,39.202
c27.59,11.621,53.725,62.436,7.265,116.161c18.878,18.87,95.828,4.356,140.842-24.689c7.325-4.722,18.869-52.27,21.779-79.851
C485.56,339.103,488.963,307.387,454.108,332.076z"/>
<path class="st0" d="M257.227,213.294c-18.928,5.164-30.439-6.27-23.234-18.869c5.811-10.167,5.266-20.69-8.712-13.068
c-29.044,17.423-11.612,66.784,24.689,62.428c49.36-17.423,27.581-62.428,14.514-60.982
C251.417,184.249,273.196,208.938,257.227,213.294z"/>
</g>
</svg>
<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>
</a>
</LinksGrid>

View File

@ -0,0 +1,338 @@
<script>
import "$lib/css/christ.css";
import "$lib/css/nordtheme.css";
import Gebet from "./Gebet.svelte";
</script>
<style>
.ccontainer{
margin: auto;
overflow-x: visible;
max-width: 1000px;
}
.container{
column-count: 2;
column-gap: 1em;
}
@media (max-width: 800px) {
.container{
column-count: 1;
padding-left: calc((100% - 600px ) * 0.5); /* ugly*/
}
}
:global(.container > *){
break-inside: avoid-column; /* Prevent children from splitting across columns */
margin-bottom: 1em;
}
h1{
text-align: center;
font-size: 3rem;
}
</style>
<h1>Gebete</h1>
<div class="ccontainer">
<div class=container>
<Gebet name={"Das heilige Kreuzzeichen"} is_bilingue={true} >
<p>
<v lang=la>In nómine <i></i> Patris, et Fílii, et Spíritus Sancti. Amen.</v>
<v lang=de>Im Namen des <i></i> Vaters und des Sohnes und des Heiligen Geistes. Amen.</v>
</p>
</Gebet>
<Gebet name={"Glória Patri"} is_bilingue={true}>
<p>
<v lang=la>Glória Patri, et Fílio, et Spirítui Sancto.</v>
<v lang=de>Ehre sei dem Vater und dem Sohne und dem Hl. Geiste.</v>
<v lang=la>Sicute erat in princípio, et nunc, et semper:</v>
<v lang=de>Wie es war am Anfang, so auch jetzt und allezeit</v>
<v lang=la>et in sǽcula sæculórum. Amen.</v>
<v lang=de>und in Ewigkeit. Amen.</v>
</p>
</Gebet>
<Gebet name={"Paternoster"} is_bilingue={true} >
<p>
<v lang=la>Pater noster, qui es in cælis</v>
<v lang=de>Vater unser, der Du bist im Himmel,</v>
<v lang=la>Sanctificétur nomen tuum</v>
<v lang=de>geheiligt werde Dein Name;</v>
<v lang=la>Advéniat regnum tuum</v>
<v lang=de>zu uns komme Dein Reich;</v>
<v lang=la>Fiat volúntas tua, sicut in cælo, et in terra.</v>
<v lang=de>Dein Wille geschehe, wie im Himmel, also auch auf Erden!</v>
<v lang=la>Panem nostrum quotidiánum da nobis hódie.</v>
<v lang=de>Unser tägliches Brot gib uns heute;</v>
<v lang=la>Et dimítte nobis debíta nostra,</v>
<v lang=de>und vergib uns unsere Schulden,</v>
<v lang=la>sicut et nos dimíttimus debitóribus nostris.</v>
<v lang=de>wie auch wir vergeben unsern Schuldigern;</v>
<v lang=la>Et ne nos indúcas in tentatiónem.</v>
<v lang=de>und führe uns nicht in Versuchung.</v>
<v lang=la>Sed líbera nos a malo. Amen.</v>
<v lang=de>Sondern erlöse uns von dem Übel. Amen.</v>
</p>
</Gebet>
<Gebet name={"Credo"} is_bilingue={true}>
<p>
<v lang=la>Credo in unum <i><sup></sup></i> Deum, Patrem omnipoténtem,</v>
<v lang=de>Ich glaub an den einen <i><sup></sup></i> Gott. Den allmächtigen Vater,</v>
<v lang=la>factórem cæli et terræ,</v>
<v lang=de>Schöpfer des Himmels und der Erde,</v>
<v lang=la>visibílium ómnium et invisibílium.</v>
<v lang=de>aller sichtbaren und unsichtbaren Dinge.</v>
<v lang=la>Et in unum Dóminum <i><sup></sup></i> Jesum Christum,</v>
<v lang=de>Und an den einen Herrn <i><sup></sup></i> Jesus Christus,</v>
<v lang=la>Fílium Dei unigénitum.</v>
<v lang=de>Gottes eingeborenen Sohn.</v>
<v lang=la>Et ex Patre natum ante ómnia sǽcula.</v>
<v lang=de>Er ist aus dem Vater geboren vor aller Zeit.</v>
<v lang=la>Deum de Deo,</v>
<v lang=de>Gott von Gott,</v>
<v lang=la>lumen de lúmine,</v>
<v lang=de>Licht vom Lichte,</v>
<v lang=la>Deum verum de Deo vero.</v>
<v lang=de>wahrer Gott vom wahren Gott;</v>
<v lang=la>Génitum, non factum,</v>
<v lang=de>Gezeugt, nicht geschaffen,</v>
<v lang=la>consubstantiálem Patri:</v>
<v lang=de>eines Wesens mit dem Vater;</v>
<v lang=la>per quem ómnia facta sunt.</v>
<v lang=de>durch Ihn ist alles geschaffen.</v>
<v lang=la>Qui propter nos hómines</v>
<v lang=de>Für uns Menschen</v>
<v lang=la>et propter nostram salútem</v>
<v lang=de>und um unsres Heiles willen</v>
<v lang=la>descéndit de cælis.</v>
<v lang=de>ist Er vom Himmel herabgestiegen.</v>
</p>
<p>
<v lang=la>Et incarnátus est de Spíritu Sancto</v>
<v lang=de>Er hat Fleisch angenommen durch den Hl. Geist</v>
<v lang=la>ex <i><sup></sup></i> María Vírgine:</v>
<v lang=de>aus <i><sup></sup></i> Maria, der Jungfrau</v>
<v lang=la>Et homo factus est.</v>
<v lang=de>und ist Mensch geworden.</v>
<v lang=la>Crucifíxus étiam pro nobis:</v>
<v lang=de>Gekreuzigt wurde Er sogar für uns;</v>
<v lang=la>sub Póntio Piláto passus, et sepúltus est.</v>
<v lang=de>unter Pontius Pilatus hat Er den Tod erlitten</v>
<v lang=de>und ist begraben worden</v>
</p>
<p>
<v lang=la>Et resurréxit tértia die,</v>
<v lang=de>Er ist auferstanden am dritten Tage,</v>
<v lang=la>secúndum Scriptúras.</v>
<v lang=de>gemäß der Schrift;</v>
<v lang=la>Et ascéndit in cáelum:</v>
<v lang=de>Er ist aufgefahren in den Himmel</v>
<v lang=la>sedet ad déxteram Patris.</v>
<v lang=de>und sitzet zur Rechten des Vaters.</v>
</p>
<p>
<v lang=la>Et íterum ventúrus est cum glória</v>
<v lang=de>Er wird wiederkommen in Herrlichkeit,</v>
<v lang=la>judicáre vivos et mórtuos:</v>
<v lang=de>Gericht zu halten über Lebende und Tote:</v>
<v lang=la>cujus regni non erit finis.</v>
<v lang=de>und Seines Reiches wird kein Endes sein.</v>
</p>
<p>
<v lang=la>Et in Spíritum Sanctum,</v>
<v lang=de>Ich glaube an den Heiligen Geist,</v>
<v lang=la>Dóminum et vivificántem:</v>
<v lang=de>den Herrn und Lebensspender,</v>
<v lang=la>qui ex Patre Filióque procédit.</v>
<v lang=de>der vom Vater und vom Sohne ausgeht.</v>
<v lang=la>Qui cum Patre et Fílio simul <i><sup></sup></i> adorátur et conglorificátur:</v>
<v lang=de>zugleich <i><sup></sup></i> angebetet und verherrlicht;</v>
<v lang=la>qui locútus est per Prophétas.</v>
<v lang=de>Er hat gesprochen durch die Propheten.</v>
<v lang=la>Et unam sanctam cathólicam et apostólicam Ecclésiam.</v>
<v lang=de>Ich glaube an die eine, heilige, katholische und apostolische Kirche.</v>
<v lang=la>Confíteor unum baptísma</v>
<v lang=de>Ich bekenne die eine Taufe</v>
<v lang=la>in remissiónem peccatórum.</v>
<v lang=de>zur Vergebung der Sünden.</v>
<v lang=la>Et exspécto resurrectiónem mortuórum.</v>
<v lang=de>Ich erwarte die Auferstehung der Toten.</v>
<v lang=la><i></i> Et vitam ventúri sǽculi. Amen.</v>
<v lang=de><i></i> Und das Leben der zukünftigen Welt. Amen.</v>
</p>
</Gebet>
<Gebet name={"Ave Maria"} is_bilingue={true}>
<p>
<v lang=la>Ave María, grátia plena. Dóminus tecum,</v>
<v lang=de>Gegrüsset seist du Maria, voll der Gnade; der Herr ist mit dir;</v>
<v lang=la>benedícta tu in muliéribus,</v>
<v lang=de>du bist gebenedeit unter den Weibern,</v>
<v lang=la>et benedíctus fructus ventris tui, Jesus.</v>
<v lang=de>und gebenedeit ist die Frucht deines Leibes, Jesus.</v>
</p>
<p>
<v lang=la>Sancta María, mater Dei, ora pro nobis peccatóribus,</v>
<v lang=de>Heilige Maria, Mutter Gottes, bitte für uns Sünder</v>
<v lang=la>nunc, et in hora mortis nostræ. Amen.</v>
<v lang=de>jetzt und in der Stunde unseres Todes! Amen.</v>
</p>
</Gebet>
<Gebet name={"Salve Regina"} is_bilingue={true}>
<p>
<v lang=la>Salve, Regína,</v>
<v lang=de>Sei gegrüßt, o Königin,</v>
<v lang=la>máter misericórdiae;</v>
<v lang=de>Mutter der Barmherzigkeit,</v>
<v lang=la>Víta, dulcédo et spes nóstra, sálve.</v>
<v lang=de>unser Leben, unsre Wonne</v>
<v lang=de>und unsere Hoffnung, sei gegrüßt!</v>
</p>
<p>
<v lang=la>Ad te clamámus, éxsules fílii Hévae.</v>
<v lang=de>Zu dir rufen wir verbannte Kinder Evas;</v>
<v lang=la>Ad te suspirámus,</v>
<v lang=de>zu dir seufzen wir</v>
<v lang=la>geméntes et fléntes in hac lacrimárum válle.</v>
<v lang=de>trauernd und weinend in diesem Tal der Tränen.</v>
<v lang=la>Eia ergo, Advocáta nóstra,</v>
<v lang=de>Wohlan denn, unsre Fürsprecherin,</v>
<v lang=la>íllos túos misericórdes óculos ad nos convérte.</v>
<v lang=de>deine barmherzigen Augen wende zu uns</v>
<v lang=la>Et Jésum, benedíctum frúctum véntris túi,</v>
<v lang=de>und nach diesem Elend zeige uns Jesus,</v>
<v lang=la>nóbis post hoc exsílíum osténde.</v>
<v lang=de>die gebenedeite Frucht deines Leibes.</v>
<v lang=la>O clémens, o pía, o dúlcis Vírgo María.</v>
<v lang=de>O gütige, o milde, o süße Jungfrau Maria.</v>
</p>
</Gebet>
<Gebet name={"Das Fatimagebet"} is_bilingue={true}>
<v lang=la>Ó mí Jésú, dímitte nóbís débita nostra,</v>
<v lang=de>O mein Jesus, verzeih' uns unsere Sünden,</v>
<v lang=la>líberá nós ab igne ínferní,</v>
<v lang=de>bewahre uns vor den Feuern der Hölle</v>
<v lang=la>condúc in cælum omnés animás, </v>
<v lang=de>und führe alle Seelen in den Himmel,</v>
<v lang=la>præsertim illás,</v>
<v lang=de>besonders jene,</v>
<v lang=la>quæ maximé indigent misericordiá tuá. Amen.</v>
<v lang=de>die Deiner Barmherzigkeit am meisten bedürfen. Amen.</v>
</Gebet>
<Gebet name={"Glória"} is_bilingue={true}>
<p slot="intro">Der uralte Gesang beginnt mit den Worten, mit denen die Engelscharen den neugeborenen Welterlöser feierten. Er preist zunächst Gott Vater, dann Gott Sohn; er schließt mit einer Huldigung an die Heiligste Dreifaltigkeit, wobei man sich mit dem großen Kreuze bezeichnet.</p>
<p>
<v lang=la>Glória in excélsis <i><sup></sup></i> Deo.</v>
<v lang=de>Ehre sei <i><sup></sup></i> Gott in der Höhe.</v>
<v lang=la>Et in terra pax homínibus</v>
<v lang=de>Und auf Erden Friede den Mesnchen,</v>
<v lang=la>bonæ voluntátis.</v>
<v lang=de>die guten Willens sind.</v>
<v lang=la>Laudámus te.</v>
<v lang=de>Wir loben Dich.</v>
<v lang=la>Benedícimus te.</v>
<v lang=de>Wir preisen Dich.</v>
<v lang=la><i><sup></sup></i> Adorámus te.</v>
<v lang=de><i><sup></sup></i> Wir beten Dich an.</v>
<v lang=la>Glorificámus te.</v>
<v lang=de>Wir verherrlichen Dich.</v>
<v lang=la><i><sup></sup></i> Grátias ágimus tibi</v>
<v lang=de><i><sup></sup></i> Wir sagen Dir Dank</v>
<v lang=la>propter magnam glóriam tuam.</v>
<v lang=de>ob Deiner großen Herrlichkeit.</v>
<v lang=la>Dómine Deus, Rex cæléstis,</v>
<v lang=de>Herr und Gott, König des Himmels,</v>
<v lang=la>Deus Pater omnípotens.</v>
<v lang=de>Gott allmächtiger Vater!</v>
<v lang=la>Dómine Fili unigénite, <i><sup></sup></i> Jesu Christe.</v>
<v lang=de>Herr <i><sup></sup></i> Jesus Christus, eingeborener Sohn!</v>
<v lang=la>Dómine Deus, Agnus Dei,</v>
<v lang=de>Herr und Gott, Lamm Gottes,</v>
<v lang=la>Fílius Patris.</v>
<v lang=de>Sohn des Vaters!</v>
<v lang=la>Qui tollis peccáta mundi,</v>
<v lang=de>Du nimmst hinweg die Sünden der Welt:</v>
<v lang=la>miserére nobis.</v>
<v lang=de>erbarme Dich unser.</v>
<v lang=la>Qui tollis peccáta mundi,</v>
<v lang=de>Du nimmst hinwerg die Sünden der Welt.</v>
<v lang=la><i><sup></sup></i> súscipe depreciatiónem nostram.</v>
<v lang=de><i><sup></sup></i> nimm unser Flehen gnädig auf.</v>
<v lang=la>Qui sedes ad déxteram Patris,</v>
<v lang=de>Du sitzt zur Rechten des Vaters:</v>
<v lang=la>miserére nobis.</v>
<v lang=de>erbarme Dich unser.</v>
<v lang=la>Quóniam tu solus Sanctus.</v>
<v lang=de>Denn Du allein bist der Heilige.</v>
<v lang=la>Tu solus Altíssimus,</v>
<v lang=de>Du allein der Höchste,</v>
<v lang=la><i><sup></sup></i> Jesu Christe.</v>
<v lang=de><i><sup></sup></i> Jesus Christus,</v>
<v lang=la>Cum Sancto Spíritu</v>
<v lang=de>Mit dem Hl. Geiste,</v>
<v lang=la><i></i> in glória Dei Patris. Amen.</v>
<v lang=de><i></i> in der Herrlichkeit Gottes des Vaters. Amen.</v>
</p>
</Gebet>
<Gebet name={"Gebet zum hl. Erzengel Michael"} is_bilingue={true}>
<p>
<v lang=la>Sáncte Míchael Archángele,</v>
<v lang=de>Heiliger Erzengel Michael,</v>
<v lang=la>defénde nos in proélio,</v>
<v lang=de>verteidige uns im Kampfe!</v>
<v lang=la>cóntra nequítam et insídias</v>
<v lang=de>Gegen die Bosheit und Nachstellungen</v>
<v lang=la>diáboli ésto præsídium.</v>
<v lang=de>des Teufels sei unser Schutz. </v>
<v lang=la>Ímperet ílli Déus, súpplices deprecámur:</v>
<v lang=de>»Gott gebiete ihm!«, so bitten wir flehentlich.</v>
<v lang=la>tuque, Prínceps milítæ cæléstis,</v>
<v lang=de>Du aber, Fürst der himmlischen Heerscharen,</v>
<v lang=la>Sátanam aliósque spíritus malígnos,</v>
<v lang=de>stoße den Satan und die anderen bösen Geister,</v>
<v lang=la>qui ad perditiónem animárum</v>
<v lang=la>pervagántur in múndo,</v>
<v lang=de>die in der Welt umhergehen,</v>
<v lang=de>um die Seelen zu verderben,</v>
<v lang=la>divína virtúte, in inférnum detrúde. Amen.</v>
<v lang=de>durch die Kraft Gottes in die Hölle. Amen.</v>
</p>
</Gebet>
<Gebet name={"Bruder Klaus Gebet"} is_bilingue={false}>
<p>
<v lang=de>Mein Herr und mein Gott,</v>
<v lang=de>nimm alles von mir,</v>
<v lang=de>was mich hindert zu Dir.</v>
</p>
<p>
<v lang=de>Mein Herr und mein Gott,</v>
<v lang=de>gib alles mir,</v>
<v lang=de>was mich führet zu Dir.</v>
</p>
<p>
<v lang=de>Mein Herr und mein Gott,</v>
<v lang=de>nimm mich mir</v>
<v lang=de>und gib mich ganz zu eigen Dir.</v>
</p>
</Gebet>
<Gebet name={"Josephgebet des hl. Papst Pius X"} is_bilingue={false}>
<p slot="intro">Wenn man mehr zum hl. Joseph als <q>Patrone Morientium</q> wissen möchte kann man <a href="predigten/20220319-hl._joseph">hier</a> die Predigt zum Festtag des hl. Joseph nachlesen.</p>
<p>
<v>Jungfräulicher Vater Jesu,</v>
<v>Reinster Bräutigam Mariä,</v>
<v>Sankt Joseph, bitte Tag für Tag bei Jesus, dem Sohn Gottes.</v>
<v>Seine Kraft und Gnade soll uns stärken,</v>
<v>dass wir siegreich streiten im Leben</v>
<v>und die Krone von Ihm erhalten im Sterben.</v>
</p>
</Gebet>
</div>
</div>

View File

@ -0,0 +1,52 @@
<script>
export let is_bilingue;
export let name;
</script>
<style>
div.gebet{
text-align: center;
font-size: 1.25em;
}
:global(.gebet v){
margin:0;
display: block;
}
:global(.gebet v:lang(la)) {
color: var(--nord6);
}
:global(.bilingue v:lang(de)){
color: grey;
}
.gebet_wrapper h2{
text-align: center;
padding-bottom: 0.5em;
}
:global(.gebet i){
font-style: normal;
color: var(--nord11);
font-weight: 900;
}
.gebet_wrapper{
padding: 1em;
background-color: var(--accent-dark);
box-shadow: 0 0 1em black;
max-width: 600px;
}
@media(prefers-color-scheme: light){
.gebet_wrapper{
background-color: var(--accent-light);
}
:global(.gebet v:lang(la)){
color: black;
}
}
</style>
<div class="gebet_wrapper">
<h2>{name}</h2>
<slot name="intro"></slot>
<div class="gebet" class:bilingue={is_bilingue}>
<slot></slot>
</div>
</div>

View File

@ -0,0 +1,314 @@
<script>
import "$lib/css/nordtheme.css"
import "$lib/css/christ.css"
</script>
<style>
@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;
}
</style>
<h1>13. Februar 2022 - Septuagesima</h1>
<p>Das ganze hier ist noch ein Platzhalter</p>
<h2>Der Osterfestkreis</h2>
<div class=schott>
<p>
Der Weihnachtsfestkreis schließt mit der Woche vor Septuagesima.
Der ersehnte Erlöser ist gekommen und hat in seiner ersten Ankunft zugleich seine zweite, die am Gerichtstage erfolgen wird, begründet und begonnen.
</p>
<p>
Jetzt ist die Zeit des anstrengten Kampfes gegen Sünde, Welt und Fleisch gekommen, die Zeit der mühevollen Aussaat, des sturmumtobten Wachsens.
Durch Kampf zum Sieg, durch Sterben zum Leben, zur Auferstehung, zur Vollherrschaft Christi und schließlich zur Verklärung im Osterlichte!
Christus soll in uns den Thron seiner Herrschaft errichten, einer Herrschaft, die uns nicht erdrückt, sondern erhöht; nicht beraubt, sondern bereichert; nicht einschränkt, sondern innerlich weitet und uns einmal mitherrschen läßt im ewigen Ostern des Himmels.
</p>
<p>
Der Osterfestkreis umfaßt drei Abschnitte:
die Zeit der Vorbereitung: Vorfasten- und Fastenzeit;
die eigentliche Festzeit: Oster- und Pfingstfest;
endlich die Zeit nach Pfingsten
</p>
</div>
<h2> I. Die Zeit der Vorbereitung </h2>
<h3> 1. Die Vorfastenzeit</h3>
<div class=schott >
<p>
Sie umfaßt die Sonntage Septuagesima, Sexagesima und Quinquagesima.
Diese Namen bezeichnen nicht die genauen Abstände bis zum Osterfest, sonder deuten auf die rund berechnete 70tägige, 60tägige, 50tägige Vorbereitungzeit auf Ostern.
</p>
<p>
Der Name Septuagesima weckt die Erinnerung an die 70 Jahre der Gefangenschaft, welche die Juden zur Strafe für ihre Untreue fern von Jerusalem, zu Babylon, verbringen mußten, bevor sie wieder ins Gelobte Land zurückkerhen durften.
So mahnt uns diese Zeit an unsre eigene Pilgerschaft aus der Fremde, aus der gottfernen Welt (Babylon), zum wahren Vaterland (Jerusalem).
Diese Pilgerschaft ist für uns eine beständiger Kampf gegen die Feinde unsres Heiles.
Für den göttlichen Heiland bedeutet das öffentliche Wirken Mühsal und Leiden und schließlich den Tod;
so muß sich auch unser Leben, soll es dem seinen nachgebildet werden, auf Kämpfe, selbst auf ein geistiges Sterben gefaßt machen;
erst dann wird es mit dem Heiland zum endlichen Triumph gelangen.
</p>
</div>
<h2>Sonntag Septuagesima</h2>
<p>13. Februar 2022 - <i>2. Klasse - Farbe violett</i></p>
<h3> Schott </h3>
<p class=schott>
Durch den Kampf zum Sieg, duch Sterben zum Leben, zur Aufersteheung, zur Verklärung: das sind die Gedanken der Vorfastenzeit.
Eine lichtvolle Darstellung dieser Gedanken ist der hl. Laurentius, der Patron der Katechumen in Rom und Patron heutigen Stationskirche.
Larentius - in Todesnöten, auf dem glühenden Roste (Intr.). über ihm die Siegeskrone der Verklärung (vgl. Introituspsalm) - ist ein Wegweiser für die Katechumen und für uns.
Mit ihm treten wir entschlossen und kampfbereit in die Rennbahn und eignen uns Pauli Geist und Grundsätze an (Epistel).
Wir folgen dem Ruf des Hausvaters (Christi) in seinen Weinberg und sind bereit, seinen Willen zu tun (Evang.).
Wir entsagen uns selbst und bringen uns in der Opfergabe dar.
Gestützt auf die Kraft der Gnade Christi, die über uns im hl. Opfer uns besonders ind der hl. Kommunion verklärend aufleuchtet (Comm.), gehen wir neu gestärkt in den Kampf und die Mühsal unseres Christenberufes.
</p>
<h3> Epistel </h3>
<div class="epistel bibel">
<ol><i>1 Cor. 9</i>
<li value=24>Wisset ihr nicht, daß die, welche in der Rennbahn laufen, zwar alle laufen, aber nur einer erlangt den Preis? Laufet fo, daß ihr ihn erlanget!</li>
<li>Jeder aber, der im Kampfspiele ringt, enthält sich von allem, und zwar jene, um eine vergängliche Krone zu empfangen, wir aber eine unvergängliche.</li>
<li>Ich laufe demnach, nicht wie in's Ungewisse; ich kämpfe, nicht indem ich Luftstreiche thue,</li>
<li>sondern ich züchtige meinen Leib, und bringe ihn in die Botmäßigkeit, damit ich nicht etwa, nachdem ich anderen gepredigt habe, selbst verworfen werde.</li>
<i>1 Cor. 10</i>
<li value=1>Denn ich will euch nicht in Unwissenheit lassen, Brüder! Daß unsere Väter alle unter der Wolke waren, und alle durch das Meer hindurch gingen,</li>
<li>und alle auf Moses getauft wurden, in der Wolke und in dem Meere,</li>
<li>und alle dieselbe geistige Speise aßen,</li>
<li>und alle dieselbe geistigen Trank tranken (sie tranken nämlich aus einem geistigen, sie begleitenden Felsen, der Felsen aber war Christus);</li>
<li>aber an der Mehrzahl von ihnen hatte Gott kein Wohlgefallen; denn sie wurden niedergestreckt in der Wüste.</li>
</ol>
</div>
<h3> Evangelium </h3>
<div class="evangelium bibel">
<ol><i>Matth. 20</i>
<li>Das Himmelreich ist gleich einem Hausvater, der am frühen Morgen ausging, um Arbeiter in seinen Weinberg zu dingen.</li>
<li>Nachdem er nun mit den Arbeitern um einen Denar für den Tag übereingekommen war, sandte er sie in seinen Weinberg.</li>
<li>Und als er um die dritte Stunde ausging, sah er andere au dem Markte müßig stehen,</li>
<li>und sprach zu ihnen: Gehet auch ihr in meinen Weinberg, und was recht ist, werde ich euch geben.</li>
<li>Sie aber gingen hin. Abermals ging er um die sechste und neunte Stunde aus, und that ebenso.</li>
<li>Um die elfte Stunde aber ging er aus, und fand andere andere stehen, und sprach zu ihnen: Was stehet ihr hier den ganzen Tag müßig?</li>
<li>Sie antworteten ihm: Weil uns niemand gedungen hat. Da sprach er zu ihnen: Gehet auch ihr in meinen Weinberg,</li>
<li>Als es nun Abend geworden, sagte der Herr des Weinberges zu seinem Verwalter: Rufe die Arbeiter, und gib ihnen den Lohn, von den letzten anfangend, bis zu den ersten.</li>
<li>Da nun die kamen, welche um die elfte Stunde eingetreten waren, empfingen sie jeder einen Denar.</li>
<li>Wie aber auch die ersten kamen, meinten sie, daß sie mehr empfangen würden, aber auch sie erhielten jeder einen Denar.</li>
<li>Und da sie ihn empfingen, murrten sie wider den Hausvater.</li>
<li>und sprachen: Siese letzten haben eine einzige Stunde gearbeitet, und du hast sie uns gleich gehalten, die wir die Last und Hitze des Tages getragen haben.</li>
<li>Er aber antowrtete einem aus ihnen, und sprach: Freund! ich thue dir nicht Unrecht; bist du nicht auf einen Denar mit mir eins geworden?</li>
<li>Nimm, was dein ist, und gehe hin; ich will aber auch diesem letzten geben, wie dir.</li>
<li>Oder ist es mir nicht erlaubt zu thun, was ich will? Ist etwa dein Auge darum böse, weil ich gut bin?</li>
<li>So werden die Letzten die Ersten, und die Ersten die Letzten sein; denn viele sind berufen, aber wenige auserwählt!</li>
</ol>
</div>
<h3> Predigt </h3>
<p class="predigt einleitung" >
Es handelt sich dabei um die Predigt von H. H. Pater Cadiet zu Zaitzkofen welche man <a href="https://www.youtube.com/watch?v=mzqmcbYq9Xk">hier</a> (<a href="https://bocken.org/static/predigten/20220213-Predigt_am_Sonntag_Septuagesima.mp4">Mirror</a>) auch noch nachschauen kann. Die Messe wurde via Livestream mitverfolgt.
</p>
<div class="predigt inhalt">
<p>Es ist nicht einfach in der heutigen Zeit als Gläubiger. Vieles was unnatürlich ist wird als natürlich verlogen und Gottes Werk verneint. Somit ist der Aufruf zum Kampf passender den je.
Wir sollen gleich den Sportlern verzichten für das Heil der Seelen, dem Heil der eigenen Seele.
</p>
<p>
Diese Bildnis der Spiele im Stadion waren vermutlich ein gutes Bildnis für die Korinter. So hatten sie alle zwei Jahre Sportspiele von April bis Anfang Mai, welche wie auch Fußball heute noch, vieles der Gesellschaft lahmlegte.
Auch die Gläubigen gehörten damals wie heute zu den Begeisterten solcher Spiele.
</p>
<p>
Jedoch ist die heutige Lehre, dass es nicht nur eine Medaillie gibt, wie vielleicht die Epistel einen erwarten lässt.
Daher das gewählte Evangelium für den heutigen Tag, um zu zeigen, dass das Relevante ist zu kämpfen als gäbe es nur einen Sieger.
Auch diese, welche erst <q>zur elften Stunde</q> anfangen zu kämpfen werden einen Sieg erreichen können.
<p>
<p>
Diese aber, die nicht kämpfen, werden es bitter bereuen. Dazu gibt es 3 Beispiele aus der heiligen Schrift:
</p>
<h4>1. König David:</h4>
<p>
Sein Leben ist ein Kampf, aber als er seine Macht erreicht hatte und keine Gegner mehr sah, da ruhte er sich aus und sandte seine Kämpfer aus während er auf seiner Terasse zurückblieb.
</p>
<p>
Er hat sich auf seinen Loorbeeren ausgeruht. <q>Was hat er noch zu tun?</q> <q>Hat er nicht alles bereits erreicht?</q>
Somit wird mit einem Blick besiegt.
Nicht von Anderen Menschen, aber vom Teufel durch sich selbst.
So beging er zwei Todsünden durch einen Blick auf eine Frau: Ehebruch und Mord am Manne dieser Frau.
</p>
<p>
Als Kind hat er Löwen und Bären mit bloßen Händen besiegt, aber nun wird dieser einst mutige und starke Mann bezwungen wegen seinem <em>Müßiggang</em>.
Wie man zu sagen pflegt: Wer man kein Beschäftigugn welche Platz einnimmt so wird der Teufel selbst den ganzen Platz einnehmen, den wir freigelassen haben.
Es wäre besser gewesen für David, noch in der Ängstlichkeit der Flucht vor Saulus zu sein, als in seinem Palast in Jerusalem.
</p>
<div class=quote>
<p class=title>
<a href=https://en.wikipedia.org/wiki/Ignatius_of_Loyola>Der Heilige Ignatius von Loyola</a>; Regel zur Unterscheidung der Geister:
</p>
<q>Wenn wir in Schwierigkeiten sind, dann beten wir den lieben Gott treu zu bleiben.
Wenn alles gut geht denken wir schon daran, welche Schwierigkeiten kommen können und beten wir um Vorrat von Mut und von Eifer für die Zeit der Trostlosigkeit, der Prüfung.</q>
</div>
<p>
Müßiggang hat ihn besiegt und der Gedanke, dass er nichts mehr zu erobern hatte.
Es gibt immer etwas weiteres zu erobern. Es gibt immer eine Ecke in unserer Seele die uns, und damit Gott, nicht gehört.
Es gibt immer etwas, was man besser tun kann, es gibt immer schlechte Gewohnheiten die man ablegen muss.
In dieser Vorfastenzeit geht es darum sich diesem deutlicher bewusst zu werden und seinen Kampf gegen diese Müßigkeit für die kommende Fastenzeit zu planen.
</p>
<p>
Als Beispiel hilft hier das Königreich Spanien. Jahrhunderte von Kampf um die Anwesenheit der Muslime zu bekämpfen. Als sie endlich die letzte Stadt, welche unter der Vollmacht der Muslime war, erobert hatten, hat die Vorsehung ihnen noch etwas zu erobern gegeben:
Im Jahre 1492, dem Fall von Granada, wurde Spanien (und Portugal) ein ganzer Kontinent zum Erobern im Namen Christi geschenkt.
Als Spanien endlich von der Versuchung der Apostasie, dem Glaubensabfall, befreit wurde hat sich diese Chance eröffnet.
Es gibt immer noch etwas zu erobern.
Falls wir nichts finden, dann wird die Vorsehung uns etwas zeigen. (Siehe Zitat oben).
</p>
<h4>2. Die Hebräer in der Wüste: <i>4 Mose 13</i></h4>
<p>
Von den Ägyptern durch die Wunder Gottes befreit, sind sie nun auf dem weiten Weg zum versprochenen Land.
Sie schicken Kundschafter in dieses Land. Diese Kundschafter gehen und verbringen 40 Tage dort.
Diese Kundschafter finden ein wunderbares Land. Es fließt Honig und Milch. Aber es ist nicht unbevölkert. Es gibt viele, starke Stämme.
Die Kundschafter haben Angst, sie verbreiteten Lügen über dieses Land da sie Angst haben zu fallen im Versuch es einzunehmen.
Das Volk will murren, beklagen. Sie wollen nicht kämpfen.
</p>
<p>
Warum haben sie gelogen? Sie wollten die eigene Angst rechtfertigen. Der liebe Gott lässt sie daher als Strafe 40 Jahre lang leben in der Wüste, sodass nur die nächst e Generation in Besitzt kommen wird des versprochenem Lande.
Wie konnten sie Gott so verletzen? Als ob Er keine Wunder für sie getätigt hätte? </p>
<div class=quote><p class=title>Hl. Thomas von Aquin in einem Kommentar zur Bibel</p>
<div class=bibel>
<ol><i>Kol. 3</i>
<li value=21>Ihr Väter! reizet eure Kinder nicht zum Zorne, damit sie nicht mutlos werden.</li>
</ol>
</div>
<q>
Der Sinn dieses Ratschlags ist, dass der Mensch den Eindruck seiner Kindheit behält.
Es ist natürlich demjenigen, der in Knechtschaft erzogen wurde, immer kleinmütig, mutlos zu bleiben.
Und daraus haben die Israeliten in der Wüste Angst gehabt vor dem Kampf weil sie immer in Knechtschaft erzogen wurden.
Sie hatten keinen Mut mehr.
</q>
</div>
<p>
Diese Knechtschaft soll abgelegt werden um so zu einem kämpferischen Geist zu kommen. Das wird auch bestätigt im Johannesevangelium wo steht:
<div class=bibel>
<ol><i> Johannes 15</i>
<li value=15>Ich nenne euch nun nicht mehr Knechte, denn der Knecht weiß nicht, was sein Herr tut; euch aber habe ich Freunde genannt; denn alles, was ich von meinem Vater gehört, habe ich euch kundgetan.
</li>
</ol>
</div>
<p>
Was der Herr von seinen Jüngern erwartet ist Liebe, inneres Verständnis und Kühnheit.
Kampf gegen unsere Menschenfurcht.
Dazu erzieht Er seine Jünger zur Freiheit.
So sagt auch Paulus in seinen Briefen an die Galater:
<div class=bibel>
<ol><i>Galater 4</i>
<li value=31>Demnach, Brüder! sind wir nicht Kinder der Magd, sondern der Freien, vermöge der Freiheit, mit der Christus uns befreit hat.</li>
</ol>
</div>
<p>
Eine Lektion für uns:
Jeder Ausbilder erzieht zur Freiheit, zur Autonomie. Jeder Ausbilder arbeitet um nutzlos zu werden, das seine Jünglinge alleine tun können, was er mit ihm erlernt.
Der Jüngling alles selbst tun lassen, damit er lernt.
</p>
<h4>3. Petrus:</h4>
<p>
Der hl. Petrus, der immer mutig ist, Jesus zu verteidigen und seine Treue zu bekennen. Er wird von einer Magd besiegt werden.
</p>
<div class=bibel>
<ol><i>Johannes 18</i>
<li value=10>Simon Petrus also, der sein Schwert hatte, zog es und schlug den Knecht des Hohenpriesters, und hieb ihm sein rechtes Ohr ab. Der Name des Knechtes aber war Malchus.</li>
<i>Matthäus 26</i>
<li value=35>Da sprach Petrus zu ihm: Wenn ich auch mit dir sterben müsste, werde ich dich doch nicht verleugnen. In gleicher Weise sprachen auch alle Jünger.</li>
<li value=69>Petrus aber saß draußen in dem Hofe; und eine Magd trat zu ihm hin, und sprach: Du warest auch bei Jesus, dem Galiläer!</li>
<li>Doch er leugnete vor allen, und sprach: Ich weiss nicht, was du sagst.</li>
</ol>
</div>
<p>
War Petrus nicht kühn genug? War er nicht kämpferisch genug?
Er wird dieses mal gegen sich Selbst kämpfen müssen.
Daraus sollte er beten. Er hat nicht gebetet, er hat geschlafen.
</p>
<p>
Lektion für uns:
Nur im Gebet werden wir die Kraft und Hellsichtigkeit schöpfen, um richtig zu kämpfen.
Nur durch das Gebet hätte Petrus verstanden, was Jesus gesagt hatte nach dem Abendmahl.
</p>
<div class=bibel>
<ol><i>Matthäus 26</i>
<li value=37>Und er nahm den Petrus und die zwei Söhne des Zebedäus mit sich, und fing an, sich zu betrüben und zu bangen.</li>
<li>Da sprach er zu ihnen: Meine Seele ist betrübt bis in den Tod, bleibet hier und wachet mit mir!</li>
<li>Und nachdem er ein wenig vorwärts gegangen war, fiel er auf sein Angesicht, betete, und sprach: Mein Vater! wenn es möglich ist, so gehe dieser Kelch an mir vorüber; jedoch nicht wie ich will, sondern wie du.</li>
<li>Und er kam zu seinen Jüngern, und fand sie schlafend, und sprach zu Petrus: So vermochtet ihr nicht eine Stunde mit mir zu wachen?</li>
<li>Wachet und betet, damit ihr nicht in Versuchung geratet! Der Geist zwar ist willig, das Fleisch aber ist schwach.</li>
</ol>
</div>
<p>
Die neue Lebensweise der Apostel: Es wird Schwert geben, es wird Kampf geben, nimmt euch Beutel Stab usw mit euch.
Drei Beispiele von Leuten die nicht kämpften. Aber: Die Hebräer haben trotzdem 40 Jahre später das Land erobert. König David bekehrte sich, hat das wunderbare Vorbild der Reue gegeben. So auch der hl. Petrus und wurde zum Fürst der Apostel und unser Glaube hängt an seinem.</p>
<p>
Alle bekamen die Kraft Gottes, Petrus sogar so sehr, dass er lehrte bis an seinen eigenen Kreuzestod.
Es ist die Zeit gekommen um sich selbst zu erobern, sich selbst zu besiegen um Gott völlig gefällig zu sein.
</p>
</div>

File diff suppressed because one or more lines are too long

View File

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

View File

@ -2,9 +2,9 @@
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
let user;
if(data.session){
user = data.session.user
}
</script>
@ -15,7 +15,8 @@ if(data.user){
<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 {username}></UserHeader>
<UserHeader slot=right_side {user}></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", "Unterwegs"]
const categories = ["Hauptspeise", "Nudel", "Brot", "Dessert", "Suppe", "Beilage", "Salat", "Kuchen", "Frühstück", "Sauce", "Zutat", "Getränk", "Aufstrich", "Guetzli", "Snack"]
</script>
<style>
h1{
@ -14,30 +14,36 @@ 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>
<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/al_ragu.webp" />
<meta property="og:image:secure_url" content="https://bocken.org/static/rezepte/thumb/al_ragu.webp" />
<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>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}></Card>
<Card {recipe} {current_month} loading_strat={"eager"} do_margin_right={true}></Card>
{/each}
</MediaScroller>
{#each categories as category}
<MediaScroller title={category}>
{#each data.all_brief.filter(recipe => recipe.category == category) as recipe}
<Card {recipe} {current_month}></Card>
<Card {recipe} {current_month} do_margin_right={true}></Card>
{/each}
</MediaScroller>
{/each}

View File

@ -15,8 +15,8 @@
export let data: PageData;
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"
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
export let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]
function season_intervals() {
let interval_arr = []
@ -185,6 +185,7 @@ h1{
}
.icon{
font-family: "Noto Color Emoji", emoji;
position: absolute;
top: -1em;
right: -0.75em;
@ -292,7 +293,9 @@ h4{
<h4>Saison:</h4>
{#each season_iv as season}
<a class=tag href="/rezepte/season/{season[0]}">
{months[season[0] - 1]}
{#if season[0]}
{months[season[0] - 1]}
{/if}
{#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,
name: short_name.trim(),
}
await fetch(`/api/rezepte/img/add`, {
method: 'POST',
@ -108,9 +108,9 @@
recipe: {
...card_data,
...add_info,
images: {mediapath: short_name + '.webp', alt: "", caption: ""}, // TODO
images: {mediapath: short_name.trim() + '.webp', alt: "", caption: ""}, // TODO
season: season_local,
short_name,
short_name : short_name.trim(),
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"
let image_preview_url="https://bocken.org/static/rezepte/thumb/" + data.recipe.short_name + ".webp?v=" + data.recipe.dateModified;
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,
name: short_name.trim(),
}
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,
new_name: short_name.trim(),
})
})
if(!res_img.ok){
@ -208,7 +208,7 @@
...add_info,
images, // TODO
season: season_local,
short_name,
short_name: short_name.trim(),
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);
url.push(short_name.trim());
location.assign(url.join('/'))
}
else{

View File

@ -1,6 +1,6 @@
<script lang="ts">
import type { PageData } from './$types';
import '$lib/components/nordtheme.css';
import '$lib/css/nordtheme.css';
import Recipes from '$lib/components/Recipes.svelte';
import MediaScroller from '$lib/components/MediaScroller.svelte';
import SeasonLayout from '$lib/components/SeasonLayout.svelte'
@ -10,6 +10,7 @@
</script>
<style>
a{
font-family: "Noto Color Emoji", emoji, sans-serif;
--padding: 0.5em;
font-size: 3rem;
text-decoration: none;

View File

@ -1,6 +1,6 @@
<script lang="ts">
import type { PageData } from './$types';
import '$lib/components/nordtheme.css';
import '$lib/css/nordtheme.css';
import Recipes from '$lib/components/Recipes.svelte';
import MediaScroller from '$lib/components/MediaScroller.svelte';
import SeasonLayout from '$lib/components/SeasonLayout.svelte'

View File

@ -0,0 +1,62 @@
<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

@ -0,0 +1,68 @@
<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>

Some files were not shown because too many files have changed in this diff Show More