feat: rename memoire to tfe and improve styling

- Rename memoire.php to tfe.php throughout codebase
- Create dedicated tfe.css with rounded header/main/footer layout
- Move metadata (orientation, AP program, finality, keywords) to header
- Move back button from header to footer
- Create shared templates/head.php for common HTML head section
- Maintain rounded borders (40px) matching main site design
- Keep purple header (#9557b5), green main (#3c856b), dark footer (#222)
- Improve content readability with centered max-width layout
- Add responsive design for mobile devices
This commit is contained in:
Théophile Gervreau-Mercier
2026-02-12 12:46:44 +01:00
parent 9f6147577b
commit 73b0093b26
8 changed files with 631 additions and 161 deletions

View File

@@ -11,12 +11,12 @@ body {
/* Layout ratios: header 2/5, main 3/5, footer 1/5 */
header {
flex: 1;
flex: 2;
min-height: 0;
}
main {
flex: 4;
flex: 7;
min-height: 0;
}

389
public/assets/tfe.css Normal file
View File

@@ -0,0 +1,389 @@
/* TFE (Thesis) Page Styling */
.tfe-body {
margin: 0;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Header */
.tfe-header {
flex: 2;
min-height: 0;
background: #9557b5;
color: white;
padding: 1.5rem 2rem;
margin: 0;
border-radius: 40px;
display: flex;
align-items: center;
box-sizing: border-box;
overflow-y: auto;
}
.header-content {
width: 100%;
}
.tfe-title {
font-family: "police1", sans-serif;
font-size: 1.75rem;
font-weight: 700;
margin: 0 0 0.5rem 0;
line-height: 1.2;
color: white;
}
.tfe-subtitle {
font-size: 1.15rem;
margin: 0 0 1rem 0;
opacity: 0.9;
font-style: italic;
}
.header-metadata {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.meta-group {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
font-size: 0.95rem;
opacity: 0.95;
}
.meta-group.keywords {
margin-top: 0.25rem;
}
.meta-group .label {
font-weight: 600;
opacity: 0.85;
}
.meta-group .author {
font-weight: 500;
}
.meta-group .separator {
opacity: 0.6;
}
.meta-group .year {
font-weight: 600;
}
/* Main Content */
.tfe-main {
flex: 7;
min-height: 0;
background: #3c856b;
padding: 2rem;
margin: 0;
border-radius: 40px;
box-sizing: border-box;
overflow-y: auto;
}
.tfe-container {
max-width: 900px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 2rem;
}
/* Metadata Section */
.tfe-metadata {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.subtitle {
font-size: 1.25rem;
color: #666;
margin: 0 0 1.5rem 0;
font-style: italic;
}
.metadata-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem 3rem;
margin-bottom: 1rem;
}
.metadata-column {
display: flex;
flex-direction: column;
gap: 1rem;
}
.meta-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.meta-item strong {
color: #9557b5;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.meta-item span {
color: #333;
font-size: 1rem;
line-height: 1.5;
}
.context-note {
margin-top: 1.5rem;
padding: 1rem;
background: #f8f8f8;
border-left: 4px solid #9557b5;
border-radius: 4px;
}
.context-note em {
color: #555;
font-size: 0.95rem;
line-height: 1.6;
}
/* Synopsis Section */
.tfe-synopsis {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.tfe-synopsis h3 {
font-size: 1.5rem;
color: #9557b5;
margin: 0 0 1rem 0;
font-weight: 700;
}
.synopsis-content {
color: #333;
font-size: 1rem;
line-height: 1.7;
}
/* Files Section */
.tfe-files {
display: flex;
flex-direction: column;
gap: 2rem;
}
.file-block {
background: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.file-block embed,
.file-block video {
border-radius: 8px;
display: block;
}
.image-figure {
margin: 0;
text-align: center;
}
.image-figure img {
max-width: 100%;
height: auto;
border-radius: 8px;
display: block;
margin: 0 auto;
}
.file-description {
margin: 1rem 0 0 0;
color: #666;
font-size: 0.9rem;
font-style: italic;
text-align: center;
}
.no-files {
background: #fff3cd;
color: #856404;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
border: 1px solid #ffeaa7;
}
/* Footer */
.tfe-footer {
flex: 1;
min-height: 0;
background: #222;
color: white;
padding: 1rem 2rem;
margin: 0;
border-radius: 40px;
box-sizing: border-box;
overflow: hidden;
}
.footer-content {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.back-button {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
color: white;
text-decoration: none;
transition: all 0.2s ease;
flex-shrink: 0;
}
.back-button:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateX(-2px);
}
.back-button svg {
display: block;
}
.footer-meta {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.9rem;
opacity: 0.9;
}
.footer-meta .separator {
opacity: 0.6;
}
/* Responsive Design */
@media (max-width: 768px) {
.tfe-header {
padding: 1rem;
}
.tfe-title {
font-size: 1.35rem;
}
.tfe-subtitle {
font-size: 1rem;
}
.meta-group {
font-size: 0.85rem;
}
.back-button {
width: 40px;
height: 40px;
}
.back-button svg {
width: 20px;
height: 20px;
}
.tfe-main {
padding: 1.5rem;
}
.tfe-container {
gap: 1.5rem;
}
.tfe-metadata,
.tfe-synopsis,
.file-block {
padding: 1.5rem;
}
.metadata-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.footer-content {
flex-direction: column;
text-align: center;
justify-content: center;
}
.tfe-footer {
padding: 1rem;
}
}
@media (max-width: 480px) {
.tfe-title {
font-size: 1.15rem;
}
.tfe-subtitle {
font-size: 0.9rem;
}
.meta-group {
font-size: 0.8rem;
gap: 0.4rem;
}
.back-button {
width: 36px;
height: 36px;
}
.back-button svg {
width: 18px;
height: 18px;
}
.tfe-synopsis h3 {
font-size: 1.25rem;
}
.file-block embed {
height: 500px;
}
.tfe-main {
padding: 1rem;
}
.tfe-container {
gap: 1rem;
}
}

View File

@@ -44,7 +44,7 @@ include APP_ROOT . '/templates/header.php';
<main>
<?php foreach ($itemsToLoad as $item): ?>
<a href="memoire.php?id=<?= (int)$item["id"] ?>" class="card-link">
<a href="tfe.php?id=<?= (int)$item["id"] ?>" class="card-link">
<div class="card">
<div class="card-content">
<h2 class="title"><?= htmlspecialchars($item["title"]) ?></h2>

View File

@@ -1,154 +0,0 @@
<?php
// Bootstrap application
require_once __DIR__ . '/../config/bootstrap.php';
// Load required libraries and classes
require_once APP_ROOT . '/src/Database.php';
// Check if an id parameter is provided in the URL
if (isset($_GET['id'])) {
$thesisId = intval($_GET['id']);
try {
$db = Database::getInstance();
$data = $db->getThesisById($thesisId);
if (!$data) {
// Thesis not found or not published
header('Location: index.php');
exit;
}
} catch (Exception $e) {
error_log("Error loading thesis: " . $e->getMessage());
header('Location: index.php');
exit;
}
} else {
// Redirect to the index page if no id parameter is provided
header('Location: index.php');
exit;
}
// Include the header template
include APP_ROOT . '/templates/header.php'; ?>
<main>
<div class="item">
<div class="card-content">
<!-- Display the title and author from the database -->
<h1 class="title">
<?= htmlspecialchars($data['title']); ?>
<?php if (!empty($data['subtitle'])): ?>
<br><small><?= htmlspecialchars($data['subtitle']); ?></small>
<?php endif; ?>
</h1>
<h2 class="subtitle">par
<?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu'); ?>
</h2>
<h3 class="subtitle"></h3>
<div class="columns">
<div class="column is-half ">
<?php if (!empty($data['orientation']) || !empty($data['ap_program'])): ?>
<h3 class="subtitle">
<?php if (!empty($data['orientation'])): ?>
<?= htmlspecialchars($data['orientation']); ?>
<?php endif; ?>
<?php if (!empty($data['orientation']) && !empty($data['ap_program'])): ?>
et
<?php endif; ?>
<?php if (!empty($data['ap_program'])): ?>
<?= htmlspecialchars($data['ap_program']); ?>
<?php endif; ?>
</h3>
<?php endif; ?>
<p class="block tag subtitle is-6">
<?= htmlspecialchars($data['year']); ?>
</p>
<?php if (!empty($data['finality_type'])): ?>
<p class="block">
<strong>Finalité:</strong> <?= htmlspecialchars($data['finality_type']); ?>
</p>
<?php endif; ?>
</div>
<div class="column">
<?php if (!empty($data['context_note'])): ?>
<p class="block">
<em><?= htmlspecialchars($data['context_note']); ?></em>
</p>
<?php endif; ?>
<?php if (!empty($data['supervisors'])): ?>
<p class="block">
<strong>Promoteur.ice.s:</strong>
<?= htmlspecialchars($data['supervisors']); ?>
</p>
<?php endif; ?>
<?php if (!empty($data['languages'])): ?>
<p class="block">
<strong>Langue(s):</strong>
<?= htmlspecialchars($data['languages']); ?>
</p>
<?php endif; ?>
<?php if (!empty($data['formats'])): ?>
<p class="block">
<strong>Format(s):</strong>
<?= htmlspecialchars($data['formats']); ?>
</p>
<?php endif; ?>
<?php if (!empty($data['keywords'])): ?>
<p class="block">
<strong>Mots-clés:</strong>
<?= htmlspecialchars($data['keywords']); ?>
</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="box">
<?php if (!empty($data['synopsis'])): ?>
<?= nl2br(htmlspecialchars($data['synopsis'])); ?>
<?php endif; ?>
</div>
</div>
<div class="column is-two-third">
<div class="content">
<!-- Check if there are any files in the database -->
<?php if (isset($data['files']) && count($data['files']) > 0): ?>
<!-- Loop through the files and display them based on their file type -->
<?php foreach ($data['files'] as $file): ?>
<?php $ext = strtolower(pathinfo($file['file_path'], PATHINFO_EXTENSION)); ?>
<div class="block">
<?php if ($ext === 'pdf'): ?>
<!-- Display PDF files using the embed element -->
<embed src="/media.php?path=<?= urlencode($file['file_path']); ?>" type="application/pdf" width="100%" height="600px" />
<?php elseif (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'bmp'])): ?>
<!-- Display image files using the img element -->
<figure>
<img src="/media.php?path=<?= urlencode($file['file_path']); ?>" alt="<?= htmlspecialchars($file['file_name']); ?>">
</figure>
<?php elseif ($ext === 'mp4'): ?>
<!-- Display MP4 video files using the video element -->
<video width="100%" height="auto" controls>
<source src="/media.php?path=<?= urlencode($file['file_path']); ?>" type="video/mp4">
Your browser does not support the video tag.
</video>
<?php endif; ?>
<?php if (!empty($file['description'])): ?>
<p class="help"><?= htmlspecialchars($file['description']); ?></p>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<p class="notification is-warning">Aucun fichier disponible pour ce mémoire.</p>
<?php endif; ?>
</div>
</main>
<!-- Include the footer template -->
<?php include APP_ROOT . '/templates/footer.php'; ?>

View File

@@ -333,7 +333,7 @@ include APP_ROOT . '/templates/header.php'; ?>
<div class="columns is-multiline">
<?php foreach ($results as $item): ?>
<div class="column is-one-fifth">
<a href="memoire.php?id=<?= (int)$item['id']; ?>" class="card-link">
<a href="tfe.php?id=<?= (int)$item['id']; ?>" class="card-link">
<div class="card">
<?php
// Get cover image from thesis_files if available

202
public/tfe.php Normal file
View File

@@ -0,0 +1,202 @@
<?php
// Bootstrap application
require_once __DIR__ . '/../config/bootstrap.php';
// Load required libraries and classes
require_once APP_ROOT . '/src/Database.php';
// Check if an id parameter is provided in the URL
if (isset($_GET['id'])) {
$thesisId = intval($_GET['id']);
try {
$db = Database::getInstance();
$data = $db->getThesisById($thesisId);
if (!$data) {
// Thesis not found or not published
header('Location: index.php');
exit;
}
} catch (Exception $e) {
error_log("Error loading thesis: " . $e->getMessage());
header('Location: index.php');
exit;
}
} else {
// Redirect to the index page if no id parameter is provided
header('Location: index.php');
exit;
}
// Set page title and additional CSS
$pageTitle = $data['title'];
$additionalCSS = ['assets/tfe.css'];
// Include shared head template
include APP_ROOT . '/templates/head.php';
?>
<body class="tfe-body">
<header class="tfe-header">
<div class="header-content">
<h1 class="tfe-title"><?= htmlspecialchars($data['title']); ?></h1>
<?php if (!empty($data['subtitle'])): ?>
<p class="tfe-subtitle"><?= htmlspecialchars($data['subtitle']); ?></p>
<?php endif; ?>
<div class="header-metadata">
<div class="meta-group">
<span class="author"><?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu'); ?></span>
<span class="separator">•</span>
<span class="year"><?= htmlspecialchars($data['year']); ?></span>
</div>
<?php if (!empty($data['orientation']) || !empty($data['ap_program'])): ?>
<div class="meta-group">
<?php if (!empty($data['orientation'])): ?>
<span><?= htmlspecialchars($data['orientation']); ?></span>
<?php endif; ?>
<?php if (!empty($data['orientation']) && !empty($data['ap_program'])): ?>
<span class="separator">•</span>
<?php endif; ?>
<?php if (!empty($data['ap_program'])): ?>
<span><?= htmlspecialchars($data['ap_program']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($data['finality_type'])): ?>
<div class="meta-group">
<span><?= htmlspecialchars($data['finality_type']); ?></span>
</div>
<?php endif; ?>
<?php if (!empty($data['keywords'])): ?>
<div class="meta-group keywords">
<span class="label">Mots-clés:</span>
<span><?= htmlspecialchars($data['keywords']); ?></span>
</div>
<?php endif; ?>
</div>
</div>
</header>
<main class="tfe-main">
<div class="tfe-container">
<!-- Metadata Section -->
<section class="tfe-metadata">
<?php if (!empty($data['subtitle'])): ?>
<h2 class="subtitle"><?= htmlspecialchars($data['subtitle']); ?></h2>
<?php endif; ?>
<div class="metadata-grid">
<div class="metadata-column">
<?php if (!empty($data['orientation']) || !empty($data['ap_program'])): ?>
<div class="meta-item">
<strong>Programme:</strong>
<span>
<?php if (!empty($data['orientation'])): ?>
<?= htmlspecialchars($data['orientation']); ?>
<?php endif; ?>
<?php if (!empty($data['orientation']) && !empty($data['ap_program'])): ?>
et
<?php endif; ?>
<?php if (!empty($data['ap_program'])): ?>
<?= htmlspecialchars($data['ap_program']); ?>
<?php endif; ?>
</span>
</div>
<?php endif; ?>
<?php if (!empty($data['finality_type'])): ?>
<div class="meta-item">
<strong>Finalité:</strong>
<span><?= htmlspecialchars($data['finality_type']); ?></span>
</div>
<?php endif; ?>
<?php if (!empty($data['supervisors'])): ?>
<div class="meta-item">
<strong>Promoteur·ice·s:</strong>
<span><?= htmlspecialchars($data['supervisors']); ?></span>
</div>
<?php endif; ?>
</div>
<div class="metadata-column">
<?php if (!empty($data['languages'])): ?>
<div class="meta-item">
<strong>Langue(s):</strong>
<span><?= htmlspecialchars($data['languages']); ?></span>
</div>
<?php endif; ?>
<?php if (!empty($data['formats'])): ?>
<div class="meta-item">
<strong>Format(s):</strong>
<span><?= htmlspecialchars($data['formats']); ?></span>
</div>
<?php endif; ?>
</div>
</div>
<?php if (!empty($data['context_note'])): ?>
<div class="context-note">
<em><?= htmlspecialchars($data['context_note']); ?></em>
</div>
<?php endif; ?>
</section>
<!-- Synopsis Section -->
<?php if (!empty($data['synopsis'])): ?>
<section class="tfe-synopsis">
<h3>Synopsis</h3>
<div class="synopsis-content">
<?= nl2br(htmlspecialchars($data['synopsis'])); ?>
</div>
</section>
<?php endif; ?>
<!-- Files Section -->
<section class="tfe-files">
<?php if (isset($data['files']) && count($data['files']) > 0): ?>
<?php foreach ($data['files'] as $file): ?>
<?php $ext = strtolower(pathinfo($file['file_path'], PATHINFO_EXTENSION)); ?>
<div class="file-block">
<?php if ($ext === 'pdf'): ?>
<embed src="/media.php?path=<?= urlencode($file['file_path']); ?>" type="application/pdf" width="100%" height="800px" />
<?php elseif (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'])): ?>
<figure class="image-figure">
<img src="/media.php?path=<?= urlencode($file['file_path']); ?>" alt="<?= htmlspecialchars($file['file_name']); ?>">
</figure>
<?php elseif ($ext === 'mp4'): ?>
<video width="100%" controls>
<source src="/media.php?path=<?= urlencode($file['file_path']); ?>" type="video/mp4">
Votre navigateur ne supporte pas la lecture de vidéos.
</video>
<?php endif; ?>
<?php if (!empty($file['description'])): ?>
<p class="file-description"><?= htmlspecialchars($file['description']); ?></p>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<p class="no-files">Aucun fichier disponible pour ce TFE.</p>
<?php endif; ?>
</section>
</div>
</main>
<footer class="tfe-footer">
<div class="footer-content">
<a href="index.php" class="back-button" aria-label="Retour à l'accueil" title="Retour à l'accueil">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</a>
<div class="footer-meta">
<span><?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu'); ?></span>
<span class="separator">•</span>
<span><?= htmlspecialchars($data['year']); ?></span>
</div>
</div>
</footer>
</body>
</html>

31
templates/head.php Normal file
View File

@@ -0,0 +1,31 @@
<!-- head.php - Shared HTML head section -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="">
<meta name="description" content="">
<title><?= isset($pageTitle) ? htmlspecialchars($pageTitle) . ' - Posterg' : 'Posterg' ?></title>
<link rel="stylesheet" href="assets/modern-normalize.css">
<link rel="stylesheet" href="assets/common.css">
<?php if (isset($additionalCSS)): ?>
<?php foreach ((array)$additionalCSS as $css): ?>
<link rel="stylesheet" href="<?= htmlspecialchars($css) ?>">
<?php endforeach; ?>
<?php endif; ?>
<?php if (php_sapi_name() === 'cli-server'): ?>
<!-- Live reload for development -->
<script>
(function poll() {
fetch('/live-reload.php')
.then(r => r.json())
.then(d => {
if (d.changed) location.reload();
else setTimeout(poll, 1000);
})
.catch(() => setTimeout(poll, 2000));
})();
</script>
<?php endif; ?>
</head>

View File

@@ -17,7 +17,10 @@
(function poll() {
fetch('/live-reload.php')
.then(r => r.json())
.then(d => { if (d.changed) location.reload(); else setTimeout(poll, 1000); })
.then(d => {
if (d.changed) location.reload();
else setTimeout(poll, 1000);
})
.catch(() => setTimeout(poll, 2000));
})();
</script>
@@ -31,8 +34,7 @@
</a>
<section>
<p class="apropos">
Ce site post-ERG a été créé pour répertorier et valoriser les mémoires de l'ERG - École de Recherches Graphique de Bruxelles.
Lobjectif est à la fois doffrir une vitrine aux projets des ancien·nes étudiant·es et de mettre en lumière la diversité des disciplines et des parcours qui façonnent lhistoire de lécole à travers les âges, depuis près de 100 ans.
Ce site archive et valorise les mémoires de l'ERG (Bruxelles). Il expose les projets des diplômé·es pour illustrer la diversité des parcours qui marquent l'histoire centenaire de l'école.
</p>
<p class="colophon">
Design & développement : Olivia Marly, Théo Hennequin & Théophile Gervreau-Mercie