14 Commits
dev ... kh-test

Author SHA1 Message Date
7694fc1e08 ValidateProfiles gemacht, Link zu Profiles wenn Jahrgang und fachrichtung fehlt und bei save wieder zurück zum Event gemacht 2025-11-06 14:39:45 +01:00
835795f526 ein fehler ist wieder zurückgekommen aber ist jetzt wieder gefixed 2025-11-05 09:40:36 +01:00
e12e4c5325 noch ein kleiner fix bei den buttons 2025-11-04 20:30:05 +01:00
fad59d9311 Merge branch 'kh-test' of https://git.kocoder.xyz/Diplomarbeit-Absolventenverein/Module.EventRegistration into kh-test 2025-11-04 20:19:25 +01:00
d93b29a324 bug fixes( mobile ansicht delele overlay fixed) wartet auf testing. Status offensichtlicher gemacht 2025-11-04 20:18:26 +01:00
a1d863e805 Set Event Description as EMail Body. Fix: Typo in Subject Line 2025-11-04 10:30:20 +01:00
45f203a8d4 Anmeldetool cannot import Register Control
Needs-Future-Attention
#17
Removal of unset Fields and comments
2025-10-23 20:02:51 +02:00
bdf4563e0d Fix: EventRegistration Tool (CERS überschreibt SERS)
idea/source: https://github.com/oqtane/oqtane.framework/discussions/5541
2025-10-16 19:12:54 +02:00
97cf2a0b83 Fix: Versions 9.0.8 im Client 2025-10-16 14:17:31 +02:00
7ca97bcb9b Änderung der Version und Anpassen der Anzeige der events für mobil und pcs 2025-10-14 11:00:11 +02:00
1c13c72a53 Module-Styling mb-6 & mt-3 classes. 2025-06-22 12:01:57 +02:00
e5d955370a Edit Form: Move description to the bottom. Add RichTextEditor and AspNetCore.InputDate instead of HTML Input. 2025-06-22 12:01:10 +02:00
939b42a90d Event status styling & render description as MarkupString 2025-06-22 12:00:13 +02:00
16cfe54fed Tabelle auf der Index Seite & Dialog Message bearbeitet.
Changed-Files: Index.razor & Index.resx
2025-06-22 11:58:41 +02:00
11 changed files with 369 additions and 98 deletions

View File

