Compare commits
64 Commits
9da0a2740d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
b255fc01e9
|
|||
|
b90a42b1aa
|
|||
|
7ba0995bf8
|
|||
|
9177164ddf
|
|||
|
207efcc38e
|
|||
|
f074c0af08
|
|||
|
d0a01a75e7
|
|||
|
53da9ad26d
|
|||
|
0ea09e424e
|
|||
|
716c6cc6e6
|
|||
|
eeb3030186
|
|||
|
16d891fc2f
|
|||
|
cf73e6b62f
|
|||
|
8db7ca6bcc
|
|||
|
13fd2143d9
|
|||
|
a074fdc7e3
|
|||
|
dbf6744479
|
|||
|
28057e88d5
|
|||
|
e58c8e46ef
|
|||
|
2024551e0e
|
|||
|
c53aee7123
|
|||
|
7e94758b23
|
|||
|
10dd3158fe
|
|||
|
7922563c4d
|
|||
|
c855cdd25c
|
|||
|
f900d4217d
|
|||
|
0e9daf296d
|
|||
|
4191012cf1
|
|||
|
a435a1142f
|
|||
|
8c984f3064
|
|||
|
044fddd1c9
|
|||
|
db28629c7d
|
|||
|
0e0af55ce7
|
|||
|
601e2f6513
|
|||
|
aa64cd8306
|
|||
|
96a91ed8dd
|
|||
|
a0146927b6
|
|||
|
443e3300a1
|
|||
|
2f711c66b0
|
|||
|
7901d56b5b
|
|||
|
2c364ed351
|
|||
|
904c5c0df0
|
|||
|
091c23a0bd
|
|||
|
7bdc62489c
|
|||
|
01ccc705ee
|
|||
|
35ea60e637
|
|||
|
ac4c00a082
|
|||
|
8560077759
|
|||
|
b3c3f34e50
|
|||
|
6eaf0bb4f4
|
|||
|
07554f16df
|
|||
|
bf3014337e
|
|||
|
6182b8f943
|
|||
|
8246906a76
|
|||
|
a5e119f976
|
|||
|
a4738134fe
|
|||
|
6433576b28
|
|||
|
ea6d2cab5c
|
|||
|
cda8fe0885
|
|||
|
83de5fed34
|
|||
|
8776ab894b
|
|||
|
7d6a80442a
|
|||
|
1c100a4534
|
|||
|
d65886b4e7
|
@@ -5,7 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"prebuild": "npx vite-node scripts/generate-mystery-verses.ts",
|
"prebuild": "bash scripts/subset-emoji-font.sh && npx vite-node scripts/generate-mystery-verses.ts",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
|||||||
@@ -5,48 +5,82 @@
|
|||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { lookupReference } from '../src/lib/server/bible';
|
import { lookupReference } from '../src/lib/server/bible';
|
||||||
import { mysteryReferences } from '../src/lib/data/mysteryDescriptions';
|
import { mysteryReferences, mysteryReferencesEnglish, theologicalVirtueReference, theologicalVirtueReferenceEnglish } from '../src/lib/data/mysteryDescriptions';
|
||||||
import type { MysteryDescription, VerseData } from '../src/lib/data/mysteryDescriptions';
|
import type { MysteryDescription, VerseData } from '../src/lib/data/mysteryDescriptions';
|
||||||
|
|
||||||
const tsvPath = resolve('static/allioli.tsv');
|
function generateVerseData(
|
||||||
|
references: Record<string, readonly { title: string; reference: string }[]>,
|
||||||
|
tsvPath: string
|
||||||
|
): Record<string, MysteryDescription[]> {
|
||||||
|
const result: Record<string, MysteryDescription[]> = {};
|
||||||
|
|
||||||
const mysteryDescriptions: Record<string, MysteryDescription[]> = {};
|
for (const [mysteryType, refs] of Object.entries(references)) {
|
||||||
|
const descriptions: MysteryDescription[] = [];
|
||||||
|
|
||||||
for (const [mysteryType, references] of Object.entries(mysteryReferences)) {
|
for (const ref of refs) {
|
||||||
const descriptions: MysteryDescription[] = [];
|
const lookup = lookupReference(ref.reference, tsvPath);
|
||||||
|
|
||||||
for (const ref of references) {
|
let text = '';
|
||||||
const result = lookupReference(ref.reference, tsvPath);
|
let verseData: VerseData | null = null;
|
||||||
|
|
||||||
let text = '';
|
if (lookup && lookup.verses.length > 0) {
|
||||||
let verseData: VerseData | null = null;
|
text = `«${lookup.verses.map((v) => v.text).join(' ')}»`;
|
||||||
|
verseData = {
|
||||||
|
book: lookup.book,
|
||||||
|
chapter: lookup.chapter,
|
||||||
|
verses: lookup.verses
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.warn(`No verses found for: ${ref.reference} in ${tsvPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (result && result.verses.length > 0) {
|
descriptions.push({
|
||||||
text = `«${result.verses.map((v) => v.text).join(' ')}»`;
|
title: ref.title,
|
||||||
verseData = {
|
reference: ref.reference,
|
||||||
book: result.book,
|
text,
|
||||||
chapter: result.chapter,
|
verseData
|
||||||
verses: result.verses
|
});
|
||||||
};
|
|
||||||
} else {
|
|
||||||
console.warn(`No verses found for: ${ref.reference}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptions.push({
|
result[mysteryType] = descriptions;
|
||||||
title: ref.title,
|
|
||||||
reference: ref.reference,
|
|
||||||
text,
|
|
||||||
verseData
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mysteryDescriptions[mysteryType] = descriptions;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dePath = resolve('static/allioli.tsv');
|
||||||
|
const enPath = resolve('static/drb.tsv');
|
||||||
|
|
||||||
|
const mysteryVerseDataDe = generateVerseData(mysteryReferences, dePath);
|
||||||
|
const mysteryVerseDataEn = generateVerseData(mysteryReferencesEnglish, enPath);
|
||||||
|
|
||||||
|
// Generate theological virtue (1 Cor 13) verse data
|
||||||
|
function generateSingleRef(ref: { title: string; reference: string }, tsvPath: string): MysteryDescription {
|
||||||
|
const lookup = lookupReference(ref.reference, tsvPath);
|
||||||
|
let text = '';
|
||||||
|
let verseData: VerseData | null = null;
|
||||||
|
if (lookup && lookup.verses.length > 0) {
|
||||||
|
text = `«${lookup.verses.map((v) => v.text).join(' ')}»`;
|
||||||
|
verseData = { book: lookup.book, chapter: lookup.chapter, verses: lookup.verses };
|
||||||
|
} else {
|
||||||
|
console.warn(`No verses found for: ${ref.reference} in ${tsvPath}`);
|
||||||
|
}
|
||||||
|
return { title: ref.title, reference: ref.reference, text, verseData };
|
||||||
|
}
|
||||||
|
|
||||||
|
const theologicalVirtueDataDe = generateSingleRef(theologicalVirtueReference, dePath);
|
||||||
|
const theologicalVirtueDataEn = generateSingleRef(theologicalVirtueReferenceEnglish, enPath);
|
||||||
|
|
||||||
const output = `// Auto-generated by scripts/generate-mystery-verses.ts — do not edit manually
|
const output = `// Auto-generated by scripts/generate-mystery-verses.ts — do not edit manually
|
||||||
import type { MysteryDescription } from './mysteryDescriptions';
|
import type { MysteryDescription } from './mysteryDescriptions';
|
||||||
|
|
||||||
export const mysteryVerseData: Record<string, MysteryDescription[]> = ${JSON.stringify(mysteryDescriptions, null, '\t')};
|
export const mysteryVerseDataDe: Record<string, MysteryDescription[]> = ${JSON.stringify(mysteryVerseDataDe, null, '\t')};
|
||||||
|
|
||||||
|
export const mysteryVerseDataEn: Record<string, MysteryDescription[]> = ${JSON.stringify(mysteryVerseDataEn, null, '\t')};
|
||||||
|
|
||||||
|
export const theologicalVirtueVerseDataDe: MysteryDescription = ${JSON.stringify(theologicalVirtueDataDe, null, '\t')};
|
||||||
|
|
||||||
|
export const theologicalVirtueVerseDataEn: MysteryDescription = ${JSON.stringify(theologicalVirtueDataEn, null, '\t')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const outPath = resolve('src/lib/data/mysteryVerseData.ts');
|
const outPath = resolve('src/lib/data/mysteryVerseData.ts');
|
||||||
|
|||||||
54
scripts/subset-emoji-font.sh
Executable file
54
scripts/subset-emoji-font.sh
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Subset NotoColorEmoji to only the emojis we actually use.
|
||||||
|
# Requires: fonttools (provides pyftsubset) and woff2 (provides woff2_compress)
|
||||||
|
#
|
||||||
|
# Source font: system-installed NotoColorEmoji.ttf
|
||||||
|
# Output: static/fonts/NotoColorEmoji.woff2 + .ttf
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
OUT_DIR="$PROJECT_ROOT/static/fonts"
|
||||||
|
|
||||||
|
SRC_FONT="/usr/share/fonts/noto/NotoColorEmoji.ttf"
|
||||||
|
|
||||||
|
if [ ! -f "$SRC_FONT" ]; then
|
||||||
|
echo "Error: Source font not found at $SRC_FONT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Fixed list of emojis to include ────────────────────────────────
|
||||||
|
# Recipe icons (from database + hardcoded)
|
||||||
|
# Season/liturgical: ☀️ ✝️ ❄️ 🌷 🍂 🎄 🐇
|
||||||
|
# Food/recipe: 🍽️ 🥫
|
||||||
|
# UI/cospend categories: 🛒 🛍️ 🚆 ⚡ 🎉 🤝 💸
|
||||||
|
# Status/feedback: ❤️ 🖤 ✅ ❌ 🚀 ⚠️ ✨ 🔄
|
||||||
|
# Features: 📋 🖼️ 📖 🤖 🌐 🔐 🔍 🚫
|
||||||
|
|
||||||
|
EMOJIS="☀✝❄🌷🍂🎄🐇🍽🥫🛒🛍🚆⚡🎉🤝💸❤🖤✅❌🚀⚠✨🔄📋🖼📖🤖🌐🔐🔍🚫"
|
||||||
|
# ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Build Unicode codepoint list from the emoji string (Python for reliable Unicode handling)
|
||||||
|
UNICODES=$(python3 -c "print(','.join(f'U+{ord(c):04X}' for c in '$EMOJIS'))")
|
||||||
|
GLYPH_COUNT=$(python3 -c "print(len('$EMOJIS'))")
|
||||||
|
|
||||||
|
echo "Subsetting NotoColorEmoji with $GLYPH_COUNT glyphs..."
|
||||||
|
|
||||||
|
# Subset to TTF
|
||||||
|
pyftsubset "$SRC_FONT" \
|
||||||
|
--unicodes="$UNICODES" \
|
||||||
|
--output-file="$OUT_DIR/NotoColorEmoji.ttf" \
|
||||||
|
--no-ignore-missing-unicodes
|
||||||
|
|
||||||
|
# Convert to WOFF2
|
||||||
|
woff2_compress "$OUT_DIR/NotoColorEmoji.ttf"
|
||||||
|
|
||||||
|
ORIG_SIZE=$(stat -c%s "$SRC_FONT" 2>/dev/null || stat -f%z "$SRC_FONT")
|
||||||
|
TTF_SIZE=$(stat -c%s "$OUT_DIR/NotoColorEmoji.ttf" 2>/dev/null || stat -f%z "$OUT_DIR/NotoColorEmoji.ttf")
|
||||||
|
WOFF2_SIZE=$(stat -c%s "$OUT_DIR/NotoColorEmoji.woff2" 2>/dev/null || stat -f%z "$OUT_DIR/NotoColorEmoji.woff2")
|
||||||
|
|
||||||
|
echo "Done!"
|
||||||
|
echo " Original: $(numfmt --to=iec "$ORIG_SIZE")"
|
||||||
|
echo " TTF: $(numfmt --to=iec "$TTF_SIZE")"
|
||||||
|
echo " WOFF2: $(numfmt --to=iec "$WOFF2_SIZE")"
|
||||||
215
src/app.css
215
src/app.css
@@ -15,6 +15,15 @@
|
|||||||
url(/fonts/crosses.ttf) format('truetype');
|
url(/fonts/crosses.ttf) format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Color Emoji Subset';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/fonts/NotoColorEmoji.woff2) format('woff2'),
|
||||||
|
url(/fonts/NotoColorEmoji.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
COLOR SYSTEM
|
COLOR SYSTEM
|
||||||
Based on Nord Theme with semantic naming
|
Based on Nord Theme with semantic naming
|
||||||
@@ -109,6 +118,35 @@
|
|||||||
--color-warning: var(--nord13);
|
--color-warning: var(--nord13);
|
||||||
--color-error: var(--nord11);
|
--color-error: var(--nord11);
|
||||||
--color-info: var(--nord10);
|
--color-info: var(--nord10);
|
||||||
|
|
||||||
|
/* Shared transitions & shadows */
|
||||||
|
--transition-fast: 100ms;
|
||||||
|
--transition-normal: 200ms;
|
||||||
|
--shadow-sm: 0 0 0.4em 0.05em rgba(0,0,0,0.2);
|
||||||
|
--shadow-md: 0 0 0.5em 0.1em rgba(0,0,0,0.3);
|
||||||
|
--shadow-lg: 0 0 1em 0.1em rgba(0,0,0,0.4);
|
||||||
|
--shadow-hover: 0.1em 0.1em 0.5em 0.1em rgba(0,0,0,0.3);
|
||||||
|
--radius-pill: 1000px;
|
||||||
|
--radius-card: 20px;
|
||||||
|
--radius-sm: 0.3rem;
|
||||||
|
--radius-md: 0.5rem;
|
||||||
|
--radius-lg: 0.75rem;
|
||||||
|
|
||||||
|
/* Spacing scale */
|
||||||
|
--space-xs: 0.25rem;
|
||||||
|
--space-sm: 0.5rem;
|
||||||
|
--space-md: 1rem;
|
||||||
|
--space-lg: 1.5rem;
|
||||||
|
--space-xl: 2rem;
|
||||||
|
--space-2xl: 3rem;
|
||||||
|
|
||||||
|
/* Font size scale */
|
||||||
|
--text-sm: 0.85rem;
|
||||||
|
--text-base: 1rem;
|
||||||
|
--text-lg: 1.1rem;
|
||||||
|
--text-xl: 1.5rem;
|
||||||
|
--text-2xl: 2rem;
|
||||||
|
--text-3xl: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
@@ -208,65 +246,142 @@ a:focus-visible {
|
|||||||
color: var(--color-link-hover);
|
color: var(--color-link-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
FORM STYLES
|
GLOBAL UTILITY CLASSES
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|
||||||
form {
|
/* Pill-shaped element base */
|
||||||
background-color: var(--color-bg-secondary);
|
.g-pill {
|
||||||
display: flex;
|
border-radius: var(--radius-pill);
|
||||||
flex-direction: column;
|
|
||||||
max-width: 600px;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin-inline: auto;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding-block: 2rem;
|
|
||||||
margin-block: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
form label {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
form input {
|
|
||||||
display: block;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
form:not(.search) button {
|
|
||||||
background-color: var(--color-accent);
|
|
||||||
color: var(--color-text-on-accent);
|
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.5em 1em;
|
|
||||||
font-size: 1.3em;
|
|
||||||
border-radius: 1000px;
|
|
||||||
margin-top: 1em;
|
|
||||||
transition: 100ms;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
form:not(.search) button:hover,
|
/* Interactive hover/focus effects */
|
||||||
form:not(.search) button:focus-visible {
|
.g-interactive {
|
||||||
background-color: var(--color-accent-hover);
|
transition: var(--transition-fast);
|
||||||
scale: 1.1;
|
}
|
||||||
|
.g-interactive:hover,
|
||||||
|
.g-interactive:focus-visible {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
}
|
||||||
|
.g-interactive:focus {
|
||||||
|
scale: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
form:not(.search) button:active {
|
/* Light background button (with dark mode) */
|
||||||
background-color: var(--color-accent-active);
|
.g-btn-light {
|
||||||
|
background-color: var(--nord5);
|
||||||
|
color: var(--nord0);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
form p {
|
.g-btn-light {
|
||||||
max-width: 400px;
|
background-color: var(--nord0);
|
||||||
margin-top: 0;
|
color: white;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
form h4 {
|
|
||||||
margin-bottom: 0;
|
/* Dark background button */
|
||||||
}
|
.g-btn-dark,
|
||||||
|
.g-btn-dark:visited,
|
||||||
@media screen and (max-width: 600px) {
|
.g-btn-dark:link {
|
||||||
form {
|
background-color: var(--nord0);
|
||||||
margin-top: 0;
|
color: var(--nord6);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
.g-btn-dark:hover,
|
||||||
|
.g-btn-dark:focus-visible {
|
||||||
|
background-color: var(--nord1);
|
||||||
|
color: var(--nord6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon badge (circular icon container) */
|
||||||
|
.g-icon-badge {
|
||||||
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
.g-icon-badge:hover,
|
||||||
|
.g-icon-badge:focus-visible {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag/chip styling */
|
||||||
|
.g-tag,
|
||||||
|
.g-tag:visited,
|
||||||
|
.g-tag:link {
|
||||||
|
padding: 0.25em 1em;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background-color: var(--nord5);
|
||||||
|
color: var(--nord0);
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform var(--transition-fast), background-color var(--transition-fast), box-shadow var(--transition-fast), color var(--transition-fast);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.g-tag:hover,
|
||||||
|
.g-tag:focus-visible {
|
||||||
|
transform: scale(1.05);
|
||||||
|
background-color: var(--nord8);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.g-tag,
|
||||||
|
.g-tag:visited,
|
||||||
|
.g-tag:link {
|
||||||
|
background-color: var(--nord0);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.g-tag:hover,
|
||||||
|
.g-tag:focus-visible {
|
||||||
|
background-color: var(--nord8);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
RECIPE GRID
|
||||||
|
Responsive card grid used across recipe pages
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.recipe-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 0.8em;
|
||||||
|
padding: 0 0.8em;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto 2em;
|
||||||
|
}
|
||||||
|
@media (max-width: 250px) {
|
||||||
|
.recipe-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
.recipe-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 1.5em;
|
||||||
|
padding: 0 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.recipe-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
gap: 1.8em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,11 @@ async function authorization({ event, resolve }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bible verse functionality for error pages
|
// Bible verse functionality for error pages
|
||||||
async function getRandomVerse(fetch: typeof globalThis.fetch): Promise<any> {
|
async function getRandomVerse(fetch: typeof globalThis.fetch, pathname: string): Promise<any> {
|
||||||
|
const isEnglish = pathname.startsWith('/faith/') || pathname.startsWith('/recipes/');
|
||||||
|
const endpoint = isEnglish ? '/api/faith/bibel/zufallszitat' : '/api/glaube/bibel/zufallszitat';
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/glaube/bibel/zufallszitat');
|
const response = await fetch(endpoint);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -80,11 +82,14 @@ export const handleError: HandleServerError = async ({ error, event, status, mes
|
|||||||
console.error('Error occurred:', { error, status, message, url: event.url.pathname });
|
console.error('Error occurred:', { error, status, message, url: event.url.pathname });
|
||||||
|
|
||||||
// Add Bible verse to error context
|
// Add Bible verse to error context
|
||||||
const bibleQuote = await getRandomVerse(event.fetch);
|
const bibleQuote = await getRandomVerse(event.fetch, event.url.pathname);
|
||||||
|
|
||||||
|
const isEnglish = event.url.pathname.startsWith('/faith/') || event.url.pathname.startsWith('/recipes/');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: message,
|
message: message,
|
||||||
bibleQuote
|
bibleQuote,
|
||||||
|
lang: isEnglish ? 'en' : 'de'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
let { href, ariaLabel = undefined, children } = $props<{ href: string, ariaLabel?: string, children?: Snippet }>();
|
let { href, ariaLabel = undefined, children } = $props<{ href: string, ariaLabel?: string, children?: Snippet }>();
|
||||||
import "$lib/css/nordtheme.css"
|
|
||||||
import "$lib/css/action_button.css"
|
import "$lib/css/action_button.css"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -14,9 +13,9 @@ right:0;
|
|||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
margin: 2rem;
|
margin: 2rem;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
background-color: var(--red);
|
background-color: var(--red);
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
<script>
|
|
||||||
let { x = 0, y = 0, size = 40 } = $props();
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.bg{
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
.fg{
|
|
||||||
stroke: var(--nord2);
|
|
||||||
fill: var(--nord2);
|
|
||||||
stroke-linejoin: round;
|
|
||||||
stroke-linecap: round;
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
.bg{
|
|
||||||
fill: var(--nord3);
|
|
||||||
}
|
|
||||||
.fg{
|
|
||||||
stroke: white;
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<svg {x} {y} width={size} height={size} viewBox="0 0 334 326" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path class="bg" d="m168.72 17.281c-80.677 0-146.06 65.386-146.06 146.06 0 80.677 65.386 146.09 146.06 146.09 80.677 0 146.09-65.417 146.09-146.09 0-80.677-65.417-146.06-146.09-146.06zm2.9062 37.812c21.086 0.35166 41.858 7.6091 59.156 19.688 40.942 26.772 56.481 83.354 38.875 128.22-16.916 45.3-67.116 74.143-114.72 67.844-50.947-5.2807-92.379-52.101-94.563-102.72-4.0889-58.654 48.31-113.56 107.03-113 1.4077-0.03846 2.813-0.05469 4.2188-0.03125z"/>
|
|
||||||
<path class="bg" d="m166.45 51.969c-11.386 0.159-21.538 7.2129-24 12.25 3.2629 3.3685 6.337 8.536 7.375 19.5v159.78c-1.0775 10.727-4.1463 15.792-7.375 19.125 2.4156 4.9422 12.251 11.811 23.375 12.219v0.0312h4.8124c11.386-0.159 21.538-7.2129 24-12.25-3.2629-3.3685-6.337-8.536-7.375-19.5v-159.78c1.0775-10.727 4.1463-15.792 7.375-19.125-2.41-4.938-12.25-11.807-23.37-12.215v-0.03125h-4.8124z"/>
|
|
||||||
<path class="bg" d="m280 161.33c-0.159-11.386-7.2129-21.538-12.25-24-3.3685 3.2629-8.536 6.337-19.5 7.375h-159.78c-10.727-1.0775-15.792-4.1463-19.125-7.375-4.9422 2.4156-11.811 12.251-12.219 23.375h-0.0312v4.8124c0.159 11.386 7.2129 21.538 12.25 24 3.3685-3.2629 8.536-6.337 19.5-7.375h159.78c10.727 1.0775 15.792 4.1463 19.125 7.375 4.9422-2.4156 11.811-12.251 12.219-23.375h0.0312v-4.8124z"/>
|
|
||||||
<path class="bg" transform="matrix(.86578 0 0 .86578 78.719 48.374)" d="m69.839 72.529c0 14.565-11.807 26.373-26.373 26.373-14.565 0-26.373-11.807-26.373-26.373 0-14.565 11.807-26.373 26.373-26.373 14.565 0 26.373 11.807 26.373 26.373z"/>
|
|
||||||
<path class="bg" transform="matrix(.86578 0 0 .86578 182.94 48.396)" d="m69.839 72.529c0 14.565-11.807 26.373-26.373 26.373-14.565 0-26.373-11.807-26.373-26.373 0-14.565 11.807-26.373 26.373-26.373 14.565 0 26.373 11.807 26.373 26.373z"/>
|
|
||||||
<path class="bg" transform="matrix(.86578 0 0 .86578 78.848 152.7)" d="m69.839 72.529c0 14.565-11.807 26.373-26.373 26.373-14.565 0-26.373-11.807-26.373-26.373 0-14.565 11.807-26.373 26.373-26.373 14.565 0 26.373 11.807 26.373 26.373z"/>
|
|
||||||
<path class="bg" transform="matrix(.86578 0 0 .86578 183.14 152.6)" d="m69.839 72.529c0 14.565-11.807 26.373-26.373 26.373-14.565 0-26.373-11.807-26.373-26.373 0-14.565 11.807-26.373 26.373-26.373 14.565 0 26.373 11.807 26.373 26.373z"/>
|
|
||||||
<g class="fg" transform="matrix(.99979 .020664 -.020664 .99979 2.2515 -4.8909)" stroke="var(--nord2)">
|
|
||||||
<path d="m125.64 31.621-0.0722-0.2536 7.1008-2.0212c2.5247-0.71861 4.4393-0.95897 5.7437-0.72108 1.3044 0.23795 2.3382 0.70216 3.1013 1.3926 0.76311 0.69054 1.3404 1.7233 1.7318 3.0984 0.59992 2.1077 0.32369 3.8982-0.82869 5.3715-1.1524 1.4734-2.9805 2.542-5.4842 3.2059l-0.0674-0.23669c1.5779-0.44913 2.7221-1.2987 3.4324-2.5488 0.71031-1.25 0.84089-2.664 0.39175-4.242-0.34971-1.2285-0.89961-2.2508-1.6497-3.0669-0.75012-0.81603-1.6297-1.3485-2.6387-1.5974-1.009-0.24887-2.404-0.11987-4.1848 0.387l-1.7752 0.50529 6.0683 21.319c0.34327 1.206 0.68305 1.886 1.0193 2.0401 0.33626 0.15406 0.88198 0.12362 1.6372-0.09134l0.0674 0.23669-6.5767 1.872-0.0674-0.23669c1.1722-0.33365 1.6059-1.0359 1.3011-2.1066l-5.871-20.626c-0.25024-0.87912-0.52897-1.4303-0.8362-1.6536-0.30723-0.22322-0.82152-0.23218-1.5429-0.02688z"/>
|
|
||||||
<path d="m169.99 38.101-7.5573 0.14169-3.7357 9.8628c-0.2563 0.70806-0.38292 1.1441-0.37984 1.3081 0.007 0.38665 0.29793 0.57459 0.87205 0.56383l0.005 0.24605-3.8489 0.07216-0.005-0.24605c0.51554-0.0097 0.94669-0.14375 1.2935-0.40225 0.34678-0.2585 0.72118-0.91895 1.1232-1.9814l8.9409-23.867 0.43938-0.0082 9.6735 22.709c0.002 0.11717 0.24981 0.66634 0.74293 1.6475 0.49306 0.98116 1.2258 1.4626 2.1984 1.4444l0.005 0.24605-6.0458 0.11335-0.005-0.24605c0.55067-0.01032 0.82195-0.23224 0.81384-0.66576-0.006-0.29292-0.14904-0.75906-0.43059-1.3984-0.0478-0.04599-0.0902-0.12138-0.12731-0.22617-0.0257-0.11672-0.0443-0.17498-0.056-0.17476zm-7.3247-0.5835 6.9949-0.13115-3.6628-8.7571z"/>
|
|
||||||
<path d="m215.05 32.098-0.0754 0.25265c-0.67276-0.28643-1.3926-0.12832-2.1595 0.47432-0.0112-0.0033-0.0331 0.0085-0.0656 0.03545-0.0213 0.03035-0.0465 0.0534-0.0757 0.06913l-0.19006 0.14505c-0.0763 0.05063-0.11777 0.08716-0.12445 0.10961l-0.21872 0.11815-8.9764 7.0246 4.3093 13.156c0.45546 1.5057 0.85105 2.4952 1.1868 2.9684 0.33566 0.47322 0.71066 0.75333 1.125 0.84033l-0.0704 0.23581-6.1647-1.8404 0.0704-0.23581c0.51768 0.19124 0.83688 0.08474 0.95758-0.3195 0.0972-0.32565 0.0472-0.79308-0.15004-1.4023l-3.7548-11.449-9.4239 7.2945c-0.003 0.01123-0.19115 0.16919-0.56339 0.47387-0.41047 0.26882-0.6576 0.54359-0.7414 0.82431-0.10057 0.33687 0.13489 0.57227 0.70641 0.7062l-0.0704 0.23581-3.7056-1.1063 0.0704-0.23581c0.53899 0.16091 1.0726 0.09397 1.6009-0.20082l0.37685-0.2177 11.393-8.8529-4.2181-12.908c0.0469-0.15718-0.0793-0.51283-0.37858-1.0669-0.29932-0.55407-0.71956-0.88743-1.2607-1.0001l0.0754-0.25265 6.249 1.8656-0.0754 0.25265c-0.67376-0.20112-1.0659-0.11641-1.1766 0.25413-0.0805 0.26952-0.002 0.76385 0.23602 1.483l3.0954 9.4177 8.2667-6.4293c0.0145-0.0079 0.14851-0.13908 0.40187-0.39368 0.25331-0.25456 0.39171-0.42114 0.4152-0.49977 0.057-0.19088 0.034-0.32919-0.0688-0.41494-0.10284-0.08571-0.32269-0.17886-0.65953-0.27945l0.0754-0.25265z"/>
|
|
||||||
</g>
|
|
||||||
<g class="fg">
|
|
||||||
<path d="m235.06 72.827-0.26589-0.20212 6.4642-23.724c0.44911-1.6752 0.58908-2.7501 0.4199-3.2247-0.16922-0.47452-0.43107-0.84654-0.78557-1.116l0.15956-0.20991 4.758 3.6168-0.15956 0.20991c-0.39857-0.3471-0.73258-0.3434-1.002 0.01109-0.10639 0.13996-0.19187 0.3105-0.25644 0.51163l-5.6785 20.745 18.416-11.04c0.24704-0.15074 0.42022-0.29142 0.51954-0.42204 0.24109-0.31719 0.10378-0.64972-0.41192-0.99761l0.15957-0.20991 3.0927 2.3509-0.15957 0.20991c-0.21461-0.1631-0.46389-0.30476-0.74785-0.42496-0.28403-0.12019-0.71153-0.06612-1.2825 0.16221-0.57105 0.22835-0.87187 0.35297-0.90244 0.37386z"/>
|
|
||||||
<path d="m278.77 79.16 0.22305-0.14062 3.9185 6.2156c0.99367 1.5762 1.6777 2.7381 2.052 3.4857 0.37431 0.74758 0.59849 1.6141 0.67255 2.5995 0.074 0.98539-0.10481 1.8774-0.53644 2.6759-0.43168 0.79855-1.1332 1.5041-2.1047 2.1165-1.1103 0.69996-2.3795 1.0325-3.8075 0.99774-1.4281-0.0348-2.8734-0.44312-4.336-1.225l-1.4042 3.0464c-0.38231 0.71202-1.1219 2.4285-2.2186 5.1495-1.0968 2.721-1.6158 4.617-1.5569 5.6882 0.0588 1.0711 0.3226 1.9785 0.79134 2.722 0.46246 0.73355 1.2762 1.3981 2.4412 1.9935l-0.24115 0.27671c-1.7215-0.57713-3.1822-1.8174-4.3821-3.7207-0.79997-1.2689-1.1132-2.5953-0.93972-3.9791 0.17346-1.3839 1.233-4.2683 3.1785-8.6534 0.007-0.03233 0.0209-0.05474 0.0407-0.06724l2.0029-4.3381-1.2875-1.9104-1.1156-1.7695-8.3865 5.2872c-0.6741 0.42497-1.1011 0.81886-1.2811 1.1817-0.17994 0.3628-0.0543 0.8862 0.37693 1.5702l-0.20818 0.13124-3.5717-5.6654 0.20818-0.13124c0.47346 0.68508 0.89871 1.03 1.2757 1.0347 0.37702 0.0047 0.97693-0.25225 1.7997-0.77097l17.412-10.978c0.56503-0.35622 0.98655-0.72586 1.2646-1.1089 0.27798-0.38305 0.18445-0.9544-0.28059-1.7141zm2.2305 4.329-10.275 6.4778 1.4437 2.2899c1.1374 1.8042 2.4074 2.8737 3.8099 3.2086 1.4025 0.33488 2.7977 0.06485 4.1855-0.8101 1.3482-0.84996 2.3542-2.0833 3.0181-3.7002 0.66383-1.6168 0.31454-3.5058-1.0478-5.6668z"/>
|
|
||||||
<path d="m304.97 139.01-2.8793 1.9196-0.0812-0.19782c0.0114-0.003 0.27603-0.39661 0.7939-1.182 0.51781-0.78539 0.8634-1.6276 1.0368-2.5266 0.17331-0.89905 0.14258-1.8627-0.0922-2.8909-0.39397-1.7251-1.2005-2.9804-2.4196-3.7658-1.2191-0.78541-2.5256-1.019-3.9194-0.7007-0.92542 0.21132-1.6796 0.63296-2.2625 1.2649-0.58294 0.63196-0.99926 1.7698-1.249 3.4135-0.27608 2.7917-0.52511 4.7147-0.7471 5.7691-0.22203 1.0544-0.75747 2.1443-1.6063 3.2697-0.84889 1.1254-2.0959 1.876-3.741 2.2517-0.68549 0.15652-1.3578 0.22591-2.017 0.20816-0.65917-0.0178-1.3177-0.13787-1.9755-0.36027-0.65783-0.22243-1.2805-0.52799-1.8681-0.91668-0.58762-0.38872-1.0629-0.81208-1.426-1.2701-0.36304-0.45803-0.73392-1.2268-1.1126-2.3063-0.37873-1.0795-0.60853-1.7963-0.68941-2.1505l-1.5379-6.7348 7.3346-1.6749 0.0587 0.25706c-2.4282 0.57854-3.9944 1.5372-4.6984 2.876-0.70401 1.3388-0.78599 3.1906-0.24595 5.5555 0.49047 2.1478 1.3713 3.6626 2.6424 4.5443 1.2712 0.8817 2.6208 1.1595 4.0489 0.8334 0.86826-0.19828 1.5637-0.56143 2.0862-1.0894 0.5225-0.52802 0.93554-1.1933 1.2391-1.9959 0.30354-0.80258 0.56488-2.2506 0.78402-4.3441 0.23314-2.0847 0.51456-3.5764 0.84426-4.4751 0.32968-0.89869 0.94577-1.7275 1.8483-2.4866 0.90249-0.75903 1.885-1.2599 2.9475-1.5025 1.9536-0.44612 3.7463-0.14929 5.3781 0.89047s2.6968 2.6507 3.1952 4.8328c0.19303 0.84542 0.30463 1.7816 0.3348 2.8084 0.0301 1.0269 0.0297 1.604-0.001 1.7312-0.0124 0.0509-0.0134 0.0992-0.003 0.14492z"/>
|
|
||||||
<path d="m305.53 190.02-0.62918 4.9701-0.26159-0.0331c0.0724-0.75866-0.0212-1.3021-0.28088-1.6302-0.25969-0.32821-0.86619-0.55264-1.8195-0.6733l-23.386-2.9605 24.41-17.729-19.008-2.4064c-0.60455-0.0765-1.058-0.0867-1.3605-0.0305-0.30242 0.0562-0.51699 0.16489-0.6437 0.32604-0.12671 0.16113-0.28653 0.58386-0.47947 1.2682l-0.24415-0.0309 0.62477-4.9352 0.24414 0.0309c-0.11185 0.88357-0.0244 1.4528 0.26223 1.7076 0.28667 0.25481 1.0229 0.45728 2.2088 0.6074l17.387 2.201c1.523 0.1928 2.7938 0.1381 3.8125-0.16408 1.0186-0.3022 1.5902-1.225 1.7148-2.7685l0.26159 0.0331-0.61153 4.8306-22.945 16.656 18.136 2.296c0.97656 0.1236 1.5945 0.0246 1.8537-0.29688 0.25922-0.32158 0.42342-0.75557 0.49261-1.302z"/>
|
|
||||||
<path d="m289.19 232.48-3.433-0.43565 0.0682-0.20268c0.0103 0.005 0.46837-0.11886 1.3741-0.37304 0.90573-0.25423 1.7186-0.66421 2.4385-1.2299 0.71988-0.56577 1.3279-1.314 1.824-2.2447 0.83238-1.5615 1.0453-3.0383 0.63863-4.4303s-1.2408-2.4243-2.5024-3.0969c-0.83765-0.44653-1.6837-0.62197-2.5381-0.52631-0.85443 0.0956-1.9143 0.68265-3.1797 1.761-2.0373 1.9285-3.4852 3.2184-4.3436 3.8696-0.85845 0.65123-1.977 1.124-3.3556 1.4183-1.3786 0.29426-2.8125 0.0445-4.3016-0.7493-0.62047-0.33077-1.1739-0.71876-1.6603-1.164-0.48641-0.44522-0.90529-0.96731-1.2566-1.5663-0.35134-0.59898-0.62169-1.2378-0.81106-1.9164-0.18935-0.67863-0.27117-1.3099-0.24544-1.8937 0.0257-0.58389 0.24909-1.4077 0.67005-2.4714 0.42097-1.0637 0.7169-1.7559 0.88779-2.0765l3.2497-6.0961 6.639 3.5391-0.12403 0.23268c-2.2137-1.1535-4.025-1.4551-5.4339-0.90469-1.4089 0.55038-2.6839 1.8959-3.825 4.0365-1.0364 1.9441-1.3631 3.6656-0.98022 5.1646 0.38289 1.4989 1.2207 2.5929 2.5133 3.282 0.78593 0.41894 1.5492 0.60008 2.2899 0.54342 0.74068-0.0567 1.4886-0.28881 2.2436-0.69635 0.75509-0.40756 1.9011-1.3305 3.438-2.7687 1.5418-1.4224 2.7315-2.3652 3.5693-2.8282 0.83779-0.46308 1.8462-0.68576 3.0254-0.66807 1.1791 0.0177 2.2495 0.28285 3.2113 0.79553 1.7683 0.94264 2.9284 2.3412 3.4803 4.1958 0.55182 1.8545 0.30129 3.7694-0.75159 5.7446-0.40795 0.76523-0.93686 1.5457-1.5867 2.3413-0.6499 0.7956-1.0282 1.2313-1.1351 1.3072-0.0428 0.0303-0.0751 0.0662-0.0972 0.10756z"/>
|
|
||||||
<path d="m253.71 273.01-3.0849 2.6673-0.17245-0.19946c0.99118-0.94999 1.0384-1.9435 0.14161-2.9807-0.19161-0.22165-0.48263-0.50449-0.87307-0.84851l-14.878-13.069c-0.76791-0.63734-1.3195-0.9931-1.6548-1.0673-0.33522-0.0742-0.76579 0.0773-1.2917 0.45457l-0.16096-0.18616 4.4013-3.8055 0.16096 0.18616c-0.59151 0.48045-0.63435 1.0132-0.1285 1.5983 0.11499 0.13295 0.36769 0.37146 0.75811 0.71554l14.434 12.663-6.994-22.279 0.13297-0.11497 21.053 10.078-10.527-16.18c-0.15615-0.25228-0.35687-0.52025-0.60214-0.80391-0.44455-0.51416-0.96379-0.51447-1.5577-0.00093l-0.16096-0.18616 2.9918-2.5868 0.16096 0.18616c-0.4521 0.39089-0.69259 0.69953-0.72149 0.92591s0.0266 0.49211 0.16644 0.79721c0.13987 0.30509 0.32237 0.62367 0.54751 0.95573l11.448 17.755c1.1222 0.56333 1.9417 0.77653 2.4585 0.63959 0.51673-0.13698 0.94353-0.35109 1.2804-0.64232l0.17246 0.19945-3.6966 3.1962-20.742-10.067z"/>
|
|
||||||
<path d="m205.18 269.68 0.31132-0.12093 16.841 17.917c1.193 1.2589 2.036 1.9403 2.529 2.0443 0.49295 0.10393 0.94698 0.0753 1.3621-0.0859l0.0955 0.24578-5.571 2.164-0.0955-0.24578c0.50429-0.1582 0.67581-0.44484 0.51457-0.85991-0.0636-0.16387-0.16431-0.32592-0.30198-0.48614l-14.712-15.689-0.2212 21.471c-0.0007 0.2894 0.0286 0.51058 0.088 0.66354 0.14428 0.37137 0.49953 0.42824 1.0657 0.17061l0.0955 0.24578-3.6212 1.4066-0.0955-0.24578c0.25125-0.0976 0.50236-0.23603 0.75332-0.4152 0.25098-0.17924 0.42846-0.57191 0.53244-1.178 0.104-0.60616 0.15509-0.92773 0.15327-0.96471z"/>
|
|
||||||
</g>
|
|
||||||
<g class="fg">
|
|
||||||
<path d="m113.7 291.67 0.12516-3.4583 0.20799 0.0497c-0.005 0.0108 0.1605 0.45578 0.49511 1.335 0.33465 0.87919 0.81606 1.6518 1.4442 2.318 0.62822 0.66608 1.4281 1.2043 2.3996 1.6148 1.6301 0.68858 3.12 0.76779 4.4698 0.23762 1.3498-0.53021 2.3029-1.4538 2.8593-2.7708 0.3694-0.87442 0.46804-1.7328 0.29593-2.5751-0.17209-0.84236-0.85204-1.8452-2.0399-3.0085-2.1039-1.8556-3.5187-3.1816-4.2446-3.978-0.7258-0.7964-1.2972-1.8679-1.7143-3.2144-0.41705-1.3466-0.29725-2.7971 0.35942-4.3516 0.27363-0.6477 0.61027-1.2338 1.0099-1.7583 0.39968-0.52448 0.88198-0.98862 1.4469-1.3924 0.56495-0.40378 1.1768-0.73048 1.8357-0.98011 0.65885-0.24961 1.2802-0.38787 1.864-0.41475 0.58383-0.0269 1.4244 0.12148 2.5217 0.44508s1.8132 0.55609 2.1479 0.69745l6.3637 2.6883-2.9277 6.9304-0.24288-0.1026c0.94975-2.3085 1.0872-4.1396 0.41234-5.4932-0.67485-1.3537-2.1296-2.5025-4.3641-3.4465-2.0295-0.85733-3.7734-1.0279-5.2318-0.5118-1.4584 0.51614-2.4726 1.4489-3.0426 2.7983-0.34656 0.82043-0.45832 1.5969-0.33528 2.3295 0.12307 0.73258 0.4215 1.4566 0.89529 2.1719 0.47383 0.71537 1.4961 1.7737 3.0667 3.1751 1.5553 1.4076 2.6012 2.5078 3.1378 3.3005 0.53654 0.79274 0.84901 1.7771 0.93743 2.953 0.0884 1.1759-0.0794 2.2658-0.50351 3.2698-0.7798 1.8459-2.0684 3.1271-3.8658 3.8435-1.7974 0.71637-3.727 0.63906-5.7889-0.23193-0.79882-0.33748-1.6237-0.79406-2.4745-1.3697-0.85083-0.57572-1.3188-0.91336-1.404-1.0129-0.034-0.0398-0.0726-0.0689-0.11586-0.0871z"/>
|
|
||||||
<path d="m68.299 260.77-3.0403-2.718 0.17573-0.19658c1.0691 0.86139 2.0605 0.78098 2.9743-0.2412 0.19529-0.21842 0.43854-0.54326 0.72973-0.97453l11.056-16.429c0.53378-0.8432 0.81598-1.4358 0.8466-1.7778 0.03067-0.34197-0.17473-0.74959-0.61622-1.2229l0.16402-0.18347 4.3377 3.8778-0.16402 0.18347c-0.55224-0.52512-1.0861-0.49938-1.6016 0.0772-0.11713 0.13106-0.32133 0.41222-0.61258 0.84348l-10.71 15.937 21.201-9.7892 0.13105 0.11715-7.2989 22.17 14.699-12.513c0.23021-0.18718 0.47027-0.42055 0.7202-0.70012 0.453-0.50672 0.38682-1.0217-0.19854-1.545l0.16402-0.18347 2.9486 2.636-0.16402 0.18347c-0.44556-0.39832-0.78245-0.59732-1.0107-0.597-0.22821 0.00033-0.48466 0.0894-0.76934 0.26716-0.28467 0.17777-0.57725 0.39956-0.87775 0.66536l-16.14 13.63c-0.415 1.1851-0.52151 2.0252-0.31953 2.5202 0.20202 0.49494 0.46901 0.89081 0.80098 1.1876l-0.17573 0.19657-3.6432-3.2569 7.3287-21.86z"/>
|
|
||||||
<path d="m39.292 218.38c-1.4886-3.3138-1.6984-6.4762-0.62946-9.4873 1.069-3.011 3.1108-5.1937 6.1252-6.5479 3.8804-1.7431 7.6935-1.9915 11.439-0.74522 3.7459 1.2464 6.5241 3.8845 8.3344 7.9145 1.4694 3.271 1.6034 6.429 0.40185 9.4739-1.2015 3.0449-3.2988 5.2396-6.292 6.5842-2.0203 0.90759-4.3198 1.3368-6.8985 1.2876-2.5786-0.0492-4.9925-0.78268-7.2417-2.2004-2.2491-1.4177-3.9956-3.5108-5.2393-6.2795zm23.133-10.276c-0.7299-1.6248-1.8858-3.0326-3.4677-4.2233s-3.3413-1.897-5.2781-2.119c-1.9368-0.22191-3.7123 0.0297-5.3264 0.75478-2.2876 1.0277-4.0918 2.5736-5.4127 4.638-1.3208 2.0644-2.0722 4.3096-2.2541 6.7359-0.18187 2.4263 0.09934 4.4679 0.84363 6.1248 1.0517 2.341 2.888 4.0694 5.5089 5.1852 2.621 1.1158 5.241 1.0854 7.8599-0.0911 3.3459-1.503 5.7621-3.9888 7.2487-7.4572s1.5792-6.6511 0.27787-9.548z"/>
|
|
||||||
<path d="m54.305 176.72-0.24585 0.0109c-0.03577-0.8078-0.21116-1.325-0.52618-1.5515-0.31502-0.22652-0.8413-0.32345-1.5789-0.29079l-21.178 0.93782c-0.74924 0.0332-1.2866 0.15082-1.6119 0.35291-0.32534 0.20209-0.47899 0.77194-0.46096 1.7096l-0.26341 0.0117-0.30716-6.9366 0.26341-0.0117c0.02854 0.64391 0.13045 1.091 0.30573 1.3413 0.17533 0.25031 0.37602 0.41151 0.60206 0.48361 0.22609 0.0721 0.73132 0.0908 1.5157 0.056l18.158-0.80407c1.5571-0.0689 2.638-0.49218 3.2429-1.2697 0.60487-0.77752 0.86712-2.0736 0.78677-3.8882l-0.141-3.16c-0.06739-1.5219-0.4914-2.6205-1.272-3.2956-0.78063-0.67509-2.2088-1.0077-4.2847-0.99795l-0.01089-0.24585 6.2166-0.27528z"/>
|
|
||||||
<path d="m32.995 125.27 0.25545 0.0653c-0.11822 0.32055-0.16075 0.69977-0.12759 1.1376 0.03321 0.4379 0.17094 0.72712 0.41317 0.86767 0.24228 0.14058 0.85161 0.33569 1.828 0.58533l19.261 4.9248c1.0445 0.26708 1.694 0.38779 1.9486 0.36214 0.25452-0.0256 0.48962-0.14091 0.70531-0.34582 0.21568-0.20491 0.40336-0.61958 0.56302-1.244l0.23842 0.061-1.7113 6.6929-0.23842-0.061c0.21482-0.84016 0.18656-1.4038-0.08476-1.6909-0.27132-0.28709-0.98033-0.57724-2.127-0.87044l-19.074-4.8769c-1.1921-0.3048-2.0017-0.39084-2.4287-0.25812-0.42702 0.13273-0.71944 0.60227-0.87726 1.4086l-0.25545-0.0653z"/>
|
|
||||||
<path d="m71.729 102.86-0.18056 0.28098-24.16-4.5763c-1.7054-0.31583-2.788-0.37074-3.2477-0.16472-0.45973 0.20605-0.80997 0.49638-1.0507 0.871l-0.22182-0.14254 3.231-5.0279 0.22182 0.14254c-0.31464 0.42465-0.28466 0.75734 0.08995 0.99806 0.1479 0.09505 0.32464 0.16684 0.53023 0.21536l21.127 4.0277-12.455-17.49c-0.16972-0.23441-0.3236-0.39597-0.46163-0.4847-0.33518-0.21537-0.65588-0.05231-0.96208 0.48918l-0.22182-0.14254 2.1001-3.2682 0.22182 0.14254c-0.1457 0.22678-0.26729 0.48645-0.36477 0.77899-0.09746 0.29261-0.0099 0.71453 0.26268 1.2658 0.2726 0.5513 0.42051 0.84137 0.44374 0.8702z"/>
|
|
||||||
<path d="m76.563 57.963-0.15835-0.21082 5.383-4.0433c1.5742-1.1823 2.7385-1.9543 3.4929-2.3158 0.75443-0.36145 1.5705-0.58234 2.4482-0.66269 0.87768-0.08029 1.7895 0.07023 2.7355 0.45156 0.94598 0.38138 1.7533 1.0171 2.4219 1.9073 1.3161 1.7522 1.4922 3.7818 0.52818 6.0887 1.6798-0.86602 3.267-1.0945 4.7614-0.68544 1.4944 0.40911 2.766 1.3117 3.8146 2.7078 0.97826 1.3024 1.543 2.7323 1.6941 4.2896s-0.0607 2.8266-0.63536 3.8079c-0.5747 0.98128-1.6163 2.0385-3.1249 3.1716l-7.9973 6.0069-0.1478-0.19677c0.63716-0.47858 0.94798-0.91357 0.93246-1.305-0.01552-0.39139-0.42092-1.1165-1.2162-2.1753l-11.718-15.602c-0.56302-0.74958-0.96767-1.2444-1.214-1.4845-0.24625-0.24004-0.53578-0.33768-0.86857-0.29293-0.33277 0.04479-0.70998 0.22553-1.1316 0.54222zm4.02-2.6677 6.8303 9.0936 2.5158-1.8897c1.7053-1.2809 2.4708-2.618 2.2964-4.0112-0.17444-1.3932-0.6241-2.5724-1.349-3.5375-0.78121-1.04-1.6495-1.7472-2.6048-2.1216-0.95534-0.37429-1.9814-0.42805-3.078-0.16128-1.0967 0.26683-2.4508 1.0055-4.0625 2.216zm8.7739 8.3152-1.6163 1.214 4.9301 6.5637c0.68268 0.90889 1.1612 1.4728 1.4356 1.6917 0.27437 0.21895 0.64832 0.38509 1.1218 0.49842 0.47351 0.11334 1.0553 0.05374 1.7454-0.17879 0.69006-0.23252 1.5551-0.73939 2.5952-1.5206 1.6116-1.2105 2.4703-2.4381 2.5761-3.6827 0.10574-1.2446-0.39035-2.5978-1.4883-4.0595-1.1402-1.5179-2.3643-2.5331-3.6725-3.0454-1.3082-0.51233-2.4121-0.59181-3.3119-0.23845-0.89975 0.3534-2.3382 1.2726-4.3152 2.7576z"/>
|
|
||||||
</g>
|
|
||||||
<g class="fg">
|
|
||||||
<path d="m177.99 64.817 0.96679 2.7422h-0.24609c-2.5078-2.4961-5.461-3.7441-8.8594-3.7441-3.5274 0.000026-6.3779 1.0489-8.5518 3.1465-2.1738 2.0977-3.2608 4.6348-3.2607 7.6113-0.00001 1.8399 0.48339 3.9551 1.4502 6.3457 0.96679 2.3906 2.4932 4.3564 4.5791 5.8975 2.0859 1.541 4.6113 2.3115 7.5762 2.3115 1.9453 0 3.8525-0.31348 5.7217-0.94043 1.8691-0.62695 3.2607-1.4678 4.1748-2.5225l0.22851 0.07031-1.8105 2.7773c-2.9297 0.63281-4.8311 1.0078-5.7041 1.125-0.87307 0.11719-2.042 0.17578-3.5068 0.17578-5.4258 0-9.3076-1.2568-11.646-3.7705-2.3379-2.5137-3.5068-5.5225-3.5068-9.0264-0.00001-2.3672 0.60058-4.6435 1.8018-6.8291 1.2012-2.1855 2.9326-3.9082 5.1943-5.168 2.2617-1.2597 4.8457-1.8896 7.752-1.8896 1.4531 0.000026 2.7099 0.13186 3.7705 0.39551 1.0605 0.2637 1.9424 0.53616 2.6455 0.81738l1.1602 0.43945c0.0351 0.01174 0.0586 0.02346 0.0703 0.03516z"/>
|
|
||||||
<path d="m173.39 106.17 1.2305 3.2344-0.21094 0.0352c-0.00002-0.0117-0.32521-0.3574-0.97559-1.0371-0.6504-0.67966-1.3945-1.2041-2.2324-1.5732-0.8379-0.36911-1.7842-0.55369-2.8389-0.55371-1.7695 0.00002-3.1728 0.50686-4.21 1.5205-1.0371 1.0137-1.5557 2.2354-1.5557 3.665 0 0.94923 0.24316 1.7783 0.72949 2.4873s1.5029 1.3682 3.0498 1.9775c2.6601 0.89064 4.4795 1.5615 5.458 2.0127 0.97851 0.45118 1.9219 1.2158 2.8301 2.2939 0.90819 1.0781 1.3623 2.461 1.3623 4.1484-0.00002 0.70313-0.0821 1.374-0.2461 2.0127-0.16408 0.63868-0.42775 1.2539-0.79101 1.8457-0.3633 0.5918-0.79982 1.1309-1.3096 1.6172-0.50978 0.48633-1.0283 0.85547-1.5557 1.1074-0.52735 0.25195-1.3594 0.44238-2.4961 0.57129-1.1367 0.1289-1.8867 0.19335-2.25 0.19335h-6.9082v-7.5234h0.26367c0.0234 2.4961 0.60937 4.2363 1.7578 5.2207 1.1484 0.98438 2.9355 1.4766 5.3613 1.4766 2.2031 0 3.876-0.52148 5.0186-1.5644 1.1426-1.043 1.7139-2.2969 1.7139-3.7617-0.00001-0.89062-0.19923-1.6494-0.59766-2.2764-0.39845-0.62694-0.95509-1.1777-1.6699-1.6523-0.71485-0.4746-2.0684-1.0518-4.0605-1.7314-1.9805-0.69139-3.3721-1.2978-4.1748-1.8193-0.80274-0.52147-1.4736-1.3066-2.0127-2.3555-0.53907-1.0488-0.8086-2.1182-0.8086-3.208 0-2.0039 0.68848-3.6855 2.0654-5.0449 1.377-1.3594 3.1846-2.039 5.4228-2.0391 0.86718 0.00002 1.8047 0.0996 2.8125 0.29883 1.0078 0.19924 1.5703 0.32815 1.6875 0.38671 0.0469 0.0235 0.0937 0.0352 0.14063 0.0352z"/>
|
|
||||||
<path d="m173.39 148.48 1.2305 3.2344-0.21094 0.0352c-0.00002-0.0117-0.32521-0.3574-0.97559-1.0371-0.6504-0.67966-1.3945-1.2041-2.2324-1.5732-0.8379-0.36911-1.7842-0.55368-2.8389-0.55371-1.7695 0.00003-3.1728 0.50686-4.21 1.5205-1.0371 1.0137-1.5557 2.2354-1.5557 3.665 0 0.94924 0.24316 1.7783 0.72949 2.4873s1.5029 1.3682 3.0498 1.9775c2.6601 0.89064 4.4795 1.5615 5.458 2.0127 0.97851 0.45118 1.9219 1.2158 2.8301 2.2939 0.90819 1.0781 1.3623 2.461 1.3623 4.1484-0.00002 0.70313-0.0821 1.374-0.2461 2.0127-0.16408 0.63867-0.42775 1.2539-0.79101 1.8457-0.3633 0.5918-0.79982 1.1309-1.3096 1.6172-0.50978 0.48633-1.0283 0.85547-1.5557 1.1074-0.52735 0.25195-1.3594 0.44238-2.4961 0.57129-1.1367 0.1289-1.8867 0.19336-2.25 0.19336h-6.9082v-7.5234h0.26367c0.0234 2.4961 0.60937 4.2363 1.7578 5.2207 1.1484 0.98438 2.9355 1.4766 5.3613 1.4766 2.2031 0.00001 3.876-0.52148 5.0186-1.5644 1.1426-1.043 1.7139-2.2969 1.7139-3.7617-0.00001-0.89062-0.19923-1.6494-0.59766-2.2764-0.39845-0.62695-0.95509-1.1777-1.6699-1.6524-0.71485-0.4746-2.0684-1.0517-4.0605-1.7314-1.9805-0.6914-3.3721-1.2978-4.1748-1.8193-0.80274-0.52147-1.4736-1.3066-2.0127-2.3555-0.53907-1.0488-0.8086-2.1181-0.8086-3.208 0-2.0039 0.68848-3.6855 2.0654-5.0449 1.377-1.3594 3.1846-2.039 5.4228-2.0391 0.86718 0.00003 1.8047 0.0996 2.8125 0.29883 1.0078 0.19924 1.5703 0.32815 1.6875 0.38672 0.0469 0.0235 0.0937 0.0352 0.14063 0.0352z"/>
|
|
||||||
<path d="m177.34 190.47h4.0781v0.26367c-1.3711 0.0703-2.0567 0.79104-2.0566 2.1621-0.00003 0.29299 0.0351 0.69729 0.10547 1.2129l2.707 19.617c0.16403 0.98438 0.3486 1.6143 0.55371 1.8896 0.20505 0.27539 0.62985 0.44238 1.2744 0.50097v0.2461h-5.8184v-0.2461c0.76169 0.0234 1.1426-0.35156 1.1426-1.125-0.00002-0.17577-0.0352-0.52148-0.10546-1.0371l-2.6367-19.02-9.2812 21.428h-0.17578l-9.334-21.393-2.6191 19.125c-0.0469 0.29297-0.0703 0.62696-0.0703 1.002 0 0.67969 0.39257 1.0195 1.1777 1.0195v0.2461h-3.9551v-0.2461c0.59766 0.00001 0.98145-0.0762 1.1514-0.22851 0.16992-0.15234 0.30176-0.38965 0.39551-0.71191 0.0937-0.32227 0.16406-0.68262 0.21094-1.0811l2.9531-20.918c-0.48047-1.1601-0.96094-1.8574-1.4414-2.0918-0.48048-0.23434-0.94337-0.35153-1.3887-0.35156v-0.26367h4.8867l9.1055 21.182z"/>
|
|
||||||
<path d="m158.85 258.68v-0.24609c0.80859 0 1.333-0.15234 1.5732-0.45703 0.24023-0.30469 0.36035-0.82617 0.36035-1.5645v-21.199c0-0.74998-0.0937-1.292-0.28125-1.626-0.1875-0.33396-0.75-0.51267-1.6875-0.53613v-0.26368h6.9434v0.26368c-0.64454 0.00002-1.0957 0.0821-1.3535 0.24609-0.25782 0.16409-0.42774 0.35745-0.50977 0.58008-0.082 0.22268-0.12305 0.72659-0.12305 1.5117v18.176c0 1.5586 0.375 2.6572 1.125 3.2959 0.75 0.63867 2.0332 0.95801 3.8496 0.958h3.1641c1.5234 0.00001 2.6396-0.37499 3.3486-1.125 0.70897-0.74999 1.1045-2.1621 1.1865-4.2363h0.2461v6.2226z"/>
|
|
||||||
</g>
|
|
||||||
<rect class="bg" height="33.325" width="33.325" y="146.77" x="151.78"/>
|
|
||||||
<g class="fg">
|
|
||||||
<path d="m95.864 150.69h5.0098v0.26367c-0.76174 0.0235-1.2891 0.18459-1.582 0.4834-0.293 0.29885-0.43948 0.92873-0.43945 1.8896v23.572l-20.654-21.99v19.16c-0.000005 0.60938 0.04687 1.0606 0.14062 1.3535 0.09374 0.29297 0.22851 0.49219 0.4043 0.59765 0.17578 0.10547 0.61523 0.21094 1.3184 0.31641v0.24609h-4.9746v-0.24609c0.89062 0 1.4443-0.1582 1.6611-0.47461 0.21679-0.3164 0.32519-1.0723 0.3252-2.2676v-17.525c-0.000004-1.5351-0.21387-2.789-0.6416-3.7617-0.42774-0.97263-1.415-1.4238-2.9619-1.3535v-0.26367h4.8691l19.406 20.672v-18.281c-0.000025-0.98435-0.17581-1.5849-0.52734-1.8018-0.35159-0.21677-0.80276-0.32517-1.3535-0.32519z"/>
|
|
||||||
<path id="path3063" d="m116.49 150.96v-0.26367h11.883c3.0234 0.00002 5.4111 0.23733 7.1631 0.71191 1.7519 0.47463 3.2783 1.2217 4.5791 2.2412 1.3008 1.0196 2.332 2.3233 3.0938 3.9111 0.76169 1.5879 1.1425 3.3604 1.1426 5.3174-0.00003 1.8867-0.39553 3.7412-1.1865 5.5635-0.79104 1.8223-1.8662 3.3926-3.2256 4.7109-1.3594 1.3184-2.792 2.2207-4.2978 2.707-1.5059 0.48633-3.835 0.72949-6.9873 0.72949h-12.164v-0.24609h0.3164c0.63281 0 1.0488-0.25488 1.248-0.76465 0.19921-0.50976 0.29882-1.4326 0.29883-2.7686v-18.861c-0.00001-1.4062-0.12012-2.2558-0.36035-2.5488-0.24024-0.29294-0.74122-0.43943-1.5029-0.43945zm8.8242 0.28125h-3.9726v20.092c-0.00001 1.2656 0.21386 2.2061 0.6416 2.8213 0.42773 0.61524 1.1572 1.0606 2.1885 1.3359 1.0312 0.27539 2.4199 0.41309 4.166 0.41309 4.8516 0 8.2588-1.2451 10.222-3.7354 1.9629-2.4902 2.9443-5.206 2.9443-8.1475-0.00003-3.457-1.3448-6.4512-4.0342-8.9824-2.6895-2.5312-6.7412-3.7968-12.155-3.7969z"/>
|
|
||||||
<path d="m172.31 151.03 1.2305 3.2344-0.21094 0.0352c-0.00002-0.0117-0.32521-0.3574-0.97559-1.0371-0.6504-0.67966-1.3945-1.2041-2.2324-1.5732-0.8379-0.36912-1.7842-0.55369-2.8389-0.55371-1.7695 0.00002-3.1728 0.50686-4.21 1.5205-1.0371 1.0137-1.5557 2.2354-1.5557 3.665 0 0.94923 0.24316 1.7783 0.72949 2.4873s1.5029 1.3682 3.0498 1.9775c2.6601 0.89064 4.4795 1.5615 5.458 2.0127 0.97851 0.45119 1.9219 1.2158 2.8301 2.294 0.90819 1.0781 1.3623 2.461 1.3623 4.1484-0.00002 0.70313-0.082 1.374-0.2461 2.0127-0.16408 0.63868-0.42775 1.2539-0.79101 1.8457-0.3633 0.5918-0.79982 1.1309-1.3096 1.6172-0.50978 0.48633-1.0283 0.85547-1.5557 1.1074-0.52735 0.25196-1.3594 0.44239-2.4961 0.57129-1.1367 0.12891-1.8867 0.19336-2.25 0.19336h-6.9082v-7.5234h0.26367c0.0234 2.4961 0.60937 4.2363 1.7578 5.2207 1.1484 0.98438 2.9355 1.4766 5.3613 1.4766 2.2031 0 3.876-0.52148 5.0186-1.5644 1.1426-1.043 1.7139-2.2969 1.7139-3.7617-0.00001-0.89062-0.19923-1.6494-0.59766-2.2764-0.39845-0.62694-0.95509-1.1777-1.6699-1.6523-0.71485-0.4746-2.0684-1.0518-4.0605-1.7314-1.9805-0.69139-3.3721-1.2978-4.1748-1.8193-0.80274-0.52147-1.4736-1.3066-2.0127-2.3555-0.53907-1.0488-0.8086-2.1182-0.8086-3.208 0-2.0039 0.68848-3.6855 2.0654-5.0449 1.377-1.3594 3.1846-2.039 5.4228-2.0391 0.86718 0.00002 1.8047 0.0996 2.8125 0.29882 1.0078 0.19925 1.5703 0.32816 1.6875 0.38672 0.0469 0.0235 0.0937 0.0352 0.14063 0.0352z"/>
|
|
||||||
<path d="m213.81 150.69h4.0781v0.26367c-1.3711 0.0703-2.0567 0.79104-2.0566 2.1621-0.00002 0.29299 0.0351 0.69728 0.10547 1.2129l2.707 19.617c0.16404 0.98438 0.34861 1.6143 0.55371 1.8896 0.20505 0.27539 0.62986 0.44239 1.2744 0.50098v0.24609h-5.8184v-0.24609c0.76169 0.0234 1.1426-0.35156 1.1426-1.125-0.00003-0.17578-0.0352-0.52148-0.10547-1.0371l-2.6367-19.02-9.2812 21.428h-0.17578l-9.334-21.393-2.6191 19.125c-0.0469 0.29297-0.0703 0.62695-0.0703 1.002 0 0.67969 0.39258 1.0195 1.1777 1.0195v0.24609h-3.9551v-0.24609c0.59765 0 0.98144-0.0762 1.1514-0.22852 0.16992-0.15234 0.30176-0.38964 0.39551-0.71191 0.0937-0.32226 0.16406-0.68262 0.21094-1.081l2.9531-20.918c-0.48047-1.1601-0.96094-1.8574-1.4414-2.0918-0.48047-0.23435-0.94336-0.35154-1.3887-0.35156v-0.26367h4.8867l9.1055 21.182z"/>
|
|
||||||
<path d="m234.95 150.96v-0.26367h11.883c3.0234 0.00002 5.4111 0.23733 7.1631 0.71191 1.7519 0.47463 3.2783 1.2217 4.5791 2.2412 1.3008 1.0196 2.332 2.3233 3.0938 3.9111 0.76169 1.5879 1.1426 3.3604 1.1426 5.3174-0.00003 1.8867-0.39554 3.7412-1.1865 5.5635-0.79105 1.8223-1.8662 3.3926-3.2256 4.7109-1.3594 1.3184-2.792 2.2207-4.2978 2.707-1.5059 0.48633-3.835 0.72949-6.9873 0.72949h-12.164v-0.24609h0.31641c0.63281 0 1.0488-0.25488 1.248-0.76465 0.19922-0.50976 0.29883-1.4326 0.29883-2.7686v-18.861c0-1.4062-0.12012-2.2558-0.36035-2.5488-0.24024-0.29294-0.74121-0.43943-1.5029-0.43945zm8.8242 0.28125h-3.9727v20.092c0 1.2656 0.21386 2.2061 0.6416 2.8213 0.42773 0.61524 1.1572 1.0606 2.1885 1.3359 1.0312 0.27539 2.4199 0.41309 4.166 0.41309 4.8515 0 8.2588-1.2451 10.222-3.7354 1.9629-2.4902 2.9443-5.206 2.9443-8.1475-0.00002-3.457-1.3448-6.4512-4.0342-8.9824-2.6895-2.5312-6.7412-3.7968-12.155-3.7969z"/>
|
|
||||||
<g class="fg">
|
|
||||||
<path d="m124.76 99.299 0.9668 2.7422h-0.2461c-2.5078-2.4961-5.461-3.7441-8.8594-3.7441-3.5274 0.000025-6.3779 1.0489-8.5518 3.1465-2.1738 2.0977-3.2607 4.6348-3.2607 7.6113 0 1.8399 0.48339 3.9551 1.4502 6.3457 0.96679 2.3906 2.4932 4.3564 4.5791 5.8975 2.0859 1.541 4.6113 2.3115 7.5762 2.3115 1.9453 0 3.8525-0.31348 5.7217-0.94043 1.8691-0.62695 3.2607-1.4678 4.1748-2.5225l0.22852 0.0703-1.8106 2.7773c-2.9297 0.63282-4.8311 1.0078-5.7041 1.125-0.87307 0.11719-2.042 0.17578-3.5068 0.17578-5.4258 0-9.3076-1.2568-11.646-3.7705-2.3379-2.5137-3.5068-5.5225-3.5068-9.0264 0-2.3672 0.60058-4.6435 1.8018-6.8291 1.2012-2.1855 2.9326-3.9082 5.1943-5.168 2.2617-1.2597 4.8457-1.8896 7.752-1.8896 1.4531 0.000026 2.7099 0.13186 3.7705 0.39551 1.0605 0.2637 1.9424 0.53616 2.6455 0.81738l1.1602 0.43945c0.0351 0.01174 0.0586 0.02346 0.0703 0.03516z"/>
|
|
||||||
<path d="m226.63 97.821 1.2305 3.2344-0.21093 0.0352c-0.00002-0.0117-0.32521-0.3574-0.97559-1.0371-0.6504-0.67966-1.3945-1.2041-2.2324-1.5732-0.8379-0.36912-1.7842-0.55369-2.8389-0.55371-1.7695 0.000025-3.1729 0.50686-4.21 1.5205-1.0371 1.0137-1.5557 2.2354-1.5557 3.665-0.00001 0.94923 0.24316 1.7783 0.72949 2.4873 0.48632 0.709 1.5029 1.3682 3.0498 1.9775 2.6602 0.89064 4.4795 1.5615 5.458 2.0127 0.9785 0.45119 1.9219 1.2158 2.8301 2.294 0.90819 1.0781 1.3623 2.461 1.3623 4.1484-0.00001 0.70313-0.082 1.374-0.24609 2.0127-0.16408 0.63868-0.42775 1.2539-0.79102 1.8457-0.36329 0.5918-0.79981 1.1309-1.3096 1.6172-0.50977 0.48633-1.0283 0.85547-1.5557 1.1074-0.52736 0.25195-1.3594 0.44238-2.4961 0.57128-1.1367 0.12891-1.8867 0.19336-2.25 0.19336h-6.9082v-7.5234h0.26368c0.0234 2.4961 0.60937 4.2363 1.7578 5.2207 1.1484 0.98438 2.9355 1.4766 5.3613 1.4766 2.2031 0 3.876-0.52148 5.0186-1.5644 1.1426-1.043 1.7138-2.2969 1.7139-3.7617-0.00002-0.89062-0.19924-1.6494-0.59766-2.2764-0.39845-0.62694-0.95509-1.1777-1.6699-1.6523-0.71486-0.4746-2.0684-1.0518-4.0606-1.7314-1.9805-0.69139-3.3721-1.2978-4.1748-1.8193-0.80274-0.52147-1.4736-1.3066-2.0127-2.3555-0.53906-1.0488-0.80859-2.1182-0.80859-3.208 0-2.0039 0.68847-3.6855 2.0654-5.0449 1.377-1.3593 3.1846-2.039 5.4228-2.0391 0.86718 0.000026 1.8047 0.09963 2.8125 0.29883 1.0078 0.19924 1.5703 0.32815 1.6875 0.38672 0.0469 0.02346 0.0937 0.03518 0.14063 0.03516z" />
|
|
||||||
<path d="m212.54 202.63v-0.26367h6.7324c1.9687 0.00003 3.3633 0.0821 4.1836 0.24609 0.8203 0.16409 1.6055 0.47757 2.3555 0.94043 0.74999 0.46292 1.3887 1.1309 1.916 2.0039 0.52732 0.87307 0.791 1.8662 0.79101 2.9795-0.00001 2.1914-1.0781 3.9199-3.2344 5.1856 1.8633 0.31642 3.2695 1.0869 4.2188 2.3115 0.9492 1.2246 1.4238 2.71 1.4238 4.4561-0.00002 1.6289-0.40725 3.1113-1.2217 4.4473-0.81447 1.3359-1.7461 2.2236-2.7949 2.6631-1.0488 0.43945-2.5166 0.65918-4.4033 0.65918h-10.002v-0.24609c0.79687 0 1.3066-0.16114 1.5293-0.4834 0.22265-0.32227 0.33398-1.1455 0.33398-2.4697v-19.512c0-0.93747-0.0264-1.5762-0.0791-1.916-0.0527-0.33982-0.22559-0.59178-0.51855-0.75586-0.29297-0.16404-0.70313-0.24607-1.2305-0.2461zm4.8164 0.28125v11.373h3.1465c2.1328 0.00001 3.5478-0.60936 4.2451-1.8281 0.69725-1.2187 1.0459-2.4316 1.0459-3.6387-0.00001-1.3008-0.26954-2.3877-0.80859-3.2607-0.53908-0.87302-1.3272-1.5322-2.3643-1.9775-1.0371-0.44529-2.5635-0.66794-4.5791-0.66797zm2.0215 11.918h-2.0215v8.209c0 1.1367 0.0439 1.875 0.13184 2.2148 0.0879 0.33985 0.2871 0.69727 0.59766 1.0723 0.31054 0.375 0.81151 0.67675 1.5029 0.90527s1.6875 0.34277 2.9883 0.34277c2.0156 0 3.4394-0.46582 4.2715-1.3975 0.83202-0.93164 1.248-2.3115 1.248-4.1396-0.00002-1.8984-0.36916-3.4453-1.1074-4.6406-0.7383-1.1953-1.5733-1.9219-2.5049-2.1797-0.93165-0.2578-2.6338-0.3867-5.1064-0.38672z"/>
|
|
||||||
<path d="m108.68 203.42v-0.26367h7.3828c2.625 0.00002 4.5322 0.29299 5.7217 0.8789 1.1894 0.58597 2.0566 1.3155 2.6016 2.1885 0.5449 0.87307 0.81736 2.0244 0.81738 3.4541-0.00002 2.1914-0.75588 3.8379-2.2676 4.9394-1.5117 1.1016-3.5625 1.6289-6.1523 1.582v-0.2461c1.6406 0.00002 2.9736-0.50389 3.999-1.5117s1.5381-2.332 1.5381-3.9726c-0.00002-1.2773-0.24904-2.4111-0.74707-3.4014-0.49806-0.99021-1.1983-1.7431-2.1006-2.2588-0.90235-0.5156-2.2793-0.77341-4.1309-0.77344h-1.8457v22.166c-0.00001 1.2539 0.14062 2.001 0.42187 2.2412 0.28125 0.24023 0.81445 0.36035 1.5996 0.36035v0.2461h-6.8379v-0.2461c1.2188 0 1.8281-0.55664 1.8281-1.6699v-21.445c-0.00001-0.91404-0.11719-1.5205-0.35156-1.8193-0.23438-0.2988-0.72657-0.44822-1.4766-0.44824z"/>
|
|
||||||
<path d="m139.19 59.343c0 9.9799-8.0903 18.07-18.07 18.07-9.9799 0-18.07-8.0903-18.07-18.07 0-9.9799 8.0903-18.07 18.07-18.07 9.9799 0 18.07 8.0903 18.07 18.07z" transform="matrix(.15488 0 0 .15488 96.011 40.384)"/>
|
|
||||||
<path d="m139.19 59.343c0 9.9799-8.0903 18.07-18.07 18.07-9.9799 0-18.07-8.0903-18.07-18.07 0-9.9799 8.0903-18.07 18.07-18.07 9.9799 0 18.07 8.0903 18.07 18.07z" transform="matrix(.15488 0 0 .15488 203.55 40.283)"/>
|
|
||||||
<path d="m139.19 59.343c0 9.9799-8.0903 18.07-18.07 18.07-9.9799 0-18.07-8.0903-18.07-18.07 0-9.9799 8.0903-18.07 18.07-18.07 9.9799 0 18.07 8.0903 18.07 18.07z" transform="matrix(.17299 0 0 .17299 147.99 279.77)"/>
|
|
||||||
<path d="m139.19 59.343c0 9.9799-8.0903 18.07-18.07 18.07-9.9799 0-18.07-8.0903-18.07-18.07 0-9.9799 8.0903-18.07 18.07-18.07 9.9799 0 18.07 8.0903 18.07 18.07z" transform="matrix(.15488 0 0 .15488 131.66 277.84)"/>
|
|
||||||
<path d="m139.19 59.343c0 9.9799-8.0903 18.07-18.07 18.07-9.9799 0-18.07-8.0903-18.07-18.07 0-9.9799 8.0903-18.07 18.07-18.07 9.9799 0 18.07 8.0903 18.07 18.07z" transform="matrix(.15488 0 0 .15488 168.54 277.98)"/>
|
|
||||||
<path d="m61.398 206.07c0.38726-6.2993 0.78765-12.891-3.9191-17.556 2.2141 1.3159 3.7733 2.2888 5.016 5.4372 1.2085 3.0616 2.4354 10.148 0.93876 15.254-0.47418-1.2005-1.5449-2.5682-2.0357-3.1354z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
<script lang='ts'>
|
|
||||||
import Check from '$lib/assets/icons/Check.svelte';
|
|
||||||
import Cross from '$lib/assets/icons/Cross.svelte';
|
|
||||||
import SeasonSelect from '$lib/components/SeasonSelect.svelte';
|
|
||||||
import '$lib/css/action_button.css'
|
|
||||||
import '$lib/css/nordtheme.css'
|
|
||||||
import '$lib/css/shake.css'
|
|
||||||
import { redirect } from '@sveltejs/kit';
|
|
||||||
import { RecipeModelType } from '../../types/types';
|
|
||||||
import type { PageData } from './$types';
|
|
||||||
import CardAdd from '$lib/components/CardAdd.svelte';
|
|
||||||
import CreateIngredientList from '$lib/components/CreateIngredientList.svelte';
|
|
||||||
import CreateStepList from '$lib/components/CreateStepList.svelte';
|
|
||||||
|
|
||||||
let {
|
|
||||||
data,
|
|
||||||
actions,
|
|
||||||
title,
|
|
||||||
card_data = $bindable({
|
|
||||||
icon: data.icon,
|
|
||||||
category: data.category,
|
|
||||||
name: data.name,
|
|
||||||
description: data.description,
|
|
||||||
tags: data.tags,
|
|
||||||
}),
|
|
||||||
add_info = $bindable({
|
|
||||||
preparation: data.preparation,
|
|
||||||
fermentation: {
|
|
||||||
bulk: data.fermentation.bulk,
|
|
||||||
final: data.fermentation.final,
|
|
||||||
},
|
|
||||||
baking: {
|
|
||||||
length: data.baking.length,
|
|
||||||
temperature: data.baking.temperature,
|
|
||||||
mode: data.baking.mode,
|
|
||||||
},
|
|
||||||
total_time: data.total_time,
|
|
||||||
}),
|
|
||||||
portions = $bindable(data.portions),
|
|
||||||
ingredients = $bindable(data.ingredients),
|
|
||||||
instructions = $bindable(data.instructions)
|
|
||||||
}: {
|
|
||||||
data: PageData,
|
|
||||||
actions: [String],
|
|
||||||
title: string,
|
|
||||||
card_data?: any,
|
|
||||||
add_info?: any,
|
|
||||||
portions?: any,
|
|
||||||
ingredients?: any,
|
|
||||||
instructions?: any
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
let preamble = $state(data.preamble);
|
|
||||||
let addendum = $state(data.addendum);
|
|
||||||
|
|
||||||
import { season } from '$lib/js/season_store';
|
|
||||||
season.update(() => data.season)
|
|
||||||
let season_local = $state();
|
|
||||||
season.subscribe((s) => {
|
|
||||||
season_local = s
|
|
||||||
});
|
|
||||||
|
|
||||||
let old_short_name = $state(data.short_name);
|
|
||||||
let images = $state(data.images);
|
|
||||||
let short_name = $state(data.short_name);
|
|
||||||
let password = $state();
|
|
||||||
let datecreated = $state(data.datecreated);
|
|
||||||
let datemodified = $state(new Date());
|
|
||||||
|
|
||||||
|
|
||||||
function get_season(){
|
|
||||||
let season = []
|
|
||||||
const el = document.getElementById("labels");
|
|
||||||
for(var i = 0; i < el.children.length; i++){
|
|
||||||
if(el.children[i].children[0].children[0].checked){
|
|
||||||
season.push(i+1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return season
|
|
||||||
}
|
|
||||||
function write_season(season){
|
|
||||||
const el = document.getElementById("labels");
|
|
||||||
for(var i = 0; i < season.length; i++){
|
|
||||||
el.children[i].children[0].children[0].checked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doDelete(){
|
|
||||||
const response = confirm("Bist du dir sicher, dass du das Rezept löschen willst?")
|
|
||||||
if(!response){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const res = await fetch('/api/delete', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
old_short_name,
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
bearer: password,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
async function doEdit() {
|
|
||||||
const res = await fetch('/api/edit', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
: {
|
|
||||||
...card_data,
|
|
||||||
...add_info,
|
|
||||||
images, // TODO
|
|
||||||
season: season_local,
|
|
||||||
short_name,
|
|
||||||
datecreated,
|
|
||||||
datemodified,
|
|
||||||
instructions,
|
|
||||||
ingredients,
|
|
||||||
addendum,
|
|
||||||
preamble
|
|
||||||
},
|
|
||||||
old_short_name,
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
bearer: password,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const item = await res.json();
|
|
||||||
}
|
|
||||||
async function doAdd () {
|
|
||||||
const res = await fetch('/api/add', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
recipe: {
|
|
||||||
...card_data,
|
|
||||||
...add_info,
|
|
||||||
images: {mediapath: short_name + '.webp', alt: "", caption: ""}, // TODO
|
|
||||||
season: season_local,
|
|
||||||
short_name,
|
|
||||||
datecreated,
|
|
||||||
datemodified,
|
|
||||||
instructions,
|
|
||||||
ingredients,
|
|
||||||
preamble,
|
|
||||||
addendum,
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
bearer: password,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
input{
|
|
||||||
display: block;
|
|
||||||
border: unset;
|
|
||||||
margin: 1rem auto;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
border-radius: 1000px;
|
|
||||||
background-color: var(--nord4);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
transition: 100ms;
|
|
||||||
|
|
||||||
}
|
|
||||||
input:hover,
|
|
||||||
input:focus-visible
|
|
||||||
{
|
|
||||||
scale: 1.05 1.05;
|
|
||||||
}
|
|
||||||
.list_wrapper{
|
|
||||||
margin-inline: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
max-width: 1000px;
|
|
||||||
gap: 2rem;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 700px){
|
|
||||||
.list_wrapper{
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input[type=password]{
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
padding-block: 0.5em;
|
|
||||||
display: inline;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.submit_wrapper{
|
|
||||||
position: relative;
|
|
||||||
margin-inline: auto;
|
|
||||||
width: max(300px, 50vw)
|
|
||||||
}
|
|
||||||
.submit_wrapper button{
|
|
||||||
position: absolute;
|
|
||||||
right:-1em;
|
|
||||||
bottom: -0.5em;
|
|
||||||
}
|
|
||||||
.submit_wrapper h2{
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
h1{
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.title_container{
|
|
||||||
max-width: 1000px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
position: relative;
|
|
||||||
width: min(800px, 80vw);
|
|
||||||
margin-block: 2rem;
|
|
||||||
margin-inline: auto;
|
|
||||||
background-color: var(--nord6);
|
|
||||||
background-color: red;
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
}
|
|
||||||
.title p{
|
|
||||||
border: 2px solid var(--nord1);
|
|
||||||
border-radius: 10000px;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
transition: 200ms;
|
|
||||||
}
|
|
||||||
.title p:hover,
|
|
||||||
.title p:focus-within{
|
|
||||||
scale: 1.02 1.02;
|
|
||||||
}
|
|
||||||
.addendum{
|
|
||||||
font-size: 1.1rem;
|
|
||||||
max-width: 90%;
|
|
||||||
margin-inline: auto;
|
|
||||||
border: 2px solid var(--nord1);
|
|
||||||
border-radius: 45px;
|
|
||||||
padding: 1em 1em;
|
|
||||||
transition: 100ms;
|
|
||||||
}
|
|
||||||
.addendum:hover,
|
|
||||||
.addendum:focus-within
|
|
||||||
{
|
|
||||||
scale: 1.02 1.02;
|
|
||||||
}
|
|
||||||
.addendum_wrapper{
|
|
||||||
max-width: 1000px;
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
h3{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.delete{
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
margin: 2rem;
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: dark){
|
|
||||||
.title{
|
|
||||||
background-color: var(--nord6-dark);
|
|
||||||
background-color: green;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<h1>{title}</h1>
|
|
||||||
|
|
||||||
<CardAdd {card_data}></CardAdd>
|
|
||||||
|
|
||||||
<h3>Kurzname (für URL):</h3>
|
|
||||||
<input bind:value={short_name} placeholder="Kurzname"/>
|
|
||||||
|
|
||||||
<div class=title_container>
|
|
||||||
<div class=title>
|
|
||||||
<h4>Eine etwas längere Beschreibung:</h4>
|
|
||||||
<p bind:innerText={preamble} contenteditable></p>
|
|
||||||
<div class=tags>
|
|
||||||
<h4>Saison:</h4>
|
|
||||||
<SeasonSelect></SeasonSelect>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=list_wrapper>
|
|
||||||
<div>
|
|
||||||
<CreateIngredientList {ingredients} {portions}></CreateIngredientList>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<CreateStepList {instructions} {add_info}></CreateStepList>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=addendum_wrapper>
|
|
||||||
<h3>Nachtrag:</h3>
|
|
||||||
<div class=addendum bind:innerText={addendum} contenteditable></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if actions.includes('add')}
|
|
||||||
<div class=submit_wrapper>
|
|
||||||
<h2>Neues Rezept hinzufügen:</h2>
|
|
||||||
<input type="password" placeholder=Passwort bind:value={password}>
|
|
||||||
<button class=action_button onclick={doAdd}><Check fill=white width=2rem height=2rem></Check></button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if actions.includes('edit')}
|
|
||||||
<div class=submit_wrapper>
|
|
||||||
<h2>Editiertes Rezept abspeichern:</h2>
|
|
||||||
<input type="password" placeholder=Passwort bind:value={password}>
|
|
||||||
<button class=action_button onclick={doEdit}><Check fill=white width=2rem height=2rem></Check></button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if actions.includes('delete')}
|
|
||||||
<div class=submit_wrapper>
|
|
||||||
<h2>Rezept löschen:</h2>
|
|
||||||
<input type="password" placeholder=Passwort bind:value={password}>
|
|
||||||
<button class=action_button onclick={doDelete}><Cross fill=white width=2rem height=2rem></Cross></button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
let {
|
|
||||||
ingredients = $bindable([]),
|
|
||||||
translationMetadata = null,
|
|
||||||
onchange
|
|
||||||
}: {
|
|
||||||
ingredients?: any[],
|
|
||||||
translationMetadata?: any[] | null | undefined,
|
|
||||||
onchange?: (detail: { ingredients: any[] }) => void
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
function handleChange() {
|
|
||||||
onchange?.({ ingredients });
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateIngredientGroupName(groupIndex: number, event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
ingredients[groupIndex].name = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateIngredientItem(groupIndex: number, itemIndex: number, field: string, event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
ingredients[groupIndex].list[itemIndex][field] = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base recipe reference handlers
|
|
||||||
function updateLabelOverride(groupIndex: number, event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
ingredients[groupIndex].labelOverride = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateItemBefore(groupIndex: number, itemIndex: number, field: string, event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
if (!ingredients[groupIndex].itemsBefore) {
|
|
||||||
ingredients[groupIndex].itemsBefore = [];
|
|
||||||
}
|
|
||||||
ingredients[groupIndex].itemsBefore[itemIndex][field] = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateItemAfter(groupIndex: number, itemIndex: number, field: string, event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
if (!ingredients[groupIndex].itemsAfter) {
|
|
||||||
ingredients[groupIndex].itemsAfter = [];
|
|
||||||
}
|
|
||||||
ingredients[groupIndex].itemsAfter[itemIndex][field] = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a group name was re-translated
|
|
||||||
function isGroupNameTranslated(groupIndex: number): boolean {
|
|
||||||
return translationMetadata?.[groupIndex]?.nameTranslated ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a specific item was re-translated
|
|
||||||
function isItemTranslated(groupIndex: number, itemIndex: number): boolean {
|
|
||||||
return translationMetadata?.[groupIndex]?.itemsTranslated?.[itemIndex] ?? false;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.ingredients-editor {
|
|
||||||
background: var(--nord0);
|
|
||||||
border: 1px solid var(--nord3);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.ingredients-editor {
|
|
||||||
background: var(--nord5);
|
|
||||||
border-color: var(--nord3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-group {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-group:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-name {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
background: var(--nord1);
|
|
||||||
border: 1px solid var(--nord3);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--nord6);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.group-name {
|
|
||||||
background: var(--nord6);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 60px 60px 1fr;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-item input {
|
|
||||||
padding: 0.4rem;
|
|
||||||
background: var(--nord1);
|
|
||||||
border: 1px solid var(--nord3);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--nord6);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.ingredient-item input {
|
|
||||||
background: var(--nord6);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-item input:focus {
|
|
||||||
outline: 2px solid var(--nord14);
|
|
||||||
border-color: var(--nord14);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-item input.amount {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Highlight re-translated items with red border */
|
|
||||||
.retranslated {
|
|
||||||
border: 2px solid var(--nord11) !important;
|
|
||||||
animation: highlight-flash 0.6s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes highlight-flash {
|
|
||||||
0% {
|
|
||||||
box-shadow: 0 0 10px var(--nord11);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 0 transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: var(--nord9);
|
|
||||||
color: var(--nord6);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-section {
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: var(--nord2);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.reference-section {
|
|
||||||
background: var(--nord4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-section-label {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--nord8);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="ingredients-editor">
|
|
||||||
{#each ingredients as group, groupIndex}
|
|
||||||
<div class="ingredient-group">
|
|
||||||
{#if group.type === 'reference'}
|
|
||||||
<span class="reference-badge">🔗 Base Recipe Reference</span>
|
|
||||||
|
|
||||||
{#if group.labelOverride !== undefined}
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="group-name"
|
|
||||||
value={group.labelOverride || ''}
|
|
||||||
on:input={(e) => updateLabelOverride(groupIndex, e)}
|
|
||||||
placeholder="Label override (optional)"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if group.itemsBefore && group.itemsBefore.length > 0}
|
|
||||||
<div class="reference-section">
|
|
||||||
<div class="reference-section-label">Items Before Base Recipe:</div>
|
|
||||||
{#each group.itemsBefore as item, itemIndex}
|
|
||||||
<div class="ingredient-item">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="amount"
|
|
||||||
value={item.amount || ''}
|
|
||||||
on:input={(e) => updateItemBefore(groupIndex, itemIndex, 'amount', e)}
|
|
||||||
placeholder="Amt"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="unit"
|
|
||||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
|
||||||
value={item.unit || ''}
|
|
||||||
on:input={(e) => updateItemBefore(groupIndex, itemIndex, 'unit', e)}
|
|
||||||
placeholder="Unit"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="name"
|
|
||||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
|
||||||
value={item.name || ''}
|
|
||||||
on:input={(e) => updateItemBefore(groupIndex, itemIndex, 'name', e)}
|
|
||||||
placeholder="Ingredient name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if group.itemsAfter && group.itemsAfter.length > 0}
|
|
||||||
<div class="reference-section">
|
|
||||||
<div class="reference-section-label">Items After Base Recipe:</div>
|
|
||||||
{#each group.itemsAfter as item, itemIndex}
|
|
||||||
<div class="ingredient-item">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="amount"
|
|
||||||
value={item.amount || ''}
|
|
||||||
on:input={(e) => updateItemAfter(groupIndex, itemIndex, 'amount', e)}
|
|
||||||
placeholder="Amt"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="unit"
|
|
||||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
|
||||||
value={item.unit || ''}
|
|
||||||
on:input={(e) => updateItemAfter(groupIndex, itemIndex, 'unit', e)}
|
|
||||||
placeholder="Unit"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="name"
|
|
||||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
|
||||||
value={item.name || ''}
|
|
||||||
on:input={(e) => updateItemAfter(groupIndex, itemIndex, 'name', e)}
|
|
||||||
placeholder="Ingredient name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="group-name"
|
|
||||||
class:retranslated={isGroupNameTranslated(groupIndex)}
|
|
||||||
value={group.name || ''}
|
|
||||||
on:input={(e) => updateIngredientGroupName(groupIndex, e)}
|
|
||||||
placeholder="Ingredient group name"
|
|
||||||
/>
|
|
||||||
{#each group.list as item, itemIndex}
|
|
||||||
<div class="ingredient-item">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="amount"
|
|
||||||
value={item.amount || ''}
|
|
||||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'amount', e)}
|
|
||||||
placeholder="Amt"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="unit"
|
|
||||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
|
||||||
value={item.unit || ''}
|
|
||||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'unit', e)}
|
|
||||||
placeholder="Unit"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="name"
|
|
||||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
|
||||||
value={item.name || ''}
|
|
||||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'name', e)}
|
|
||||||
placeholder="Ingredient name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
let {
|
|
||||||
instructions = $bindable([]),
|
|
||||||
translationMetadata = null,
|
|
||||||
onchange
|
|
||||||
}: {
|
|
||||||
instructions?: any[],
|
|
||||||
translationMetadata?: any[] | null | undefined,
|
|
||||||
onchange?: (detail: { instructions: any[] }) => void
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
function handleChange() {
|
|
||||||
onchange?.({ instructions });
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateInstructionGroupName(groupIndex: number, event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
instructions[groupIndex].name = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStep(groupIndex: number, stepIndex: number, event: Event) {
|
|
||||||
const target = event.target as HTMLTextAreaElement;
|
|
||||||
instructions[groupIndex].steps[stepIndex] = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base recipe reference handlers
|
|
||||||
function updateLabelOverride(groupIndex: number, event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
instructions[groupIndex].labelOverride = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStepBefore(groupIndex: number, stepIndex: number, event: Event) {
|
|
||||||
const target = event.target as HTMLTextAreaElement;
|
|
||||||
if (!instructions[groupIndex].stepsBefore) {
|
|
||||||
instructions[groupIndex].stepsBefore = [];
|
|
||||||
}
|
|
||||||
instructions[groupIndex].stepsBefore[stepIndex] = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStepAfter(groupIndex: number, stepIndex: number, event: Event) {
|
|
||||||
const target = event.target as HTMLTextAreaElement;
|
|
||||||
if (!instructions[groupIndex].stepsAfter) {
|
|
||||||
instructions[groupIndex].stepsAfter = [];
|
|
||||||
}
|
|
||||||
instructions[groupIndex].stepsAfter[stepIndex] = target.value;
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a group name was re-translated
|
|
||||||
function isGroupNameTranslated(groupIndex: number): boolean {
|
|
||||||
return translationMetadata?.[groupIndex]?.nameTranslated ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a specific step was re-translated
|
|
||||||
function isStepTranslated(groupIndex: number, stepIndex: number): boolean {
|
|
||||||
return translationMetadata?.[groupIndex]?.stepsTranslated?.[stepIndex] ?? false;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.instructions-editor {
|
|
||||||
background: var(--nord0);
|
|
||||||
border: 1px solid var(--nord3);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.instructions-editor {
|
|
||||||
background: var(--nord5);
|
|
||||||
border-color: var(--nord3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.instruction-group {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instruction-group:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-name {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
background: var(--nord1);
|
|
||||||
border: 1px solid var(--nord3);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--nord6);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.group-name {
|
|
||||||
background: var(--nord6);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-item {
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-number {
|
|
||||||
min-width: 2rem;
|
|
||||||
padding: 0.4rem 0.5rem;
|
|
||||||
background: var(--nord3);
|
|
||||||
border-radius: 4px;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--nord6);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.step-number {
|
|
||||||
background: var(--nord4);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-item textarea {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: var(--nord1);
|
|
||||||
border: 1px solid var(--nord3);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--nord6);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-family: inherit;
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.step-item textarea {
|
|
||||||
background: var(--nord6);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-item textarea:focus {
|
|
||||||
outline: 2px solid var(--nord14);
|
|
||||||
border-color: var(--nord14);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Highlight re-translated items with red border */
|
|
||||||
.retranslated {
|
|
||||||
border: 2px solid var(--nord11) !important;
|
|
||||||
animation: highlight-flash 0.6s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes highlight-flash {
|
|
||||||
0% {
|
|
||||||
box-shadow: 0 0 10px var(--nord11);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 0 transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: var(--nord9);
|
|
||||||
color: var(--nord6);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-section {
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: var(--nord2);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.reference-section {
|
|
||||||
background: var(--nord4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-section-label {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--nord8);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="instructions-editor">
|
|
||||||
{#each instructions as group, groupIndex}
|
|
||||||
<div class="instruction-group">
|
|
||||||
{#if group.type === 'reference'}
|
|
||||||
<span class="reference-badge">🔗 Base Recipe Reference</span>
|
|
||||||
|
|
||||||
{#if group.labelOverride !== undefined}
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="group-name"
|
|
||||||
value={group.labelOverride || ''}
|
|
||||||
on:input={(e) => updateLabelOverride(groupIndex, e)}
|
|
||||||
placeholder="Label override (optional)"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if group.stepsBefore && group.stepsBefore.length > 0}
|
|
||||||
<div class="reference-section">
|
|
||||||
<div class="reference-section-label">Steps Before Base Recipe:</div>
|
|
||||||
{#each group.stepsBefore as step, stepIndex}
|
|
||||||
<div class="step-item">
|
|
||||||
<div class="step-number">{stepIndex + 1}</div>
|
|
||||||
<textarea
|
|
||||||
class:retranslated={isStepTranslated(groupIndex, stepIndex)}
|
|
||||||
value={step || ''}
|
|
||||||
on:input={(e) => updateStepBefore(groupIndex, stepIndex, e)}
|
|
||||||
placeholder="Step description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if group.stepsAfter && group.stepsAfter.length > 0}
|
|
||||||
<div class="reference-section">
|
|
||||||
<div class="reference-section-label">Steps After Base Recipe:</div>
|
|
||||||
{#each group.stepsAfter as step, stepIndex}
|
|
||||||
<div class="step-item">
|
|
||||||
<div class="step-number">{stepIndex + 1}</div>
|
|
||||||
<textarea
|
|
||||||
class:retranslated={isStepTranslated(groupIndex, stepIndex)}
|
|
||||||
value={step || ''}
|
|
||||||
on:input={(e) => updateStepAfter(groupIndex, stepIndex, e)}
|
|
||||||
placeholder="Step description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="group-name"
|
|
||||||
class:retranslated={isGroupNameTranslated(groupIndex)}
|
|
||||||
value={group.name || ''}
|
|
||||||
on:input={(e) => updateInstructionGroupName(groupIndex, e)}
|
|
||||||
placeholder="Instruction section name"
|
|
||||||
/>
|
|
||||||
{#each group.steps as step, stepIndex}
|
|
||||||
<div class="step-item">
|
|
||||||
<div class="step-number">{stepIndex + 1}</div>
|
|
||||||
<textarea
|
|
||||||
class:retranslated={isStepTranslated(groupIndex, stepIndex)}
|
|
||||||
value={step || ''}
|
|
||||||
on:input={(e) => updateStep(groupIndex, stepIndex, e)}
|
|
||||||
placeholder="Step description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
@@ -41,9 +41,10 @@
|
|||||||
<style>
|
<style>
|
||||||
.favorite-button {
|
.favorite-button {
|
||||||
all: unset;
|
all: unset;
|
||||||
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0.5em;
|
bottom: 0.5em;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "$lib/css/nordtheme.css"
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Symbol from "./Symbol.svelte"
|
import Symbol from "./Symbol.svelte"
|
||||||
@@ -24,10 +23,9 @@ let underlineWidth = $state(0);
|
|||||||
let disableTransition = $state(false);
|
let disableTransition = $state(false);
|
||||||
|
|
||||||
function toggle_sidebar(state){
|
function toggle_sidebar(state){
|
||||||
// state: force hidden state (optional)
|
const checkbox = document.getElementById('nav-toggle')
|
||||||
const nav_el = document.querySelector("nav")
|
if(state === undefined) checkbox.checked = !checkbox.checked
|
||||||
if(state === undefined) nav_el.hidden = !nav_el.hidden
|
else checkbox.checked = !state
|
||||||
else nav_el.hidden = state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUnderline() {
|
function updateUnderline() {
|
||||||
@@ -94,16 +92,17 @@ nav{
|
|||||||
background-color: var(--nord0);
|
background-color: var(--nord0);
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
display: flex !important;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between !important;
|
justify-content: space-between !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-shadow: 0 1em 1rem 0rem rgba(0,0,0,0.4);
|
box-shadow: 0 1em 1rem 0rem rgba(0,0,0,0.4);
|
||||||
height: 4rem;
|
height: var(--header-h);
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
|
view-transition-name: site-header;
|
||||||
}
|
}
|
||||||
nav[hidden]{
|
.nav-toggle{
|
||||||
display:block;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.site_header li),
|
:global(.site_header li),
|
||||||
@@ -117,22 +116,20 @@ nav[hidden]{
|
|||||||
:global(.site_header li>a)
|
:global(.site_header li>a)
|
||||||
{
|
{
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-family: sans-serif;
|
font-size: 1rem;
|
||||||
font-size: 1.2rem;
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.4rem 0.6rem;
|
||||||
}
|
}
|
||||||
:global(a.entry),
|
:global(a.entry),
|
||||||
:global(a.entry:link),
|
:global(a.entry:link),
|
||||||
:global(a.entry:visited)
|
:global(a.entry:visited)
|
||||||
{
|
{
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-family: sans-serif;
|
font-size: 1rem;
|
||||||
font-size: 1.2rem;
|
|
||||||
color: white !important;
|
color: white !important;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.4rem 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.site_header li:hover),
|
:global(.site_header li:hover),
|
||||||
@@ -179,6 +176,9 @@ nav[hidden]{
|
|||||||
display: none;
|
display: none;
|
||||||
padding-inline: 0.5rem;
|
padding-inline: 0.5rem;
|
||||||
}
|
}
|
||||||
|
.header-shadow{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.right-buttons{
|
.right-buttons{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -190,9 +190,10 @@ nav[hidden]{
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
:global(svg.symbol){
|
:global(svg.symbol){
|
||||||
height: 4rem;
|
--symbol-size: calc(var(--header-h) - 1rem);
|
||||||
width: 4rem;
|
width: var(--symbol-size);
|
||||||
border-radius: 10000px;
|
border-radius: 10000px;
|
||||||
|
margin: 0.25rem;
|
||||||
}
|
}
|
||||||
/*:global(a:has(svg.symbol)){
|
/*:global(a:has(svg.symbol)){
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
@@ -201,6 +202,8 @@ nav[hidden]{
|
|||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}*/
|
}*/
|
||||||
.wrapper{
|
.wrapper{
|
||||||
|
--header-h: 3rem;
|
||||||
|
--symbol-size: calc(var(--header-h) - 1rem);
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100svh;
|
min-height: 100svh;
|
||||||
@@ -209,95 +212,111 @@ footer{
|
|||||||
padding-block: 1rem;
|
padding-block: 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
.button_wrapper{
|
.button_wrapper{
|
||||||
box-shadow: 0 1em 1rem 0rem rgba(0,0,0,0.4);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
background-color: var(--nord0);
|
background-color: var(--nord0);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 4rem;
|
height: var(--header-h);
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
.nav_button{
|
.header-shadow{
|
||||||
border: unset;
|
|
||||||
background-color: unset;
|
|
||||||
display: block;
|
display: block;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: var(--header-h);
|
||||||
|
margin-top: calc(-1 * var(--header-h));
|
||||||
|
box-shadow: 0 1em 1rem 0rem rgba(0,0,0,0.4);
|
||||||
|
z-index: 9997;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.nav_button{
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
fill: white;
|
fill: white;
|
||||||
margin-inline: 0.5rem;
|
margin-inline: 0.5rem;
|
||||||
width: 2rem;
|
width: 1.25rem;
|
||||||
aspect-ratio: 1;
|
height: 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.nav_button svg{
|
.nav_button svg{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
.nav_button:focus{
|
.nav_button:hover,
|
||||||
fill: var(--red);
|
.nav_button:active,
|
||||||
|
.nav-toggle:focus-visible + .nav_button{
|
||||||
|
fill: var(--nord8);
|
||||||
scale: 0.9;
|
scale: 0.9;
|
||||||
}
|
}
|
||||||
.nav_site{
|
.nav_site:not(.no-links){
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 100vh; /* dvh does not work, breaks because of transition and only being applied after scroll ends*/
|
height: 100vh; /* dvh does not work, breaks because of transition and only being applied after scroll ends*/
|
||||||
margin-bottom: 50vh;
|
margin-bottom: 50vh;
|
||||||
width: min(95svw, 25em);
|
width: min(95svw, 25em);
|
||||||
transition: transform 100ms;
|
z-index: 9998;
|
||||||
z-index: 10;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start !important;
|
|
||||||
align-items: left;
|
|
||||||
justify-content: space-between!important;
|
|
||||||
padding-inline: 0.5rem;
|
padding-inline: 0.5rem;
|
||||||
}
|
}
|
||||||
:global(.nav_site ul){
|
.nav_site:not(.no-links)::before{
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
:global(.nav_site:not(.no-links) ul){
|
||||||
width: 100% ;
|
width: 100% ;
|
||||||
}
|
}
|
||||||
.nav_site :first-child{
|
.nav_site:not(.no-links) :first-child{
|
||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
.nav_site[hidden]{
|
.nav_site:not(.no-links){
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
:global(.nav_site a:last-child){
|
.wrapper:has(.nav-toggle:checked) .nav_site:not(.no-links){
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 100ms;
|
||||||
|
}
|
||||||
|
:global(.nav_site:not(.no-links) a:last-child){
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav_site .links-wrapper {
|
.nav_site:not(.no-links) .links-wrapper {
|
||||||
align-self: flex-start;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 2rem;
|
padding: 0 2rem;
|
||||||
}
|
}
|
||||||
:global(.site_header){
|
:global(.site_header){
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: min(10rem, 10vh);
|
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
:global(.site_header li, .site_header a){
|
:global(.site_header li, .site_header a){
|
||||||
font-size: 4rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
:global(.site_header li > a, .site_header a){
|
:global(.site_header li > a, .site_header a){
|
||||||
font-size: 2rem;
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
:global(.site_header li:hover),
|
:global(.site_header li:hover),
|
||||||
:global(.site_header li:focus-within){
|
:global(.site_header li:focus-within){
|
||||||
transform: unset;
|
transform: unset;
|
||||||
}
|
}
|
||||||
.nav_site .header-right{
|
.nav_site:not(.no-links) .header-right{
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2rem;
|
bottom: 2rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
.language-selector-desktop{
|
.nav_site:not(.no-links) .language-selector-desktop{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.active-underline {
|
.active-underline {
|
||||||
@@ -310,17 +329,44 @@ footer{
|
|||||||
text-underline-offset: 0.3rem;
|
text-underline-offset: 0.3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.no-links :global(button) {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
.no-links :global(#options) {
|
||||||
|
top: calc(100% + 10px) !important;
|
||||||
|
bottom: unset !important;
|
||||||
|
right: 0 !important;
|
||||||
|
left: unset !important;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
.no-links :global(.top.speech::after) {
|
||||||
|
border: 20px solid transparent !important;
|
||||||
|
border-bottom-color: var(--nord3) !important;
|
||||||
|
border-top: 0 !important;
|
||||||
|
top: -10px !important;
|
||||||
|
bottom: unset !important;
|
||||||
|
left: unset !important;
|
||||||
|
right: 0.25rem !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
.no-links :global(button::before) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class=wrapper lang=de>
|
<div class=wrapper lang=de>
|
||||||
<div>
|
<div>
|
||||||
|
{#if links}
|
||||||
<div class=button_wrapper>
|
<div class=button_wrapper>
|
||||||
<a href="/" aria-label="Home"><Symbol></Symbol></a>
|
<a href="/" aria-label="Home"><Symbol></Symbol></a>
|
||||||
<div class="right-buttons">
|
<div class="right-buttons">
|
||||||
{@render language_selector_mobile?.()}
|
{@render language_selector_mobile?.()}
|
||||||
<button class=nav_button onclick={() => {toggle_sidebar()}} aria-label="Toggle navigation menu"><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>
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" aria-label="Toggle navigation menu" />
|
||||||
|
<label for="nav-toggle" class=nav_button aria-hidden="true"><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></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<nav hidden class=nav_site>
|
<div class="header-shadow"></div>
|
||||||
|
{/if}
|
||||||
|
<nav class=nav_site class:no-links={!links}>
|
||||||
<a href="/" aria-label="Home"><Symbol></Symbol></a>
|
<a href="/" aria-label="Home"><Symbol></Symbol></a>
|
||||||
<div class="links-wrapper">
|
<div class="links-wrapper">
|
||||||
{@render links?.()}
|
{@render links?.()}
|
||||||
|
|||||||
@@ -1,564 +0,0 @@
|
|||||||
<script lang='ts'>
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
import Pen from '$lib/assets/icons/Pen.svelte'
|
|
||||||
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/css/action_button.css"
|
|
||||||
|
|
||||||
let { list = $bindable(), list_index } = $props<{ list: any, list_index: number }>();
|
|
||||||
|
|
||||||
let edit_ingredient = $state({
|
|
||||||
amount: "",
|
|
||||||
unit: "",
|
|
||||||
name: "",
|
|
||||||
sublist: "",
|
|
||||||
list_index: "",
|
|
||||||
ingredient_index: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
let edit_heading = $state({
|
|
||||||
name:"",
|
|
||||||
list_index: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
function get_sublist_index(sublist_name, list){
|
|
||||||
for(var i =0; i < list.length; i++){
|
|
||||||
if(list[i].name == sublist_name){
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
export function show_modal_edit_subheading_ingredient(list_index){
|
|
||||||
edit_heading.name = ingredients[list_index].name
|
|
||||||
edit_heading.list_index = list_index
|
|
||||||
const el = document.querySelector('#edit_subheading_ingredient_modal')
|
|
||||||
el.showModal()
|
|
||||||
}
|
|
||||||
export function edit_subheading_and_close_modal(){
|
|
||||||
ingredients[edit_heading.list_index].name = edit_heading.name
|
|
||||||
const el = document.querySelector('#edit_subheading_ingredient_modal')
|
|
||||||
el.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function add_new_ingredient(){
|
|
||||||
if(!new_ingredient.name){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let list_index = get_sublist_index(new_ingredient.sublist, ingredients)
|
|
||||||
if(list_index == -1){
|
|
||||||
ingredients.push({
|
|
||||||
name: new_ingredient.sublist,
|
|
||||||
list: [],
|
|
||||||
})
|
|
||||||
list_index = ingredients.length - 1
|
|
||||||
}
|
|
||||||
ingredients[list_index].list.push({ ...new_ingredient})
|
|
||||||
ingredients = ingredients //tells svelte to update dom
|
|
||||||
}
|
|
||||||
export function remove_list(list_index){
|
|
||||||
if(ingredients[list_index].list.length > 1){
|
|
||||||
const response = confirm("Bist du dir sicher, dass du diese Liste löschen möchtest? Alle Zutaten der Liste werden hiermit auch gelöscht.");
|
|
||||||
if(!response){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ingredients.splice(list_index, 1);
|
|
||||||
ingredients = ingredients //tells svelte to update dom
|
|
||||||
}
|
|
||||||
export function remove_ingredient(list_index, ingredient_index){
|
|
||||||
ingredients[list_index].list.splice(ingredient_index, 1)
|
|
||||||
ingredients = ingredients //tells svelte to update dom
|
|
||||||
}
|
|
||||||
|
|
||||||
export function show_modal_edit_ingredient(list_index, ingredient_index){
|
|
||||||
edit_ingredient = {...ingredients[list_index].list[ingredient_index]}
|
|
||||||
edit_ingredient.list_index = list_index
|
|
||||||
edit_ingredient.ingredient_index = ingredient_index
|
|
||||||
edit_ingredient.sublist = ingredients[list_index].name
|
|
||||||
const modal_el = document.querySelector("#edit_ingredient_modal");
|
|
||||||
modal_el.showModal();
|
|
||||||
}
|
|
||||||
export function edit_ingredient_and_close_modal(){
|
|
||||||
ingredients[edit_ingredient.list_index].list[edit_ingredient.ingredient_index] = {
|
|
||||||
amount: edit_ingredient.amount,
|
|
||||||
unit: edit_ingredient.unit,
|
|
||||||
name: edit_ingredient.name,
|
|
||||||
}
|
|
||||||
ingredients[edit_ingredient.list_index].name = edit_ingredient.sublist
|
|
||||||
const modal_el = document.querySelector("#edit_ingredient_modal");
|
|
||||||
modal_el.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
let ghost;
|
|
||||||
let grabbed;
|
|
||||||
|
|
||||||
let lastTarget;
|
|
||||||
|
|
||||||
let mouseY = 0; // pointer y coordinate within client
|
|
||||||
let offsetY = 0; // y distance from top of grabbed element to pointer
|
|
||||||
let layerY = 0; // distance from top of list to top of client
|
|
||||||
|
|
||||||
function grab(clientY, element) {
|
|
||||||
// modify grabbed element
|
|
||||||
grabbed = element;
|
|
||||||
grabbed.dataset.grabY = clientY;
|
|
||||||
|
|
||||||
// modify ghost element (which is actually dragged)
|
|
||||||
ghost.innerHTML = grabbed.innerHTML;
|
|
||||||
|
|
||||||
// record offset from cursor to top of element
|
|
||||||
// (used for positioning ghost)
|
|
||||||
offsetY = grabbed.getBoundingClientRect().y - clientY;
|
|
||||||
drag(clientY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// drag handler updates cursor position
|
|
||||||
function drag(clientY) {
|
|
||||||
if (grabbed) {
|
|
||||||
mouseY = clientY;
|
|
||||||
layerY = ghost.parentNode.getBoundingClientRect().y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// touchEnter handler emulates the mouseenter event for touch input
|
|
||||||
// (more or less)
|
|
||||||
function touchEnter(ev) {
|
|
||||||
drag(ev.clientY);
|
|
||||||
// trigger dragEnter the first time the cursor moves over a list item
|
|
||||||
let target = document.elementFromPoint(ev.clientX, ev.clientY).closest(".item");
|
|
||||||
if (target && target != lastTarget) {
|
|
||||||
lastTarget = target;
|
|
||||||
dragEnter(ev, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragEnter(ev, target) {
|
|
||||||
// swap items in data
|
|
||||||
if (grabbed && target != grabbed && target.classList.contains("item")) {
|
|
||||||
moveDatum(parseInt(grabbed.dataset.index), parseInt(target.dataset.index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// does the actual moving of items in data
|
|
||||||
function moveDatum(from, to) {
|
|
||||||
let temp = list[0].list[from];
|
|
||||||
list[0].list = [...list[0].list.slice(0, from), ...list[0].list.slice(from + 1)];
|
|
||||||
list[0].list= [...list[0].list.slice(0, to), temp, ...list[0].list.slice(to)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function release(ev) {
|
|
||||||
grabbed = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeDatum(index) {
|
|
||||||
list= [...list.slice(0, index), ...list.slice(index + 1)];
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
input::placeholder{
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag_handle{
|
|
||||||
cursor: grab;
|
|
||||||
display:flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.drag_handle_header{
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
input{
|
|
||||||
color: unset;
|
|
||||||
font-size: unset;
|
|
||||||
padding: unset;
|
|
||||||
background-color: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.heading{
|
|
||||||
all: unset;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: var(--nord0);
|
|
||||||
padding: 1rem;
|
|
||||||
padding-inline: 2rem;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 1000px;
|
|
||||||
color: white;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
transition: 200ms;
|
|
||||||
}
|
|
||||||
input.heading:hover{
|
|
||||||
background-color: var(--nord1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading_wrapper{
|
|
||||||
position: relative;
|
|
||||||
width: 300px;
|
|
||||||
margin-inline: auto;
|
|
||||||
transition: 200ms;
|
|
||||||
}
|
|
||||||
.heading_wrapper:hover
|
|
||||||
{
|
|
||||||
transform:scale(1.1,1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading_wrapper button{
|
|
||||||
position: absolute;
|
|
||||||
bottom: -1.5rem;
|
|
||||||
right: -2rem;
|
|
||||||
}
|
|
||||||
.adder{
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin-inline: auto;
|
|
||||||
position: relative;
|
|
||||||
margin-block: 3rem;
|
|
||||||
width: 90%;
|
|
||||||
border-radius: 20px;
|
|
||||||
transition: 200ms;
|
|
||||||
}
|
|
||||||
.adder button{
|
|
||||||
position: absolute;
|
|
||||||
right: -1.5rem;
|
|
||||||
bottom: -1.5rem;
|
|
||||||
}
|
|
||||||
.category{
|
|
||||||
border: none;
|
|
||||||
position: absolute;
|
|
||||||
--font_size: 1.5rem;
|
|
||||||
top: -1em;
|
|
||||||
left: -1em;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
background-color: var(--nord0);
|
|
||||||
color: var(--nord4);
|
|
||||||
border-radius: 1000000px;
|
|
||||||
width: 23ch;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
transition: 100ms;
|
|
||||||
box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
.category:hover{
|
|
||||||
background-color: var(--nord1);
|
|
||||||
transform: scale(1.05,1.05);
|
|
||||||
}
|
|
||||||
.adder:hover,
|
|
||||||
.adder:focus-within
|
|
||||||
{
|
|
||||||
transform: scale(1.05, 1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.add_ingredient{
|
|
||||||
font-family: sans-serif;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding: 2rem;
|
|
||||||
padding-top: 2.5rem;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: var(--blue);
|
|
||||||
color: #bbb;
|
|
||||||
transition: 200ms;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.add_ingredient input{
|
|
||||||
border: 2px solid var(--nord4);
|
|
||||||
color: var(--nord4);
|
|
||||||
border-radius: 1000px;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
transition: 100ms;
|
|
||||||
}
|
|
||||||
.add_ingredient input:hover,
|
|
||||||
.add_ingredient input:focus-visible
|
|
||||||
{
|
|
||||||
border-color: white;
|
|
||||||
color: white;
|
|
||||||
transform: scale(1.02, 1.02);
|
|
||||||
|
|
||||||
}
|
|
||||||
.add_ingredient input:nth-of-type(1){
|
|
||||||
max-width: 8ch;
|
|
||||||
}
|
|
||||||
.add_ingredient input:nth-of-type(2){
|
|
||||||
max-width: 8ch;
|
|
||||||
}
|
|
||||||
.add_ingredient input:nth-of-type(3){
|
|
||||||
max-width: 30ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog{
|
|
||||||
box-sizing: content-box;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: transparent;
|
|
||||||
border: unset;
|
|
||||||
margin: 0;
|
|
||||||
transition: 500ms;
|
|
||||||
}
|
|
||||||
dialog[open]::backdrop{
|
|
||||||
animation: show 200ms ease forwards;
|
|
||||||
}
|
|
||||||
@keyframes show{
|
|
||||||
from {
|
|
||||||
backdrop-filter: blur(0px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialog .adder{
|
|
||||||
margin-top: 5rem;
|
|
||||||
}
|
|
||||||
dialog h2{
|
|
||||||
font-size: 3rem;
|
|
||||||
font-family: sans-serif;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 30vh;
|
|
||||||
margin-top: 30dvh;
|
|
||||||
filter: drop-shadow(0 0 0.4em black)
|
|
||||||
drop-shadow(0 0 1em black)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
.mod_icons{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-left: 2rem;
|
|
||||||
}
|
|
||||||
.button_subtle{
|
|
||||||
padding: 0em;
|
|
||||||
animation: unset;
|
|
||||||
margin: 0.2em 0.1em;
|
|
||||||
background-color: transparent;
|
|
||||||
box-shadow: unset;
|
|
||||||
}
|
|
||||||
.button_subtle:hover{
|
|
||||||
scale: 1.2 1.2;
|
|
||||||
}
|
|
||||||
h3{
|
|
||||||
width: fit-content;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
max-width: 1000px;
|
|
||||||
justify-content: space-between;
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.ingredients_grid > span{
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: grid;
|
|
||||||
font-size: 1.1em;
|
|
||||||
grid-template-columns: 1em 2fr 3fr 2em;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
grid-auto-flow: row;
|
|
||||||
align-items: center;
|
|
||||||
row-gap: 0.5em;
|
|
||||||
column-gap: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredients_grid > *{
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.ingredients_grid>*:nth-child(3n+1){
|
|
||||||
min-width: 5ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list_wrapper{
|
|
||||||
padding-inline: 2em;
|
|
||||||
padding-block: 1em;
|
|
||||||
}
|
|
||||||
.list_wrapper p[contenteditable]{
|
|
||||||
border: 2px solid grey;
|
|
||||||
border-radius: 1000px;
|
|
||||||
padding: 0.25em 1em;
|
|
||||||
background-color: white;
|
|
||||||
transition: 200ms;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 500px){
|
|
||||||
dialog h2{
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
dialog .heading_wrapper{
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
.ingredients_grid .mod_icons{
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
cursor: grab;
|
|
||||||
z-index: 5;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
min-height: 3em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
border-radius: 2px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:not(#grabbed):not(#ghost) {
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item > * {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
width: 32px;
|
|
||||||
min-width: 32px;
|
|
||||||
margin: auto 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons button {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0);
|
|
||||||
background-color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons button:focus {
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#grabbed {
|
|
||||||
opacity: 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ghost {
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -5;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ghost * {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ghost.haunting {
|
|
||||||
z-index: 20;
|
|
||||||
opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<div class=dragdroplist>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ghost}
|
|
||||||
id="ghost"
|
|
||||||
class={grabbed ? "item haunting" : "item"}
|
|
||||||
style={"top: " + (mouseY + offsetY - layerY) + "px"}><p></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<h3 onclick={() => show_modal_edit_subheading_ingredient(list_index)}>
|
|
||||||
<div class="drag_handle drag_handle_header"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><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></div>
|
|
||||||
<div>
|
|
||||||
{#if list.name }
|
|
||||||
{list.name}
|
|
||||||
{:else}
|
|
||||||
Leer
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class=mod_icons>
|
|
||||||
<button class="action_button button_subtle" onclick={() => show_modal_edit_subheading_ingredient(list_index)}>
|
|
||||||
<Pen fill=var(--nord1)></Pen> </button>
|
|
||||||
<button class="action_button button_subtle" onclick={() => remove_list(list_index)}>
|
|
||||||
<Cross fill=var(--nord1)></Cross></button>
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="ingredients_grid list"
|
|
||||||
on:mousemove={function(ev) {ev.stopPropagation(); drag(ev.clientY);}}
|
|
||||||
on:touchmove={function(ev) {ev.stopPropagation(); drag(ev.touches[0].clientY);}}
|
|
||||||
on:mouseup={function(ev) {ev.stopPropagation(); release(ev);}}
|
|
||||||
on:touchend={function(ev) {ev.stopPropagation(); release(ev.touches[0]);}}
|
|
||||||
>
|
|
||||||
{#each list.list as ingredient, ingredient_index}
|
|
||||||
<span
|
|
||||||
id={(grabbed && (ingredient.id ? ingredient.id : JSON.stringify(ingredient)) == grabbed.dataset.id) ? "grabbed" : ""}
|
|
||||||
class="item"
|
|
||||||
data-index={ingredient_index}
|
|
||||||
data-id={(ingredient.id ? ingredient.id : JSON.stringify(ingredient))}
|
|
||||||
data-grabY="0"
|
|
||||||
on:mousedown={function(ev) {grab(ev.clientY, this);}}
|
|
||||||
on:touchstart={function(ev) {grab(ev.touches[0].clientY, this);}}
|
|
||||||
on:mouseenter={function(ev) {ev.stopPropagation(); dragEnter(ev, ev.target);}}
|
|
||||||
on:touchmove={function(ev) {ev.stopPropagation(); ev.preventDefault(); touchEnter(ev.touches[0]);}}
|
|
||||||
>
|
|
||||||
<div class=drag_handle><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><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></div>
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<div onclick={() => show_modal_edit_ingredient(list_index, ingredient_index)} >{ingredient.amount} {ingredient.unit}</div>
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<div onclick={() => show_modal_edit_ingredient(list_index, ingredient_index)} >{@html ingredient.name}</div>
|
|
||||||
<div class=mod_icons><button class="action_button button_subtle" onclick={() => show_modal_edit_ingredient(list_index, ingredient_index)}>
|
|
||||||
<Pen fill=var(--nord1) height=1em width=1em></Pen></button>
|
|
||||||
<button class="action_button button_subtle" onclick="{() => remove_ingredient(list_index, ingredient_index)}"><Cross fill=var(--nord1) height=1em width=1em></Cross></button></div>
|
|
||||||
</span>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<dialog id=edit_ingredient_modal>
|
|
||||||
<h2>Zutat verändern</h2>
|
|
||||||
<div class=adder>
|
|
||||||
<input class=category type="text" bind:value={edit_ingredient.sublist} placeholder="Kategorie (optional)">
|
|
||||||
<div class=add_ingredient onkeydown={(event) => do_on_key(event, 'Enter', false, edit_ingredient_and_close_modal)}>
|
|
||||||
<input type="text" placeholder="250..." bind:value={edit_ingredient.amount} onkeydown={(event) => do_on_key(event, 'Enter', false, edit_ingredient_and_close_modal)}>
|
|
||||||
<input type="text" placeholder="mL..." bind:value={edit_ingredient.unit} onkeydown={(event) => do_on_key(event, 'Enter', false, edit_ingredient_and_close_modal)}>
|
|
||||||
<input type="text" placeholder="Milch..." bind:value={edit_ingredient.name} onkeydown={(event) => do_on_key(event, 'Enter', false, edit_ingredient_and_close_modal)}>
|
|
||||||
<button class=action_button onkeydown={(event) => do_on_key(event, 'Enter', false, edit_ingredient_and_close_modal)} onclick={edit_ingredient_and_close_modal}>
|
|
||||||
<Check fill=white style="width: 2rem; height: 2rem;"></Check>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog id=edit_subheading_ingredient_modal>
|
|
||||||
<h2>Kategorie umbenennen</h2>
|
|
||||||
<div class=heading_wrapper>
|
|
||||||
<input class=heading type="text" bind:value={edit_heading.name} onkeydown={(event) => do_on_key(event, 'Enter', false, edit_subheading_and_close_modal)} >
|
|
||||||
<button class=action_button onkeydown={(event) => do_on_key(event, 'Enter', false, edit_subheading_and_close_modal)} onclick={edit_subheading_and_close_modal}>
|
|
||||||
<Check fill=white style="width:2rem; height:2rem;"></Check>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
@@ -5,14 +5,19 @@
|
|||||||
import { languageStore } from '$lib/stores/language';
|
import { languageStore } from '$lib/stores/language';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let { lang = undefined }: { lang?: 'de' | 'en' } = $props();
|
||||||
|
|
||||||
|
// Use prop for display if provided (SSR-safe), otherwise fall back to store
|
||||||
|
const displayLang = $derived(lang ?? $languageStore);
|
||||||
|
|
||||||
let currentPath = $state('');
|
let currentPath = $state('');
|
||||||
let langButton: HTMLButtonElement;
|
let langButton: HTMLButtonElement;
|
||||||
let langOptions: HTMLDivElement;
|
let isOpen = $state(false);
|
||||||
|
|
||||||
// Faith subroute mappings
|
// Faith subroute mappings
|
||||||
const faithSubroutes: Record<string, Record<string, string>> = {
|
const faithSubroutes: Record<string, Record<string, string>> = {
|
||||||
en: { gebete: 'prayers', rosenkranz: 'rosary', angelus: 'angelus' },
|
en: { gebete: 'prayers', rosenkranz: 'rosary' },
|
||||||
de: { prayers: 'gebete', rosary: 'rosenkranz', angelus: 'angelus' }
|
de: { prayers: 'gebete', rosary: 'rosenkranz' }
|
||||||
};
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -34,30 +39,58 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function toggle_language_options(){
|
function toggle_language_options(){
|
||||||
if (langOptions) {
|
isOpen = !isOpen;
|
||||||
langOptions.hidden = !langOptions.hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertFaithPath(path: string, targetLang: 'de' | 'en'): string {
|
function convertFaithPath(path: string, targetLang: 'de' | 'en'): string {
|
||||||
// Extract the current base and subroute
|
|
||||||
const faithMatch = path.match(/^\/(glaube|faith)(\/(.+))?$/);
|
const faithMatch = path.match(/^\/(glaube|faith)(\/(.+))?$/);
|
||||||
if (!faithMatch) return path;
|
if (!faithMatch) return path;
|
||||||
|
|
||||||
const targetBase = targetLang === 'en' ? 'faith' : 'glaube';
|
const targetBase = targetLang === 'en' ? 'faith' : 'glaube';
|
||||||
const subroute = faithMatch[3]; // e.g., "gebete", "rosenkranz", "angelus"
|
const rest = faithMatch[3]; // e.g., "gebete", "rosenkranz/sub", "angelus"
|
||||||
|
|
||||||
if (!subroute) {
|
if (!rest) {
|
||||||
// Main faith page
|
|
||||||
return `/${targetBase}`;
|
return `/${targetBase}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert subroute
|
// Split on / to convert just the first segment (gebete→prayers, etc.)
|
||||||
const convertedSubroute = faithSubroutes[targetLang][subroute] || subroute;
|
const parts = rest.split('/');
|
||||||
return `/${targetBase}/${convertedSubroute}`;
|
parts[0] = faithSubroutes[targetLang][parts[0]] || parts[0];
|
||||||
|
return `/${targetBase}/${parts.join('/')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute target paths for each language (used as href for no-JS)
|
||||||
|
function computeTargetPath(targetLang: 'de' | 'en'): string {
|
||||||
|
const path = currentPath || $page.url.pathname;
|
||||||
|
|
||||||
|
if (path.startsWith('/glaube') || path.startsWith('/faith')) {
|
||||||
|
return convertFaithPath(path, targetLang);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use translated recipe slugs from page data when available (works during SSR)
|
||||||
|
const pageData = $page.data;
|
||||||
|
if (targetLang === 'en' && path.startsWith('/rezepte')) {
|
||||||
|
if (pageData?.englishShortName) {
|
||||||
|
return `/recipes/${pageData.englishShortName}`;
|
||||||
|
}
|
||||||
|
return path.replace('/rezepte', '/recipes');
|
||||||
|
}
|
||||||
|
if (targetLang === 'de' && path.startsWith('/recipes')) {
|
||||||
|
if (pageData?.germanShortName) {
|
||||||
|
return `/rezepte/${pageData.germanShortName}`;
|
||||||
|
}
|
||||||
|
return path.replace('/recipes', '/rezepte');
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dePath = $derived(computeTargetPath('de'));
|
||||||
|
const enPath = $derived(computeTargetPath('en'));
|
||||||
|
|
||||||
async function switchLanguage(lang: 'de' | 'en') {
|
async function switchLanguage(lang: 'de' | 'en') {
|
||||||
|
isOpen = false;
|
||||||
|
|
||||||
// Update the shared language store immediately
|
// Update the shared language store immediately
|
||||||
languageStore.set(lang);
|
languageStore.set(lang);
|
||||||
|
|
||||||
@@ -117,7 +150,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
const handleClick = (e: MouseEvent) => {
|
const handleClick = (e: MouseEvent) => {
|
||||||
if(langButton && !langButton.contains(e.target as Node)){
|
if(langButton && !langButton.contains(e.target as Node)){
|
||||||
if (langOptions) langOptions.hidden = true;
|
isOpen = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,8 +192,27 @@
|
|||||||
width: 10ch;
|
width: 10ch;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
.language-options button{
|
.language-options::after {
|
||||||
|
content: "";
|
||||||
|
border: 10px solid transparent;
|
||||||
|
border-bottom-color: var(--bg_color);
|
||||||
|
border-top: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
/* Show via JS toggle */
|
||||||
|
.language-options.open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* Show via CSS focus-within (no-JS fallback) */
|
||||||
|
.language-selector:focus-within .language-options {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.language-options a{
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -171,32 +223,38 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
text-decoration: none;
|
||||||
transition: background-color 100ms;
|
transition: background-color 100ms;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.language-options button:hover{
|
.language-options a:hover{
|
||||||
background-color: var(--nord2);
|
background-color: var(--nord2);
|
||||||
}
|
}
|
||||||
.language-options button.active{
|
.language-options a.active{
|
||||||
background-color: var(--nord14);
|
background-color: var(--nord8);
|
||||||
|
color: var(--nord0);
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="language-selector">
|
<div class="language-selector">
|
||||||
<button bind:this={langButton} onclick={toggle_language_options} class="language-button">
|
<button bind:this={langButton} onclick={toggle_language_options} class="language-button">
|
||||||
{$languageStore.toUpperCase()}
|
{displayLang.toUpperCase()}
|
||||||
</button>
|
</button>
|
||||||
<div bind:this={langOptions} class="language-options" hidden>
|
<div class="language-options" class:open={isOpen}>
|
||||||
<button
|
<a
|
||||||
class:active={$languageStore === 'de'}
|
href={dePath}
|
||||||
onclick={() => switchLanguage('de')}
|
class:active={displayLang === 'de'}
|
||||||
|
onclick={(e) => { e.preventDefault(); switchLanguage('de'); }}
|
||||||
>
|
>
|
||||||
DE
|
DE
|
||||||
</button>
|
</a>
|
||||||
<button
|
<a
|
||||||
class:active={$languageStore === 'en'}
|
href={enPath}
|
||||||
onclick={() => switchLanguage('en')}
|
class:active={displayLang === 'en'}
|
||||||
|
onclick={(e) => { e.preventDefault(); switchLanguage('en'); }}
|
||||||
>
|
>
|
||||||
EN
|
EN
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<style>
|
<style>
|
||||||
|
|
||||||
:global(.links_grid a:nth-child(4n)),
|
:global(.links_grid a:nth-child(4n)),
|
||||||
:global(.links_grid a:nth-child(4n) svg){
|
:global(.links_grid a:nth-child(4n) svg:not(.lock-icon)){
|
||||||
background-color: var(--nord4);
|
background-color: var(--nord4);
|
||||||
fill: var(--nord11);
|
fill: var(--nord11);
|
||||||
}
|
}
|
||||||
:global(.links_grid a:nth-child(4n+1)),
|
:global(.links_grid a:nth-child(4n+1)),
|
||||||
:global(.links_grid a:nth-child(4n+1) svg){
|
:global(.links_grid a:nth-child(4n+1) svg:not(.lock-icon)){
|
||||||
background-color: var(--nord6);
|
background-color: var(--nord6);
|
||||||
fill: var(--nord10);
|
fill: var(--nord10);
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
:global(a){
|
:global(a){
|
||||||
text-decoration: unset;
|
text-decoration: unset;
|
||||||
color: var(--nord0);
|
color: var(--nord0);
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
:global(.links_grid a:hover){
|
:global(.links_grid a:hover){
|
||||||
box-shadow: 1em 1em 2em 1em rgba(0,0,0, 0.3);
|
box-shadow: 1em 1em 2em 1em rgba(0,0,0, 0.3);
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
.links_grid{
|
.links_grid{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(min(250px, calc(50% - 1rem)), 1fr));
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-decoration: unset;
|
text-decoration: unset;
|
||||||
color: var(--nord0);
|
color: var(--nord0);
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -64,8 +64,50 @@
|
|||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
fill: var(--nord0);
|
fill: var(--nord3);
|
||||||
opacity: 0.6;
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.links_grid {
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
:global(.links_grid a :is(svg, img)) {
|
||||||
|
height: 90px;
|
||||||
|
}
|
||||||
|
:global(.links_grid h3) {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
:global(.links_grid a) {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
:global(.links_grid a .lock-icon) {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 410px) {
|
||||||
|
.links_grid {
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 1rem 0.5rem;
|
||||||
|
}
|
||||||
|
:global(.links_grid a :is(svg, img)) {
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
:global(.links_grid h3) {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
:global(.links_grid a) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
:global(.links_grid a .lock-icon) {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
top: 0.3rem;
|
||||||
|
right: 0.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark){
|
@media (prefers-color-scheme: dark){
|
||||||
@@ -73,26 +115,25 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
:global(.links_grid a .lock-icon){
|
:global(.links_grid a .lock-icon){
|
||||||
fill: white;
|
fill: var(--nord3);
|
||||||
}
|
}
|
||||||
:global(.links_grid a:nth-child(4n)),
|
:global(.links_grid a:nth-child(4n)),
|
||||||
:global(.links_grid a:nth-child(4n) svg){
|
:global(.links_grid a:nth-child(4n) svg:not(.lock-icon)){
|
||||||
background-color: var(--nord6-dark);
|
background-color: var(--nord6-dark);
|
||||||
fill: var(--nord11);
|
fill: var(--nord11);
|
||||||
}
|
}
|
||||||
:global(.links_grid a:nth-child(4n+1)),
|
:global(.links_grid a:nth-child(4n+1)),
|
||||||
:global(.links_grid a:nth-child(4n+1) svg){
|
:global(.links_grid a:nth-child(4n+1) svg:not(.lock-icon)){
|
||||||
background-color: var(--accent-dark);
|
background-color: var(--accent-dark);
|
||||||
fill: var(--nord9);
|
fill: var(--nord9);
|
||||||
}
|
}
|
||||||
:global(.links_grid a:nth-child(4n+2)),
|
:global(.links_grid a:nth-child(4n+2)),
|
||||||
:global(.links_grid a:nth-child(4n+2) svg){
|
:global(.links_grid a:nth-child(4n+2) svg:not(.lock-icon)){
|
||||||
background-color: var(--nord1);
|
background-color: var(--nord1);
|
||||||
fill: var(--nord8);
|
fill: var(--nord8);
|
||||||
|
|
||||||
}
|
}
|
||||||
:global(.links_grid a:nth-child(4n+3)),
|
:global(.links_grid a:nth-child(4n+3)),
|
||||||
:global(.links_grid a:nth-child(4n+3) svg){
|
:global(.links_grid a:nth-child(4n+3) svg:not(.lock-icon)){
|
||||||
background-color: var(--background-dark);
|
background-color: var(--background-dark);
|
||||||
fill: var(--nord7);
|
fill: var(--nord7);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
let {
|
|
||||||
germanUrl,
|
|
||||||
englishUrl,
|
|
||||||
currentLang = 'de',
|
|
||||||
hasTranslation = true
|
|
||||||
}: {
|
|
||||||
germanUrl: string,
|
|
||||||
englishUrl: string,
|
|
||||||
currentLang?: 'de' | 'en',
|
|
||||||
hasTranslation?: boolean
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
function setLanguagePreference(lang: 'de' | 'en') {
|
|
||||||
if (typeof localStorage !== 'undefined') {
|
|
||||||
localStorage.setItem('preferredLanguage', lang);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.language-switcher {
|
|
||||||
position: fixed;
|
|
||||||
top: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
z-index: 1000;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
background: var(--nord0);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.language-switcher {
|
|
||||||
background: var(--nord6);
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-switcher a {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--nord4);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: all 0.2s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.language-switcher a {
|
|
||||||
color: var(--nord2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-switcher a:hover {
|
|
||||||
background: var(--nord3);
|
|
||||||
color: var(--nord6);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
|
||||||
.language-switcher a:hover {
|
|
||||||
background: var(--nord4);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-switcher a.active {
|
|
||||||
background: var(--nord14);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-switcher a.active:hover {
|
|
||||||
background: var(--nord15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-switcher a.disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flag {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.language-switcher {
|
|
||||||
top: 0.5rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
padding: 0.25rem;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-switcher a {
|
|
||||||
padding: 0.4rem 0.7rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flag {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="language-switcher">
|
|
||||||
<a
|
|
||||||
href={germanUrl}
|
|
||||||
class:active={currentLang === 'de'}
|
|
||||||
aria-label="Switch to German"
|
|
||||||
onclick={() => setLanguagePreference('de')}
|
|
||||||
>
|
|
||||||
<span class="flag">🇩🇪</span>
|
|
||||||
<span class="label">DE</span>
|
|
||||||
</a>
|
|
||||||
{#if hasTranslation}
|
|
||||||
<a
|
|
||||||
href={englishUrl}
|
|
||||||
class:active={currentLang === 'en'}
|
|
||||||
aria-label="Switch to English"
|
|
||||||
onclick={() => setLanguagePreference('en')}
|
|
||||||
>
|
|
||||||
<span class="flag">🇬🇧</span>
|
|
||||||
<span class="label">EN</span>
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
<span
|
|
||||||
class="disabled"
|
|
||||||
title="English translation not available"
|
|
||||||
aria-label="English translation not available"
|
|
||||||
>
|
|
||||||
<span class="flag">🇬🇧</span>
|
|
||||||
<span class="label">EN</span>
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
value = $bindable(''),
|
value = $bindable(''),
|
||||||
@@ -17,11 +16,10 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.7rem 2rem;
|
padding: 0.7rem 2rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
input::placeholder {
|
input::placeholder {
|
||||||
@@ -36,7 +34,7 @@ input::placeholder {
|
|||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
filter: drop-shadow(0.4em 0.5em 0.4em rgba(0,0,0,0.4));
|
filter: drop-shadow(0.4em 0.5em 0.4em rgba(0,0,0,0.4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
:root{
|
:root{
|
||||||
--icon_fill: var(--nord4);
|
--icon_fill: var(--nord4);
|
||||||
}
|
}
|
||||||
svg{
|
svg{
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
height: 3em;
|
height: var(--symbol-size, 3em);
|
||||||
}
|
}
|
||||||
svg:hover,
|
svg:hover,
|
||||||
svg:focus-visible
|
svg:focus-visible
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { tag, ref } = $props<{ tag: string, ref: string }>();
|
let { tag, ref } = $props<{ tag: string, ref: string }>();
|
||||||
import '$lib/css/nordtheme.css'
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
a{
|
a{
|
||||||
background-color: var(--blue);
|
background-color: var(--blue);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 2rem;
|
padding: clamp(0.4rem, 0.8vw, 0.8rem) clamp(0.8rem, 1.5vw, 1.5rem);
|
||||||
border-radius: 1000000px;
|
border-radius: 1000000px;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
font-size: 2rem;
|
font-size: clamp(0.85rem, 1.8vw, 1.5rem);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
a:hover{
|
a:hover{
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ div{
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-inline:auto;
|
margin-inline:auto;
|
||||||
gap: 1rem;
|
gap: clamp(0.4rem, 1vw, 1rem);
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { checked = $bindable(false), label = "", accentColor = "var(--nord14)" } = $props<{ checked?: boolean, label?: string, accentColor?: string }>();
|
let { checked = $bindable(false), label = "", accentColor = "var(--nord14)", href = undefined as string | undefined } = $props<{ checked?: boolean, label?: string, accentColor?: string, href?: string }>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -7,17 +7,20 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-wrapper label {
|
.toggle-wrapper label,
|
||||||
|
.toggle-wrapper a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
@media(prefers-color-scheme: light) {
|
||||||
.toggle-wrapper label {
|
.toggle-wrapper label,
|
||||||
|
.toggle-wrapper a {
|
||||||
color: var(--nord2);
|
color: var(--nord2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,10 +29,12 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iOS-style toggle switch */
|
/* iOS-style toggle switch — shared by checkbox and link variants */
|
||||||
|
.toggle-track,
|
||||||
.toggle-wrapper input[type="checkbox"] {
|
.toggle-wrapper input[type="checkbox"] {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
width: 44px;
|
width: 44px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background: var(--nord2);
|
background: var(--nord2);
|
||||||
@@ -40,18 +45,22 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
@media(prefers-color-scheme: light) {
|
||||||
|
.toggle-track,
|
||||||
.toggle-wrapper input[type="checkbox"] {
|
.toggle-wrapper input[type="checkbox"] {
|
||||||
background: var(--nord4);
|
background: var(--nord4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-track.checked,
|
||||||
.toggle-wrapper input[type="checkbox"]:checked {
|
.toggle-wrapper input[type="checkbox"]:checked {
|
||||||
background: var(--accent-color);
|
background: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-track::before,
|
||||||
.toggle-wrapper input[type="checkbox"]::before {
|
.toggle-wrapper input[type="checkbox"]::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -65,14 +74,22 @@
|
|||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-track.checked::before,
|
||||||
.toggle-wrapper input[type="checkbox"]:checked::before {
|
.toggle-wrapper input[type="checkbox"]:checked::before {
|
||||||
transform: translateX(20px);
|
transform: translateX(20px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="toggle-wrapper" style="--accent-color: {accentColor}">
|
<div class="toggle-wrapper" style="--accent-color: {accentColor}">
|
||||||
<label>
|
{#if href}
|
||||||
<input type="checkbox" bind:checked />
|
<a {href} onclick={(e) => { e.preventDefault(); checked = !checked; }}>
|
||||||
<span>{label}</span>
|
<span class="toggle-track" class:checked></span>
|
||||||
</label>
|
<span>{label}</span>
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked />
|
||||||
|
<span>{label}</span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
background-color: var(--bg_color);
|
background-color: var(--bg_color);
|
||||||
width: 30ch;
|
width: 30ch;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
#options ul{
|
#options ul{
|
||||||
color: white;
|
color: white;
|
||||||
@@ -97,7 +98,7 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: white;
|
color: white;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
#options li:hover a{
|
#options li:hover a{
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
@@ -116,22 +117,31 @@ h2 + p{
|
|||||||
#options{
|
#options{
|
||||||
top: unset;
|
top: unset;
|
||||||
bottom: calc(100% + 15px);
|
bottom: calc(100% + 15px);
|
||||||
right: -200%;
|
left: 50%;
|
||||||
z-index: 99999999999999999999;
|
right: unset;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.top.speech::after {
|
.top.speech::after {
|
||||||
/* (B2-1) DOWN TRIANGLE */
|
border: 20px solid transparent;
|
||||||
border-top-color: #a53d38;
|
border-top-color: var(--bg_color);
|
||||||
border-bottom: 0;
|
border-bottom-width: 0;
|
||||||
z-index: 99999999999999999999;
|
top: unset;
|
||||||
|
bottom: -20px;
|
||||||
/* (B2-2) POSITION AT BOTTOM */
|
left: 50%;
|
||||||
bottom: -20px; left: 50%;
|
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
}
|
}
|
||||||
button{
|
button{
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
button::before{
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: inherit;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { Chart, registerables } from 'chart.js';
|
import { Chart, registerables } from 'chart.js';
|
||||||
|
|
||||||
let { data = { labels: [], datasets: [] }, title = '', height = '400px' } = $props<{ data?: any, title?: string, height?: string }>();
|
let { data = { labels: [], datasets: [] }, title = '', height = '400px', onFilterChange = null } = $props<{ data?: any, title?: string, height?: string, onFilterChange?: ((categories: string[] | null) => void) | null }>();
|
||||||
|
|
||||||
let canvas = $state();
|
let canvas = $state();
|
||||||
let chart = $state();
|
let chart = $state();
|
||||||
@@ -42,6 +42,19 @@
|
|||||||
return categoryColorMap[category] || nordColors[index % nordColors.length];
|
return categoryColorMap[category] || nordColors[index % nordColors.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emitFilter() {
|
||||||
|
if (!onFilterChange || !chart) return;
|
||||||
|
const allVisible = chart.data.datasets.every((_, idx) => !chart.getDatasetMeta(idx).hidden);
|
||||||
|
if (allVisible) {
|
||||||
|
onFilterChange(null);
|
||||||
|
} else {
|
||||||
|
const visible = chart.data.datasets
|
||||||
|
.filter((_, idx) => !chart.getDatasetMeta(idx).hidden)
|
||||||
|
.map(ds => ds.label.toLowerCase());
|
||||||
|
onFilterChange(visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createChart() {
|
function createChart() {
|
||||||
if (!canvas || !data.datasets) return;
|
if (!canvas || !data.datasets) return;
|
||||||
|
|
||||||
@@ -135,7 +148,6 @@
|
|||||||
},
|
},
|
||||||
onClick: (event, legendItem, legend) => {
|
onClick: (event, legendItem, legend) => {
|
||||||
const datasetIndex = legendItem.datasetIndex;
|
const datasetIndex = legendItem.datasetIndex;
|
||||||
const clickedMeta = chart.getDatasetMeta(datasetIndex);
|
|
||||||
|
|
||||||
// Check if only this dataset is currently visible
|
// Check if only this dataset is currently visible
|
||||||
const onlyThisVisible = chart.data.datasets.every((dataset, idx) => {
|
const onlyThisVisible = chart.data.datasets.every((dataset, idx) => {
|
||||||
@@ -156,6 +168,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
chart.update();
|
chart.update();
|
||||||
|
emitFilter();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
@@ -229,6 +242,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
chart.update();
|
chart.update();
|
||||||
|
emitFilter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import ProfilePicture from './ProfilePicture.svelte';
|
import ProfilePicture from './ProfilePicture.svelte';
|
||||||
import EditButton from './EditButton.svelte';
|
import EditButton from '$lib/components/EditButton.svelte';
|
||||||
import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories';
|
import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories';
|
||||||
import { formatCurrency as formatCurrencyUtil } from '$lib/utils/formatters';
|
import { formatCurrency as formatCurrencyUtil } from '$lib/utils/formatters';
|
||||||
|
|
||||||
@@ -58,7 +58,6 @@
|
|||||||
splitAmounts[user] = splitAmount;
|
splitAmounts[user] = splitAmount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
splitAmounts = { ...splitAmounts };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateFullPayment() {
|
function calculateFullPayment() {
|
||||||
@@ -75,7 +74,6 @@
|
|||||||
splitAmounts[user] = amountPerOtherUser;
|
splitAmounts[user] = amountPerOtherUser;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
splitAmounts = { ...splitAmounts };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculatePersonalEqualSplit() {
|
function calculatePersonalEqualSplit() {
|
||||||
@@ -100,7 +98,6 @@
|
|||||||
splitAmounts[user] = totalOwed;
|
splitAmounts[user] = totalOwed;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
splitAmounts = { ...splitAmounts };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSplitMethodChange() {
|
function handleSplitMethodChange() {
|
||||||
@@ -116,7 +113,6 @@
|
|||||||
splitAmounts[user] = 0;
|
splitAmounts[user] = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
splitAmounts = { ...splitAmounts };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,19 +5,23 @@
|
|||||||
reference = '',
|
reference = '',
|
||||||
title = '',
|
title = '',
|
||||||
verseData = null,
|
verseData = null,
|
||||||
|
lang = 'de',
|
||||||
onClose
|
onClose
|
||||||
}: {
|
}: {
|
||||||
reference?: string,
|
reference?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
verseData?: VerseData | null,
|
verseData?: VerseData | null,
|
||||||
|
lang?: string,
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
|
const isEnglish = $derived(lang === 'en');
|
||||||
|
|
||||||
let book: string = $state(verseData?.book || '');
|
let book: string = $state(verseData?.book || '');
|
||||||
let chapter: number = $state(verseData?.chapter || 0);
|
let chapter: number = $state(verseData?.chapter || 0);
|
||||||
let verses: Array<{ verse: number; text: string }> = $state(verseData?.verses || []);
|
let verses: Array<{ verse: number; text: string }> = $state(verseData?.verses || []);
|
||||||
let loading = $state(false);
|
let loading = $state(false);
|
||||||
let error = $state(verseData ? '' : 'Keine Versdaten verfügbar');
|
let error = $state(verseData ? '' : (lang === 'en' ? 'No verse data available' : 'Keine Versdaten verfügbar'));
|
||||||
|
|
||||||
function handleBackdropClick(event: MouseEvent) {
|
function handleBackdropClick(event: MouseEvent) {
|
||||||
if (event.target === event.currentTarget) {
|
if (event.target === event.currentTarget) {
|
||||||
@@ -49,7 +53,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<p class="modal-reference">{reference}</p>
|
<p class="modal-reference">{reference}</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="close-button" onclick={onClose} aria-label="Schließen">
|
<button class="close-button" onclick={onClose} aria-label={isEnglish ? 'Close' : 'Schließen'}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
@@ -59,7 +63,7 @@
|
|||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<p class="loading">Lädt...</p>
|
<p class="loading">{isEnglish ? 'Loading...' : 'Lädt...'}</p>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<p class="error">{error}</p>
|
<p class="error">{error}</p>
|
||||||
{:else if verses.length > 0}
|
{:else if verses.length > 0}
|
||||||
@@ -72,7 +76,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="error">Keine Verse gefunden</p>
|
<p class="error">{isEnglish ? 'No verses found' : 'Keine Verse gefunden'}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,9 +188,9 @@
|
|||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
color: white;
|
color: white;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
box-shadow: 0 0 1em 0.2em rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 1em 0.2em rgba(0, 0, 0, 0.3);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -244,6 +248,7 @@
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(prefers-color-scheme: light) {
|
@media(prefers-color-scheme: light) {
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
holy?: boolean;
|
holy?: boolean;
|
||||||
burst?: boolean;
|
burst?: boolean;
|
||||||
|
fire ?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { holy = false, burst = false }: Props = $props();
|
let { holy = false, burst = false, fire = false}: Props = $props();
|
||||||
|
|
||||||
const burstParticles = [
|
const burstParticles = [
|
||||||
{ x: 10, y: 0, size: 8, delay: 0, dur: 1.6 },
|
{ x: 10, y: 0, size: 8, delay: 0, dur: 1.6 },
|
||||||
@@ -52,22 +53,22 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="fire" class:holy-fire={holy}>
|
<div class="fire" class:holy-fire={holy}>
|
||||||
<div class="fire-left">
|
<div class="fire-left">
|
||||||
<div class="main-fire"></div>
|
{#if fire}<div class="main-fire"></div>{/if}
|
||||||
<div class="particle-fire"></div>
|
<div class="particle-fire"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fire-center">
|
<div class="fire-center">
|
||||||
<div class="main-fire"></div>
|
{#if fire}<div class="main-fire"></div>{/if}
|
||||||
<div class="particle-fire"></div>
|
<div class="particle-fire"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fire-right">
|
<div class="fire-right">
|
||||||
<div class="main-fire"></div>
|
{#if fire}<div class="main-fire"></div>{/if}
|
||||||
<div class="particle-fire"></div>
|
<div class="particle-fire"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fire-bottom">
|
<div class="fire-bottom">
|
||||||
<div class="main-fire"></div>
|
{#if fire}<div class="main-fire"></div>{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { getLanguageContext } from '$lib/contexts/languageContext.js';
|
import { getLanguageContext } from '$lib/contexts/languageContext.js';
|
||||||
import Toggle from './Toggle.svelte';
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
|
|
||||||
|
export let initialLatin = undefined;
|
||||||
|
export let hasUrlLatin = false;
|
||||||
|
export let href = undefined;
|
||||||
|
|
||||||
// Get the language context (must be created by parent page)
|
// Get the language context (must be created by parent page)
|
||||||
const { showLatin, lang } = getLanguageContext();
|
const { showLatin, lang } = getLanguageContext();
|
||||||
|
|
||||||
// Local state for the checkbox
|
// Local state for the checkbox
|
||||||
let showBilingual = true;
|
let showBilingual = initialLatin !== undefined ? initialLatin : true;
|
||||||
|
|
||||||
// Flag to prevent saving before we've loaded from localStorage
|
// Flag to prevent saving before we've loaded from localStorage
|
||||||
let hasLoadedFromStorage = false;
|
let hasLoadedFromStorage = false;
|
||||||
@@ -26,10 +30,12 @@
|
|||||||
: 'Lateinisch und Deutsch anzeigen';
|
: 'Lateinisch und Deutsch anzeigen';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Load from localStorage
|
// Only load from localStorage if no URL param was set
|
||||||
const saved = localStorage.getItem('rosary_showBilingual');
|
if (!hasUrlLatin) {
|
||||||
if (saved !== null) {
|
const saved = localStorage.getItem('rosary_showBilingual');
|
||||||
showBilingual = saved === 'true';
|
if (saved !== null) {
|
||||||
|
showBilingual = saved === 'true';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now allow saving
|
// Now allow saving
|
||||||
@@ -40,5 +46,6 @@
|
|||||||
<Toggle
|
<Toggle
|
||||||
bind:checked={showBilingual}
|
bind:checked={showBilingual}
|
||||||
{label}
|
{label}
|
||||||
|
{href}
|
||||||
accentColor="var(--nord14)"
|
accentColor="var(--nord14)"
|
||||||
/>
|
/>
|
||||||
119
src/lib/components/faith/PipImage.svelte
Normal file
119
src/lib/components/faith/PipImage.svelte
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* @param {ReturnType<import('$lib/js/pip.svelte').createPip>} pip - a createPip() instance
|
||||||
|
* @param {string} src - image source
|
||||||
|
* @param {string} [alt] - image alt text
|
||||||
|
* @param {boolean} [visible] - whether the PiP should be shown
|
||||||
|
* @param {(e: Event) => void} [onload] - callback when image loads
|
||||||
|
*/
|
||||||
|
let { pip, src, alt = '', visible = false, onload, el = $bindable(null) } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
class="pip-container"
|
||||||
|
class:visible
|
||||||
|
class:enlarged={pip.enlarged}
|
||||||
|
class:fullscreen={pip.fullscreen}
|
||||||
|
bind:this={el}
|
||||||
|
onpointerdown={pip.onpointerdown}
|
||||||
|
onpointermove={pip.onpointermove}
|
||||||
|
onpointerup={pip.onpointerup}
|
||||||
|
>
|
||||||
|
{#if src}
|
||||||
|
<img {src} {alt} {onload}>
|
||||||
|
{/if}
|
||||||
|
{#if pip.showControls}
|
||||||
|
<button
|
||||||
|
class="pip-fullscreen-btn"
|
||||||
|
aria-label="Fullscreen"
|
||||||
|
onpointerdown={(e) => e.stopPropagation()}
|
||||||
|
onclick={(e) => { e.stopPropagation(); pip.toggleFullscreen(); }}
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="8 3 3 3 3 8"/>
|
||||||
|
<polyline points="16 3 21 3 21 8"/>
|
||||||
|
<polyline points="8 21 3 21 3 16"/>
|
||||||
|
<polyline points="16 21 21 21 21 16"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pip-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
opacity: 0;
|
||||||
|
touch-action: none;
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.pip-container:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
.pip-container img {
|
||||||
|
height: 25vh;
|
||||||
|
width: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: height 0.25s ease;
|
||||||
|
}
|
||||||
|
.pip-container.enlarged img {
|
||||||
|
height: 37.5vh;
|
||||||
|
}
|
||||||
|
.pip-container.fullscreen {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.95);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.pip-container.fullscreen img {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.pip-fullscreen-btn {
|
||||||
|
all: unset;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: transparent;
|
||||||
|
filter: drop-shadow(0 0 1px black);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
outline: none;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
.pip-fullscreen-btn:hover,
|
||||||
|
.pip-fullscreen-btn:active {
|
||||||
|
transform: translate(-50%, -50%) scale(1.2);
|
||||||
|
}
|
||||||
|
.pip-container.fullscreen .pip-fullscreen-btn {
|
||||||
|
top: auto;
|
||||||
|
left: auto;
|
||||||
|
bottom: 10vw;
|
||||||
|
right: 10vw;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
.pip-container.fullscreen .pip-fullscreen-btn:hover,
|
||||||
|
.pip-container.fullscreen .pip-fullscreen-btn:active {
|
||||||
|
transform: scale(0.85);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
173
src/lib/components/faith/StickyImage.svelte
Normal file
173
src/lib/components/faith/StickyImage.svelte
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { createPip } from '$lib/js/pip.svelte';
|
||||||
|
import PipImage from '$lib/components/faith/PipImage.svelte';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {'layout' | 'overlay'} mode
|
||||||
|
* - 'layout': flex row on desktop (image sticky right, content left). Use as page-level wrapper.
|
||||||
|
* - 'overlay': image floats over the page (fixed position, IntersectionObserver show/hide). Use when nested inside existing layouts.
|
||||||
|
*/
|
||||||
|
let { src, alt = '', mode = 'layout', children } = $props();
|
||||||
|
|
||||||
|
let pipEl = $state(null);
|
||||||
|
let contentEl = $state(null);
|
||||||
|
let inView = $state(false);
|
||||||
|
|
||||||
|
const pip = createPip({ fullscreenEnabled: true });
|
||||||
|
|
||||||
|
function isMobile() {
|
||||||
|
return !window.matchMedia('(min-width: 1024px)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PiP drag behavior only on mobile for both modes
|
||||||
|
function isPipActive() {
|
||||||
|
return isMobile();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVisibility() {
|
||||||
|
if (!pipEl) return;
|
||||||
|
if (isPipActive()) {
|
||||||
|
// Mobile PiP mode
|
||||||
|
if (inView) {
|
||||||
|
pip.show(pipEl);
|
||||||
|
} else {
|
||||||
|
pip.hide();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Desktop (both modes): CSS handles everything
|
||||||
|
pipEl.style.opacity = '';
|
||||||
|
pipEl.style.transform = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
inView;
|
||||||
|
updateVisibility();
|
||||||
|
});
|
||||||
|
|
||||||
|
function onResize() {
|
||||||
|
if (!pipEl) return;
|
||||||
|
if (isPipActive() && inView) {
|
||||||
|
pip.reposition();
|
||||||
|
} else {
|
||||||
|
updateVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
updateVisibility();
|
||||||
|
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
|
||||||
|
let observer;
|
||||||
|
if (contentEl) {
|
||||||
|
observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
inView = entry.isIntersecting;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0 }
|
||||||
|
);
|
||||||
|
observer.observe(contentEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', onResize);
|
||||||
|
observer?.disconnect();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="sticky-image-layout" class:overlay={mode === 'overlay'}>
|
||||||
|
<div class="image-wrap-desktop">
|
||||||
|
<img {src} {alt}>
|
||||||
|
</div>
|
||||||
|
<PipImage {pip} {src} {alt} visible={inView} bind:el={pipEl} />
|
||||||
|
<div class="content-scroll" bind:this={contentEl}>
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sticky-image-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: auto;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
.sticky-image-layout.overlay {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.image-wrap-desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.content-scroll {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
.overlay .content-scroll {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.sticky-image-layout.overlay {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: 2rem;
|
||||||
|
width: calc(100% + 25vw + 2rem);
|
||||||
|
}
|
||||||
|
.image-wrap-desktop {
|
||||||
|
display: block;
|
||||||
|
position: sticky;
|
||||||
|
top: 4rem;
|
||||||
|
align-self: start;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
.overlay .image-wrap-desktop img {
|
||||||
|
height: auto;
|
||||||
|
max-height: calc(100vh - 5rem);
|
||||||
|
width: auto;
|
||||||
|
max-width: 25vw;
|
||||||
|
}
|
||||||
|
.sticky-image-layout:not(.overlay) {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 2em;
|
||||||
|
}
|
||||||
|
.sticky-image-layout:not(.overlay) .content-scroll {
|
||||||
|
flex: 0 1 700px;
|
||||||
|
}
|
||||||
|
.sticky-image-layout:not(.overlay) .image-wrap-desktop {
|
||||||
|
display: block;
|
||||||
|
position: sticky;
|
||||||
|
top: 4rem;
|
||||||
|
flex: 1;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
.sticky-image-layout:not(.overlay) .image-wrap-desktop img {
|
||||||
|
max-height: calc(100vh - 4rem);
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
.sticky-image-layout:not(.overlay) .image-wrap-desktop {
|
||||||
|
background-color: var(--nord5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) and (min-width: 1024px) {
|
||||||
|
.sticky-image-layout:not(.overlay) .image-wrap-desktop {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1400px) {
|
||||||
|
.sticky-image-layout:not(.overlay)::before {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -54,11 +54,11 @@
|
|||||||
|
|
||||||
|
|
||||||
{#if phase >= 2}
|
{#if phase >= 2}
|
||||||
<FireEffect holy={phase>=4} />
|
<FireEffect holy={phase>=4} fire={phase>=3}/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showBurst}
|
{#if showBurst}
|
||||||
<FireEffect holy={phase>=4} burst />
|
<FireEffect holy={phase>=4} burst fire={phase>=3}/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span class="number">{value}</span>
|
<span class="number">{value}</span>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { getRosaryStreak } from '$lib/stores/rosaryStreak.svelte';
|
import { getRosaryStreak } from '$lib/stores/rosaryStreak.svelte';
|
||||||
import StreakAura from '$lib/components/StreakAura.svelte';
|
import StreakAura from '$lib/components/faith/StreakAura.svelte';
|
||||||
import { tick, onMount } from 'svelte';
|
import { tick, onMount } from 'svelte';
|
||||||
|
|
||||||
let burst = $state(false);
|
let burst = $state(false);
|
||||||
@@ -10,9 +10,10 @@ let streak = $state<ReturnType<typeof getRosaryStreak> | null>(null);
|
|||||||
interface Props {
|
interface Props {
|
||||||
streakData?: { length: number; lastPrayed: string | null } | null;
|
streakData?: { length: number; lastPrayed: string | null } | null;
|
||||||
lang?: 'de' | 'en';
|
lang?: 'de' | 'en';
|
||||||
|
isLoggedIn?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { streakData = null, lang = 'de' }: Props = $props();
|
let { streakData = null, lang = 'de', isLoggedIn = false }: Props = $props();
|
||||||
|
|
||||||
const isEnglish = $derived(lang === 'en');
|
const isEnglish = $derived(lang === 'en');
|
||||||
|
|
||||||
@@ -29,9 +30,12 @@ const labels = $derived({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Initialize store on mount (client-side only)
|
// Initialize store on mount (client-side only)
|
||||||
|
// Init with server data BEFORE assigning to streak, so displayLength
|
||||||
|
// never sees stale localStorage data from the singleton
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
streak = getRosaryStreak();
|
const s = getRosaryStreak();
|
||||||
streak.initWithServerData(streakData, streakData !== null);
|
s.initWithServerData(streakData, isLoggedIn);
|
||||||
|
streak = s;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function pray() {
|
async function pray() {
|
||||||
@@ -42,23 +46,25 @@ async function pray() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="streak-container">
|
<div class="streak-container" class:no-js-hidden={!isLoggedIn}>
|
||||||
<div class="streak-display">
|
<div class="streak-display">
|
||||||
<StreakAura value={displayLength} {burst} />
|
<StreakAura value={displayLength} {burst} />
|
||||||
<span class="streak-label">{labels.days}</span>
|
<span class="streak-label">{labels.days}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<form method="POST" action="?/pray" onsubmit={(e) => { e.preventDefault(); pray(); }}>
|
||||||
class="streak-button"
|
<button
|
||||||
onclick={pray}
|
class="streak-button"
|
||||||
disabled={prayedToday}
|
type="submit"
|
||||||
aria-label={labels.ariaLabel}
|
disabled={prayedToday}
|
||||||
>
|
aria-label={labels.ariaLabel}
|
||||||
{#if prayedToday}
|
>
|
||||||
{labels.prayedToday}
|
{#if prayedToday}
|
||||||
{:else}
|
{labels.prayedToday}
|
||||||
{labels.prayed}
|
{:else}
|
||||||
{/if}
|
{labels.prayed}
|
||||||
</button>
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -119,6 +125,15 @@ async function pray() {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide for non-logged-in users without JS (no form action available) */
|
||||||
|
.no-js-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.js-enabled) .no-js-hidden {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.streak-button:disabled {
|
.streak-button:disabled {
|
||||||
background: var(--nord4);
|
background: var(--nord4);
|
||||||
64
src/lib/components/faith/prayers/AblassGebete.svelte
Normal file
64
src/lib/components/faith/prayers/AblassGebete.svelte
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
import Paternoster from './Paternoster.svelte';
|
||||||
|
import AveMaria from './AveMaria.svelte';
|
||||||
|
import GloriaPatri from './GloriaPatri.svelte';
|
||||||
|
|
||||||
|
export let verbose = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<div class="monolingual">
|
||||||
|
<p>
|
||||||
|
<v lang=de>
|
||||||
|
Seele Christi, heilige mich.
|
||||||
|
<i>✻</i>
|
||||||
|
Leib Christi erlöse mich.
|
||||||
|
<i>✻</i>
|
||||||
|
Blut Christi, tränke mich.
|
||||||
|
<i>✻</i>
|
||||||
|
Wasser der Seite Christi, wasche mich.
|
||||||
|
<i>✻</i>
|
||||||
|
Leiden Christi, stärke mich.
|
||||||
|
<i>✻</i>
|
||||||
|
O gütiger Jesus, erhöre mich.
|
||||||
|
<i>✻</i>
|
||||||
|
Verbirg in Deine Wunden mich.
|
||||||
|
<i>✻</i>
|
||||||
|
Von Dir lass nimmer scheiden mich.
|
||||||
|
<i>✻</i>
|
||||||
|
In meiner Todesstunde rufe mich,
|
||||||
|
<i>✻</i>
|
||||||
|
Und heisse zur Dir kommen mich,
|
||||||
|
<i>✻</i>
|
||||||
|
Damit ich möge loben Dich
|
||||||
|
<i>✻</i>
|
||||||
|
Mit Deinen Heiligen ewiglich. Amen.
|
||||||
|
</v>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h3> Vollkommener Ablass</h3>
|
||||||
|
<h4> Paternoster </h4>
|
||||||
|
{#if verbose }
|
||||||
|
<Paternoster />
|
||||||
|
{/if}
|
||||||
|
<h4> Ave Maria </h4>
|
||||||
|
{#if verbose }
|
||||||
|
<AveMaria />
|
||||||
|
{/if}
|
||||||
|
<h4> Gloria Patri </h4>
|
||||||
|
{#if verbose }
|
||||||
|
<GloriaPatri />
|
||||||
|
{/if}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}
|
||||||
|
<v lang=la >En ego, o bone et dulcíssime Jesu, ante contspéctum tuum génibusme provólvo ac máximo ánimi ardóre te oro atque obtéstor, ut meum in in cor vívidos fídei, spei et caritátis sensus, atque veram peccatórum meórum pœniténtiam, éaque emendándi firmíssimam voluntátem velis imprímere; dum magno ánimi afféctu et dolóre tua quinque vúlnera mecum ipse consídero ac mente contémplor, illud præ óculis habens, quod jam in ore ponébat tuo David Prophéta de te, o bone Jesu: Fodérunt manus meas et pedes meos; dinumeravérunt ómnio ossa mea (Ps. 21, 17-18)</v>
|
||||||
|
{/if}
|
||||||
|
<v lang=de>
|
||||||
|
Siehe, o gütiger und milder Jesus, ich werfe mich vor Deinen Augen auf die Knie. Inbrünstig bitte und beschwöre ich Dich: Präge meinem Herzen lebendige Gefühle des Glaubens, der Hoffung und der Liebe ein sowie wahre Reue über meine Sünden und den ganz festen Willen, mich zu bessern. Voll Liebe und Schmerz schaue ich Deine fünf Wunden und betrachte sie in meinem Geiste. Dabei halte ich mir vor Augen, was im Hinblick auf Dich, o guter Jesus, schon der Prophet David Dir in den Mund legte: «Sie haben Meine Hände und Meine Füsse durchbohrt; alle meine Gebeine haben sie gezählt.» (Ps. 21, 17-18)
|
||||||
|
</v>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
108
src/lib/components/faith/prayers/Angelus.svelte
Normal file
108
src/lib/components/faith/prayers/Angelus.svelte
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
import AveMaria from './AveMaria.svelte';
|
||||||
|
|
||||||
|
let { verbose = false } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la"><i>℣.</i> Ángelus Dómini nuntiávit Maríæ.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℣.</i> Der Engel des Herrn brachte Maria die Botschaft</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℣.</i> The Angel of the Lord declared unto Mary.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la"><i>℟.</i> Et concépit de Spíritu Sancto.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℟.</i> und sie empfing vom Heiligen Geist.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℟.</i> And she conceived of the Holy Spirit.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
|
||||||
|
{#if verbose}
|
||||||
|
<AveMaria />
|
||||||
|
{:else}
|
||||||
|
<p class="ave-indicator"><i>— Ave Maria —</i></p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la"><i>℣.</i> Ecce ancílla Dómini,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℣.</i> Maria sprach: Siehe, ich bin die Magd des Herrn</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℣.</i> Behold the handmaid of the Lord.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la"><i>℟.</i> Fiat mihi secúndum verbum tuum.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℟.</i> mir geschehe nach Deinem Wort.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℟.</i> Be it done unto me according to thy word.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
|
||||||
|
{#if verbose}
|
||||||
|
<AveMaria />
|
||||||
|
{:else}
|
||||||
|
<p class="ave-indicator"><i>— Ave Maria —</i></p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la"><i>℣.</i> Et Verbum caro factum est,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℣.</i> Und das Wort ist Fleisch geworden</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℣.</i> And the Word was made flesh.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la"><i>℟.</i> Et habitávit in nobis.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℟.</i> und hat unter uns gewohnt.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℟.</i> And dwelt among us.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
|
||||||
|
{#if verbose}
|
||||||
|
<AveMaria />
|
||||||
|
{:else}
|
||||||
|
<p class="ave-indicator"><i>— Ave Maria —</i></p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la"><i>℣.</i> Ora pro nobis, sancta Dei Génetrix,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℣.</i> Bitte für uns, heilige Gottesmutter,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℣.</i> Pray for us, O holy Mother of God.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la"><i>℟.</i> Ut digni efficiámur promissiónibus Christi.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℟.</i> auf dass wir würdig werden der Verheissungen Christi.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℟.</i> That we may be made worthy of the promises of Christ.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la"><i>℣.</i> Orémus.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℣.</i> Lasset uns beten.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℣.</i> Let us pray:</v>{/if}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Grátiam tuam, quǽsumus, Dómine, méntibus nostris infúnde;</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Allmächtiger Gott, giesse deine Gnade in unsere Herzen ein.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Pour forth, we beseech Thee, O Lord, Thy grace into our hearts,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">ut qui, Ángelo nuntiánte, Christi Fílii tui incarnatiónem cognóvimus,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Durch die Botschaft des Engels haben wir die Menschwerdung Christi, deines Sohnes, erkannt.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">that we to whom the Incarnation of Christ Thy Son was made known by the message of an angel,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">per passiónem eius et crucem ad resurrectiónis glóriam perducámur.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Lass uns durch sein Leiden und Kreuz zur Herrlichkeit der Auferstehung gelangen.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">may by His Passion and Cross be brought to the glory of His Resurrection.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">Per eúmdem Christum Dóminum nostrum. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Darum bitten wir durch Christus, unseren Herrn. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Through the same Christ Our Lord. Amen.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ave-indicator {
|
||||||
|
text-align: center;
|
||||||
|
color: grey;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
53
src/lib/components/faith/prayers/AnimaChristi.svelte
Normal file
53
src/lib/components/faith/prayers/AnimaChristi.svelte
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
import Paternoster from './Paternoster.svelte';
|
||||||
|
import AveMaria from './AveMaria.svelte';
|
||||||
|
import GloriaPatri from './GloriaPatri.svelte';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang=la>Ánima Christi, santífica me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Seele Christi, heilige mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Soul of Christ, sanctify me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Corpus Christi, salva me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Leib Christi, erlöse mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Body of Christ, save me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Sanguis Christi, inébria me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Blut Christi, tränke mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Blood of Christ, inebriate me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Aqua láteris Christi, lava me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Wasser der Seite Christi, wasche mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Water from the side of Christ, wash me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Pássio Christi, confórta me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Leiden Christi, stärke mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Passion of Christ, strenghten me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>O bone Iesu, exáudi me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>O gütiger Jesus, erhöre mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>O good Jesus, hear me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Intra tua vúlnera abscónde me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Verbirg in Deine Wunden mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Within Thy wounds hide me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Ne permíttas me separári a te.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Von Dir lass nimmer scheiden mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Separated from Thee let me never be.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Ab hoste malígno defénde me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Vor dem bösen Feind beschütze mich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>From the malignant enemeny, defend me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>In hora mortis meæ voca me.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>In meiner Todesstunde rufe mich,</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>At the hour of death, call me.</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Et iube me veníre ad te,</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Und heisse zur Dir kommen mich,</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>And bid me come unto Thee</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>Ut cum Sanctis tuis laudem te</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Damit ich möge loben Dich</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>That with Thy Saints I may praise Thee</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>in sǽcula sæculórum.</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Mit Deinen Heiligen ewiglich.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>forever and ever.</v>{/if}
|
||||||
|
<v lang=und>Amen.</v>
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
68
src/lib/components/faith/prayers/ApostlesCreed.svelte
Normal file
68
src/lib/components/faith/prayers/ApostlesCreed.svelte
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Credo in Deum Patrem omnipoténtem,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Ich glaube an Gott, den Vater, den Allmächtigen,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">I believe in God, the Father almighty,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">Creatórem cæli et terræ.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">den Schöpfer des Himmels und der Erde.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Creator of heaven and earth.</v>{/if}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Et in Iesum Christum, Fílium eius únicum, Dóminum nostrum,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Und an Jesus Christus, seinen eingeborenen Sohn, unsern Herrn,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">And in Jesus Christ, His only Son, our Lord,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">qui concéptus est de Spíritu Sancto,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">der empfangen ist vom Heiligen Geist,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">who was conceived by the Holy Spirit,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">natus ex María Vírgine,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">geboren von der Jungfrau Maria,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">born of the Virgin Mary,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">passus sub Póntio Piláto,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">gelitten unter Pontius Pilatus,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">suffered under Pontius Pilate,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">crucifíxus, mórtuus, et sepúltus,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">gekreuzigt, gestorben und begraben,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">was crucified, died, and was buried.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">descéndit ad ínferos,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">hinabgestiegen in das Reich des Todes,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">He descended into hell.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">tértia die resurréxit a mórtuis,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">am dritten Tage auferstanden von den Toten,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">On the third day He rose again from the dead.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">ascéndit ad cælos,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">aufgefahren in den Himmel,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">He ascended into heaven,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">sedet ad déxteram Dei Patris omnipoténtis,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">er sitzet zur Rechten Gottes, des allmächtigen Vaters;</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">and sits at the right hand of God the Father almighty.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">inde ventúrus est iudicáre vivos et mórtuos.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">von dort wird er kommen, zu richten die Lebenden und die Toten.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">From thence He shall come to judge the living and the dead.</v>{/if}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Credo in Spíritum Sanctum,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Ich glaube an den Heiligen Geist,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">I believe in the Holy Spirit,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">sanctam Ecclésiam cathólicam,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">die heilige katholische Kirche,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">the holy catholic Church,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">sanctórum communiónem,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Gemeinschaft der Heiligen,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">the communion of saints,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">remissiónem peccatórum,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Vergebung der Sünden,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">the forgiveness of sins,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">carnis resurrectiónem,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Auferstehung der Toten</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">the resurrection of the body,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">vitam ætérnam. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">und das ewige Leben. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">and life everlasting. Amen.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
@@ -1,7 +1,20 @@
|
|||||||
<script>
|
<script>
|
||||||
import Prayer from './Prayer.svelte';
|
import Prayer from './Prayer.svelte';
|
||||||
|
|
||||||
|
let { intro = false } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if intro}
|
||||||
|
<Prayer hasLatin={false}>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p class="intro">
|
||||||
|
{#if urlLang === 'en'}This ancient hymn begins with the words the angels used to celebrate the newborn Savior. It first praises God the Father, then God the Son; it concludes with homage to the Most Holy Trinity, during which one makes the sign of the cross.{/if}
|
||||||
|
{#if urlLang === 'de'}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 schliesst mit einer Huldigung an die Heiligste Dreifaltigkeit, wobei man sich mit dem grossen Kreuze bezeichnet.{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<Prayer>
|
<Prayer>
|
||||||
{#snippet children(showLatin, urlLang)}
|
{#snippet children(showLatin, urlLang)}
|
||||||
<p>
|
<p>
|
||||||
25
src/lib/components/faith/prayers/GuardianAngel.svelte
Normal file
25
src/lib/components/faith/prayers/GuardianAngel.svelte
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Ángele Dei,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Engel Gottes,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Angel of God,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">qui custos es mei,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">mein Beschützer,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">my guardian dear,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">me, tibi commíssum pietáte supérna,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">dir hat Gottes Vorsehung mich anvertraut;</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">to whom God's love commits me here,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">illúmina, custódi, rege et gubérna.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">erleuchte, beschütze, leite und führe mich.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">ever this day be at my side, to light and guard, to rule and guide.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Amen.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
41
src/lib/components/faith/prayers/Postcommunio.svelte
Normal file
41
src/lib/components/faith/prayers/Postcommunio.svelte
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
import Paternoster from './Paternoster.svelte';
|
||||||
|
import AveMaria from './AveMaria.svelte';
|
||||||
|
import GloriaPatri from './GloriaPatri.svelte';
|
||||||
|
import AnimaChristi from './AnimaChristi.svelte';
|
||||||
|
import PrayerBeforeACrucifix from './PrayerBeforeACrucifix.svelte';
|
||||||
|
|
||||||
|
let {onlyIntro = false } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer hasLatin={false}>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p class="intro">
|
||||||
|
{#if urlLang === 'en'}A plenary indulgence is granted to the faithful who devoutly recite these prayers after Holy Communion. The usual conditions apply: sacramental confession, Eucharistic communion, prayer for the intentions of the Holy Father, and detachment from all sin, even venial.{/if}
|
||||||
|
{#if urlLang === 'de'}Den Gläubigen, die diese Gebete nach der heiligen Kommunion andächtig verrichten, wird ein vollkommener Ablass gewährt. Die üblichen Bedingungen gelten: sakramentale Beichte, eucharistische Kommunion, Gebet in den Anliegen des Heiligen Vaters und Loslösung von jeder Sünde, auch von lässlichen.{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
{#if !onlyIntro}
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<h3> Ánima Christi </h3>
|
||||||
|
<AnimaChristi />
|
||||||
|
<h3>
|
||||||
|
{#if urlLang=='en'}Plenary Indulgence{:else}Vollkommener Ablass{/if}
|
||||||
|
</h3>
|
||||||
|
<h3>
|
||||||
|
{#if urlLang=='en'}Prayer Before a Crucifix{:else}Gebet vor einem Kruzifix{/if}
|
||||||
|
</h3>
|
||||||
|
<h4> Paternoster </h4>
|
||||||
|
<Paternoster />
|
||||||
|
<h4> Ave Maria </h4>
|
||||||
|
<AveMaria />
|
||||||
|
<h4> Gloria Patri </h4>
|
||||||
|
<GloriaPatri />
|
||||||
|
<PrayerBeforeACrucifix />
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
import Paternoster from './Paternoster.svelte';
|
||||||
|
import AveMaria from './AveMaria.svelte';
|
||||||
|
import GloriaPatri from './GloriaPatri.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang=la>En ego, o bone et dulcíssime Jesu,</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Siehe, o gütiger und milder Jesus,</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>Behold, O good and sweetest Jesus,</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>ante conspéctum tuum génibus me provólvo </v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>ich werfe mich vor Deinen Augen auf die Knie. </v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>I cast myself upon my knees in Thy sight,</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>ac máximo ánimi ardóre te oro atque obtéstor, </v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Inbrünstig bitte und beschwöre ich Dich:</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>and with the most fervent desire of my soul I pray and beseech Thee</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la> ut meum in in cor vívidos fídei, spei et caritátis sensus, atque veram peccatórum meórum pœniténtiam,</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Präge meinem Herzen lebendige Gefühle des Glaubens, der Hoffung und der Liebe ein</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>to impress upon my heart lively sentiments of faith, hope and charity,</v>{/if}
|
||||||
|
{#if showLatin} <v lang=la>éaque emendándi firmíssimam voluntátem velis</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>sowie wahre Reue über meine Sünden und den ganz festen Willen, mich zu bessern.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>with true repentance for my sins and a most firm desire of amendment:</v>{/if}
|
||||||
|
{#if showLatin} <v lang=la> imprímere; dum magno ánimi afféctu et dolóre tua quinque vúlnera mecum ipse consídero ac mente contémplor,</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Voll Liebe und Schmerz schaue ich Deine fünf Wunden und betrachte sie in meinem Geiste.</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>whilst with deep affection and grief of soul I consider within myself and mentally contemplate Thy five most precious Wounds,</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la>illud præ óculis habens, quod jam in ore ponébat tuo David Prophéta de te, o bone Jesu:</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>Dabei halte ich mir vor Augen, was im Hinblick auf Dich, o guter Jesus, schon der Prophet David Dir in den Mund legte:</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>having before mine eyes that which David, the prophet, long ago spoke in Thine Own person concerning Thee, my Jesus:</v>{/if}
|
||||||
|
{#if showLatin}<v lang=la> «Fodérunt manus meas et pedes meos; dinumeravérunt ómnio ossa mea.» (Ps. 21, 17-18)</v>{/if}
|
||||||
|
{#if urlLang=='de'}<v lang=de>«Sie haben Meine Hände und Meine Füsse durchbohrt; alle meine Gebeine haben sie gezählt.» (Ps. 21, 17-18)</v>{/if}
|
||||||
|
{#if urlLang=='en'}<v lang=en>"They have pierced My hands and My feet, they have numbered all My bones." (Ps. 21:17-18</v>{/if}
|
||||||
|
<v lang=und>Amen.</v>
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
46
src/lib/components/faith/prayers/ReginaCaeli.svelte
Normal file
46
src/lib/components/faith/prayers/ReginaCaeli.svelte
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Regína Cæli, lætáre, allelúia.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Freu dich, du Himmelskönigin, alleluja.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Queen of Heaven, rejoice, alleluia.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">Quia quem meruísti portáre, allelúia.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Den du zu tragen würdig warst, alleluja.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">For He whom thou didst merit to bear, alleluia.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">Resurréxit, sicut dixit, allelúia.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Er ist auferstanden, wie er gesagt hat, alleluja.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Has risen, as He said, alleluia.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">Ora pro nobis Deum, allelúia.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Bitt Gott für uns, alleluja.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Pray for us to God, alleluia.</v>{/if}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la"><i>℣.</i> Gaude et lætáre, Virgo María, allelúia.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℣.</i> Freu dich und frohlocke, Jungfrau Maria, alleluja.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℣.</i> Rejoice and be glad, O Virgin Mary, alleluia.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la"><i>℟.</i> Quia surréxit Dóminus vere, allelúia.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℟.</i> Denn der Herr ist wahrhaft auferstanden, alleluja.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℟.</i> For the Lord has truly risen, alleluia.</v>{/if}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la"><i>℣.</i> Orémus.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de"><i>℣.</i> Lasset uns beten.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en"><i>℣.</i> Let us pray:</v>{/if}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Deus, qui per resurrectiónem Fílii tui Dómini nostri Iesu Christi mundum lætificáre dignátus es,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">O Gott, der du durch die Auferstehung deines Sohnes, unseres Herrn Jesus Christus, die Welt zu erfreuen dich gewürdigt hast,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">O God, who through the resurrection of Thy Son our Lord Jesus Christ didst vouchsafe to give joy to the world,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">præsta, quǽsumus, ut per eius Genetrícem Vírginem Maríam perpétuæ capiámus gáudia vitæ.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">verleihe uns, wir bitten dich, dass wir durch seine Mutter, die Jungfrau Maria, die Freuden des ewigen Lebens erlangen.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">grant, we beseech Thee, that through His Mother the Virgin Mary we may obtain the joys of everlasting life.</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">Per eúmdem Christum Dóminum nostrum. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Durch Christus, unseren Herrn. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Through the same Christ our Lord. Amen.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
{#if showLatin}<v lang="la">nóbis post hoc exsílíum osténde.</v>{/if}
|
{#if showLatin}<v lang="la">nóbis post hoc exsílíum osténde.</v>{/if}
|
||||||
{#if urlLang === 'de'}<v lang="de">die gebenedeite Frucht deines Leibes.</v>{/if}
|
{#if urlLang === 'de'}<v lang="de">die gebenedeite Frucht deines Leibes.</v>{/if}
|
||||||
{#if urlLang === 'en'}<v lang="en">the blessed fruit of thy womb, <i><sup>⚬</sup></i>Jesus.</v>{/if}
|
{#if urlLang === 'en'}<v lang="en">the blessed fruit of thy womb, <i><sup>⚬</sup></i>Jesus.</v>{/if}
|
||||||
{#if showLatin}<v lang="la">O clémens, o pía, o dúlcis Vírgo <i><sup>⚬</sup></i>María.</v>{/if}
|
{#if showLatin}<v lang="la">O clémens, o pía, o dúlcis Vírgo <i><sup>⚬</sup></i>María. Amen.</v>{/if}
|
||||||
{#if urlLang === 'de'}<v lang="de">O gütige, o milde, o süsse Jungfrau <i><sup>⚬</sup></i>Maria.</v>{/if}
|
{#if urlLang === 'de'}<v lang="de">O gütige, o milde, o süsse Jungfrau <i><sup>⚬</sup></i>Maria. Amen.</v>{/if}
|
||||||
{#if urlLang === 'en'}<v lang="en">O clement, O loving, O sweet Virgin <i><sup>⚬</sup></i>Mary.</v>{/if}
|
{#if urlLang === 'en'}<v lang="en">O clement, O loving, O sweet Virgin <i><sup>⚬</sup></i>Mary. Amen.</v>{/if}
|
||||||
</p>
|
</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Prayer>
|
</Prayer>
|
||||||
48
src/lib/components/faith/prayers/TantumErgo.svelte
Normal file
48
src/lib/components/faith/prayers/TantumErgo.svelte
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
{#snippet children(showLatin, urlLang)}
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Tantum ergo Sacraméntum</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Darum lasst uns tief verehren</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">Therefore so great a Sacrament</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">venerémur cérnui:</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">ein so grosses Sakrament;</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">let us venerate with bowed heads;</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">et antíquum documéntum</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">dieser Bund soll ewig währen</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">and the old rite</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">novo cedat rítui:</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">und den neuen Bund ersetzt.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">give way to the new:</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">præstet fides suppleméntum</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Unser Glaube soll uns lehren,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">let faith provide a supplement</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">sénsuum deféctui.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">was das Auge nicht erkennt.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">for the failure of the senses.</v>{/if}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if showLatin}<v lang="la">Genitóri, Genitóque</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Gott dem Vater und dem Sohne</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">To the Begetter and the Begotten</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">laus et iubilátio,</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">sei Lob und Preis und Ehre,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">be praise and jubilation,</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">salus, honor, virtus quoque</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">Heil und Ruhm und Macht und Wonne</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">salvation, honour, virtue also</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">sit et benedíctio:</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">und Segen immerdar,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">and blessing too:</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">procedénti ab utróque</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">und dem der von beiden ausgeht,</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">to Him proceeding from both</v>{/if}
|
||||||
|
{#if showLatin}<v lang="la">compar sit laudátio. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'de'}<v lang="de">sei gleiche Ehre. Amen.</v>{/if}
|
||||||
|
{#if urlLang === 'en'}<v lang="en">let there be equal praise. Amen.</v>{/if}
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</Prayer>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { do_on_key } from '$lib/components/do_on_key.js'
|
import { do_on_key } from '$lib/components/recipes/do_on_key.js'
|
||||||
import Check from '$lib/assets/icons/Check.svelte'
|
import Check from '$lib/assets/icons/Check.svelte'
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -108,7 +108,6 @@ dialog[open]::backdrop {
|
|||||||
|
|
||||||
dialog h2 {
|
dialog h2 {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-family: sans-serif;
|
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 30vh;
|
margin-top: 30vh;
|
||||||
@@ -123,7 +122,7 @@ dialog h2 {
|
|||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 20px;
|
border-radius: var(--radius-card);
|
||||||
background-color: var(--blue);
|
background-color: var(--blue);
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
|
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
|
||||||
@@ -141,12 +140,12 @@ dialog h2 {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
border: 2px solid var(--nord4);
|
border: 2px solid var(--nord4);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: var(--nord0);
|
color: var(--nord0);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector-content select:hover,
|
.selector-content select:hover,
|
||||||
@@ -176,10 +175,10 @@ dialog h2 {
|
|||||||
.button-group button {
|
.button-group button {
|
||||||
padding: 0.75em 2em;
|
padding: 0.75em 2em;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "$lib/css/nordtheme.css";
|
|
||||||
import "$lib/css/shake.css";
|
import "$lib/css/shake.css";
|
||||||
import "$lib/css/icon.css";
|
import "$lib/css/icon.css";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
@@ -37,6 +36,8 @@ const img_name = $derived(
|
|||||||
const img_alt = $derived(
|
const img_alt = $derived(
|
||||||
recipe.images?.[0]?.alt || recipe.name
|
recipe.images?.[0]?.alt || recipe.name
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const img_color = $derived(recipe.images?.[0]?.color || '');
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.card-main-link {
|
.card-main-link {
|
||||||
@@ -63,7 +64,6 @@ const img_alt = $derived(
|
|||||||
transition: var(--transition-normal);
|
transition: var(--transition-normal);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 525px;
|
height: 525px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@@ -95,21 +95,16 @@ const img_alt = $derived(
|
|||||||
transition: var(--transition-normal);
|
transition: var(--transition-normal);
|
||||||
border-top-left-radius: inherit;
|
border-top-left-radius: inherit;
|
||||||
border-top-right-radius: inherit;
|
border-top-right-radius: inherit;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.blur{
|
.image.loaded{
|
||||||
filter: blur(10px);
|
opacity: 1;
|
||||||
}
|
|
||||||
.backdrop_blur{
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
}
|
||||||
.card-image{
|
.card-image{
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 255px;
|
height: 255px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-top-left-radius: inherit;
|
border-top-left-radius: inherit;
|
||||||
border-top-right-radius: inherit;
|
border-top-right-radius: inherit;
|
||||||
@@ -234,11 +229,11 @@ const img_alt = $derived(
|
|||||||
<a href="{routePrefix}/{recipe.short_name}" class="card-main-link" aria-label="View recipe: {recipe.name}">
|
<a href="{routePrefix}/{recipe.short_name}" class="card-main-link" aria-label="View recipe: {recipe.name}">
|
||||||
<span class="visually-hidden">View recipe: {recipe.name}</span>
|
<span class="visually-hidden">View recipe: {recipe.name}</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="card-image" style="background-image:url(https://bocken.org/static/rezepte/placeholder/{img_name})">
|
<div class="card-image" style:background-color={img_color}>
|
||||||
<noscript>
|
<noscript>
|
||||||
<img class="image backdrop_blur" src="https://bocken.org/static/rezepte/thumb/{img_name}" loading={loading_strat} alt="{img_alt}"/>
|
<img class="image loaded" src="https://bocken.org/static/rezepte/thumb/{img_name}" loading={loading_strat} alt="{img_alt}"/>
|
||||||
</noscript>
|
</noscript>
|
||||||
<img class="image backdrop_blur" class:blur={!isloaded} src={'https://bocken.org/static/rezepte/thumb/' + img_name} loading={loading_strat} alt="{img_alt}" onload={() => isloaded=true}/>
|
<img class="image" class:loaded={isloaded} src={'https://bocken.org/static/rezepte/thumb/' + img_name} loading={loading_strat} alt="{img_alt}" onload={() => isloaded=true}/>
|
||||||
</div>
|
</div>
|
||||||
{#if showFavoriteIndicator && isFavorite}
|
{#if showFavoriteIndicator && isFavorite}
|
||||||
<div class="favorite-indicator">❤️</div>
|
<div class="favorite-indicator">❤️</div>
|
||||||
@@ -130,15 +130,14 @@ function remove_on_enter(event: KeyboardEvent, tag: string) {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
width: var(--card-width);
|
width: var(--card-width);
|
||||||
aspect-ratio: 4/7;
|
aspect-ratio: 4/7;
|
||||||
border-radius: 20px;
|
border-radius: var(--radius-card);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
background-color: var(--blue);
|
background-color: var(--blue);
|
||||||
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.3);
|
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.3);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
@@ -155,7 +154,7 @@ function remove_on_enter(event: KeyboardEvent, tag: string) {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 20px 20px 0 0 ;
|
border-radius: 20px 20px 0 0 ;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.img_label_wrapper:hover{
|
.img_label_wrapper:hover{
|
||||||
background-color: var(--red);
|
background-color: var(--red);
|
||||||
@@ -169,7 +168,7 @@ function remove_on_enter(event: KeyboardEvent, tag: string) {
|
|||||||
top:0;
|
top:0;
|
||||||
left: 0;
|
left: 0;
|
||||||
border-radius: 20px 20px 0 0;
|
border-radius: 20px 20px 0 0;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.img_label_wrapper:hover .delete{
|
.img_label_wrapper:hover .delete{
|
||||||
opacity: 100%;
|
opacity: 100%;
|
||||||
@@ -178,7 +177,7 @@ function remove_on_enter(event: KeyboardEvent, tag: string) {
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
fill: white;
|
fill: white;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.delete{
|
.delete{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -188,7 +187,7 @@ function remove_on_enter(event: KeyboardEvent, tag: string) {
|
|||||||
left: 2rem;
|
left: 2rem;
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
transition:200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.delete:hover{
|
.delete:hover{
|
||||||
transform: scale(1.2, 1.2);
|
transform: scale(1.2, 1.2);
|
||||||
@@ -220,14 +219,14 @@ input::placeholder{
|
|||||||
text-align:center;
|
text-align:center;
|
||||||
width: 2.6rem;
|
width: 2.6rem;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
top:-0.5em;
|
top:-0.5em;
|
||||||
right:-0.5em;
|
right:-0.5em;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
background-color: var(--nord6);
|
background-color: var(--nord6);
|
||||||
border-radius:1000px;
|
border-radius: var(--radius-pill);
|
||||||
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.6);
|
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
.card .icon:hover,
|
.card .icon:hover,
|
||||||
@@ -259,7 +258,7 @@ input::placeholder{
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
.card .name{
|
.card .name{
|
||||||
all: unset;
|
all: unset;
|
||||||
@@ -306,7 +305,7 @@ input::placeholder{
|
|||||||
padding-inline: 1em;
|
padding-inline: 1em;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
box-shadow: 0.2em 0.2em 0.2em 0.05em rgba(0, 0, 0, 0.3);
|
box-shadow: 0.2em 0.2em 0.2em 0.05em rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
.card .tag:hover,
|
.card .tag:hover,
|
||||||
@@ -330,8 +329,8 @@ input::placeholder{
|
|||||||
width: 10rem;
|
width: 10rem;
|
||||||
background-color: var(--nord0);
|
background-color: var(--nord0);
|
||||||
padding-inline: 1em;
|
padding-inline: 1em;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
}
|
}
|
||||||
.card .title .category:hover,
|
.card .title .category:hover,
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||||
import TagChip from './TagChip.svelte';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
categories = [],
|
categories = [],
|
||||||
@@ -135,7 +134,6 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
194
src/lib/components/recipes/CompactCard.svelte
Normal file
194
src/lib/components/recipes/CompactCard.svelte
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import "$lib/css/shake.css";
|
||||||
|
|
||||||
|
let {
|
||||||
|
recipe,
|
||||||
|
current_month = 0,
|
||||||
|
icon_override = false,
|
||||||
|
isFavorite = false,
|
||||||
|
showFavoriteIndicator = false,
|
||||||
|
loading_strat = "lazy",
|
||||||
|
routePrefix = '/rezepte'
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const img_name = $derived(
|
||||||
|
recipe.images?.[0]?.mediapath ||
|
||||||
|
`${recipe.germanShortName || recipe.short_name}.webp`
|
||||||
|
);
|
||||||
|
|
||||||
|
const img_alt = $derived(
|
||||||
|
recipe.images?.[0]?.alt || recipe.name
|
||||||
|
);
|
||||||
|
|
||||||
|
const img_color = $derived(recipe.images?.[0]?.color || '');
|
||||||
|
|
||||||
|
const isInSeason = $derived(icon_override || recipe.season?.includes(current_month));
|
||||||
|
|
||||||
|
function activateTransitions(event) {
|
||||||
|
const img = event.currentTarget.querySelector('.img-wrap img');
|
||||||
|
if (img) img.style.viewTransitionName = `recipe-${recipe.short_name}-img`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.compact-card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-surface);
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
|
||||||
|
transition: transform var(--transition-normal), box-shadow var(--transition-normal);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.compact-card:hover,
|
||||||
|
.compact-card:focus-within {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
|
.compact-card:hover .img-wrap img {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
.compact-card:hover .icon,
|
||||||
|
.compact-card:focus-within .icon {
|
||||||
|
animation: shake 0.6s;
|
||||||
|
}
|
||||||
|
.card-link {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.img-wrap {
|
||||||
|
aspect-ratio: 3 / 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.img-wrap img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.4s ease;
|
||||||
|
border-radius: var(--radius-card) var(--radius-card) 0 0;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5em 0.6em 0.5em;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
.info {
|
||||||
|
padding: 0.8em 0.9em 0.7em;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.3em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background-color: var(--nord5);
|
||||||
|
color: var(--nord3);
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform var(--transition-fast), background-color var(--transition-fast), box-shadow var(--transition-fast), color var(--transition-fast);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.tag:hover,
|
||||||
|
.tag:focus-visible {
|
||||||
|
transform: scale(1.05);
|
||||||
|
background-color: var(--nord8);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
.tag {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0.15rem 0.55rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.tag,
|
||||||
|
.tag:visited,
|
||||||
|
.tag:link {
|
||||||
|
background-color: var(--nord0);
|
||||||
|
color: var(--nord4);
|
||||||
|
}
|
||||||
|
.tag:hover,
|
||||||
|
.tag:focus-visible {
|
||||||
|
background-color: var(--nord8);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
top: -1.2em;
|
||||||
|
right: 0.6em;
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: var(--nord0);
|
||||||
|
color: white;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
.icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.favorite {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
filter: drop-shadow(0 0 3px rgba(0,0,0,0.8));
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="compact-card" onclick={activateTransitions}>
|
||||||
|
<a href="{routePrefix}/{recipe.short_name}" class="card-link" aria-label={recipe.name}></a>
|
||||||
|
{#if showFavoriteIndicator && isFavorite}
|
||||||
|
<span class="favorite">❤️</span>
|
||||||
|
{/if}
|
||||||
|
<div class="img-wrap" style:background-color={img_color}>
|
||||||
|
<img
|
||||||
|
src="https://bocken.org/static/rezepte/thumb/{img_name}"
|
||||||
|
alt={img_alt}
|
||||||
|
loading={loading_strat}
|
||||||
|
data-recipe={recipe.short_name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
{#if isInSeason}
|
||||||
|
<a href="{routePrefix}/icon/{recipe.icon}" class="icon g-icon-badge">{recipe.icon}</a>
|
||||||
|
{/if}
|
||||||
|
<p class="name">{@html recipe.name}</p>
|
||||||
|
{#if recipe.tags?.length}
|
||||||
|
<div class="tags">
|
||||||
|
{#each recipe.tags as tag (tag)}
|
||||||
|
<a href="{routePrefix}/tag/{tag}" class="tag">{tag}</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -8,9 +8,9 @@ import Check from '$lib/assets/icons/Check.svelte'
|
|||||||
|
|
||||||
import "$lib/css/action_button.css"
|
import "$lib/css/action_button.css"
|
||||||
|
|
||||||
import { do_on_key } from '$lib/components/do_on_key.js'
|
import { do_on_key } from '$lib/components/recipes/do_on_key.js'
|
||||||
import { portions } from '$lib/js/portions_store.js'
|
import { portions } from '$lib/js/portions_store.js'
|
||||||
import BaseRecipeSelector from '$lib/components/BaseRecipeSelector.svelte'
|
import BaseRecipeSelector from '$lib/components/recipes/BaseRecipeSelector.svelte'
|
||||||
|
|
||||||
let portions_local = $state()
|
let portions_local = $state()
|
||||||
portions.subscribe((p) => {
|
portions.subscribe((p) => {
|
||||||
@@ -398,11 +398,11 @@ input.heading{
|
|||||||
padding-inline: 2rem;
|
padding-inline: 2rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
color: white;
|
color: white;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
input.heading:hover{
|
input.heading:hover{
|
||||||
background-color: var(--nord1);
|
background-color: var(--nord1);
|
||||||
@@ -412,7 +412,7 @@ input.heading:hover{
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.heading_wrapper:hover
|
.heading_wrapper:hover
|
||||||
{
|
{
|
||||||
@@ -430,8 +430,8 @@ input.heading:hover{
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-block: 3rem;
|
margin-block: 3rem;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
border-radius: 20px;
|
border-radius: var(--radius-card);
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.shadow{
|
.shadow{
|
||||||
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
|
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
|
||||||
@@ -450,14 +450,13 @@ input.heading:hover{
|
|||||||
--font_size: 1.5rem;
|
--font_size: 1.5rem;
|
||||||
top: -1em;
|
top: -1em;
|
||||||
left: -1em;
|
left: -1em;
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
background-color: var(--nord0);
|
background-color: var(--nord0);
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
border-radius: 1000000px;
|
border-radius: 1000000px;
|
||||||
width: 23ch;
|
width: 23ch;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3);
|
box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
.category:hover{
|
.category:hover{
|
||||||
@@ -471,7 +470,6 @@ input.heading:hover{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add_ingredient{
|
.add_ingredient{
|
||||||
font-family: sans-serif;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -481,18 +479,18 @@ input.heading:hover{
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
padding-top: 2.5rem;
|
padding-top: 2.5rem;
|
||||||
border-radius: 20px;
|
border-radius: var(--radius-card);
|
||||||
background-color: var(--blue);
|
background-color: var(--blue);
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
.add_ingredient input{
|
.add_ingredient input{
|
||||||
border: 2px solid var(--nord4);
|
border: 2px solid var(--nord4);
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
.add_ingredient input:hover,
|
.add_ingredient input:hover,
|
||||||
.add_ingredient input:focus-visible
|
.add_ingredient input:focus-visible
|
||||||
@@ -537,7 +535,6 @@ dialog .adder{
|
|||||||
}
|
}
|
||||||
dialog h2{
|
dialog h2{
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-family: sans-serif;
|
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 30vh;
|
margin-top: 30vh;
|
||||||
@@ -569,7 +566,7 @@ dialog h2{
|
|||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.move_buttons_container button:hover{
|
.move_buttons_container button:hover{
|
||||||
scale: 1.4;
|
scale: 1.4;
|
||||||
@@ -611,10 +608,10 @@ h3{
|
|||||||
}
|
}
|
||||||
.list_wrapper p[contenteditable]{
|
.list_wrapper p[contenteditable]{
|
||||||
border: 2px solid grey;
|
border: 2px solid grey;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
padding: 0.25em 1em;
|
padding: 0.25em 1em;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.list_wrapper p[contenteditable]:hover,
|
.list_wrapper p[contenteditable]:hover,
|
||||||
.list_wrapper p[contenteditable]:focus-within{
|
.list_wrapper p[contenteditable]:focus-within{
|
||||||
@@ -703,12 +700,12 @@ h3{
|
|||||||
margin-block: 1rem;
|
margin-block: 1rem;
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
background-color: var(--nord9);
|
background-color: var(--nord9);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
|
box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,11 +5,10 @@ import Cross from '$lib/assets/icons/Cross.svelte'
|
|||||||
import Plus from '$lib/assets/icons/Plus.svelte'
|
import Plus from '$lib/assets/icons/Plus.svelte'
|
||||||
import Check from '$lib/assets/icons/Check.svelte'
|
import Check from '$lib/assets/icons/Check.svelte'
|
||||||
|
|
||||||
import '$lib/css/nordtheme.css'
|
|
||||||
import "$lib/css/action_button.css"
|
import "$lib/css/action_button.css"
|
||||||
|
|
||||||
import { do_on_key } from '$lib/components/do_on_key.js'
|
import { do_on_key } from '$lib/components/recipes/do_on_key.js'
|
||||||
import BaseRecipeSelector from '$lib/components/BaseRecipeSelector.svelte'
|
import BaseRecipeSelector from '$lib/components/recipes/BaseRecipeSelector.svelte'
|
||||||
|
|
||||||
let { lang = 'de' as 'de' | 'en', instructions = $bindable(), add_info = $bindable() } = $props<{ lang?: 'de' | 'en', instructions: any, add_info: any }>();
|
let { lang = 'de' as 'de' | 'en', instructions = $bindable(), add_info = $bindable() } = $props<{ lang?: 'de' | 'en', instructions: any, add_info: any }>();
|
||||||
|
|
||||||
@@ -402,7 +401,7 @@ export function update_step_position(list_index, step_index, direction){
|
|||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.move_buttons_container button:hover{
|
.move_buttons_container button:hover{
|
||||||
scale: 1.4;
|
scale: 1.4;
|
||||||
@@ -441,11 +440,11 @@ input.heading{
|
|||||||
padding-inline: 2rem;
|
padding-inline: 2rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
color: white;
|
color: white;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
input.heading:hover,
|
input.heading:hover,
|
||||||
input.heading:focus-visible
|
input.heading:focus-visible
|
||||||
@@ -457,7 +456,7 @@ input.heading:focus-visible
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: min(300px, 95dvw);
|
width: min(300px, 95dvw);
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
.heading_wrapper:hover,
|
.heading_wrapper:hover,
|
||||||
.heading_wrapper:focus-visible
|
.heading_wrapper:focus-visible
|
||||||
@@ -475,8 +474,8 @@ input.heading:focus-visible
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-block: 3rem;
|
margin-block: 3rem;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
border-radius: 20px;
|
border-radius: var(--radius-card);
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
background-color: var(--blue);
|
background-color: var(--blue);
|
||||||
padding: 1.5rem 2rem;
|
padding: 1.5rem 2rem;
|
||||||
}
|
}
|
||||||
@@ -497,14 +496,13 @@ dialog .adder{
|
|||||||
--font_size: 1.5rem;
|
--font_size: 1.5rem;
|
||||||
top: -1em;
|
top: -1em;
|
||||||
left: -1em;
|
left: -1em;
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
background-color: var(--nord0);
|
background-color: var(--nord0);
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
border-radius: 1000000px;
|
border-radius: 1000000px;
|
||||||
width: 23ch;
|
width: 23ch;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3);
|
box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
.category:hover,
|
.category:hover,
|
||||||
@@ -520,15 +518,14 @@ dialog .adder{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add_step p{
|
.add_step p{
|
||||||
font-family: sans-serif;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
border-radius: 20px;
|
border-radius: var(--radius-card);
|
||||||
border: 2px solid var(--nord4);
|
border: 2px solid var(--nord4);
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
.add_step p:hover,
|
.add_step p:hover,
|
||||||
.add_step p:focus-visible
|
.add_step p:focus-visible
|
||||||
@@ -544,14 +541,13 @@ dialog{
|
|||||||
background-color: rgba(255,255,255, 0.001);
|
background-color: rgba(255,255,255, 0.001);
|
||||||
border: unset;
|
border: unset;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
}
|
}
|
||||||
dialog .adder{
|
dialog .adder{
|
||||||
margin-top: 5rem;
|
margin-top: 5rem;
|
||||||
}
|
}
|
||||||
dialog h2{
|
dialog h2{
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-family: sans-serif;
|
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 30vh;
|
margin-top: 30vh;
|
||||||
@@ -648,10 +644,10 @@ h3{
|
|||||||
display: inline;
|
display: inline;
|
||||||
padding: 0.25em 1em;
|
padding: 0.25em 1em;
|
||||||
border: 2px solid grey;
|
border: 2px solid grey;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
}
|
}
|
||||||
.additional_info div:has(p[contenteditable]){
|
.additional_info div:has(p[contenteditable]){
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
.additional_info div:has(p[contenteditable]):hover,
|
.additional_info div:has(p[contenteditable]):hover,
|
||||||
@@ -731,12 +727,12 @@ h3{
|
|||||||
margin-block: 1rem;
|
margin-block: 1rem;
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
background-color: var(--nord9);
|
background-color: var(--nord9);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
|
box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ textarea {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
font-family: sans-serif;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
textarea::placeholder {
|
textarea::placeholder {
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
import Toggle from './Toggle.svelte';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
enabled = false,
|
enabled = false,
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
|
||||||
import CategoryFilter from './CategoryFilter.svelte';
|
import CategoryFilter from './CategoryFilter.svelte';
|
||||||
import TagFilter from './TagFilter.svelte';
|
import TagFilter from './TagFilter.svelte';
|
||||||
import IconFilter from './IconFilter.svelte';
|
import IconFilter from './IconFilter.svelte';
|
||||||
@@ -44,7 +43,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.filter-wrapper {
|
.filter-wrapper {
|
||||||
width: 900px;
|
width: 900px;
|
||||||
max-width: 95vw;
|
max-width: 80vw;
|
||||||
margin: 1rem auto 2rem;
|
margin: 1rem auto 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '$lib/css/nordtheme.css';
|
|
||||||
import "$lib/css/shake.css"
|
import "$lib/css/shake.css"
|
||||||
let { icon, ...restProps } = $props<{ icon: string, [key: string]: any }>();
|
let { icon, ...restProps } = $props<{ icon: string, [key: string]: any }>();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
a{
|
a{
|
||||||
font-family: "Noto Color Emoji", emoji;
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
background-color: var(--nord4);
|
background-color: var(--nord4);
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
box-shadow: 0em 0em 0.5em 0.2em rgba(0, 0, 0, 0.2);
|
box-shadow: 0em 0em 0.5em 0.2em rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||||
import TagChip from './TagChip.svelte';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
availableIcons = [],
|
availableIcons = [],
|
||||||
@@ -127,7 +126,7 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: "Noto Color Emoji", emoji, sans-serif;
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif;
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
@@ -147,7 +146,6 @@
|
|||||||
|
|
||||||
input::placeholder {
|
input::placeholder {
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input:hover {
|
input:hover {
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import '$lib/css/nordtheme.css';
|
import Recipes from '$lib/components/recipes/Recipes.svelte';
|
||||||
import Recipes from '$lib/components/Recipes.svelte';
|
|
||||||
import Search from './Search.svelte';
|
import Search from './Search.svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -27,12 +26,12 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
a{
|
a{
|
||||||
font-family: "Noto Color Emoji", emoji, sans-serif;
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
background-color: var(--nord4);
|
background-color: var(--nord4);
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
box-shadow: 0em 0em 0.5em 0.2em rgba(0, 0, 0, 0.2);
|
box-shadow: 0em 0em 0.5em 0.2em rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
a:hover,
|
a:hover,
|
||||||
@@ -4,8 +4,6 @@ import { onNavigate } from "$app/navigation";
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import HefeSwapper from './HefeSwapper.svelte';
|
import HefeSwapper from './HefeSwapper.svelte';
|
||||||
import '$lib/css/recipe-links.css';
|
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
// Helper function to multiply numbers in ingredient amounts
|
// Helper function to multiply numbers in ingredient amounts
|
||||||
@@ -310,9 +308,6 @@ function adjust_amount(string, multiplier){
|
|||||||
// No need for complex yeast toggle handling - everything is calculated server-side now
|
// No need for complex yeast toggle handling - everything is calculated server-side now
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
.ingredients{
|
.ingredients{
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import '$lib/css/recipe-links.css';
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
let multiplier = $state(data.multiplier || 1);
|
let multiplier = $state(data.multiplier || 1);
|
||||||
@@ -101,9 +100,6 @@ const labels = $derived({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
ol li::marker{
|
ol li::marker{
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
useAndLogic = true,
|
useAndLogic = true,
|
||||||
@@ -42,7 +41,7 @@
|
|||||||
|
|
||||||
.filter-label {
|
.filter-label {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--nord2);
|
color: var(--nord1);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -66,7 +65,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--nord4);
|
color: var(--nord3);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +86,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch.or-mode {
|
.toggle-switch.or-mode {
|
||||||
background: var(--nord13);
|
background: var(--nord12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-knob {
|
.toggle-knob {
|
||||||
@@ -122,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch.or-mode + .mode-label.or {
|
.toggle-switch.or-mode + .mode-label.or {
|
||||||
color: var(--nord13);
|
color: var(--nord12);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import "$lib/css/nordtheme.css"
|
|
||||||
let { title = '', children } = $props<{ title?: string, children?: Snippet }>();
|
let { title = '', children } = $props<{ title?: string, children?: Snippet }>();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CardAdd from '$lib/components/CardAdd.svelte';
|
import CardAdd from '$lib/components/recipes/CardAdd.svelte';
|
||||||
import MediaScroller from '$lib/components/MediaScroller.svelte';
|
import MediaScroller from '$lib/components/recipes/MediaScroller.svelte';
|
||||||
import Card from '$lib/components/Card.svelte';
|
import Search from '$lib/components/recipes/Search.svelte';
|
||||||
import Search from '$lib/components/Search.svelte';
|
import SeasonSelect from '$lib/components/recipes/SeasonSelect.svelte';
|
||||||
import SeasonSelect from '$lib/components/SeasonSelect.svelte';
|
import CreateIngredientList from '$lib/components/recipes/CreateIngredientList.svelte';
|
||||||
import CreateIngredientList from '$lib/components/CreateIngredientList.svelte';
|
import CreateStepList from '$lib/components/recipes/CreateStepList.svelte';
|
||||||
import CreateStepList from '$lib/components/CreateStepList.svelte';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
card_data = $bindable({}),
|
card_data = $bindable({}),
|
||||||
@@ -59,7 +58,7 @@ input.temp{
|
|||||||
display: block;
|
display: block;
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
padding: 0.2em 1em;
|
padding: 0.2em 1em;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
background-color: var(--nord4);
|
background-color: var(--nord4);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import "$lib/css/nordtheme.css";
|
|
||||||
import FilterPanel from './FilterPanel.svelte';
|
import FilterPanel from './FilterPanel.svelte';
|
||||||
import { getCategories } from '$lib/js/categories';
|
import { getCategories } from '$lib/js/categories';
|
||||||
|
|
||||||
@@ -264,7 +263,7 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
const loadFilterData = async () => {
|
const loadFilterData = async () => {
|
||||||
try {
|
try {
|
||||||
const apiBase = isEnglish ? '/api/recipes' : '/api/rezepte';
|
const apiBase = `/api/${isEnglish ? 'recipes' : 'rezepte'}`;
|
||||||
const [tagsRes, iconsRes] = await Promise.all([
|
const [tagsRes, iconsRes] = await Promise.all([
|
||||||
fetch(`${apiBase}/items/tag`),
|
fetch(`${apiBase}/items/tag`),
|
||||||
fetch('/api/rezepte/items/icon')
|
fetch('/api/rezepte/items/icon')
|
||||||
@@ -308,11 +307,10 @@
|
|||||||
input#search {
|
input#search {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.7rem 2rem;
|
padding: 0.7rem 2rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
input::placeholder{
|
input::placeholder{
|
||||||
@@ -320,15 +318,15 @@ input::placeholder{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
width: 500px;
|
width: 560px;
|
||||||
max-width: 85vw;
|
max-width: 88vw;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 2.5rem auto 1.2rem;
|
margin: 0 auto;
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
filter: drop-shadow(0.4em 0.5em 0.4em rgba(0,0,0,0.4))
|
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.25));
|
||||||
}
|
}
|
||||||
|
|
||||||
.search:hover,
|
.search:hover,
|
||||||
@@ -362,6 +360,20 @@ scale: 0.8 0.8;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
/* Reserves space for FilterPanel before JS hydrates, preventing layout shift. */
|
||||||
|
.filter-placeholder {
|
||||||
|
display: block;
|
||||||
|
width: 900px;
|
||||||
|
max-width: 95vw;
|
||||||
|
margin: 1rem auto 2rem;
|
||||||
|
height: 3.85rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 968px) {
|
||||||
|
.filter-placeholder {
|
||||||
|
width: auto;
|
||||||
|
height: calc(2.05rem + 2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<form class="search" method="get" action={buildSearchUrl('')} onsubmit={(e) => { e.preventDefault(); handleSubmit(e); }}>
|
<form class="search" method="get" action={buildSearchUrl('')} onsubmit={(e) => { e.preventDefault(); handleSubmit(e); }}>
|
||||||
{#if selectedCategory}<input type="hidden" name="category" value={selectedCategory} />{/if}
|
{#if selectedCategory}<input type="hidden" name="category" value={selectedCategory} />{/if}
|
||||||
@@ -408,4 +420,6 @@ scale: 0.8 0.8;
|
|||||||
onFavoritesToggle={handleFavoritesToggle}
|
onFavoritesToggle={handleFavoritesToggle}
|
||||||
onLogicModeToggle={handleLogicModeToggle}
|
onLogicModeToggle={handleLogicModeToggle}
|
||||||
/>
|
/>
|
||||||
|
{:else}
|
||||||
|
<div class="filter-placeholder"></div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||||
import TagChip from './TagChip.svelte';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
selectedSeasons = [],
|
selectedSeasons = [],
|
||||||
@@ -121,7 +120,6 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import '$lib/css/nordtheme.css';
|
import Recipes from '$lib/components/recipes/Recipes.svelte';
|
||||||
import Recipes from '$lib/components/Recipes.svelte';
|
|
||||||
import Search from './Search.svelte';
|
import Search from './Search.svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -29,12 +28,11 @@
|
|||||||
<style>
|
<style>
|
||||||
a.month{
|
a.month{
|
||||||
text-decoration: unset;
|
text-decoration: unset;
|
||||||
font-family: sans-serif;
|
border-radius: var(--radius-pill);
|
||||||
border-radius: 1000px;
|
|
||||||
background-color: var(--blue);
|
background-color: var(--blue);
|
||||||
color: var(--nord5);
|
color: var(--nord5);
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
min-width: 4em;
|
min-width: 4em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang=ts>
|
<script lang=ts>
|
||||||
import "$lib/css/nordtheme.css"
|
|
||||||
import { season } from '$lib/js/season_store.js'
|
import { season } from '$lib/js/season_store.js'
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
import {do_on_key} from "./do_on_key";
|
import {do_on_key} from "./do_on_key";
|
||||||
@@ -46,15 +45,15 @@ label{
|
|||||||
padding: 0.25em 1em;
|
padding: 0.25em 1em;
|
||||||
margin-inline: 0.1em;
|
margin-inline: 0.1em;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox_container{
|
.checkbox_container{
|
||||||
transition: 100ms;
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
.checkbox_container:hover,
|
.checkbox_container:hover,
|
||||||
.checkbox_container:focus-within
|
.checkbox_container:focus-within
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
tag = '',
|
tag = '',
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
.tag-chip {
|
.tag-chip {
|
||||||
all: unset;
|
all: unset;
|
||||||
padding: 0.4rem 0.8rem;
|
padding: 0.4rem 0.8rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 100ms ease;
|
transition: all 100ms ease;
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import "$lib/css/nordtheme.css";
|
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||||
import TagChip from './TagChip.svelte';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
availableTags = [],
|
availableTags = [],
|
||||||
@@ -118,7 +117,6 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let { src, placeholder_src, alt = "", children } = $props();
|
let { src, color = '', alt = "", transitionName = '', children } = $props();
|
||||||
|
|
||||||
let isloaded = $state(false);
|
|
||||||
let isredirected = $state(false);
|
let isredirected = $state(false);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const el = document.querySelector("img")
|
|
||||||
if(el?.complete){
|
|
||||||
isloaded = true
|
|
||||||
}
|
|
||||||
fetch(src, { method: 'HEAD' })
|
fetch(src, { method: 'HEAD' })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
isredirected = response.redirected
|
isredirected = response.redirected
|
||||||
@@ -21,9 +16,7 @@
|
|||||||
if(isredirected){
|
if(isredirected){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(document.querySelector("img").complete){
|
document.querySelector("#img_carousel").showModal();
|
||||||
document.querySelector("#img_carousel").showModal();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function close_dialog_img(){
|
function close_dialog_img(){
|
||||||
document.querySelector("#img_carousel").close();
|
document.querySelector("#img_carousel").close();
|
||||||
@@ -31,7 +24,7 @@
|
|||||||
import Cross from "$lib/assets/icons/Cross.svelte";
|
import Cross from "$lib/assets/icons/Cross.svelte";
|
||||||
import "$lib/css/action_button.css";
|
import "$lib/css/action_button.css";
|
||||||
import "$lib/css/shake.css";
|
import "$lib/css/shake.css";
|
||||||
import { do_on_key } from "./do_on_key";
|
import { do_on_key } from "$lib/components/recipes/do_on_key";
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
@@ -79,21 +72,25 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-inline: auto;
|
||||||
|
width: min(1000px, 100dvw);
|
||||||
|
height: max(60dvh,600px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.image{
|
.image{
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: min(1000px, 100dvw);
|
width: min(1000px, 100dvw);
|
||||||
z-index: -1;
|
|
||||||
opacity: 0;
|
|
||||||
transition: 200ms;
|
|
||||||
height: max(60dvh,600px);
|
height: max(60dvh,600px);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: 50% 20%;
|
object-position: 50% 20%;
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
filter: blur(20px);
|
|
||||||
z-index: -10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container::after {
|
.image-container::after {
|
||||||
@@ -106,34 +103,6 @@
|
|||||||
:global(h1){
|
:global(h1){
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.placeholder{
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: 50% 20%;
|
|
||||||
position: absolute;
|
|
||||||
width: min(1000px, 100dvw);
|
|
||||||
height: max(60dvh,600px);
|
|
||||||
z-index: -2;
|
|
||||||
}
|
|
||||||
.placeholder_blur{
|
|
||||||
width: inherit;
|
|
||||||
height: inherit;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
}
|
|
||||||
div:has(.placeholder){
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin-inline: auto;
|
|
||||||
width: min(1000px, 100dvw);
|
|
||||||
height: max(60dvh,600px);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.unblur.image{
|
|
||||||
filter: blur(0px) !important;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DIALOG */
|
/* DIALOG */
|
||||||
dialog{
|
dialog{
|
||||||
@@ -174,15 +143,13 @@ dialog button{
|
|||||||
<figure class="image-container">
|
<figure class="image-container">
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class:zoom-in={isloaded && !isredirected} onclick={show_dialog_img}>
|
<div class:zoom-in={!isredirected} onclick={show_dialog_img}>
|
||||||
<div class=placeholder style="background-image:url({placeholder_src})" >
|
<div class="image-wrap" style:background-color={color}>
|
||||||
<div class=placeholder_blur>
|
<img class="image" {src} {alt} style:view-transition-name={transitionName || null}/>
|
||||||
<img class="image" class:unblur={isloaded} {src} onload={() => {isloaded=true}} {alt}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<noscript>
|
<noscript>
|
||||||
<div class=placeholder style="background-image:url({placeholder_src})" >
|
<div class="image-wrap" style:background-color={color}>
|
||||||
<img class="image unblur" {src} onload={() => {isloaded=true}} {alt}/>
|
<img class="image" {src} {alt}/>
|
||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,7 +158,7 @@ dialog button{
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<dialog id=img_carousel>
|
<dialog id=img_carousel>
|
||||||
<img class:unblur={isloaded} {src} {alt}>
|
<img {src} {alt}>
|
||||||
<button class=action_button onkeydown={(event) => do_on_key(event, 'Enter', false, close_dialog_img)} onclick={close_dialog_img}>
|
<button class=action_button onkeydown={(event) => do_on_key(event, 'Enter', false, close_dialog_img)} onclick={close_dialog_img}>
|
||||||
<Cross fill=white width=2rem height=2rem></Cross>
|
<Cross fill=white width=2rem height=2rem></Cross>
|
||||||
</button>
|
</button>
|
||||||
162
src/lib/components/recipes/ToTryCard.svelte
Normal file
162
src/lib/components/recipes/ToTryCard.svelte
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<script>
|
||||||
|
let { item, ondelete, onedit, isEnglish = false } = $props();
|
||||||
|
|
||||||
|
function getDomain(url) {
|
||||||
|
try {
|
||||||
|
return new URL(url).hostname.replace(/^www\./, '');
|
||||||
|
} catch {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-surface);
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
|
||||||
|
transition: transform var(--transition-normal), box-shadow var(--transition-normal);
|
||||||
|
}
|
||||||
|
.card:hover,
|
||||||
|
.card:focus-within {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
|
.accent {
|
||||||
|
height: 6px;
|
||||||
|
background: linear-gradient(90deg, var(--nord10), var(--nord9));
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
padding: 0.8em 0.9em 0.6em;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.35em;
|
||||||
|
}
|
||||||
|
.link-pill {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
padding: 0.15rem 0.55rem;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background-color: var(--nord5);
|
||||||
|
color: var(--nord3);
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: transform var(--transition-fast), background-color var(--transition-fast), box-shadow var(--transition-fast), color var(--transition-fast);
|
||||||
|
}
|
||||||
|
.link-pill:hover,
|
||||||
|
.link-pill:focus-visible {
|
||||||
|
transform: scale(1.05);
|
||||||
|
background-color: var(--nord8);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.link-pill {
|
||||||
|
background-color: var(--nord0);
|
||||||
|
color: var(--nord4);
|
||||||
|
}
|
||||||
|
.link-pill:hover,
|
||||||
|
.link-pill:focus-visible {
|
||||||
|
background-color: var(--nord8);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.notes {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--nord3);
|
||||||
|
margin: 0;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.notes {
|
||||||
|
color: var(--nord4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--nord3);
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 0.3em;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.footer {
|
||||||
|
color: var(--nord4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
background: var(--nord11);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
width: 1.6em;
|
||||||
|
height: 1.6em;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity var(--transition-fast);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.card:hover .card-btn,
|
||||||
|
.card:focus-within .card-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.delete-btn {
|
||||||
|
right: 0.5em;
|
||||||
|
}
|
||||||
|
.delete-btn:hover {
|
||||||
|
background: var(--nord12);
|
||||||
|
}
|
||||||
|
.edit-btn {
|
||||||
|
right: 2.4em;
|
||||||
|
background: var(--nord10);
|
||||||
|
}
|
||||||
|
.edit-btn:hover {
|
||||||
|
background: var(--nord9);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="accent"></div>
|
||||||
|
<button class="card-btn edit-btn" onclick={() => onedit(item)} aria-label={isEnglish ? 'Edit' : 'Bearbeiten'}>✎</button>
|
||||||
|
<button class="card-btn delete-btn" onclick={() => ondelete(item._id)} aria-label={isEnglish ? 'Delete' : 'Löschen'}>✕</button>
|
||||||
|
<div class="body">
|
||||||
|
<p class="name">{item.name}</p>
|
||||||
|
{#if item.links?.length}
|
||||||
|
<div class="links">
|
||||||
|
{#each item.links as link (link.url)}
|
||||||
|
<a class="link-pill g-pill" href={link.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{link.label || getDomain(link.url)}
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if item.notes}
|
||||||
|
<p class="notes">{item.notes}</p>
|
||||||
|
{/if}
|
||||||
|
<div class="footer">
|
||||||
|
{isEnglish ? 'Added by' : 'Hinzugefügt von'} {item.addedBy}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { TranslatedRecipeType } from '$types/types';
|
import type { TranslatedRecipeType } from '$types/types';
|
||||||
import TranslationFieldComparison from './TranslationFieldComparison.svelte';
|
import TranslationFieldComparison from './TranslationFieldComparison.svelte';
|
||||||
import CreateIngredientList from './CreateIngredientList.svelte';
|
import CreateIngredientList from '$lib/components/recipes/CreateIngredientList.svelte';
|
||||||
import CreateStepList from './CreateStepList.svelte';
|
import CreateStepList from '$lib/components/recipes/CreateStepList.svelte';
|
||||||
import GenerateAltTextButton from './GenerateAltTextButton.svelte';
|
import GenerateAltTextButton from './GenerateAltTextButton.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -6,9 +6,10 @@ const LANGUAGE_CONTEXT_KEY = Symbol('language');
|
|||||||
/**
|
/**
|
||||||
* Creates or updates a language context for prayer components
|
* Creates or updates a language context for prayer components
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {'de' | 'en'} options.urlLang - The URL language (de for /glaube, en for /faith)
|
* @param {'de' | 'en'} [options.urlLang] - The URL language (de for /glaube, en for /faith)
|
||||||
|
* @param {boolean} [options.initialLatin] - Initial state for Latin/bilingual display
|
||||||
*/
|
*/
|
||||||
export function createLanguageContext({ urlLang = 'de' } = {}) {
|
export function createLanguageContext({ urlLang = 'de', initialLatin = true } = {}) {
|
||||||
// Check if context already exists (e.g., during navigation)
|
// Check if context already exists (e.g., during navigation)
|
||||||
if (hasContext(LANGUAGE_CONTEXT_KEY)) {
|
if (hasContext(LANGUAGE_CONTEXT_KEY)) {
|
||||||
const existing = getContext(LANGUAGE_CONTEXT_KEY);
|
const existing = getContext(LANGUAGE_CONTEXT_KEY);
|
||||||
@@ -17,7 +18,7 @@ export function createLanguageContext({ urlLang = 'de' } = {}) {
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showLatin = writable(true); // true = bilingual (Latin + vernacular), false = monolingual
|
const showLatin = writable(initialLatin); // true = bilingual (Latin + vernacular), false = monolingual
|
||||||
const lang = writable(urlLang); // 'de' or 'en' based on URL
|
const lang = writable(urlLang); // 'de' or 'en' based on URL
|
||||||
|
|
||||||
setContext(LANGUAGE_CONTEXT_KEY, {
|
setContext(LANGUAGE_CONTEXT_KEY, {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import "./shake.css";
|
||||||
|
|
||||||
:root{
|
:root{
|
||||||
--angle: 15deg;
|
--angle: 15deg;
|
||||||
}
|
}
|
||||||
@@ -5,10 +7,10 @@
|
|||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--red);
|
background-color: var(--red);
|
||||||
transition: 200ms;
|
transition: var(--transition-normal);
|
||||||
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
|
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 1000px;
|
border-radius: var(--radius-pill);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -26,33 +28,3 @@
|
|||||||
transition: 50ms;
|
transition: 50ms;
|
||||||
scale: 0.8 0.8;
|
scale: 0.8 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes shake{
|
|
||||||
0%{
|
|
||||||
transform: rotate(0)
|
|
||||||
scale(1,1);
|
|
||||||
}
|
|
||||||
25%{
|
|
||||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
|
||||||
transform: rotate(--angle)
|
|
||||||
scale(1.2,1.2)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
50%{
|
|
||||||
|
|
||||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
|
||||||
transform: rotate(calc(-1 * --angle))
|
|
||||||
scale(1.2,1.2);
|
|
||||||
}
|
|
||||||
74%{
|
|
||||||
|
|
||||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
|
||||||
transform: rotate(--angle)
|
|
||||||
scale(1.2, 1.2);
|
|
||||||
}
|
|
||||||
100%{
|
|
||||||
transform: rotate(0)
|
|
||||||
scale(1.2, 1.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
form{
|
|
||||||
background-color: var(--nord5);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 600px;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin-inline: auto;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding-block: 2rem;
|
|
||||||
margin-block: 2rem;
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: dark){
|
|
||||||
form{
|
|
||||||
background-color: var(--accent-dark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
form label{
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
form input{
|
|
||||||
display: block;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
form button{
|
|
||||||
background-color: var(--red);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
font-size: 1.3em;
|
|
||||||
border-radius: 1000px;
|
|
||||||
margin-top: 1em;
|
|
||||||
transition: 100ms;
|
|
||||||
}
|
|
||||||
form button:hover,
|
|
||||||
form button:focus-visible
|
|
||||||
{
|
|
||||||
scale: 1.1;
|
|
||||||
}
|
|
||||||
form p{
|
|
||||||
max-width: 400px;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
form h4{
|
|
||||||
margin-bottom:0;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 600px){
|
|
||||||
form{
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +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);
|
|
||||||
--nord6-dark: #292c31;
|
|
||||||
--accent-dark: #1f1f21;
|
|
||||||
--background-dark: #21201b;
|
|
||||||
--font-default-dark: #ffffff;
|
|
||||||
/* Shared transitions & shadows */
|
|
||||||
--transition-fast: 100ms;
|
|
||||||
--transition-normal: 200ms;
|
|
||||||
--shadow-sm: 0 0 0.4em 0.05em rgba(0,0,0,0.2);
|
|
||||||
--shadow-md: 0 0 0.5em 0.1em rgba(0,0,0,0.3);
|
|
||||||
--shadow-lg: 0 0 1em 0.1em rgba(0,0,0,0.4);
|
|
||||||
--shadow-hover: 0.1em 0.1em 0.5em 0.1em rgba(0,0,0,0.3);
|
|
||||||
--radius-pill: 1000px;
|
|
||||||
--radius-card: 20px;
|
|
||||||
--radius-sm: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:not(:visited){
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited{
|
|
||||||
color: var(--purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
a:not(:visited){
|
|
||||||
color: var(--nord8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========================================
|
|
||||||
Global Utility Classes
|
|
||||||
Use these in components to avoid CSS duplication
|
|
||||||
======================================== */
|
|
||||||
|
|
||||||
/* Pill-shaped element base */
|
|
||||||
.g-pill {
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: var(--transition-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Interactive hover/focus effects */
|
|
||||||
.g-interactive {
|
|
||||||
transition: var(--transition-fast);
|
|
||||||
}
|
|
||||||
.g-interactive:hover,
|
|
||||||
.g-interactive:focus-visible {
|
|
||||||
transform: scale(1.05);
|
|
||||||
box-shadow: var(--shadow-hover);
|
|
||||||
}
|
|
||||||
.g-interactive:focus {
|
|
||||||
scale: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Light background button (with dark mode) */
|
|
||||||
.g-btn-light {
|
|
||||||
background-color: var(--nord5);
|
|
||||||
color: var(--nord0);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.g-btn-light {
|
|
||||||
background-color: var(--nord0);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark background button */
|
|
||||||
.g-btn-dark,
|
|
||||||
.g-btn-dark:visited,
|
|
||||||
.g-btn-dark:link {
|
|
||||||
background-color: var(--nord0);
|
|
||||||
color: var(--nord6);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
}
|
|
||||||
.g-btn-dark:hover,
|
|
||||||
.g-btn-dark:focus-visible {
|
|
||||||
background-color: var(--nord1);
|
|
||||||
color: var(--nord6);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Icon badge (circular icon container) */
|
|
||||||
.g-icon-badge {
|
|
||||||
font-family: "Noto Color Emoji", emoji, sans-serif;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: var(--transition-fast);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
}
|
|
||||||
.g-icon-badge:hover,
|
|
||||||
.g-icon-badge:focus-visible {
|
|
||||||
transform: scale(1.1);
|
|
||||||
box-shadow: var(--shadow-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tag/chip styling */
|
|
||||||
.g-tag,
|
|
||||||
.g-tag:visited,
|
|
||||||
.g-tag:link {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
padding: 0.25em 1em;
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
background-color: var(--nord5);
|
|
||||||
color: var(--nord0);
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: var(--transition-fast);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
border: none;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.g-tag:hover,
|
|
||||||
.g-tag:focus-visible {
|
|
||||||
transform: scale(1.05);
|
|
||||||
background-color: var(--nord8);
|
|
||||||
box-shadow: var(--shadow-hover);
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.g-tag,
|
|
||||||
.g-tag:visited,
|
|
||||||
.g-tag:link {
|
|
||||||
background-color: var(--nord0);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.g-tag:hover,
|
|
||||||
.g-tag:focus-visible {
|
|
||||||
color: var(--nord0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'LibertineMinimal';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(/fonts/LinLibertine_minimal.woff2) format('woff2'),
|
|
||||||
url(/fonts/LinLibertine_minimal.ttf) format('truetype');
|
|
||||||
}
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
@@ -105,3 +105,105 @@ export const mysteryReferences = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// Reference for the three opening Ave Marias (Faith, Hope, Love)
|
||||||
|
export const theologicalVirtueReference = {
|
||||||
|
title: "Das Hohelied der Liebe",
|
||||||
|
reference: "1 Kor 13, 1-13"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const theologicalVirtueReferenceEnglish = {
|
||||||
|
title: "The Hymn to Love",
|
||||||
|
reference: "1 Cor 13:1-13"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const mysteryReferencesEnglish = {
|
||||||
|
lichtreichen: [
|
||||||
|
{
|
||||||
|
title: "The first Luminous Mystery: The Baptism in the Jordan.",
|
||||||
|
reference: "Mt 3:16-17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The second Luminous Mystery: The Wedding at Cana.",
|
||||||
|
reference: "Jn 2:1-5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The third Luminous Mystery: The Proclamation of the Kingdom of God.",
|
||||||
|
reference: "Mk 1:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fourth Luminous Mystery: The Transfiguration.",
|
||||||
|
reference: "Mt 17:1-2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fifth Luminous Mystery: The Institution of the Holy Eucharist.",
|
||||||
|
reference: "Mt 26:26"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
freudenreich: [
|
||||||
|
{
|
||||||
|
title: "The first Joyful Mystery: The Annunciation of the Archangel Gabriel to the Virgin Mary.",
|
||||||
|
reference: "Lk 1:26-27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The second Joyful Mystery: The Visitation of Mary to Elizabeth.",
|
||||||
|
reference: "Lk 1:39-42"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The third Joyful Mystery: The Nativity of Jesus in the Stable at Bethlehem.",
|
||||||
|
reference: "Lk 2:1-7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fourth Joyful Mystery: The Presentation of Jesus in the Temple.",
|
||||||
|
reference: "Lk 2:21-24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fifth Joyful Mystery: The Finding of Jesus in the Temple.",
|
||||||
|
reference: "Lk 2:41-47"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schmerzhaften: [
|
||||||
|
{
|
||||||
|
title: "The first Sorrowful Mystery: The Agony of Jesus in the Garden.",
|
||||||
|
reference: "Mt 26:36-39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The second Sorrowful Mystery: The Scourging at the Pillar.",
|
||||||
|
reference: "Mt 27:26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The third Sorrowful Mystery: The Crowning with Thorns.",
|
||||||
|
reference: "Mt 27:27-29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fourth Sorrowful Mystery: Jesus Carries the Heavy Cross.",
|
||||||
|
reference: "Mk 15:21-22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fifth Sorrowful Mystery: The Crucifixion of Jesus.",
|
||||||
|
reference: "Lk 23:33-46"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
glorreichen: [
|
||||||
|
{
|
||||||
|
title: "The first Glorious Mystery: The Resurrection of Jesus.",
|
||||||
|
reference: "Lk 24:1-6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The second Glorious Mystery: The Ascension of Jesus.",
|
||||||
|
reference: "Mk 16:19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The third Glorious Mystery: The Descent of the Holy Spirit.",
|
||||||
|
reference: "Acts 2:1-4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fourth Glorious Mystery: The Assumption of Mary into Heaven.",
|
||||||
|
reference: "Lk 1:48-49"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The fifth Glorious Mystery: The Coronation of Mary as Queen of Heaven and Earth.",
|
||||||
|
reference: "Apo 12:1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as const;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user