mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Refactor apropos/charte/licence pages: shared layout, TOC anchors, and UI polish
Unify the three public pages (à propos, charte, licence) onto a single grid layout (.page-content) with sticky TOC sidebar, replacing the old separate / / markup. - Merge about.php, charte.php, licence.php templates into shared .page-content / .content-section structure - Add CommonMark HeadingPermalinkExtension for stable heading anchors - Use SlugNormalizer for TOC links so they match rendered heading IDs - Standardize link styling across content blocks: bold black, accent on hover (consistent with global link style) - Fix code block wrapping: use pre-wrap instead of pre, constrain grid columns with min-width:0, auto scrollbar - Fix apropos page grid placement: force content-section into column 2 so contacts and credits stay in the content area, not the sidebar Also includes accumulated WIP changes: - Header gradient: hardcoded purple-to-green (replaces CSS variables) - Search placeholder font - Duration field: replace minutes/sec/heures with h:m:s time inputs - TFE file optional for formats 1,4,6 with client-side JS toggle - Licence form: em-dash to hyphen, details/summary classes - Pill search: block Enter key form submission when no results - Draft autosave: remove CSRF rotation (broke concurrent FilePond uploads) - Language pill: clear hints for excluded main languages - Search results: gradient placeholder cards for items without covers - TFE display: format durée values as XhYm instead of decimal
This commit is contained in:
@@ -1,28 +1,22 @@
|
||||
/* ============================================================
|
||||
À PROPOS PAGE (apropos.php)
|
||||
Root class: .apropos-main
|
||||
À PROPOS / CHARTE / LICENCE pages
|
||||
Root class: .page-content
|
||||
============================================================ */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Page shell */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
.apropos-main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-xl) var(--space-l) var(--space-2xl);
|
||||
/* Override body overflow:hidden — these pages use the viewport scrollbar
|
||||
so that anchor navigation (#fragment) works natively. */
|
||||
.apropos-body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Two-column layout: sticky TOC nav | content */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
.apropos-layout {
|
||||
.page-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
scroll-behavior: smooth;
|
||||
padding: var(--space-xl) var(--space-l) var(--space-2xl);
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr;
|
||||
gap: var(--space-2xl);
|
||||
width: 100%;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@@ -36,13 +30,13 @@
|
||||
}
|
||||
|
||||
.apropos-toc-label {
|
||||
font-family: var(--font-body);
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--step--2);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
font-weight: 400;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 var(--space-2xs) 0;
|
||||
padding-bottom: var(--space-2xs);
|
||||
border-bottom: 1px solid var(--text-primary);
|
||||
}
|
||||
|
||||
.apropos-toc ul {
|
||||
@@ -62,13 +56,10 @@
|
||||
display: block;
|
||||
padding: var(--space-3xs) 0;
|
||||
transition: color 0.15s;
|
||||
border-left: 2px solid transparent;
|
||||
padding-left: var(--space-2xs);
|
||||
}
|
||||
|
||||
.apropos-toc ul a:hover {
|
||||
color: var(--accent-primary);
|
||||
border-left-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.apropos-toc-link:first-of-type {
|
||||
@@ -96,19 +87,115 @@
|
||||
/* Right — main content area */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
.apropos-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
.page-content > .content,
|
||||
.page-content > .content-section {
|
||||
grid-column: 2;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.apropos-section {
|
||||
/* Shared typography for about-page sections and charte/licence content */
|
||||
.content,
|
||||
.content-section {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--step-0);
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.content *,
|
||||
.content-section * {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.content p,
|
||||
.content-section p {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
.content p:last-child,
|
||||
.content-section p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content :where(h1, h2, h3),
|
||||
.content-section :where(h1, h2, h3) {
|
||||
margin: 1.5em 0 0.5em 0;
|
||||
}
|
||||
|
||||
.content a,
|
||||
.content-section a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content a:hover,
|
||||
.content-section a:hover {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.content ul,
|
||||
.content ol,
|
||||
.content-section ul,
|
||||
.content-section ol {
|
||||
padding-left: var(--space-m);
|
||||
margin-bottom: var(--space-s);
|
||||
}
|
||||
|
||||
.content li,
|
||||
.content-section li {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.content strong,
|
||||
.content-section strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content em,
|
||||
.content-section em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.content :where(pre, pre code, code),
|
||||
.content-section :where(pre, pre code, code) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-wrap: normal;
|
||||
word-break: normal;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 0.88em;
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0.5em 0.75em;
|
||||
border-radius: var(--radius);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#apropos-intro *,
|
||||
#apropos-contacts *,
|
||||
#apropos-credits * {
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Section separators (about page only — .content-section adds dividers) */
|
||||
.page-content > .content-section {
|
||||
padding-bottom: var(--space-xl);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.apropos-section:last-child {
|
||||
.page-content > .content-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
@@ -118,7 +205,7 @@
|
||||
/* Section titles */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
.apropos-section-title {
|
||||
.content-section-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--step-3);
|
||||
font-weight: 400;
|
||||
@@ -127,59 +214,9 @@
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Intro prose — Markdown-rendered content */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
.prose {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--step-0);
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
.prose p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.prose :where(h1, h2, h3) {
|
||||
margin: 1.5em 0 0.5em 0;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.prose ul,
|
||||
.prose ol {
|
||||
padding-left: var(--space-m);
|
||||
margin-bottom: var(--space-s);
|
||||
}
|
||||
|
||||
.prose li {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.prose strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
.prose em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 0.88em;
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: var(--radius);
|
||||
/* Hide CommonMark heading permalink anchors (needed only for their id attr) */
|
||||
.heading-permalink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -220,15 +257,15 @@
|
||||
|
||||
.apropos-contact-card a {
|
||||
font-size: var(--step--1);
|
||||
color: var(--accent-primary);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
transition: opacity 0.15s;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.apropos-contact-card a:hover {
|
||||
color: var(--accent-primary);
|
||||
opacity: 1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -284,7 +321,7 @@
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.apropos-layout {
|
||||
.page-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-l);
|
||||
}
|
||||
@@ -309,32 +346,27 @@
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.apropos-toc ul a {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.apropos-toc-link {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.prose {
|
||||
.content-section {
|
||||
font-size: var(--step-0);
|
||||
}
|
||||
|
||||
.apropos-section-title {
|
||||
.content-section-title {
|
||||
font-size: var(--step-2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.apropos-main {
|
||||
.page-content {
|
||||
padding: var(--space-m) var(--space-s) var(--space-xl);
|
||||
}
|
||||
|
||||
.prose {
|
||||
.content-section {
|
||||
font-size: var(--step-0);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@
|
||||
--accent-red: #f25a5a;
|
||||
|
||||
/* Gradient (header) */
|
||||
--gradient-1: #3c856c;
|
||||
--gradient-2: #60ecb4;
|
||||
--gradient-3: #e390ff;
|
||||
--gradient-4: #9557b5;
|
||||
--gradient-1: #42963f;
|
||||
--gradient-2: #65e478;
|
||||
--gradient-3: #57abc7;
|
||||
--gradient-4: #db53ed;
|
||||
|
||||
/* Header decorative */
|
||||
--header-gradient-fade: rgba(149, 87, 181, 0);
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
header {
|
||||
vertical-align: center;
|
||||
flex-shrink: 0;
|
||||
background: #9557B5;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--gradient-1) 0%,
|
||||
var(--gradient-2) 33%,
|
||||
var(--gradient-3) 66%,
|
||||
var(--gradient-4) 100%
|
||||
0deg,
|
||||
rgba(149, 87, 181, 1) 0%,
|
||||
rgba(192, 93, 225, 1) 25%,
|
||||
rgba(51, 191, 135, 1) 75%,
|
||||
rgba(60, 133, 108, 1) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,7 +61,6 @@ header nav ul a[aria-current="page"] {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
|
||||
/* ── Logo ───────────────────────────────────────────────────────────── */
|
||||
|
||||
.nav-logo {
|
||||
@@ -130,8 +130,12 @@ header nav ul a[aria-current="page"] {
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.navicon::before { top: -7px; }
|
||||
.navicon::after { bottom: -7px; }
|
||||
.navicon::before {
|
||||
top: -7px;
|
||||
}
|
||||
.navicon::after {
|
||||
bottom: -7px;
|
||||
}
|
||||
|
||||
/* ── Mobile (≤ 640px) ───────────────────────────────────────────────── */
|
||||
|
||||
@@ -160,11 +164,14 @@ header nav ul a[aria-current="page"] {
|
||||
}
|
||||
|
||||
header nav[aria-label="Navigation principale"]
|
||||
.nav-left-links li:not(:first-child) {
|
||||
.nav-left-links
|
||||
li:not(:first-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-icon { display: flex; }
|
||||
.menu-icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
header nav[aria-label="Navigation principale"] .nav-mobile-links {
|
||||
display: block;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.header-search-wrap {
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(180deg, var(--gradient-4) 0%, #ffffffee 100%);
|
||||
background: linear-gradient(180deg, #9557B5 0%, #ffffffee 100%);
|
||||
}
|
||||
|
||||
.header-search-form { width: 100%; }
|
||||
@@ -40,4 +40,6 @@
|
||||
|
||||
.header-search-input-wrap input::placeholder {
|
||||
color: var(--accent-primary) !important;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@@ -352,6 +352,34 @@
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.duration-time-inputs {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.duration-time-fields {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3xs);
|
||||
}
|
||||
|
||||
.duration-time-fields span {
|
||||
font-size: var(--step--1);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.licence-details {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.licence-summary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3xs);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.licence-degree h4 {
|
||||
margin: 0 0 var(--space-2xs);
|
||||
font-weight: 600;
|
||||
|
||||
@@ -111,8 +111,8 @@
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(60, 133, 108, 1) 0%,
|
||||
rgba(96, 236, 180, 1) 33%,
|
||||
rgba(227, 144, 255, 1) 66%,
|
||||
rgba(51, 191, 135, 1) 25%,
|
||||
rgba(192, 93, 225, 1) 75%,
|
||||
rgba(149, 87, 181, 1) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,6 +223,56 @@
|
||||
gap: var(--space-3xs);
|
||||
}
|
||||
|
||||
.result-card__cover {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.result-card__cover img {
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
border-radius: 7px 7px 0 0;
|
||||
}
|
||||
|
||||
.result-card__gradient {
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-s);
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: 7px 7px 0 0;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(60, 133, 108, 1) 0%,
|
||||
rgba(51, 191, 135, 1) 25%,
|
||||
rgba(192, 93, 225, 1) 75%,
|
||||
rgba(149, 87, 181, 1) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.result-card__gradient-author {
|
||||
color: var(--accent-foreground);
|
||||
font-size: var(--step--2);
|
||||
opacity: 0.85;
|
||||
margin-bottom: 0.25rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-card__gradient-title {
|
||||
color: var(--accent-foreground);
|
||||
font-size: var(--step--1);
|
||||
font-weight: 600;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.result-card__authors {
|
||||
font-size: var(--step--1);
|
||||
font-weight: 500;
|
||||
@@ -391,8 +441,8 @@
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(60, 133, 108, 1) 0%,
|
||||
rgba(96, 236, 180, 1) 33%,
|
||||
rgba(227, 144, 255, 1) 66%,
|
||||
rgba(51, 191, 135, 1) 25%,
|
||||
rgba(192, 93, 225, 1) 75%,
|
||||
rgba(149, 87, 181, 1) 100%
|
||||
);
|
||||
display: flex;
|
||||
|
||||
@@ -643,6 +643,7 @@
|
||||
enableFilepondMode();
|
||||
_xamxamFilepondReady = false;
|
||||
window.XamxamInitFilePonds();
|
||||
if (window.XamxamUpdateTfeRequired) window.XamxamUpdateTfeRequired();
|
||||
setTimeout(() => {
|
||||
_xamxamFilepondReady = true;
|
||||
}, 0);
|
||||
@@ -694,6 +695,68 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ── TFE file optional when Site web (1), Performance (4) or Installation (6) ──
|
||||
// The format checkboxes no longer trigger HTMX swaps; this JS toggles the TFE
|
||||
// required attribute and asterisk client-side so the student sees immediate feedback.
|
||||
// admin_mode hidden input (value="1") suppresses required toggling for admins.
|
||||
(function () {
|
||||
var optionalFormatIds = ["1", "4", "6"];
|
||||
|
||||
function isAdminMode() {
|
||||
var el = document.querySelector('input[name="admin_mode"]');
|
||||
return el && el.value === "1";
|
||||
}
|
||||
|
||||
function updateTfeRequired() {
|
||||
if (isAdminMode()) return;
|
||||
|
||||
var tfeInput = document.getElementById("tfe-files-input");
|
||||
if (!tfeInput) return;
|
||||
|
||||
var checkedAny = false;
|
||||
var boxes = document.querySelectorAll('input[name="formats[]"]:checked');
|
||||
for (var i = 0; i < boxes.length; i++) {
|
||||
if (optionalFormatIds.indexOf(boxes[i].value) !== -1) {
|
||||
checkedAny = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the label for the TFE input (its parent group's <label>)
|
||||
var fieldGroup = tfeInput.closest(".admin-files-fieldgroup");
|
||||
var label = fieldGroup ? fieldGroup.querySelector("label[for='tfe-files-input']") : null;
|
||||
|
||||
if (checkedAny) {
|
||||
tfeInput.removeAttribute("required");
|
||||
// Replace asterisk + optional text
|
||||
if (label) {
|
||||
label.textContent = "TFE (optionnel pour ce format)";
|
||||
}
|
||||
} else {
|
||||
tfeInput.setAttribute("required", "");
|
||||
if (label) {
|
||||
label.innerHTML = "TFE <span class='asterisk'>*</span>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate change events on the format fieldset
|
||||
var formatFieldset = document.getElementById("fieldset-formats");
|
||||
if (formatFieldset) {
|
||||
formatFieldset.addEventListener("change", function (e) {
|
||||
if (e.target && e.target.name === "formats[]") {
|
||||
updateTfeRequired();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Run once on page load
|
||||
updateTfeRequired();
|
||||
|
||||
// Expose for HTMX afterSwap re-init
|
||||
window.XamxamUpdateTfeRequired = updateTfeRequired;
|
||||
})();
|
||||
|
||||
// ── Relink file browser ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -159,8 +159,14 @@
|
||||
}
|
||||
highlight(selectedIdx);
|
||||
} else if (e.key === "Enter") {
|
||||
// Always prevent Enter from submitting the form.
|
||||
// If there are no suggestions (e.g., "anglais" in language
|
||||
// search — excluded main language), the Enter key would
|
||||
// otherwise propagate to the form and trigger its hx-post to
|
||||
// draft.php, causing the JSON response to replace the form
|
||||
// content.
|
||||
e.preventDefault();
|
||||
if (items.length > 0) {
|
||||
e.preventDefault();
|
||||
if (selectedIdx >= 0 && selectedIdx < items.length) {
|
||||
selectPill(items[selectedIdx]);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user