@ -1,46 +1,56 @@
@using Oqtane.Modules.Controls
@using Oqtane
@using Oqtane.Modules.Controls
@using SZUAbsolventenverein.Module.EventRegistration.Services
@using SZUAbsolventenverein.Module.EventRegistration.Models
@using System.Text.RegularExpressions
@namespace SZUAbsolventenverein.Module.EventRegistration
@inherits ModuleBase
@inject IEventRegistrationService EventRegistrationService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IUserService UserService
@inject IProfileService ProfileService
@inject ISettingService SettingService
<h3>Anmeldung zum Event</h3>
<p>Willst du am Event (@_name) teilnehmen?</p>
<span>@_eventDate - @_location</span>
<span class="mb-6">@_eventDate.ToLocalTime() - @_location</span>
<div>
<p>@_description</p>
@((MarkupString)_description)
</div>
@if (PageState.User != null) {
@if (Status != null)
{
<p class="mt-3 Ueberschrift">
<strong>Status: </strong>
<p class="mt-2"><strong>Status: </strong>
@if (Status == true)
{
<span class="Underline">@Localizer["Zugesagt"]</span>
<br /> <button class="btn mt-1 AbsageBtn" @onclick="Absage">@Localizer["Absagen"]</button>
<span class ="fontsizeInf">@Localizer["Zugesagt"]</span><br />
<p><button class="btn btn-danger" @onclick="Absage">@Localizer["Absagen"]</button></p>
} else
{
<span class="Underline">@Localizer["Abgesagt"]</span>
<br /> <button class="btn mt-1 ZusageBtn" @onclick="Zusage">@Localizer["Zusagen"]</button>
<span class="fontsizeInf"> @Localizer["Abgesagt"]</span><br />
<p><button class="btn btn-success" @onclick="Zusage">@Localizer["Zusagen"]</button></p>
}
</p>
} else {
<div class="buttons">
<button class="AbsageBtn" @onclick="Zusage">@Localizer["Zusagen"]</button>
<button class="ZusageBtn" @onclick="Absage">@Localizer["Absagen"]</button>
<button class="btn btn-success" @onclick="Zusage">@Localizer["Zusagen"]</button>
<button class="btn btn-danger" @onclick="Absage">@Localizer["Absagen"]</button>
</div>
}
} else
{
<p class="mt-3">Um dich für dieses Event zu registrieren, muss man sich zuerst anmelden.</p> <Login /><Register />
<p class="mt-3">Um dich für dieses Event zu registrieren, muss man sich zuerst anmelden.</p>
<Login />
@* @if(PageState.Site.AllowRegistration)
{
<Register />
} *@
}
@code {
@ -64,14 +74,12 @@
private DateTime _eventDate;
private string _location;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private Response _response;
private bool? Status;
private List<Profile> _profiles = new List<Profile>();
private Dictionary<string, string> _settings;
private async Task SendResponse(bool response)
{
if(_response == null)
@ -92,18 +100,45 @@
private async void Zusage()
{
await SendResponse(true);
if(ValidateProfiles())
{
await SendResponse(true);
} else
{
var currentPathAndQuery = new Uri(NavigationManager.Uri).PathAndQuery;
var encodedReturnUrl = Uri.EscapeDataString(currentPathAndQuery);
var link = $"/profile?tab=Profile&returnurl={encodedReturnUrl}";
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], $"Vervollständige hier dein Profil mit deinem Jahrgang und deiner Fachrichtung: <a href=\"{link}\">Link zum Profil</a>"), MessageType.Warning);
}
}
private async void Absage()
{
await SendResponse(false);
if(ValidateProfiles())
{
await SendResponse(false);
} else
{
var currentPathAndQuery = new Uri(NavigationManager.Uri).PathAndQuery;
var encodedReturnUrl = Uri.EscapeDataString(currentPathAndQuery);
var link = $"/profile?tab=profile&returnurl={encodedReturnUrl}";
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], $"Vervollständige hier dein Profil mit deinem Jahrgang und deiner Fachrichtung: {link}"), MessageType.Warning);
}
}
protected override async Task OnInitializedAsync()
{
try
{
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
var user = await UserService.GetUserAsync(PageState.User.UserId, PageState.Site.SiteId);
if (user != null)
{
_settings = user.Settings;
}
_id = Int32.Parse(PageState.QueryString["id"]);
Event currentEvent;
@ -113,12 +148,8 @@
{
_name = currentEvent.Name;
_description = currentEvent.Description;
_eventDate = currentEvent.EventDate.ToLocalTime();
_eventDate = currentEvent.EventDate;
_location = currentEvent.Location;
_createdby = currentEvent.CreatedBy;
_createdon = currentEvent.CreatedOn.ToLocalTime();
_modifiedby = currentEvent.ModifiedBy;
_modifiedon = currentEvent.ModifiedOn.ToLocalTime();
}
if(rsvp != null)
@ -133,4 +164,46 @@
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
}
}
private bool ValidateProfiles()
{
foreach (Profile profile in _profiles)
{
var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{
_settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue);
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
if (profile.IsRequired && string.IsNullOrEmpty(value))
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
return false;
}
if (!string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
bool valid = regex.Match(value).Success;
if (!valid)
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
return false;
}
}
}
}
return true;
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(_settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
}

View File

