Files
xamxam/app/public/assets/css/common.css
Pontoporeia 77fd282e29 refactor: unify edit mode Format+Fichiers with add/partage HTMX fragment
- Edit mode now uses the same fichiers-fragment.php as add and partage,
  instead of duplicating the format checkboxes + new-file upload + website
  URL fieldsets.
- Edit-only elements (existing files list, cover replace) stay in
  a separate #edit-existing-files-block below the shared fragment.
- Removed .zip/.tar/.gz from the main TFE upload accept in both
  fichiers-fragment.php and fieldset-files.php. Archives go only
  in the Annexes file input.
- Removed admin/format-website-fragment.php dependency from edit
  (no longer needed — the shared fragment handles website too).

fix: jury repop crash + hx-preserve on file inputs, remove zip/tar from tfe accept

- Jury fieldset add-mode repopulation now handles both scalar (legacy)
  and array (new dynamic multi-row) values for jury_promoteur and
  jury_promoteur_ulb_name. htmlspecialchars() was choking on array value.
- All file inputs in fichiers-fragment.php wrapped in hx-preserve
  containers so HTMX swaps don't wipe user-selected files when toggling
  formats or the annexes checkbox.
- Removed .zip/.tar/.gz from main TFE file accept — archives only via
  annexes input (which already had multiple + correct accept).
- Edit mode now reuses the same fichiers-fragment.php fragment.

fix: file inputs re-initialize after HTMX swap via inline script

- Exposed window.XamxamInitFileUploads from file-upload-queue.js IIFE
  so HTMX fragments can trigger re-binding without a global listener.
- fichiers-fragment.php emits <script>XamxamInitFileUploads()</script>
  at the end of the #format-fichiers-block fragment.
- Removed hx-preserve wrappers — they prevented re-render after
  format/annexes toggles changed visible inputs.
- This also fixes .zip removal from TFE accept and jury repopulation
  array crash from the previous commit.

refactor: simplify file-upload-queue.js, remove file-preview.js

- file-upload-queue.js rewritten from ~250 lines to ~120 lines:
  no more DataTransfer machinery, no IIFE wrapper, uses .onchange
  instead of addEventListener for simpler HTMX re-init.
- window.XamxamInitFileUploads is the function itself (not an IIFE export).
- Merged file-preview.js functionality into file-upload-queue.js
  (single-file .data-preview handling). Deleted file-preview.js.
- fichiers-fragment.php inline script calls XamxamInitFileUploads()
  after every HTMX swap (same as before).

debug: add console.log to file-upload-queue.js for file input behavior

Adds logging at key points to diagnose why only one file is displayed:
- XamxamInitFileUploads called
- TFE queue picker init (id, multiple attribute state)
- onchange event (files count, names)
- fileArray post-concat length
- Single-file preview bindings (id, multiple attribute)

Remove after debug session.
2026-05-13 18:03:33 +02:00

