diff --git a/TODO.md b/TODO.md index 56b356a..0a96f71 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,7 @@ ## UI - [x] **admin header** — replace "Déconnexion" text link with SVG sign-out icon (accessible via `aria-label` + `.sr-only` span) +- [x] **toast styling** — repositioned to bottom-center; solid `--bg-secondary` background; font bumped to `--step-0`; padding increased; visible duration extended to ~6.35 s; enter animation slides up from bottom ## Bug fixes - [x] **smtp-test.php** — wrap `SmtpRelay::send()` in `try/catch SmtpSendException` so SMTP delivery failures (e.g. 550 recipient rejected) surface as a proper flash error instead of an uncaught exception/silent crash diff --git a/app/public/assets/css/admin.css b/app/public/assets/css/admin.css index b296932..b835a5d 100644 --- a/app/public/assets/css/admin.css +++ b/app/public/assets/css/admin.css @@ -142,46 +142,48 @@ filter: brightness(0.9); } -/* ── Toast messages (top-right, CSS-only auto-fade) ─────────────────── */ +/* ── Toast messages (bottom-center, CSS-only auto-fade) ─────────────── */ #toast-region { position: fixed; - top: var(--space-l); - right: var(--space-l); + bottom: var(--space-l); + left: 50%; + transform: translateX(-50%); z-index: 10000; display: flex; flex-direction: column; + align-items: center; gap: var(--space-xs); pointer-events: none; - max-width: min(480px, calc(100vw - 2 * var(--space-l))); + width: max-content; + max-width: min(560px, calc(100vw - 2 * var(--space-l))); } .toast { - padding: var(--space-xs) var(--space-s); - border-radius: 6px; - font-size: var(--step--1); - border-left: 3px solid; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + padding: var(--space-s) var(--space-m); + border-radius: 8px; + font-size: var(--step-0); + border-left: 4px solid; + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25); pointer-events: auto; - backdrop-filter: blur(6px); - /* enter then fade out — total visible ~4.35 s */ + /* enter then fade out — total visible ~6.35 s */ animation: toast-enter 0.35s ease-out, - toast-exit 0.4s ease-in 4s forwards; + toast-exit 0.5s ease-in 6s forwards; } .toast--error { - background: var(--accent-muted); + background: var(--bg-secondary); border-color: var(--error); color: var(--text-primary); } .toast--success { - background: var(--success-muted-bg); + background: var(--bg-secondary); border-color: var(--success); color: var(--text-primary); } @keyframes toast-enter { - from { opacity: 0; transform: translateY(-12px); } + from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } diff --git a/app/public/assets/css/common.css b/app/public/assets/css/common.css index e0c45b5..8897c36 100644 --- a/app/public/assets/css/common.css +++ b/app/public/assets/css/common.css @@ -3,139 +3,139 @@ *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; } html, body { - margin: 0; - padding: 0; - height: 100%; - overflow: hidden; + margin: 0; + padding: 0; + height: 100%; + overflow: hidden; } body { - font-family: var(--font-body); - background: var(--bg-primary); - color: var(--text-primary); - background: linear-gradient( - 180deg, - rgba(0, 0, 0, 0) 92%, - rgba(149, 87, 181, 1) 100% - ); - display: flex; - flex-direction: column; + font-family: var(--font-body); + background: var(--bg-primary); + color: var(--text-primary); + background: linear-gradient( + 180deg, + rgba(0, 0, 0, 0) 92%, + rgba(149, 87, 181, 1) 100% + ); + display: flex; + flex-direction: column; } a { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } a:hover { - text-decoration: none; + text-decoration: none; } header { - vertical-align: center; - flex-shrink: 0; - background: linear-gradient( - 180deg, - var(--gradient-1) 0%, - var(--gradient-2) 33%, - var(--gradient-3) 66%, - var(--gradient-4) 100% - ); + vertical-align: center; + flex-shrink: 0; + background: linear-gradient( + 180deg, + var(--gradient-1) 0%, + var(--gradient-2) 33%, + var(--gradient-3) 66%, + var(--gradient-4) 100% + ); - .nav-logo { - font-family: var(--font-display); - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--accent-foreground); - text-decoration: none; - text-shadow: - 0 0 16px var(--header-shadow-strong), - 0 0 32px var(--header-shadow-soft); - } - - .nav-left { - display: flex; - align-items: center; - gap: var(--space-l); - } - - .nav-left-links, - .nav-right-links { - font-family: var(--font-display); - display: flex; - gap: var(--space-l); - align-items: center; - list-style: none; - margin: 0; - padding: 0; - } - - nav { - padding: var(--space-s) var(--space-s); - display: flex; - align-items: center; - justify-content: space-between; - font-size: var(--step-2); - - a { - font-family: var(--font-display); - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--accent-foreground); - text-decoration: none; + .nav-logo { + font-family: var(--font-display); + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--accent-foreground); + text-decoration: none; + text-shadow: + 0 0 16px var(--header-shadow-strong), + 0 0 32px var(--header-shadow-soft); } - ul { - display: flex; - gap: var(--space-l); - align-items: center; - list-style: none; - margin: 0; - padding: 0; + .nav-left { + display: flex; + align-items: center; + gap: var(--space-l); } - ul a { - font-size: var(--step--1); - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--accent-foreground); - text-decoration: none; - transition: opacity 0.15s; + .nav-left-links, + .nav-right-links { + font-family: var(--font-display); + display: flex; + gap: var(--space-l); + align-items: center; + list-style: none; + margin: 0; + padding: 0; } - a, - ul a { - text-shadow: - 0 0 16px var(--header-shadow-strong), - 0 0 32px var(--header-shadow-soft); + nav { + padding: var(--space-s) var(--space-s); + display: flex; + align-items: center; + justify-content: space-between; + font-size: var(--step-2); + + a { + font-family: var(--font-display); + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--accent-foreground); + text-decoration: none; + } + + ul { + display: flex; + gap: var(--space-l); + align-items: center; + list-style: none; + margin: 0; + padding: 0; + } + + ul a { + font-size: var(--step--1); + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--accent-foreground); + text-decoration: none; + transition: opacity 0.15s; + } + + a, + ul a { + text-shadow: + 0 0 16px var(--header-shadow-strong), + 0 0 32px var(--header-shadow-soft); + } + + ul a:hover { + opacity: 1; + } } - ul a:hover { - opacity: 1; + ul a[aria-current="page"] { + opacity: 1; + border-bottom: 1px solid var(--header-nav-active-border); + padding-bottom: 1px; } - } - ul a[aria-current="page"] { - opacity: 1; - border-bottom: 1px solid var(--header-nav-active-border); - padding-bottom: 1px; - } - - /* nav-top-row: transparent wrapper at desktop — children become + /* nav-top-row: transparent wrapper at desktop — children become direct flex items of nav, preserving the existing layout */ - .nav-top-row { - display: contents; - } + .nav-top-row { + display: contents; + } - /* nav-mobile-links: mobile-only dropdown, hidden at desktop */ - .nav-mobile-links { - display: none; /* overridden to block inside the mobile media query */ - } + /* nav-mobile-links: mobile-only dropdown, hidden at desktop */ + .nav-mobile-links { + display: none; /* overridden to block inside the mobile media query */ + } } /* ============================================================ @@ -160,167 +160,182 @@ header { /* Off-screen checkbox — triggered by its label */ .menu-btn { - position: absolute; - top: -9999px; - left: -9999px; + position: absolute; + top: -9999px; + left: -9999px; } /* Burger label — takes no space at desktop */ .menu-icon { - display: none; - cursor: pointer; - padding: var(--space-2xs) var(--space-s); - align-items: center; - justify-content: center; + display: none; + cursor: pointer; + padding: var(--space-2xs) var(--space-s); + align-items: center; + justify-content: center; } /* Middle bar of the burger icon */ .navicon { - background: var(--accent-foreground); - display: block; - height: 2px; - width: 24px; - position: relative; - transition: all 0.3s ease-out; + background: var(--accent-foreground); + display: block; + height: 2px; + width: 24px; + position: relative; + transition: all 0.3s ease-out; } /* Top and bottom bars */ .navicon::before, .navicon::after { - content: ''; - background: var(--accent-foreground); - display: block; - height: 2px; - width: 100%; - position: absolute; - transition: all 0.3s ease-out; + content: ""; + background: var(--accent-foreground); + display: block; + height: 2px; + width: 100%; + position: absolute; + transition: all 0.3s ease-out; } -.navicon::before { top: -7px; } -.navicon::after { bottom: -7px; } +.navicon::before { + top: -7px; +} +.navicon::after { + bottom: -7px; +} /* ---- Mobile ---- */ @media screen and (max-width: 640px) { - /* Nav becomes a flex column: top-row on row 1, dropdown on row 2 */ - header nav[aria-label="Navigation principale"] { - display: flex; - flex-direction: column; - align-items: stretch; - padding: 0; - } + /* Nav becomes a flex column: top-row on row 1, dropdown on row 2 */ + header nav[aria-label="Navigation principale"] { + display: flex; + flex-direction: column; + align-items: stretch; + padding: 0; + } - /* Top row: logo left, hamburger right */ - header nav[aria-label="Navigation principale"] .nav-top-row { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--space-s); - } + /* Top row: logo left, hamburger right */ + header nav[aria-label="Navigation principale"] .nav-top-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-s); + } - /* Hide desktop link lists inside the top row */ - header nav[aria-label="Navigation principale"] .nav-left-links, - header nav[aria-label="Navigation principale"] .nav-right-links { - display: none; - } + /* Hide desktop link lists inside the top row */ + header nav[aria-label="Navigation principale"] .nav-left-links, + header nav[aria-label="Navigation principale"] .nav-right-links { + display: none; + } - /* Reveal the hamburger icon */ - .menu-icon { - display: flex; - } + /* Reveal the hamburger icon */ + .menu-icon { + display: flex; + } - /* Dropdown: shown as block but clipped to zero height by default */ - header nav[aria-label="Navigation principale"] .nav-mobile-links { - display: block; /* override the desktop display:none */ - list-style: none; - margin: 0; - padding: 0; - max-height: 0; - overflow: hidden; - transition: max-height 0.2s ease-out; - } + /* Dropdown: shown as block but clipped to zero height by default */ + header nav[aria-label="Navigation principale"] .nav-mobile-links { + display: block; /* override the desktop display:none */ + list-style: none; + margin: 0; + padding: 0; + max-height: 0; + overflow: hidden; + transition: max-height 0.2s ease-out; + } - /* ---- Open state ---- */ - .menu-btn:checked ~ nav[aria-label="Navigation principale"] .nav-mobile-links { - max-height: 300px; - } + /* ---- Open state ---- */ + .menu-btn:checked + ~ nav[aria-label="Navigation principale"] + .nav-mobile-links { + max-height: 300px; + } - /* Dropdown link rows */ - header nav[aria-label="Navigation principale"] .nav-mobile-links li { - border-top: 1px solid var(--header-nav-active-border); - text-align: left; - } + /* Dropdown link rows */ + header nav[aria-label="Navigation principale"] .nav-mobile-links li { + border-top: 1px solid var(--header-nav-active-border); + text-align: left; + } - header nav[aria-label="Navigation principale"] .nav-mobile-links li a { - display: block; - width: 100%; - padding: var(--space-s) var(--space-s); - font-size: var(--step-1); - text-align: left; - } + header nav[aria-label="Navigation principale"] .nav-mobile-links li a { + display: block; + width: 100%; + padding: var(--space-s) var(--space-s); + font-size: var(--step-1); + text-align: left; + } - /* ---- Animate burger → X ---- */ - .menu-btn:checked ~ nav[aria-label="Navigation principale"] .menu-icon .navicon { - background: transparent; - } + /* ---- Animate burger → X ---- */ + .menu-btn:checked + ~ nav[aria-label="Navigation principale"] + .menu-icon + .navicon { + background: transparent; + } - .menu-btn:checked ~ nav[aria-label="Navigation principale"] .menu-icon .navicon::before { - transform: rotate(-45deg); - top: 0; - } + .menu-btn:checked + ~ nav[aria-label="Navigation principale"] + .menu-icon + .navicon::before { + transform: rotate(-45deg); + top: 0; + } - .menu-btn:checked ~ nav[aria-label="Navigation principale"] .menu-icon .navicon::after { - transform: rotate(45deg); - bottom: 0; - } + .menu-btn:checked + ~ nav[aria-label="Navigation principale"] + .menu-icon + .navicon::after { + transform: rotate(45deg); + bottom: 0; + } } main { - flex: 1; - min-height: 0; + flex: 1; + min-height: 0; } /* ============================================================ SEARCH BAR (shared) ============================================================ */ .header-search-wrap { - padding: 0 0; - flex-shrink: 0; - background-color: var(--gradient-4); - background: linear-gradient(180deg, var(--gradient-4) 0%, #ffffffee 100%); + padding: 0 0; + flex-shrink: 0; + background-color: var(--gradient-4); + background: linear-gradient(180deg, var(--gradient-4) 0%, #ffffffee 100%); } .header-search-wrap form[role="search"] { - display: flex; - align-items: center; - gap: var(--space-2xs); - padding: var(--space-3xs) var(--space-s); - border: 1px solid var(--accent-primary); - border-radius: 10px; - background: var(--bg-primary); - width: 100%; - color: var(--accent-primary); + display: flex; + align-items: center; + gap: var(--space-2xs); + padding: var(--space-3xs) var(--space-s); + border: 1px solid var(--accent-primary); + border-radius: 10px; + background: var(--bg-primary); + width: 100%; + color: var(--accent-primary); } .header-search-wrap form[role="search"] svg { - color: var(--text-tertiary); - flex-shrink: 0; - width: 16px; - height: 16px; - stroke: var(--accent-primary); + color: var(--text-tertiary); + flex-shrink: 0; + width: 16px; + height: 16px; + stroke: var(--accent-primary); } .header-search-wrap form[role="search"] input { - flex: 1; - border: none; - font-size: var(--step--1); - color: var(--text-primary); - background: transparent; - padding: var(--space-3xs) 0; - font-family: inherit; + flex: 1; + border: none; + font-size: var(--step--1); + color: var(--text-primary); + background: transparent; + padding: var(--space-3xs) 0; + font-family: inherit; } .header-search-wrap form[role="search"] input::placeholder { - color: var(--accent-primary); + color: var(--accent-primary); } /* ============================================================ @@ -329,50 +344,50 @@ main { /* Visually-hidden but screen-reader-accessible */ .sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } /* Skip-to-content link (visible only on keyboard focus) */ .skip-link { - position: absolute; - top: -999px; - left: 1rem; - z-index: 9999; - padding: var(--space-2xs) var(--space-s); - background: var(--accent-primary); - color: var(--text-primary); - font-size: var(--step--1); - font-weight: 600; - text-decoration: none; - border-radius: 0 0 4px 4px; + position: absolute; + top: -999px; + left: 1rem; + z-index: 9999; + padding: var(--space-2xs) var(--space-s); + background: var(--accent-primary); + color: var(--text-primary); + font-size: var(--step--1); + font-weight: 600; + text-decoration: none; + border-radius: 0 0 4px 4px; } .skip-link:focus { - top: 0; + top: 0; } /* Consistent keyboard-focus outline for all interactive elements */ :focus-visible { - outline: 2px solid var(--accent-primary); - outline-offset: 2px; + outline: 2px solid var(--accent-primary); + outline-offset: 2px; } /* Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - transition-duration: 0.01ms !important; - animation-duration: 0.01ms !important; - } + *, + *::before, + *::after { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + } } /* ============================================================ @@ -380,71 +395,79 @@ main { ============================================================ */ fieldset { - background: var(--bg-secondary); - border: 1px solid var(--border-primary); - border-radius: 4px; - padding: var(--space-m); - margin: 0; + background: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 4px; + padding: var(--space-m); + margin: 0; } legend { - font-size: var(--step--1); - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - color: var(--text-secondary); - padding: 0 var(--space-2xs); + font-size: var(--step--1); + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--text-secondary); + padding: 0 var(--space-2xs); } small { - font-size: var(--step--2); - color: var(--text-secondary); - display: block; - margin-top: var(--space-3xs); + font-size: var(--step--2); + color: var(--text-secondary); + display: block; + margin-top: var(--space-3xs); } table { - width: 100%; - border-collapse: collapse; - font-size: var(--step--1); + width: 100%; + border-collapse: collapse; + font-size: var(--step--1); } th { - text-align: left; - font-size: var(--step--2); - letter-spacing: 0.08em; - text-transform: uppercase; - padding: var(--space-3xs) var(--space-xs); - border-bottom: 1px solid var(--border-primary); - color: var(--text-secondary); - font-weight: 400; - white-space: nowrap; + text-align: left; + font-size: var(--step--2); + letter-spacing: 0.08em; + text-transform: uppercase; + padding: var(--space-3xs) var(--space-xs); + border-bottom: 1px solid var(--border-primary); + color: var(--text-secondary); + font-weight: 400; + white-space: nowrap; } td { - padding: var(--space-2xs) var(--space-xs); - border-bottom: 1px solid var(--border-primary); - vertical-align: top; + padding: var(--space-2xs) var(--space-xs); + border-bottom: 1px solid var(--border-primary); + vertical-align: top; } dialog { - border: 1px solid var(--border-primary); - border-radius: 6px; - background: var(--bg-primary); - color: var(--text-primary); - padding: 0; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); + border: 1px solid var(--border-primary); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); + padding: 0; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); } dialog::backdrop { - background: rgba(0, 0, 0, 0.45); + background: rgba(0, 0, 0, 0.45); } details > summary { - cursor: pointer; - list-style: none; + cursor: pointer; + list-style: none; } details > summary::-webkit-details-marker { - display: none; + display: none; +} + +:where(video, audio, iframe) { + border: 1px solid var(--bg-primary); + border-radius: 15px; +} +audio::-webkit-media-controls-enclosure { + border-radius: 10px; } diff --git a/app/public/assets/css/tfe.css b/app/public/assets/css/tfe.css index e0b1e52..4a2fc9a 100644 --- a/app/public/assets/css/tfe.css +++ b/app/public/assets/css/tfe.css @@ -218,7 +218,7 @@ aside figcaption { RESTRICTED ACCESS UI ============================================================ */ .tfe-restricted-access { - background: var(--surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: var(--space-m); @@ -292,7 +292,9 @@ aside figcaption { border: none; border-radius: 4px; cursor: pointer; - transition: background 0.2s, opacity 0.2s; + transition: + background 0.2s, + opacity 0.2s; margin-top: var(--space-3xs); } diff --git a/app/storage/logs/form-submissions.log b/app/storage/logs/form-submissions.log index 6ee3574..49b0402 100644 --- a/app/storage/logs/form-submissions.log +++ b/app/storage/logs/form-submissions.log @@ -8,3 +8,4 @@ {"source":"partage","action":"submit","status":"success","thesis_id":22,"identifier":"2025-019","author":"Lila Dubois, Karim Nassar","share_slug":"20260429-DZESJT6X","timestamp":"2026-04-30T11:45:36+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"} {"source":"partage","action":"submit","status":"success","thesis_id":23,"identifier":"2025-020","author":"Zoé Lambert","share_slug":"20260429-DZESJT6X","timestamp":"2026-04-30T11:46:49+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"} {"source":"partage","action":"submit","status":"success","thesis_id":24,"identifier":"2025-021","author":"Emma Renard","share_slug":"20260429-DZESJT6X","timestamp":"2026-04-30T11:49:49+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"} +{"source":"partage","action":"submit","status":"success","thesis_id":25,"identifier":"2025-001","author":"Emma Renard","share_slug":"20260429-DZESJT6X","timestamp":"2026-04-30T12:17:35+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"} diff --git a/app/storage/xamxam.db-shm b/app/storage/xamxam.db-shm new file mode 100644 index 0000000..fe9ac28 Binary files /dev/null and b/app/storage/xamxam.db-shm differ diff --git a/app/storage/xamxam.db-wal b/app/storage/xamxam.db-wal new file mode 100644 index 0000000..e69de29