@ -1,6 +1,7 @@
@using Oqtane.Modules.Controls
@using SZUAbsolventenverein.Module.EventRegistration.Services
@using SZUAbsolventenverein.Module.EventRegistration.Models
@using Microsoft.AspNetCore.Components.Forms
@namespace SZUAbsolventenverein.Module.EventRegistration
@inherits ModuleBase
@ -17,22 +18,21 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="Enter a description" ResourceKey="Description">Description: </Label>
<Label Class="col-sm-3" For="location" HelpText="Enter a Location" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="description" class="form-control" @bind="@_description" required />
<input id="location" class="form-control" @bind="@_location" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="eventdate" HelpText="Enter a Date" ResourceKey="EventDate">EventDate: </Label>
<div class="col-sm-9">
<input id="eventdate" class="form-control" @bind="@_eventDate" required />
<!--<input id="eventdate" class="form-control" @bind="@_eventDate" required />-->
<InputDate id="eventdate" class="form-control" @bind-Value="@_eventDate" Type="InputDateType.DateTimeLocal" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Enter a Location" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="location" class="form-control" @bind="@_location" required />
</div>
<div class="mb-1 align-items-center">
<Label Class="" For="description" HelpText="Enter a description" ResourceKey="Description">Description: </Label>
<RichTextEditor @ref="@RichTextEditorHtml" Content="@_description" Placeholder="Enter a description"/>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="Save">@Localizer["Save"]</button>
@ -56,13 +56,15 @@
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
private RichTextEditor RichTextEditorHtml;
private ElementReference form;
private bool validated = false;
private int _id;
private string _name;
private string _description;
private DateTime _eventDate;
private DateTime _eventDate = DateTime.Now;
private string _location;
private string _createdby;
@ -105,6 +107,10 @@
{
validated = true;
var interop = new Oqtane.UI.Interop(JSRuntime);
string content = await RichTextEditorHtml.GetHtml();
content = Utilities.FormatContent(content, PageState.Alias, "save");
if (await interop.FormValid(form))
{
if (PageState.Action == "Add")
@ -112,7 +118,7 @@
Event EventRegistration = new Event();
EventRegistration.ModuleId = ModuleState.ModuleId;
EventRegistration.Name = _name;
EventRegistration.Description = _description;
EventRegistration.Description = content;
EventRegistration.EventDate = _eventDate.ToUniversalTime();
EventRegistration.Location = _location;
EventRegistration = await EventRegistrationService.AddEventAsync(EventRegistration);
@ -122,7 +128,7 @@
{
Event EventRegistration = await EventRegistrationService.GetEventAsync(_id, ModuleState.ModuleId);
EventRegistration.Name = _name;
EventRegistration.Description = _description;
EventRegistration.Description = content;
EventRegistration.EventDate = _eventDate.ToUniversalTime();
EventRegistration.Location = _location;
await EventRegistrationService.UpdateEventAsync(EventRegistration);

View File

@ -16,24 +16,38 @@ else
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add Event" ResourceKey="Add" />
<br />
<br />
<p>@Status</p>
@if (@_EventRegistrations.Count != 0)
{
<Pager Items="@_EventRegistrations">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.EventId.ToString())" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete EventRegistration" Message="Are You Sure You Wish To Delete This EventRegistration?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.EventId.ToString()" /></td>
<td>@context.Name</td>
<td><ActionLink Action="Details" Parameters="@($"id=" + context.EventId.ToString())" ResourceKey="Details"/></td>
</Row>
</Pager>
<div class="event-list">
<div class="event-list">
@foreach (var context in _EventRegistrations)
{
<div class="event-card">
<h3>@context.Name</h3>
<p><strong>@Localizer["Date"]:</strong> @context.EventDate.ToLocalTime()</p>
<p><strong>@Localizer["Location"]:</strong> @context.Location</p>
<div class="event-actions">
<ActionLink Action="Edit"
Parameters="@($"id={context.EventId}")"
ResourceKey="Edit" />
<ActionDialog Action="Delete"
Security="SecurityAccessLevel.Edit"
Class="btn btn-danger"
OnClick="@(async () => await Delete(context))"
ResourceKey="Delete"
Id="@context.EventId.ToString()" />
<ActionLink Action="Details"
Parameters="@($"id={context.EventId}")"
ResourceKey="Details" />
</div>
</div>
}
</div>
</div>
}
else
{
@ -42,8 +56,6 @@ else
}
@code {
private string Status;
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
@ -80,15 +92,4 @@ else
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
}
}
private async Task Accept(Event eventRegistration)
{
Status = ("EventRegistration Accepted " + eventRegistration.Name);
await logger.LogInformation("EventRegistration Accepted {EventRegistration}", eventRegistration);
}
private void Reject()
{
Status = "EventRegistration Rejected 1";
}
}

View File

