add contact form and mailing

Co-authored-by: Constantin Simonis <constantin@simonis.lol>
Reviewed-on: http://git.simonis.lol/sites/abiball/pulls/6
Reviewed-by: Constantin Simonis <constantin@simonis.lol>
Co-authored-by: Jan-Marlon Leibl <jleibl@proton.me>
Co-committed-by: Jan-Marlon Leibl <jleibl@proton.me>
This commit is contained in:
Hop In, I Have Puppies AND WiFi 2025-01-23 17:44:47 +00:00 committed by Constantin Simonis
parent dc64bc0a22
commit eac7c863d1
23 changed files with 1876 additions and 7 deletions

5
.env
View File

@ -18,3 +18,8 @@
APP_ENV=dev
APP_SECRET=
###< symfony/framework-bundle ###
###> symfony/mailer ###
MAILER_DSN=smtp://${MAILER_USER:-null}:${MAILER_PASSWORD:-null}@${MAILER_HOST:-localhost}:${MAILER_PORT:-1025}
CONTACT_MAIL=${CONTACT_MAIL:-contact@localhost}
###< symfony/mailer ###

View File

@ -7,12 +7,15 @@
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"php-flasher/flasher-noty-symfony": "^2.1",
"symfony/asset": "7.2.*",
"symfony/asset-mapper": "7.2.*",
"symfony/console": "7.2.*",
"symfony/dotenv": "7.2.*",
"symfony/flex": "^2",
"symfony/form": "7.2.*",
"symfony/framework-bundle": "7.2.*",
"symfony/mailer": "7.2.*",
"symfony/runtime": "7.2.*",
"symfony/twig-bundle": "7.2.*",
"symfony/yaml": "7.2.*",
@ -68,10 +71,13 @@
"extra": {
"symfony": {
"allow-contrib": false,
"require": "7.2.*"
"require": "7.2.*",
"docker": false
}
},
"require-dev": {
"symfony/maker-bundle": "^1.62"
"symfony/maker-bundle": "^1.62",
"symfony/stopwatch": "7.2.*",
"symfony/web-profiler-bundle": "7.2.*"
}
}

1299
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,4 +6,7 @@ return [
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Flasher\Symfony\FlasherSymfonyBundle::class => ['all' => true],
Flasher\Noty\Symfony\FlasherNotySymfonyBundle::class => ['all' => true],
];

11
config/packages/csrf.yaml Normal file
View File

@ -0,0 +1,11 @@
# Enable stateless CSRF protection for forms and logins/logouts
framework:
form:
csrf_protection:
token_id: submit
csrf_protection:
stateless_token_ids:
- submit
- authenticate
- logout

View File

@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'

View File

@ -0,0 +1,17 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler:
only_exceptions: false
collect_serializer_data: true
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("noty")):"function"==typeof define&&define.amd?define(["@flasher/flasher","noty"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Noty=t(e.flasher,e.Noty)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,o){if("object"==typeof e?(e=(o=e).type,t=o.message,s=o.title):"object"==typeof t?(t=(o=t).message,s=o.title):"object"==typeof s&&(s=(o=s).title),void 0===t)throw new Error("message option is required");const n={type:e,message:t,title:s||e,options:o||{},metadata:{plugin:""}};this.renderOptions(o||{}),this.renderEnvelopes([n])}}const o=new class extends s{renderEnvelopes(e){e.forEach((e=>{var s;const o=Object.assign({text:e.message,type:e.type},e.options),n=new t(o);n.show(),null===(s=n.layoutDom)||void 0===s||(s.dataset.turboTemporary="")}))}renderOptions(e){t.overrideDefaults(Object.assign({timeout:e.timeout||5e3},e))}};return e.addPlugin("noty",o),o}));

2
public/vendor/flasher/flasher.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1
public/vendor/flasher/flasher.min.js vendored Normal file

File diff suppressed because one or more lines are too long