654 lines
15 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import url("./variables.css");
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
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;
}
a {
color: inherit;
text-decoration: none;
}
a:hover {
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%
);
.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;
}
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[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
direct flex items of nav, preserving the existing layout */
.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 */
}
}
/* ============================================================
HAMBURGER MENU — public nav (pure CSS, checkbox trick)
DOM order inside <header> (public only):
input.menu-btn ← off-screen checkbox
nav
div.nav-top-row ← always-visible row (logo + burger)
div.nav-left ← logo + desktop link list
ul.nav-right-links ← desktop right links
label.menu-icon ← burger icon trigger
ul.nav-mobile-links ← full dropdown (hidden by default)
At desktop: .menu-icon and .nav-mobile-links are display:none.
.nav-top-row is display:contents so its children
participate directly in navs flex row.
At mobile: nav becomes a flex column. .nav-top-row is a real
flex row (logo | burger). .nav-mobile-links expands
via max-height on checkbox:checked.
============================================================ */
/* Off-screen checkbox — triggered by its label */
.menu-btn {
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;
}
/* 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;
}
/* 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;
}
.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;
}
/* 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;
}
/* 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;
}
/* ---- 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;
}
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;
}
.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;
}
}
main {
flex: 1;
min-height: 0;
overflow-wrap: anywhere;
}
main * {
overflow-wrap: anywhere;
word-break: break-word;
}
/* ============================================================
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%);
}
.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);
}
.header-search-wrap form[role="search"] svg {
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;
}
.header-search-wrap form[role="search"] input::placeholder {
color: var(--accent-primary);
}
/* ============================================================
ACCESSIBILITY UTILITIES
============================================================ */
/* 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;
}
/* 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;
}
.skip-link:focus {
top: 0;
}
/* Consistent keyboard-focus outline for all interactive elements */
:focus-visible {
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;
}
}
/* ============================================================
BUTTONS — shared .btn base class
Targets both <a> and <button>, always has a background.
border-radius: 10px; padding: var(--space-xs)
============================================================ */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-3xs);
padding: var(--space-xs);
border-radius: 10px;
font-size: var(--step--1);
font-family: inherit;
font-weight: 500;
letter-spacing: 0.04em;
cursor: pointer;
text-decoration: none;
line-height: 1.3;
border: none;
transition:
background 0.15s,
opacity 0.15s,
box-shadow 0.15s,
filter 0.15s;
width: fit-content;
}
.btn:hover {
filter: brightness(0.92);
}
/* Primary: accent background, white text */
.btn--primary {
background: var(--accent-primary);
color: var(--accent-foreground);
border: 1px solid transparent;
}
.btn--primary:hover {
background: var(--accent-secondary);
filter: none;
}
/* Secondary: light background with border */
.btn--secondary {
background: var(--bg-primary);
color: var(--text-secondary);
border: 1px solid var(--border-primary);
}
.btn--secondary:hover {
border-color: var(--text-secondary);
color: var(--text-primary);
filter: none;
}
/* Muted secondary: bg-secondary background */
.btn--muted {
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid var(--border-primary);
}
.btn--muted:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
filter: none;
}
/* Ghost: transparent bg, border, for links styled as buttons */
.btn--ghost {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-primary);
}
.btn--ghost:hover {
border-color: var(--text-secondary);
color: var(--text-primary);
filter: none;
}
/* Danger: red background */
.btn--danger {
background: var(--accent-red);
color: var(--accent-foreground);
}
.btn--danger:hover {
filter: brightness(0.9);
}
/* Warning: yellow background */
.btn--warning {
background: var(--accent-yellow);
color: var(--text-primary);
}
.btn--warning:hover {
filter: brightness(0.9);
}
/* Success: green background */
.btn--success {
background: var(--accent-green);
color: var(--accent-foreground);
}
.btn--success:hover {
filter: brightness(0.9);
}
/* Small size modifier */
.btn--sm {
padding: var(--space-2xs) var(--space-xs);
font-size: var(--step--2);
}
/* Large size modifier */
.btn--lg {
padding: var(--space-s) var(--space-m);
font-size: var(--step-0);
}
/* Semantic colour modifiers on the muted base (for table/action buttons) */
.btn--blue {
background: var(--blue-muted-bg);
color: var(--accent-blue);
border: 1px solid var(--blue-muted-border);
}
.btn--blue:hover {
background: var(--blue-muted-bg-hover);
filter: none;
}
.btn--yellow {
background: var(--yellow-muted-bg);
color: var(--accent-yellow);
border: 1px solid var(--yellow-muted-border);
}
.btn--yellow:hover {
background: var(--yellow-muted-bg-hover);
filter: none;
}
.btn--green {
background: var(--green-muted-bg);
color: var(--accent-green);
border: 1px solid var(--green-muted-border);
}
.btn--green:hover {
background: var(--green-muted-bg-hover);
filter: none;
}
.btn--red {
background: var(--error-muted-bg);
color: var(--error);
border: 1px solid var(--danger-border-muted);
}
.btn--red:hover {
filter: brightness(0.9);
}
/* ============================================================
SEMANTIC HTML ELEMENTS — baseline styling shared everywhere
============================================================ */
fieldset {
/*background: var(--bg-secondary);*/
border: 1px solid var(--border-primary);
border-radius: 4px;
padding: var(--space-m);
margin: 0;
}
fieldset > *:not(:last-child) {
margin-bottom: var(--space-xs);
}
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);
}
small {
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);
}
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;
}
td {
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);
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.45);
}
details > summary {
cursor: pointer;
list-style: none;
}
details > summary::-webkit-details-marker {
display: none;
}
:where(video, audio, iframe) {
border: 1px solid var(--bg-primary);
border-radius: 15px;
}
audio::-webkit-media-controls-enclosure {
border-radius: 10px;
}