@ -9,9 +9,9 @@ namespace SZUAbsolventenverein.Module.EventRegistration
{
Name = "EventRegistration",
Description = "A module to manage registration for events",
Version = "1.0.5",
Version = "1.0.14",
ServerManagerType = "SZUAbsolventenverein.Module.EventRegistration.Manager.EventRegistrationManager, SZUAbsolventenverein.Module.EventRegistration.Server.Oqtane",
ReleaseVersions = "1.0.0,1.0.5",
ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,1.0.5,1.0.6,1.0.7,1.0.8,1.0.9,1.0.10,1.0.11,1.0.12,1.0.13,1.0.14",
Dependencies = "SZUAbsolventenverein.Module.EventRegistration.Shared.Oqtane",
PackageName = "SZUAbsolventenverein.Module.EventRegistration"
};

View File

@ -127,10 +127,10 @@
<value>Delete</value>
</data>
<data name="Delete.Header" xml:space="preserve">
<value>Delete Event</value>
<value>Delete</value>
</data>
<data name="Delete.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Event?</value>
<value>Are You Sure You Wish To Delete This Event? If you delete an event, all existing registrations will be deleted as well.</value>
</data>
<data name="Message.DisplayNone" xml:space="preserve">
<value>No Events To Display</value>
@ -142,6 +142,15 @@
<value>Error Deleting Event</value>
</data>
<data name="Details.Text" xml:space="preserve">
<value>Reject</value>
<value>Details</value>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Date" xml:space="preserve">
<value>Date</value>
</data>
<data name="Location" xml:space="preserve">
<value>Location</value>
</data>
</root>

View File

@ -13,11 +13,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.8" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.8" />
</ItemGroup>
<ItemGroup>

View File

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Services;
using System.Linq;
using SZUAbsolventenverein.Module.EventRegistration.Services;
namespace SZUAbsolventenverein.Module.EventRegistration.Startup
@ -8,7 +9,10 @@ namespace SZUAbsolventenverein.Module.EventRegistration.Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IEventRegistrationService, EventRegistrationService>();
if (!services.Any(s => s.ServiceType == typeof(IEventRegistrationService)))
{
services.AddScoped<IEventRegistrationService, EventRegistrationService>();
}
}
}
}

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>$projectname$</id>
<version>1.0.0</version>
<version>1.0.14</version>
<authors>SZUAbsolventenverein</authors>
<owners>SZUAbsolventenverein</owners>
<title>EventRegistration</title>
@ -16,7 +16,7 @@
<releaseNotes></releaseNotes>
<summary></summary>
<dependencies>
<dependency id="Oqtane.Framework" version="6.1.1" />
<dependency id="Oqtane.Framework" version="6.2.0" />
</dependencies>
</metadata>
<files>

View File

@ -19,10 +19,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.8" />
</ItemGroup>
<ItemGroup>

View File

@ -81,8 +81,8 @@ namespace SZUAbsolventenverein.Module.EventRegistration.Services
Response = _ResponseRepository.UpdateResponse(Response);
Event currentEvent = _EventRepository.GetEvent(Response.EventRegistrationId);
string subject = Response.ResponseType ? $"Du bist erfolgreich für '{currentEvent.Name}' Registriert worden." : $"Du hast erfolgreich für '{currentEvent.Name}' abgesagt.";
string body = "Hier kann man die Infos des Events hineinpacken (HTML ist erlaubt)";
string subject = Response.ResponseType ? $"Du bist erfolgreich für '{currentEvent.Name}' registriert." : $"Du hast erfolgreich für '{currentEvent.Name}' abgesagt.";
string body = currentEvent.Description;
SendEventResponseNotification(subject, body);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "EventRegistration Added {NewEvent}", Response);

View File

