diff --git a/_template.scss b/_template.scss
index 73d2639..006419f 100644
--- a/_template.scss
+++ b/_template.scss
@@ -15,15 +15,14 @@
//$highlight-color: #fff318;
//$shadow: 3px 7px 26px -5px rgba(0, 0, 0, 0.15);
//$overlay: rgba(63, 63, 63, 0.82);
-$base-color: #016699;
-$shade1: #67A3C2;
-$shade2: #DDECF7;
-//$shade3: #E3E9E8;
-$shade3: #eff6ff;
-$contrast-color: #CE490F;
-$contrast-shade1: #DA764B;
+$base-color: var(--base-color);
+$shade1: var(--shade1);
+$shade2: var(--shade2);
+$shade3: var(--shade3);
+$contrast-color: var(--contrast-color);
+$contrast-shade1: var(--contrast-shade1);
$text-color: #000000;
-$border-radius: 13px;
+$border-radius: var(--border-radius);
$white: #ffffff;
$light-grey: #f3f3f3;
diff --git a/src/app/(home)/globals.css b/src/app/(home)/globals.css
index db1abba..b7cb683 100644
--- a/src/app/(home)/globals.css
+++ b/src/app/(home)/globals.css
@@ -1,3 +1,13 @@
+:root {
+ --base-color: #016699;
+ --shade1: #67A3C2;
+ --shade2: #DDECF7;
+ --shade3: #eff6ff;
+ --contrast-color: #CE490F;
+ --contrast-shade1: #DA764B;
+ --border-radius: 13px;
+}
+
html,
body {
margin: 0;
diff --git a/src/app/(home)/layout.tsx b/src/app/(home)/layout.tsx
index 4ea8ed0..ae83975 100644
--- a/src/app/(home)/layout.tsx
+++ b/src/app/(home)/layout.tsx
@@ -3,8 +3,9 @@ import './globals.css'
import { DynamicMenu, Menu } from '@/components/Menu/Menu'
import { Footer } from '@/compositions/Footer/Footer'
import { comment } from '@/app/(home)/layout-comment'
-import { defaultFont } from '@/assets/fonts'
+import { FONT_MAP, getFont } from '@/assets/fonts'
import { siteConfig } from '@/config/site'
+import { fetchDesign } from '@/fetch/design'
export const metadata: Metadata = {
title: {
@@ -24,15 +25,53 @@ export const metadata: Metadata = {
},
}
-export default function RootLayout({
+const DESIGN_DEFAULTS = {
+ baseColor: '#016699',
+ shade1: '#67A3C2',
+ shade2: '#DDECF7',
+ shade3: '#eff6ff',
+ contrastColor: '#CE490F',
+ contrastShade1: '#DA764B',
+ borderRadius: '13px',
+ defaultFont: 'cairo',
+ headerFont: 'faustina',
+}
+
+export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
+ let design
+ try {
+ design = await fetchDesign()
+ } catch {
+ design = null
+ }
+ const selectedDefaultFont = getFont(
+ design?.defaultFont || DESIGN_DEFAULTS.defaultFont,
+ FONT_MAP.cairo,
+ )
+ const selectedHeaderFont = getFont(
+ design?.headerFont || DESIGN_DEFAULTS.headerFont,
+ FONT_MAP.faustina,
+ )
+
+ const themeStyle = {
+ '--base-color': design?.baseColor || DESIGN_DEFAULTS.baseColor,
+ '--shade1': design?.shade1 || DESIGN_DEFAULTS.shade1,
+ '--shade2': design?.shade2 || DESIGN_DEFAULTS.shade2,
+ '--shade3': design?.shade3 || DESIGN_DEFAULTS.shade3,
+ '--contrast-color': design?.contrastColor || DESIGN_DEFAULTS.contrastColor,
+ '--contrast-shade1':
+ design?.contrastShade1 || DESIGN_DEFAULTS.contrastShade1,
+ '--border-radius': design?.borderRadius || DESIGN_DEFAULTS.borderRadius,
+ '--header-font': selectedHeaderFont.style.fontFamily,
+ } as React.CSSProperties
return (
-
+
diff --git a/src/assets/fontOptions.ts b/src/assets/fontOptions.ts
new file mode 100644
index 0000000..9443a80
--- /dev/null
+++ b/src/assets/fontOptions.ts
@@ -0,0 +1,15 @@
+export const FONT_OPTIONS = [
+ { label: 'Cairo (Sans-Serif)', value: 'cairo' },
+ { label: 'Roboto (Sans-Serif)', value: 'roboto' },
+ { label: 'Open Sans (Sans-Serif)', value: 'openSans' },
+ { label: 'Lato (Sans-Serif)', value: 'lato' },
+ { label: 'Nunito (Sans-Serif)', value: 'nunito' },
+ { label: 'Raleway (Sans-Serif)', value: 'raleway' },
+ { label: 'Faustina (Serif)', value: 'faustina' },
+ { label: 'Merriweather (Serif)', value: 'merriweather' },
+ { label: 'Source Sans 3 (Sans-Serif)', value: 'sourceSans3' },
+ { label: 'Playfair Display (Serif)', value: 'playfairDisplay' },
+ { label: 'Lora (Serif)', value: 'lora' },
+ { label: 'Crimson Text (Serif)', value: 'crimsonText' },
+ { label: 'EB Garamond (Serif)', value: 'ebGaramond' },
+] as const
diff --git a/src/assets/fonts.ts b/src/assets/fonts.ts
index 1d978f7..ea446a9 100644
--- a/src/assets/fonts.ts
+++ b/src/assets/fonts.ts
@@ -1,11 +1,84 @@
-import { Cairo, Faustina } from 'next/font/google'
+import {
+ Cairo,
+ Faustina,
+ Merriweather,
+ Nunito,
+ Open_Sans,
+ Playfair_Display,
+ Raleway,
+ Roboto,
+ Source_Sans_3,
+ Crimson_Text,
+ EB_Garamond, Lato,
+ Lora
+} from 'next/font/google'
+import { NextFont } from 'next/dist/compiled/@next/font'
-export const headerFont = Faustina({
+const cairo = Cairo({
+ subsets: ['latin'],
+ weight: ['300', '400'],
+ display: 'swap',
+})
+const faustina = Faustina({ subsets: ['latin'], display: 'swap' })
+const lato = Lato({
+ subsets: ['latin'],
+ weight: ['300', '400', '700'],
+ display: 'swap'
+})
+const merriweather = Merriweather({
+ subsets: ['latin'],
+ weight: ['300', '400', '700'],
+ display: 'swap',
+})
+const nunito = Nunito({ subsets: ['latin'], display: 'swap' })
+const openSans = Open_Sans({ subsets: ['latin'], display: 'swap' })
+const playfairDisplay = Playfair_Display({
subsets: ['latin'],
display: 'swap',
})
-export const defaultFont = Cairo({
+const raleway = Raleway({ subsets: ['latin'], display: 'swap' })
+const roboto = Roboto({
subsets: ['latin'],
- weight: ['400', '300'],
+ weight: ['300', '400', '700'],
display: 'swap',
-})
\ No newline at end of file
+})
+const sourceSans3 = Source_Sans_3({ subsets: ['latin'], display: 'swap' })
+const crimsonText = Crimson_Text({
+ subsets: ['latin'],
+ weight: ['400', '600', '700'],
+ display: 'swap',
+})
+const ebGaramond = EB_Garamond({ subsets: ['latin'], display: 'swap' })
+
+const lora = Lora({
+ subsets: ['latin'],
+ weight: ['400', '500', '600', '700'],
+ display: 'swap',
+})
+
+export const FONT_MAP: Record = {
+ cairo,
+ faustina,
+ lato,
+ merriweather,
+ nunito,
+ openSans,
+ playfairDisplay,
+ raleway,
+ roboto,
+ sourceSans3,
+ crimsonText,
+ ebGaramond,
+ lora
+}
+
+export const defaultFont = cairo
+export const headerFont = faustina
+
+export function getFont(
+ key: string | null | undefined,
+ fallback: NextFont,
+): NextFont {
+ if (!key) return fallback
+ return FONT_MAP[key] ?? fallback
+}
diff --git a/src/components/Banner/Banner.tsx b/src/components/Banner/Banner.tsx
index 54c5b76..673442b 100644
--- a/src/components/Banner/Banner.tsx
+++ b/src/components/Banner/Banner.tsx
@@ -1,8 +1,5 @@
import { Logo } from '@/components/Logo/Logo'
import styles from "./styles.module.scss"
-import classNames from 'classnames'
-
-import { headerFont } from '@/assets/fonts'
export interface BannerProps {
textLine1?: string | null
@@ -45,7 +42,7 @@ export const Banner = ({
-
+
{textLine1 &&
{textLine1}
}
{textLine2 &&
{textLine2}
}
{textLine3 &&
{textLine3}
}
diff --git a/src/components/Banner/styles.module.scss b/src/components/Banner/styles.module.scss
index 9633367..c0b100b 100644
--- a/src/components/Banner/styles.module.scss
+++ b/src/components/Banner/styles.module.scss
@@ -19,6 +19,7 @@
.nameContainer {
opacity: 1;
color: $white;
+ font-family: var(--header-font);
position: absolute;
bottom: 50px;
right: 0;
diff --git a/src/components/Classifieds/Ad.tsx b/src/components/Classifieds/Ad.tsx
index ce69791..c4d1a22 100644
--- a/src/components/Classifieds/Ad.tsx
+++ b/src/components/Classifieds/Ad.tsx
@@ -5,7 +5,6 @@ import styles from '@/components/Classifieds/styles.module.scss'
import { useState } from 'react'
import { SerializedEditorState } from 'lexical'
import { RichText } from '@payloadcms/richtext-lexical/react'
-import { headerFont } from '@/assets/fonts'
type AdProps = {
text: SerializedEditorState,
@@ -18,7 +17,7 @@ export const Ad = ({text, contact}: AdProps) => {
return (
<>
-
setDisplayContact(!displayContact)}>
+
setDisplayContact(!displayContact)}>
diff --git a/src/components/Classifieds/styles.module.scss b/src/components/Classifieds/styles.module.scss
index dba2c49..7adbd72 100644
--- a/src/components/Classifieds/styles.module.scss
+++ b/src/components/Classifieds/styles.module.scss
@@ -10,6 +10,7 @@
}
.ad {
+ font-family: var(--header-font);
display: flex;
flex-direction: column;
justify-content: space-between;
diff --git a/src/components/MegaMenu/MegaMenu.tsx b/src/components/MegaMenu/MegaMenu.tsx
index 6ddef4b..70eccf6 100644
--- a/src/components/MegaMenu/MegaMenu.tsx
+++ b/src/components/MegaMenu/MegaMenu.tsx
@@ -1,6 +1,5 @@
import styles from "./styles.module.scss"
import Link from 'next/link'
-import { headerFont } from '@/assets/fonts'
type MegaMenuProps = {
bibleText: string,
@@ -63,7 +62,7 @@ export const MegaMenu = ({ bibleText, bibleBook, groups, onItemClick }: MegaMenu
return (
-
+
{bibleText}
diff --git a/src/components/MegaMenu/styles.module.scss b/src/components/MegaMenu/styles.module.scss
index e6bdcbe..3ef15ed 100644
--- a/src/components/MegaMenu/styles.module.scss
+++ b/src/components/MegaMenu/styles.module.scss
@@ -17,6 +17,10 @@
justify-content: flex-end;
}
+.headerFont {
+ font-family: var(--header-font);
+}
+
.book {
text-align: right;
font-size: 14px;
diff --git a/src/components/Menu/styles.module.scss b/src/components/Menu/styles.module.scss
index 7b3ff3d..c83bd69 100644
--- a/src/components/Menu/styles.module.scss
+++ b/src/components/Menu/styles.module.scss
@@ -10,11 +10,12 @@
left: 0;
top: 0;
width: 100%;
+ height: 62px;
box-sizing: border-box;
z-index: 1;
backdrop-filter: blur(8px);
font-size: 18px;
- align-items: baseline;
+ align-items: center;
}
.navMobile {
diff --git a/src/components/Testimony/Testimony.tsx b/src/components/Testimony/Testimony.tsx
index 5c33186..5438874 100644
--- a/src/components/Testimony/Testimony.tsx
+++ b/src/components/Testimony/Testimony.tsx
@@ -2,8 +2,6 @@ import styles from './styles.module.scss'
import { Container } from '@/components/Container/Container'
import classNames from 'classnames'
-import { headerFont } from '@/assets/fonts'
-
type TestimonyProps = {
name?: string
testimony: string
@@ -15,7 +13,7 @@ export const Testimony = ({ name, testimony, occupation }: TestimonyProps) => {
-
+
{testimony}
{typeof name === 'string' &&
diff --git a/src/components/Testimony/styles.module.scss b/src/components/Testimony/styles.module.scss
index 3b8ace5..26bc5dd 100644
--- a/src/components/Testimony/styles.module.scss
+++ b/src/components/Testimony/styles.module.scss
@@ -8,6 +8,7 @@
}
.testimonyText {
+ font-family: var(--header-font);
font-size: 33px;
color: $base-color;
text-align: center;
diff --git a/src/components/Title/Title.tsx b/src/components/Title/Title.tsx
index 7f92623..c97f4c1 100644
--- a/src/components/Title/Title.tsx
+++ b/src/components/Title/Title.tsx
@@ -1,8 +1,6 @@
import styles from "./styles.module.scss";
import classNames from 'classnames'
-import { headerFont } from '@/assets/fonts'
-
type TitleProps = {
title: string;
subtitle?: string;
@@ -27,7 +25,7 @@ export const Title = ({title, subtitle, align = "left", size = "lg", fontStyle =
[styles.small]: size === "sm",
[styles.left]: align === "left",
[styles.center]: align === "center",
- [headerFont.className]: fontStyle == "serif",
+ [styles.headerFont]: fontStyle == "serif",
[styles.cancelled]: cancelled
})}>{title}
{subtitle &&
@@ -39,7 +37,7 @@ export const Title = ({title, subtitle, align = "left", size = "lg", fontStyle =
[styles.small]: ["xl", "lg"].includes(size),
[styles.left]: align === "left",
[styles.center]: align === "center",
- [headerFont.className]: fontStyle == "sans-serif"
+ [styles.headerFont]: fontStyle == "sans-serif"
})}>
{subtitle}
diff --git a/src/components/Title/styles.module.scss b/src/components/Title/styles.module.scss
index fe398f5..bd9aee8 100644
--- a/src/components/Title/styles.module.scss
+++ b/src/components/Title/styles.module.scss
@@ -52,6 +52,10 @@
text-align: center;
}
+.headerFont {
+ font-family: var(--header-font);
+}
+
.cancelled {
text-decoration: line-through;
}
diff --git a/src/compositions/Footer/styles.module.scss b/src/compositions/Footer/styles.module.scss
index b46debd..7d52581 100644
--- a/src/compositions/Footer/styles.module.scss
+++ b/src/compositions/Footer/styles.module.scss
@@ -20,7 +20,7 @@
}
.list li::marker {
- color: rgba($base-color, 0.6);
+ color: color-mix(in srgb, $base-color 60%, transparent);
}
.list li {
diff --git a/src/fetch/design.ts b/src/fetch/design.ts
new file mode 100644
index 0000000..122d589
--- /dev/null
+++ b/src/fetch/design.ts
@@ -0,0 +1,13 @@
+import { Design } from '@/payload-types'
+
+export async function fetchDesign(): Promise
{
+ const res = await fetch('http://localhost:3000/api/globals/design', {
+ next: { tags: ['design'] },
+ })
+
+ if (!res.ok) {
+ throw new Error('Could not fetch design')
+ }
+
+ return res.json()
+}
diff --git a/src/globals/Design.ts b/src/globals/Design.ts
new file mode 100644
index 0000000..499a1fc
--- /dev/null
+++ b/src/globals/Design.ts
@@ -0,0 +1,118 @@
+import { GlobalConfig } from 'payload'
+import { isAdmin } from '@/collections/access/admin'
+import { revalidateTag } from 'next/cache'
+import { FONT_OPTIONS } from '@/assets/fontOptions'
+
+const hexColorValidation = (value: string | null | undefined) => {
+ if (!value) return true
+ if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value)) return true
+ return 'Bitte geben Sie einen gültigen Hex-Farbwert ein (z.B. #016699)'
+}
+
+export const DesignGlobal: GlobalConfig = {
+ slug: 'design',
+ label: {
+ de: 'Design',
+ },
+ admin: {
+ description:
+ 'Hier können Sie die Farben und das Erscheinungsbild der Website konfigurieren.',
+ },
+ fields: [
+ {
+ type: 'collapsible',
+ label: 'Farben',
+ fields: [
+ {
+ name: 'baseColor',
+ type: 'text',
+ label: { de: 'Grundfarbe' },
+ defaultValue: '#016699',
+ validate: hexColorValidation,
+ },
+ {
+ name: 'shade1',
+ type: 'text',
+ label: { de: 'Farbton 1' },
+ defaultValue: '#67A3C2',
+ validate: hexColorValidation,
+ },
+ {
+ name: 'shade2',
+ type: 'text',
+ label: { de: 'Farbton 2' },
+ defaultValue: '#DDECF7',
+ validate: hexColorValidation,
+ },
+ {
+ name: 'shade3',
+ type: 'text',
+ label: { de: 'Farbton 3' },
+ defaultValue: '#eff6ff',
+ validate: hexColorValidation,
+ },
+ {
+ name: 'contrastColor',
+ type: 'text',
+ label: { de: 'Kontrastfarbe' },
+ defaultValue: '#CE490F',
+ validate: hexColorValidation,
+ },
+ {
+ name: 'contrastShade1',
+ type: 'text',
+ label: { de: 'Kontrastfarbton 1' },
+ defaultValue: '#DA764B',
+ validate: hexColorValidation,
+ },
+ ],
+ },
+ {
+ type: 'collapsible',
+ label: 'Schriftarten',
+ fields: [
+ {
+ name: 'defaultFont',
+ type: 'select',
+ label: { de: 'Standardschrift' },
+ defaultValue: 'cairo',
+ options: FONT_OPTIONS.map((o) => ({
+ label: o.label,
+ value: o.value,
+ })),
+ admin: {
+ description:
+ 'Die Hauptschrift für den gesamten Text der Website.',
+ },
+ },
+ {
+ name: 'headerFont',
+ type: 'select',
+ label: { de: 'Überschriftenschrift' },
+ defaultValue: 'faustina',
+ options: FONT_OPTIONS.map((o) => ({
+ label: o.label,
+ value: o.value,
+ })),
+ admin: {
+ description:
+ 'Die Schrift für Überschriften und hervorgehobenen Text.',
+ },
+ },
+ ],
+ },
+ {
+ name: 'borderRadius',
+ type: 'text',
+ label: { de: 'Eckenradius' },
+ defaultValue: '13px',
+ },
+ ],
+ access: {
+ read: () => true,
+ update: isAdmin(),
+ },
+ hooks: {
+ afterChange: [() => revalidateTag('design')],
+ },
+}
diff --git a/src/payload-types.ts b/src/payload-types.ts
index 56c2395..14ea947 100644
--- a/src/payload-types.ts
+++ b/src/payload-types.ts
@@ -126,10 +126,12 @@ export interface Config {
globals: {
menu: Menu;
footer: Footer;
+ design: Design;
};
globalsSelect: {
menu: MenuSelect | MenuSelect;
footer: FooterSelect | FooterSelect;
+ design: DesignSelect | DesignSelect;
};
locale: null;
user: User & {
@@ -1896,6 +1898,64 @@ export interface Footer {
updatedAt?: string | null;
createdAt?: string | null;
}
+/**
+ * Hier können Sie die Farben und das Erscheinungsbild der Website konfigurieren.
+ *
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "design".
+ */
+export interface Design {
+ id: string;
+ baseColor?: string | null;
+ shade1?: string | null;
+ shade2?: string | null;
+ shade3?: string | null;
+ contrastColor?: string | null;
+ contrastShade1?: string | null;
+ /**
+ * Die Hauptschrift für den gesamten Text der Website.
+ */
+ defaultFont?:
+ | (
+ | 'cairo'
+ | 'roboto'
+ | 'openSans'
+ | 'lato'
+ | 'nunito'
+ | 'raleway'
+ | 'faustina'
+ | 'merriweather'
+ | 'sourceSans3'
+ | 'playfairDisplay'
+ | 'lora'
+ | 'crimsonText'
+ | 'ebGaramond'
+ )
+ | null;
+ /**
+ * Die Schrift für Überschriften und hervorgehobenen Text.
+ */
+ headerFont?:
+ | (
+ | 'cairo'
+ | 'roboto'
+ | 'openSans'
+ | 'lato'
+ | 'nunito'
+ | 'raleway'
+ | 'faustina'
+ | 'merriweather'
+ | 'sourceSans3'
+ | 'playfairDisplay'
+ | 'lora'
+ | 'crimsonText'
+ | 'ebGaramond'
+ )
+ | null;
+ borderRadius?: string | null;
+ updatedAt?: string | null;
+ createdAt?: string | null;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "menu_select".
@@ -1999,6 +2059,24 @@ export interface FooterSelect {
createdAt?: T;
globalType?: T;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "design_select".
+ */
+export interface DesignSelect {
+ baseColor?: T;
+ shade1?: T;
+ shade2?: T;
+ shade3?: T;
+ contrastColor?: T;
+ contrastShade1?: T;
+ defaultFont?: T;
+ headerFont?: T;
+ borderRadius?: T;
+ updatedAt?: T;
+ createdAt?: T;
+ globalType?: T;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
diff --git a/src/payload.config.ts b/src/payload.config.ts
index b7f23cf..6f026cb 100644
--- a/src/payload.config.ts
+++ b/src/payload.config.ts
@@ -38,6 +38,7 @@ import { LiturgicalCalendar } from '@/collections/LiturgicalCalendar'
import { Classifieds } from '@/collections/Classifieds'
import { MenuGlobal } from '@/globals/Menu'
import { FooterGlobal } from '@/globals/Footer'
+import { DesignGlobal } from '@/globals/Design'
import { Magazine } from '@/collections/Magazine'
import { DonationForms } from '@/collections/DonationForms'
import { Pages } from '@/collections/Pages'
@@ -105,6 +106,7 @@ export default buildConfig({
globals: [
MenuGlobal,
FooterGlobal,
+ DesignGlobal,
],
graphQL: {
disable: true