8
public/vendor/flasher/manifest.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"/vendor/flasher/flasher.min.css": "/vendor/flasher/flasher.min.css?id=7a96e40c68626198d5128ad2fb5d77e0",
"/vendor/flasher/flasher.min.js": "/vendor/flasher/flasher.min.js?id=9a255a6680873c0d5fc3d394a2ba3195",
"/vendor/flasher/mint.css": "/vendor/flasher/mint.css?id=348f135fff639305dde0005c647c1d20",
"/vendor/flasher/noty.css": "/vendor/flasher/noty.css?id=bf51111a785e04cc8c86a7786e855484",
"/vendor/flasher/noty.min.js": "/vendor/flasher/noty.min.js?id=840a31ddb720ff391cfc386c009d3422",
"/vendor/flasher/flasher-noty.min.js": "/vendor/flasher/flasher-noty.min.js?id=d7d160fe2043e65250dfcaa5590d2e28"
}

37
public/vendor/flasher/mint.css vendored Normal file
View File

@ -0,0 +1,37 @@
.noty_theme__mint.noty_bar {
margin: 4px 0;
overflow: hidden;
border-radius: 2px;
position: relative; }
.noty_theme__mint.noty_bar .noty_body {
padding: 10px;
font-size: 14px; }
.noty_theme__mint.noty_bar .noty_buttons {
padding: 10px; }
.noty_theme__mint.noty_type__alert,
.noty_theme__mint.noty_type__notification {
background-color: #fff;
border-bottom: 1px solid #D1D1D1;
color: #2F2F2F; }
.noty_theme__mint.noty_type__warning {
background-color: #FFAE42;
border-bottom: 1px solid #E89F3C;
color: #fff; }
.noty_theme__mint.noty_type__error {
background-color: #DE636F;
border-bottom: 1px solid #CA5A65;
color: #fff; }
.noty_theme__mint.noty_type__info,
.noty_theme__mint.noty_type__information {
background-color: #7F7EFF;
border-bottom: 1px solid #7473E8;
color: #fff; }
.noty_theme__mint.noty_type__success {
background-color: #AFC765;
border-bottom: 1px solid #A0B55C;
color: #fff; }

216
public/vendor/flasher/noty.css vendored Normal file
View File

@ -0,0 +1,216 @@
.noty_layout_mixin, #noty_layout__top, #noty_layout__topLeft, #noty_layout__topCenter, #noty_layout__topRight, #noty_layout__bottom, #noty_layout__bottomLeft, #noty_layout__bottomCenter, #noty_layout__bottomRight, #noty_layout__center, #noty_layout__centerLeft, #noty_layout__centerRight {
position: fixed;
margin: 0;
padding: 0;
z-index: 9999999;
-webkit-transform: translateZ(0) scale(1, 1);
transform: translateZ(0) scale(1, 1);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-font-smoothing: subpixel-antialiased;
filter: blur(0);
-webkit-filter: blur(0);
max-width: 90%; }
#noty_layout__top {
top: 0;
left: 5%;
width: 90%; }
#noty_layout__topLeft {
top: 20px;
left: 20px;
width: 325px; }
#noty_layout__topCenter {
top: 5%;
left: 50%;
width: 325px;
-webkit-transform: translate(-webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__topRight {
top: 20px;
right: 20px;
width: 325px; }
#noty_layout__bottom {
bottom: 0;
left: 5%;
width: 90%; }
#noty_layout__bottomLeft {
bottom: 20px;
left: 20px;
width: 325px; }
#noty_layout__bottomCenter {
bottom: 5%;
left: 50%;
width: 325px;
-webkit-transform: translate(-webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__bottomRight {
bottom: 20px;
right: 20px;
width: 325px; }
#noty_layout__center {
top: 50%;
left: 50%;
width: 325px;
-webkit-transform: translate(-webkit-calc(-50% - .5px), -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(calc(-50% - .5px), calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__centerLeft {
top: 50%;
left: 20px;
width: 325px;
-webkit-transform: translate(0, -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(0, calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__centerRight {
top: 50%;
right: 20px;
width: 325px;
-webkit-transform: translate(0, -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(0, calc(-50% - .5px)) translateZ(0) scale(1, 1); }
.noty_progressbar {
display: none; }
.noty_has_timeout.noty_has_progressbar .noty_progressbar {
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 3px;
width: 100%;
background-color: #646464;
opacity: 0.2;
filter: alpha(opacity=10); }
.noty_bar {
-webkit-backface-visibility: hidden;
-webkit-transform: translate(0, 0) translateZ(0) scale(1, 1);
-ms-transform: translate(0, 0) scale(1, 1);
transform: translate(0, 0) scale(1, 1);
-webkit-font-smoothing: subpixel-antialiased;
overflow: hidden; }
.noty_effects_open {
opacity: 0;
-webkit-transform: translate(50%);
-ms-transform: translate(50%);
transform: translate(50%);
-webkit-animation: noty_anim_in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
animation: noty_anim_in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards; }
.noty_effects_close {
-webkit-animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards; }
.noty_fix_effects_height {
-webkit-animation: noty_anim_height 75ms ease-out;
animation: noty_anim_height 75ms ease-out; }
.noty_close_with_click {
cursor: pointer; }
.noty_close_button {
position: absolute;
top: 2px;
right: 2px;
font-weight: bold;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 2px;
cursor: pointer;
-webkit-transition: all .2s ease-out;
transition: all .2s ease-out; }
.noty_close_button:hover {
background-color: rgba(0, 0, 0, 0.1); }
.noty_modal {
position: fixed;
width: 100%;
height: 100%;
background-color: #000;
z-index: 10000;
opacity: .3;
left: 0;
top: 0; }
.noty_modal.noty_modal_open {
opacity: 0;
-webkit-animation: noty_modal_in .3s ease-out;
animation: noty_modal_in .3s ease-out; }
.noty_modal.noty_modal_close {
-webkit-animation: noty_modal_out .3s ease-out;
animation: noty_modal_out .3s ease-out;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards; }
@-webkit-keyframes noty_modal_in {
100% {
opacity: .3; } }
@keyframes noty_modal_in {
100% {
opacity: .3; } }
@-webkit-keyframes noty_modal_out {
100% {
opacity: 0; } }
@keyframes noty_modal_out {
100% {
opacity: 0; } }
@keyframes noty_modal_out {
100% {
opacity: 0; } }
@-webkit-keyframes noty_anim_in {
100% {
-webkit-transform: translate(0);
transform: translate(0);
opacity: 1; } }
@keyframes noty_anim_in {
100% {
-webkit-transform: translate(0);
transform: translate(0);
opacity: 1; } }
@-webkit-keyframes noty_anim_out {
100% {
-webkit-transform: translate(50%);
transform: translate(50%);
opacity: 0; } }
@keyframes noty_anim_out {
100% {
-webkit-transform: translate(50%);
transform: translate(50%);
opacity: 0; } }
@-webkit-keyframes noty_anim_height {
100% {
height: 0; } }
@keyframes noty_anim_height {
100% {
height: 0; } }
/*# sourceMappingURL=noty.css.map*/

17
public/vendor/flasher/noty.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
<?php
namespace App\Controller;
use App\DataObjects\ContactData;
use App\Form\ContactForm;
use App\Service\ContactService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ContactController extends AbstractController
{
#[Route(path: '/contact', name: 'app_contact', methods: ['GET', 'POST'])]
public function index(Request $request, ContactService $service): Response
{
$data = new ContactData();
$form = $this->createForm(ContactForm::class, $data)->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$service->send($data);
noty()->success('Deine Nachricht wurde erfolgreich versendet!');
return $this->redirectToRoute('app_contact');
}
return $this->render('contact/index.html.twig', [
'form' => $form->createView(),
]);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\DataObjects;
class ContactData
{
public function __construct(
public string $email = '',
public string $message = '',
public ?string $phone = null,
) {
}
}

19
src/Form/ContactForm.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class ContactForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', EmailType::class)
->add('message', TextareaType::class)
->add('phone', TextType::class);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Service;
use App\DataObjects\ContactData;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Mailer\MailerInterface;
final readonly class ContactService
{
public function __construct(
private MailerInterface $mailer,
#[Autowire(env: 'CONTACT_MAIL')]
private string $contactEmail,
){
}
public function send(ContactData $data): void
{
$mail = (new TemplatedEmail())
->htmlTemplate('email/contact.html.twig')
->context(['data' => $data])
->subject('Kontakt aufnahme')
->from($data->email)
->to($this->contactEmail);
$this->mailer->send($mail);
}
}

View File

@ -1,4 +1,10 @@
{
"php-flasher/flasher-noty-symfony": {
"version": "v2.1.2"
},
"php-flasher/flasher-symfony": {
"version": "v2.1.2"
},
"symfony/asset-mapper": {
"version": "7.2",
"recipe": {
@ -39,6 +45,18 @@
".env.dev"
]
},
"symfony/form": {
"version": "7.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.2",
"ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
},
"files": [
"config/packages/csrf.yaml"
]
},
"symfony/framework-bundle": {
"version": "7.2",
"recipe": {
@ -58,6 +76,18 @@
"src/Kernel.php"
]
},
"symfony/mailer": {
"version": "7.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "4.3",
"ref": "09051cfde49476e3c12cd3a0e44289ace1c75a4f"
},
"files": [
"config/packages/mailer.yaml"
]
},
"symfony/maker-bundle": {
"version": "1.62",
"recipe": {
@ -93,6 +123,19 @@
"templates/base.html.twig"
]
},
"symfony/web-profiler-bundle": {
"version": "7.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.1",
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
},
"files": [
"config/packages/web_profiler.yaml",
"config/routes/web_profiler.yaml"
]
},
"symfonycasts/tailwind-bundle": {
"version": "v0.7.1"
},

View File

@ -0,0 +1,55 @@
{% extends 'base.html.twig' %}
{% block title %}Kontakt{% endblock %}
{% block body %}
<div class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 relative overflow-hidden">
<header class="w-full bg-white/90 backdrop-blur-md shadow-lg fixed top-0 z-50 border-b border-gray-100">
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4 flex justify-between items-center">
<a href="{{ path('app_home') }}">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="w-32 sm:w-36 md:w-40 h-auto hover:opacity-90 transition-opacity" />
</a>
<a href="{{ path('app_home') }}" class="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white px-4 sm:px-6 md:px-8 py-2 sm:py-2.5 rounded-full text-xs sm:text-sm font-medium shadow-md hover:shadow-lg transition-all duration-300">
Zurück zur Startseite
</a>
</div>
</header>
<main class="container mx-auto px-4 sm:px-6 lg:px-8 pt-24 sm:pt-28 md:pt-32 pb-16 sm:pb-20 flex flex-col items-center justify-center relative z-10">
<div class="w-full max-w-2xl">
<h1 class="text-3xl sm:text-4xl md:text-5xl font-bold text-gray-800 text-center mb-6 sm:mb-8 tracking-tight leading-tight">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-blue-800">Kontakt</span>
</h1>
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-6 sm:p-8 md:p-10 mb-12 sm:mb-16 transform transition-all duration-300 border border-gray-100">
{{ form_start(form, { 'attr': { 'class': 'space-y-6' } }) }}
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email *</label>
<input type="email" name="{{ field_name(form.email) }}" id="email" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors">
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700 mb-1">Telefon</label>
<input type="tel" name="{{ field_name(form.phone) }}" id="phone"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors">
</div>
<div>
<label for="message" class="block text-sm font-medium text-gray-700 mb-1">Message *</label>
<textarea name="{{ field_name(form.message) }}" id="message" rows="4" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"></textarea>
</div>
<div class="text-center pt-4">
<button type="submit"
class="w-full sm:w-auto inline-block bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white px-8 sm:px-10 py-3 rounded-full text-base font-semibold shadow-xl hover:shadow-2xl transition-all duration-300">
Nachricht senden
</button>
</div>
{{ form_end(form) }}
</div>
</div>
</main>
</div>
{% endblock %}

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Contact Details</title>
</head>
<body bgcolor="#f3f4f6" style="font-family: Arial, Helvetica, sans-serif;">
<table cellpadding="20" cellspacing="0" border="0" width="100%">
<tr>
<td align="center">
<table cellpadding="30" cellspacing="0" border="1" width="600" bgcolor="#ffffff" bordercolor="#e5e7eb" style="border-collapse: collapse;">
<tr>
<td>
<font color="#1f2937" size="5" face="Arial, Helvetica, sans-serif">Contact Details</font>
<table cellpadding="15" cellspacing="0" border="1" width="100%" bgcolor="#f9fafb" bordercolor="#e5e7eb" style="border-collapse: collapse; margin-top: 20px;">
<tr>
<td>
<font color="#6b7280" size="2" face="Arial, Helvetica, sans-serif">Email</font><br>
<font color="#1f2937" size="3" face="Arial, Helvetica, sans-serif">{{ data.email }}</font>
</td>
</tr>
</table>
<table cellpadding="15" cellspacing="0" border="1" width="100%" bgcolor="#f9fafb" bordercolor="#e5e7eb" style="border-collapse: collapse; margin-top: 20px;">
<tr>
<td>
<font color="#6b7280" size="2" face="Arial, Helvetica, sans-serif">Phone</font><br>
<font color="#1f2937" size="3" face="Arial, Helvetica, sans-serif">{{ data.phone }}</font>
</td>
</tr>
</table>
<table cellpadding="15" cellspacing="0" border="1" width="100%" bgcolor="#f9fafb" bordercolor="#e5e7eb" style="border-collapse: collapse; margin-top: 20px;">
<tr>
<td>
<font color="#6b7280" size="2" face="Arial, Helvetica, sans-serif">Message</font><br>
<font color="#1f2937" size="3" face="Arial, Helvetica, sans-serif">{{ data.message }}</font>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -7,7 +7,7 @@
<header class="w-full bg-white/90 backdrop-blur-md shadow-lg fixed top-0 z-50 border-b border-gray-100">
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4 flex justify-between items-center">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="w-32 sm:w-36 md:w-40 h-auto hover:opacity-90 transition-opacity" />
<a href="#" class="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white px-4 sm:px-6 md:px-8 py-2 sm:py-2.5 rounded-full text-xs sm:text-sm font-medium shadow-md hover:shadow-lg transition-all duration-300">
<a href="{{ path('app_contact') }}" class="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white px-4 sm:px-6 md:px-8 py-2 sm:py-2.5 rounded-full text-xs sm:text-sm font-medium shadow-md hover:shadow-lg transition-all duration-300">
Kontaktaufnahme
</a>
</div>
@ -19,7 +19,7 @@
Willkommen zum <span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-blue-800">Abiball 2025</span>
</h1>
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-6 sm:p-8 md:p-10 mb-12 sm:mb-16 transform hover:scale-[1.01] transition-all duration-300 border border-gray-100">
<div class="bg-white/80 backdrop-blur-md shadow-xl rounded-2xl sm:rounded-3xl p-6 sm:p-8 md:p-10 mb-12 sm:mb-16 transform transition-all duration-300 border border-gray-100">
<p class="text-base sm:text-lg text-gray-700 leading-relaxed">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
</p>