@ -1,23 +1,201 @@
/* Module Custom Styles */
.AbsageBtn {
background-color: #e63946; /* sch<63>nes kr<6B>ftiges Rot */
color: white;
.mb-6 {
margin-bottom: 6rem;
}
.AbsageBtn:hover {
background-color: #ae262f
.mt-3 {
margin-top: 3rem;
}
.ZusageBtn {
background-color: #2a9d8f; /* angenehmes Gr<47>n */
color: white;
.mt-2 strong {
font-size: 1.4rem; /* Textgr<67><72>e <20> Standard ist ca. 1rem */
font-weight: 700;
}
.ZusageBtn:hover {
background-color: #20776d
.fontsizeInf {
font-size: 1.4rem; /* Textgr<67><72>e <20> Standard ist ca. 1rem */
}
.Ueberschrift {
font-size: 1.5rem;
.event-list {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: center;
}
.Underline {
text-decoration: underline;
.event-card {
/*background-color: var(--bs-gray-dark); */
border: 2px solid rgb(128 128 128); /* Umrandung */
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
padding: 1.5rem;
width: 280px;
/* color: #ddd; */
transition: all 0.3s ease;
}
.event-card h3 {
margin-top: 0;
margin-bottom: 0.5rem;
}
.event-card p {
margin: 0.2rem 0;
}
.event-actions {
margin-top: 1rem;
display: flex;
justify-content: space-between;
}
/* vorher:
.event-card:hover {
border-color: #66ccff;
transform: translateY(-4px); <-- das ist der <20>belt<6C>ter
box-shadow: 0 6px 14px rgba(0,0,0,0.5);
}
*/
/* nachher <20> kein transform mehr */
.event-card:hover {
border-color: #66ccff;
/* statt transform die Karte minimal "anheben" */
margin-top: -4px; /* optischer Lift */
box-shadow: 0 6px 14px rgba(0,0,0,0.5);
}
/* sicherheitshalber: Overlay darf nicht abgeschnitten werden */
.event-card {
overflow: visible;
}
/* ---------- A) Hover nur auf Ger<65>ten mit Maus ---------- */
@media (hover: hover) and (pointer: fine) {
.event-card:hover {
border-color: #66ccff;
box-shadow: 0 6px 14px rgba(0,0,0,.5);
}
}
/* Auf Touch-Ger<65>ten kein transform (verhindert Modal-Probleme) */
@media (hover: none), (pointer: coarse) {
.event-card:hover {
transform: none;
box-shadow: 0 6px 14px rgba(0,0,0,.5);
}
}
/* Sicherheitshalber: nichts abschneiden */
.event-card {
overflow: visible;
}
/* ---------- B) Overlay/Modal (Desktop-Basis) ---------- */
/* Falls schon vorhanden, kannst du diese Werte als Override nutzen */
.overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,.45);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
z-index: 2000;
overscroll-behavior: contain;
}
.modal {
background: #fff;
border-radius: 10px;
box-shadow: 0 20px 60px rgba(0,0,0,.35);
width: min(92vw, 720px);
max-height: 85vh;
display: flex;
flex-direction: column;
overflow: hidden; /* f<>r sticky header/footer */
}
.modal-header,
.modal-footer {
padding: 1rem 1.25rem;
background: #fff;
}
.modal-header {
border-bottom: 1px solid rgba(0,0,0,.1);
}
.modal-footer {
border-top: 1px solid rgba(0,0,0,.1);
display: flex;
gap: .75rem;
justify-content: flex-end;
}
.modal-body {
padding: 1rem 1.25rem;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
/* Medien responsiv im Detail-Dialog */
.modal-body img,
.modal-body video,
.modal-body canvas,
.modal-body iframe {
max-width: 100% !important;
height: auto !important;
display: block;
}
/* ---------- C) Mobile-Vollbild & Sticky-Header/Footer ---------- */
@supports (height: 100dvh) {
.modal {
max-height: 100dvh;
}
}
@media (max-width: 768px) {
.overlay {
padding: 0;
}
.modal {
width: 100vw;
height: 100vh; /* nutzt auf modernen Browsern 100dvh (s.o.) */
max-height: 100vh;
border-radius: 0;
}
.modal-header {
position: sticky;
top: 0;
z-index: 1;
}
.modal-footer {
position: sticky;
bottom: 0;
z-index: 1;
}
.modal-body {
padding: 1rem;
}
/* Lesbare <20>berschriften auf Mobile */
.modal-body h1 {
font-size: clamp(1.25rem, 5.5vw, 2rem);
}
.modal-body h2 {
font-size: clamp(1.125rem, 5vw, 1.5rem);
}
}
/* iOS Safe-Area (Notch) */
@supports (padding: max(0px)) {
.modal {
padding-bottom: max(0px, env(safe-area-inset-bottom));
}
}