first push

This commit is contained in:
2026-05-16 21:29:22 +02:00
commit 25edd4fac7
68 changed files with 3475 additions and 0 deletions
@@ -0,0 +1,103 @@
@page "/Contact"
@using System.ComponentModel.DataAnnotations
@using System.Text
@using ModelsLib
@inject EmailService mailService
@inject IJSRuntime JS
@rendermode InteractiveServer
@inject NavigationManager Navigation
<!-- Contact -->
<section class="contact-section">
<MudContainer MaxWidth="MaxWidth.Small" Class="text-center">
<!-- Titre -->
<MudText Typo="Typo.h5" Class="mb-2">
Contact
</MudText>
<!-- Sous-titre -->
<MudText Typo="Typo.body1" Class="mb-6">
Une question ? Un projet ? Envie d'une collaboration ? Écrivez-moi directement.
</MudText>
<!-- Formulaire -->
<EditForm EditContext="@editContext" OnValidSubmit="EnvoyerMessage">
<DataAnnotationsValidator />
<ValidationSummary />
<MudStack Spacing="2">
<MudTextField @bind-Value="contact.Name"
Label="Votre nom"
Variant="Variant.Outlined"
Required="true" />
<MudTextField @bind-Value="contact.Email"
Label="Votre adresse mail"
Variant="Variant.Outlined"
Required="true" />
<MudTextField @bind-Value="contact.Title"
Label="Sujet du message"
Variant="Variant.Outlined" />
<MudTextField @bind-Value="contact.Message"
Label="Message"
Variant="Variant.Outlined"
Lines="6"
TextArea="true" />
<MudButton ButtonType="ButtonType.Submit"
Variant="Variant.Filled"
Color="Color.Primary"
Class="mt-4">
Laisser un message
</MudButton>
</MudStack>
</EditForm>
<!-- Message succès -->
@if (messageEnvoye)
{
<MudAlert Severity="Severity.Success" Class="mt-6">
✅ Message envoyé, merci !
</MudAlert>
}
</MudContainer>
</section>
@code {
#region properties
private ContactModel contact = new();
private EditContext? editContext;
private bool messageEnvoye = false;
#endregion
/// <summary>
/// Chargement de la page
/// </summary>
protected override async Task OnInitializedAsync()
{
editContext = new EditContext(contact);
}
/// <summary>
/// Méthode d'envoi de mail
/// </summary>
private void EnvoyerMessage()
{
var body = new StringBuilder();
body.Append("<p>").Append(contact.Message).Append("</p>");
mailService.EnvoyerMailContactAsync(contact.Email,contact.Name+" : " +contact.Title, body).GetAwaiter();
messageEnvoye = true;
contact = new ContactModel();
editContext = new EditContext(contact);
}
}
@@ -0,0 +1,61 @@
body {
}
.contact-section {
padding: 80px 20px;
background: #f4f4f4;
}
.contact-title {
font-size: 40px;
font-weight: 700;
margin-bottom: 10px;
}
.contact-subtitle {
color: #777;
margin-bottom: 50px;
}
.contact-form {
max-width: 800px;
margin: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.contact-input {
padding: 15px;
border: 1px solid #ddd;
font-size: 16px;
width: 100%;
}
.contact-textarea {
padding: 15px;
border: 1px solid #ddd;
font-size: 16px;
width: 100%;
min-height: 180px;
}
.contact-button {
width: 250px;
padding: 15px;
border: 2px solid #222;
background: white;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.contact-button:hover {
background: #222;
color: white;
}
.success-message {
margin-top: 20px;
color: green;
font-weight: 500;
}
@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
@@ -0,0 +1,83 @@
@page "/"
@using System.ComponentModel.DataAnnotations
@using System.Text
@using ModelsLib
@using ToolsPhotoShare
@inject EmailService mailService
@inject IJSRuntime JS
@inject ConfigAppServices _configDb
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore;
@inject NavigationManager Navigation
<PageTitle>Accueil</PageTitle>
<main class="home-container">
<!-- Hero -->
<section class="hero-section text-center">
<div class="badge">Photographe Amateur</div>
<h1 class="hero-title">Hello,<br /><span class="italic">Moi c'est @DataPage.TitlePseudo</span></h1>
<p class="hero-subtitle">
@DataPage.SubTitle1
</p>
<p class="hero-subtitle">
@DataPage.SubTitle2
</p>
</section>
<section class="social-section text-center">
<div class="social-buttons">
<NavLink href="/ShootingDesire">
<h2 class="social-title">Mes envies de shooting</h2>
</NavLink>
</div>
</section>
<!-- Social -->
<section class="social-section text-center">
<h2 class="social-title">Où me retrouver ?</h2>
<p class="social-subtitle">Suivez mon travail et mes derniers shootings</p>
<div class="social-buttons">
<a href="@DataPage.InstaLink">
<div class="qr-wrapper">
<img src="@DataPage.InstaQrCode" alt="QR Code Instagram" class="qr-image" />
</div>
</a>
</div>
</section>
</main>
<footer class="footer">
<div class="footer-line"></div>
<p>© 2026 @DataPage.Credentials — Droits réservés</p>
</footer>
@code {
#region properties
private EntityHome DataPage = new();
#endregion
/// <summary>
/// Chargement de la page
/// </summary>
protected override async Task OnInitializedAsync()
{
if (!await _configDb.HasAnyParametersAsync())
{
await _configDb.FillConfigTableWithDataPerDefault();
}
DataPage = await _configDb.readParametersApplication();
}
}
@@ -0,0 +1,209 @@
/* --- Containers --- */
.home-container {
width: 90%; /* occupe 90% de l’écran sur petits écrans */
max-width: 800px; /* limite la largeur sur grands écrans */
margin: 0 auto;
padding: 3rem 1.5rem;
display: flex;
flex-direction: column;
gap: 3rem;
overflow-y: scroll; /* Permet le scroll vertical */
scrollbar-width: none; /* Firefox */
}
.home-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
/* --- Hero --- */
.badge {
display: inline-block;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
border-radius: 9999px;
border: 1px solid #d1d5db;
color: #71717a;
}
.hero-title {
font-family: 'Playfair Display', serif;
font-size: 2.5rem;
font-weight: 500;
}
.hero-subtitle {
color: #71717a;
font-weight: 300;
line-height: 1.6;
}
.hero-image-wrapper {
position: relative;
margin-top: 3rem;
border-radius: 1rem;
overflow: hidden;
}
.hero-image {
width: 100%;
height: auto;
display: block;
}
.hero-overlay {
position: absolute;
inset: 0;
background: rgba(255,255,255,0.4);
pointer-events: none;
border-radius: 1rem;
}
.dark .hero-overlay {
background: rgba(18,18,18,0.7);
}
/* --- Social --- */
.social-title {
font-family: 'Playfair Display', serif;
font-size: 1.75rem;
font-style: italic;
}
.social-subtitle {
color: #71717a;
font-weight: 300;
}
.social-buttons {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.instagram-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 2rem;
border: 1px solid #d1d5db;
border-radius: 9999px;
background: white;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.instagram-button:hover {
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.qr-wrapper {
padding: 1.5rem;
background: white;
border: 1px solid #d1d5db;
border-radius: 1rem;
display: flex;
flex-direction: column;
align-items: center;
}
.qr-image {
width: 128px;
height: 128px;
}
.qr-text {
margin-top: 0.5rem;
font-size: 0.625rem;
color: #71717a;
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 600;
}
/* --- Contact --- */
.contact-title {
font-family: 'Playfair Display', serif;
font-size: 2rem;
}
.contact-subtitle {
color: #71717a;
font-weight: 300;
margin-bottom: 1.5rem;
}
.contact-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.contact-input, .contact-textarea {
border: none;
border-bottom: 1px solid #d1d5db;
padding: 0.75rem 0;
font-size: 1rem;
outline: none;
background: transparent;
color: #1a1a1a;
}
.contact-input::placeholder, .contact-textarea::placeholder {
color: #d1d5db;
}
.contact-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem 1.5rem;
border-radius: 9999px;
background: #1a1a1a;
color: white;
font-weight: 500;
border: none;
cursor: pointer;
margin: 0 auto;
transition: all 0.2s;
}
.contact-button:hover {
opacity: 0.9;
}
.success-message {
color: green;
font-weight: 500;
}
/* --- Footer --- */
.footer {
text-align: center;
color: #71717a;
font-size: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.1em;
padding-bottom: 3rem;
}
.footer-line {
height: 1px;
width: 3rem;
background: #d1d5db;
margin: 0 auto 1rem auto;
}
/* --- Dark mode toggle --- */
.dark-toggle {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
padding: 0.75rem;
border-radius: 9999px;
background: white;
border: 1px solid #d1d5db;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
@@ -0,0 +1,9 @@
@page "/PhotoSelectError"
<h3>Oups !!!</h3>
Il semblerait que ce shooting soit inexistant ou bien que cet recherche est infructueuse.
@code {
}
@@ -0,0 +1,154 @@
@page "/Share"
@rendermode InteractiveServer
@using BlazorBootstrap;
@using Microsoft.AspNetCore.WebUtilities
@using ModelsLib
@using PhotoShareHelri.Components.Blocs
@using System.Text
@inject NavigationManager NavManager
@inject EmailService mailService
@inject PhotoService ShootingDb
@inject IJSRuntime JS
@using MudBlazor
<MudContainer MaxWidth="MaxWidth.Large" Class="d-flex justify-center mt-6 mb-6">
<MudPaper Elevation="3" Class="pa-6 selection-wrapper">
<!-- Header -->
<MudStack Spacing="1" AlignItems="AlignItems.Center" Class="mb-4 text-center">
<MudText Typo="Typo.h4">Résultat du shooting</MudText>
<MudText Typo="Typo.h6" Class="title-shoot">@Title</MudText>
</MudStack>
<!-- Galerie -->
<MudGrid Justify="Justify.Center" Spacing="3">
@for (int index = 0; index < Images.Count; index++)
{
var img = Images[index];
<MudItem xs="12" sm="6" md="4" lg="3" xl="2">
<MudPaper Class="image-card pa-2 position-relative" Elevation="2">
<MudImage Src="@img"
Alt="Photo sélection"
Class="gallery-thumb"
Fluid="true"
@onclick="() => OpenModalZoom(img)" />
</MudPaper>
</MudItem>
}
</MudGrid>
</MudPaper>
</MudContainer>
@* Modal de zoom sur une image *@
<ModalDiapo IsOpen="@IsModalZoomOpen"
CurrentImage="@CurrentImage"
CurrentIndex="@CurrentIndex"
IsSelector=false
Total="@Images.Count"
CanNext="@CanNext"
CanPrevious="@CanPrevious"
MaxSelect="@MaxSelect"
SelectedImages="@SelectedImages"
IsSelected="@(CurrentImage != null && @SelectedImages.Contains(@CurrentImage))"
OnClose="CloseModalZoom"
OnNext="NextModalZoom"
OnPrevious="PreviousModalZoom"
Toggle="() => ToggleImage(CurrentImage!)" />
@code {
#region Variables
HashSet<string> SelectedImages = new();
private int MaxSelect = 1;
// Liste des images disponibles
private List<string> Images = new();
public string Title { get; set; }
bool IsModalZoomOpen = false;
string? CurrentImage;
int CurrentIndex =>
CurrentImage == null ? -1 : Images.IndexOf(CurrentImage);
bool CanNext => CurrentIndex >= 0 && CurrentIndex < Images.Count - 1;
bool CanPrevious => CurrentIndex > 0;
private string ShootingId;
#endregion
#region modal zoom
/// <summary>
/// Ouverture de la modal à partir du clic sur image voulu
/// </summary>
void OpenModalZoom(string img)
{
CurrentImage = img;
IsModalZoomOpen = true;
}
///<summary>
/// fermeture de la modal
/// </summary>
void CloseModalZoom()
{
IsModalZoomOpen = false;
}
/// <summary>
/// photo suivante dans la modal
/// dans la liste des photos affichés
/// </summary>
void NextModalZoom()
{
if (CanNext)
CurrentImage = Images[CurrentIndex + 1];
}
/// <summary>
/// photo précédente
/// </summary>
void PreviousModalZoom()
{
if (CanPrevious)
CurrentImage = Images[CurrentIndex - 1];
}
#endregion
/// <summary>
/// arrivé sur la page
/// </summary>
protected override async Task OnInitializedAsync()
{
//récupération de l'identifiant de shooting, si identifiant null ou si recherche inexistante redirection vers la page de recherche de shooting
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("Id", out var id))
{
if (!string.IsNullOrEmpty(id))
{
ShootingId = id;
Images = ShootingDb.LireContenu(ShootingId, true);
}
}
// Code exécuté au chargement du composant
}
/// <summary>
/// Déclenche le téléchargement de l'image originale sur la modal de visualisation
/// </summary>
/// <param name="img">image sélectionné par son hash</param>
private async Task ToggleImage(string img)
{
var filePath = $"{img}"; // chemin dans wwwroot
await JS.InvokeVoidAsync("downloadFile", filePath, $"{img}");
}
}
@@ -0,0 +1,80 @@
.selection-wrapper {
width: 100%;
max-width: 1400px;
border-radius: 20px;
}
.title-shoot {
opacity: 0.85;
}
.image-card {
border-radius: 16px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
background-color: white;
}
.image-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.gallery-thumb {
width: 100%;
height: 250px;
object-fit: cover;
border-radius: 12px;
cursor: pointer;
display: block;
}
.select-fab {
position: absolute !important;
top: 14px;
right: 14px;
z-index: 10;
}
/* Mobile */
@media (max-width: 768px) {
.selection-wrapper {
padding: 20px 12px !important;
border-radius: 14px;
}
.gallery-thumb {
height: 220px;
}
}
.select-icon-btn {
position: absolute !important;
top: 12px;
right: 12px;
z-index: 10;
background-color: rgba(255, 255, 255, 0.92) !important;
backdrop-filter: blur(4px);
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0,0,0,0.18);
transition: all 0.2s ease;
}
.select-icon-btn:hover {
transform: scale(1.08);
}
.select-icon-btn.selected {
border: 2px solid #2e7d32;
color: #2e7d32 !important;
}
.select-icon-btn.not-selected {
border: 2px solid #c62828;
color: #c62828 !important;
}
.select-icon-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
@@ -0,0 +1,224 @@
@page "/PhotoSelect"
@rendermode InteractiveServer
@using BlazorBootstrap;
@using Microsoft.AspNetCore.WebUtilities
@using ModelsLib
@using PhotoShareHelri.Components.Blocs
@using System.Text
@inject NavigationManager NavManager
@inject EmailService mailService
@inject PhotoService ShootingDb
@inject IDialogService DialogService
@using MudBlazor
<MudContainer MaxWidth="MaxWidth.Large" Class="d-flex justify-center mt-6 mb-6">
<MudPaper Elevation="3" Class="pa-6 selection-wrapper">
<!-- Header -->
<MudStack Spacing="1" AlignItems="AlignItems.Center" Class="mb-4 text-center">
<MudText Typo="Typo.h4">Sélection des photos</MudText>
<MudText Typo="Typo.h6" Class="title-shoot">@Title</MudText>
</MudStack>
<!-- Action top -->
<MudStack AlignItems="AlignItems.Center" Class="mb-3">
<MudButton Variant="Variant.Outlined"
Color="Color.Dark"
Disabled="@(SelectedImages.Count == 0)"
OnClick="SendSelection">
Valider la sélection
</MudButton>
</MudStack>
<!-- Compteur -->
<MudText Typo="Typo.body1" Align="Align.Center" Class="mb-4">
@SelectedImages.Count / @MaxSelect photo(s) sélectionnée(s)
</MudText>
<!-- Galerie -->
<MudGrid Justify="Justify.Center" Spacing="3">
@for (int index = 0; index < Images.Count; index++)
{
var img = Images[index];
var isSelected = SelectedImages.Contains(img);
var buttonClass = $"select-icon-btn {(isSelected ? "selected" : "not-selected")}";
<MudItem xs="12" sm="6" md="4" lg="3" xl="2">
<MudPaper Class="image-card pa-2 position-relative" Elevation="2">
<MudImage Src="@img"
Alt="Photo sélection"
Class="gallery-thumb"
Fluid="true"
@onclick="() => OpenModalZoom(img)" />
<div @onclick:stopPropagation="true">
<MudIconButton Icon="@(isSelected? Icons.Material.Filled.Check : Icons.Material.Outlined.RadioButtonUnchecked)"
Class="@buttonClass"
Disabled="@(SelectedImages.Count >= MaxSelect && !isSelected)"
OnClick="() => ToggleImage(img)" />
</div>
</MudPaper>
</MudItem>
}
</MudGrid>
<!-- Action bottom -->
<MudStack AlignItems="AlignItems.Center" Class="mt-5">
<MudButton Variant="Variant.Outlined"
Disabled="@(SelectedImages.Count == 0)"
Color="Color.Dark"
OnClick="SendSelection">
Valider la sélection
</MudButton>
</MudStack>
</MudPaper>
</MudContainer>
<ModalDiapo IsOpen="@IsModalZoomOpen"
CurrentImage="@CurrentImage"
CurrentIndex="@CurrentIndex"
IsSelector="true"
Total="@Images.Count"
CanNext="@CanNext"
CanPrevious="@CanPrevious"
MaxSelect="@MaxSelect"
SelectedImages="@SelectedImages"
IsSelected="@(CurrentImage != null && SelectedImages.Contains(CurrentImage))"
OnClose="CloseModalZoom"
OnNext="NextModalZoom"
OnPrevious="PreviousModalZoom"
Toggle="() => ToggleImage(CurrentImage!)" />
@code {
#region Variables
HashSet<string> SelectedImages = new();
private int MaxSelect = 1;
// Liste des images disponibles
private List<string> Images = new();
public string Title { get; set; }
bool IsModalZoomOpen = false;
string? CurrentImage;
int CurrentIndex =>
CurrentImage == null ? -1 : Images.IndexOf(CurrentImage);
bool CanNext => CurrentIndex >= 0 && CurrentIndex < Images.Count - 1;
bool CanPrevious => CurrentIndex > 0;
private string ShootingId;
private ShootingData Datas { get; set; }
#endregion
#region modal zoom
/// <summary>
/// Ouverture de la modal à partir du clic sur image voulu
/// </summary>
void OpenModalZoom(string img)
{
CurrentImage = img;
IsModalZoomOpen = true;
}
///<summary>
/// fermeture de la modal
/// </summary>
void CloseModalZoom()
{
IsModalZoomOpen = false;
}
/// <summary>
/// photo suivante dans la modal
/// dans la liste des photos affichés
/// </summary>
void NextModalZoom()
{
if (CanNext)
CurrentImage = Images[CurrentIndex + 1];
}
/// <summary>
/// photo précédente
/// </summary>
void PreviousModalZoom()
{
if (CanPrevious)
CurrentImage = Images[CurrentIndex - 1];
}
#endregion
/// <summary>
/// arrivé sur la page
/// </summary>
protected override async Task OnInitializedAsync()
{
//récupération de l'identifiant de shooting, si identifiant null ou si recherche inexistante redirection vers la page de recherche de shooting
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("Id", out var id))
{
Datas = ShootingDb.GetInfosShooting(id);
if (!string.IsNullOrEmpty(id))
{
ShootingId = id;
Images = ShootingDb.LireContenu(ShootingId);
}
Title = "Résultat du shooting " + Datas.Key;
}
}
/// <summary>
/// Séléction de l'image dans la liste des photos choisies
/// </summary>
/// <param name="img">image sélectionné par son hash</param>
private void ToggleImage(string img)
{
if (SelectedImages.Contains(img))
{
SelectedImages.Remove(img);
}
else
{
SelectedImages.Add(img);
}
}
/// <summary>
/// envoi par mail de la sélection des photos
/// </summary>
private async void SendSelection()
{
if (SelectedImages == null || SelectedImages.Count == 0)
return;
var fichiers = string.Join(";", SelectedImages)
.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(Path.GetFileName)
.ToList();
var body = new StringBuilder();
body.Append("<h3>Photos sélectionnées 📸</h3>");
body.Append("<ul>");
foreach (var fichier in fichiers)
{
body.Append($"<li>{fichier}</li>");
}
body.Append("</ul>");
mailService.EnvoyerMailAsync("Confirmation sélection du shooting" + ShootingId, body).GetAwaiter();
Datas.IsPhotosSelected = true;
await ShootingDb.UpdateShootingInfo(Datas);
}
}
@@ -0,0 +1,80 @@
.selection-wrapper {
width: 100%;
max-width: 1400px;
border-radius: 20px;
}
.title-shoot {
opacity: 0.85;
}
.image-card {
border-radius: 16px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
background-color: white;
}
.image-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.gallery-thumb {
width: 100%;
height: 250px;
object-fit: cover;
border-radius: 12px;
cursor: pointer;
display: block;
}
.select-fab {
position: absolute !important;
top: 14px;
right: 14px;
z-index: 10;
}
/* Mobile */
@media (max-width: 768px) {
.selection-wrapper {
padding: 20px 12px !important;
border-radius: 14px;
}
.gallery-thumb {
height: 220px;
}
}
.select-icon-btn {
position: absolute !important;
top: 12px;
right: 12px;
z-index: 10;
background-color: rgba(255, 255, 255, 0.92) !important;
backdrop-filter: blur(4px);
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0,0,0,0.18);
transition: all 0.2s ease;
}
.select-icon-btn:hover {
transform: scale(1.08);
}
.select-icon-btn.selected {
border: 2px solid #2e7d32;
color: #2e7d32 !important;
}
.select-icon-btn.not-selected {
border: 2px solid #c62828;
color: #c62828 !important;
}
.select-icon-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
@@ -0,0 +1,58 @@
@page "/ShootingDesire"
@rendermode InteractiveServer
@inject IWebHostEnvironment Env
@inject DesireService DesireDb
<PageTitle>Mes Envies de shooting futur</PageTitle>
<main class="home-container">
<section class="shooting-section">
<MudContainer MaxWidth="MaxWidth.Medium" Class="text-center">
<!-- Titre -->
<MudText Typo="Typo.h4" Class="mb-4">
Mes envies de shootings
</MudText>
<!-- Intro -->
<MudText Typo="Typo.body1" Class="mb-6">
Envie de partager un moment ensemble ?
Un de ces projets vous parle et vous souhaiteriez qu'on le réalise ensemble ?
Contactez-moi afin que l'on puisse en discuter et mettre
tout en œuvre pour la réalisation !
</MudText>
<!-- Cartes -->
<MudGrid Justify="Justify.Center" Spacing="3">
<MudItem xs="12" sm="8" md="6">
<MudPaper Class="pa-4 text-center" Elevation="2">
@foreach (var envie in EnviesShooting)
{
<MudText Typo="Typo.body1">
📷 @envie
</MudText>
}
<!-- Texte bas -->
<MudText Typo="Typo.body2" Class="mt-6">
Et bien d'autres encore !!!
</MudText>
</MudPaper>
</MudItem>
</MudGrid>
</MudContainer>
</section>
</main>
@code {
private List<string> EnviesShooting;
protected override void OnInitialized()
{
EnviesShooting = DesireDb.GetDEsireShooting();
}
}
@@ -0,0 +1,133 @@
.social-title {
font-family: 'Playfair Display', serif;
font-size: 1.75rem;
font-style: italic;
}
/* --- Containers --- */
.home-container {
width: 90%; /* occupe 90% de l’écran sur petits écrans */
max-width: 800px; /* limite la largeur sur grands écrans */
margin: 0 auto;
padding: 3rem 1.5rem;
display: flex;
flex-direction: column;
gap: 3rem;
overflow-y: scroll; /* Permet le scroll vertical */
scrollbar-width: none; /* Firefox */
}
.home-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
.social-subtitle {
color: #71717a;
font-weight: 300;
}
.social-buttons {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.instagram-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 2rem;
border: 1px solid #d1d5db;
.title {
margin-top: 40px;
margin-bottom: 20px;
}
.intro {
max-width: 800px;
margin: auto;
margin-bottom: 40px;
}
.shooting-container {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(220px,1fr));
gap: 20px;
max-width: 900px;
margin: auto;
}
.shooting-card {
background: white;
border-radius: 12px;
padding: 15px;
box-shadow: 0 6px 15px rgba(0,0,0,0.15);
transition: transform 0.2s, box-shadow 0.2s;
font-weight: 500;
}
.shooting-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0,0,0,0.25);
}
border-radius: 9999px;
background: white;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.instagram-button:hover {
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.qr-wrapper {
padding: 1.5rem;
background: white;
border: 1px solid #d1d5db;
border-radius: 1rem;
display: flex;
flex-direction: column;
align-items: center;
}
.qr-image {
width: 128px;
height: 128px;
}
.qr-text {
margin-top: 0.5rem;
font-size: 0.625rem;
color: #71717a;
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 600;
}
.gallery-wrapper {
display: flex;
flex-direction: column;
}
.header-selection {
display: flex;
align-items: center;
width: 60%;
}
.titles {
flex: 1;
text-align: center;
}
.shooting-box {
border: 2px solid #444;
border-radius: 12px;
padding: 20px;
max-width: 500px;
margin: auto;
background-color: #f8f8f8;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}