justfile: standardise test recipes to lint-php/lint-css/lint-js/test + add fix recipe

- Removed ambiguous aliases: phpstan, cs-check, syntax
- Split lint-biome into lint-css and lint-js with correct paths
- Added lint meta-recipe and fix recipe (biome --unsafe + php-cs-fixer)
- Fixed FormBootstrap dead null check, CSS shorthand override bug
- Updated phpstan baseline, suppressed noDescendingSpecificity/noInnerDeclarations
- Applied ~74 biome auto-fixes across CSS/JS
This commit is contained in:
Pontoporeia
2026-07-05 11:07:41 +02:00
parent 34b5d3e585
commit 3e93150c76
59 changed files with 5459 additions and 5196 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -18,4 +18,4 @@
- [x] Add bulk select/delete to tmp cleanup dialog (like admin/index.php pattern) - [x] Add bulk select/delete to tmp cleanup dialog (like admin/index.php pattern)
- [x] Move cleanup UI from modal to dedicated page (like add.php/edit.php) - [x] Move cleanup UI from modal to dedicated page (like add.php/edit.php)
- [x] Replace <details> with <fieldset>/<legend> in cleanup fragments (like contenus.php) - [x] Replace <details> with <fieldset>/<legend> in cleanup fragments (like contenus.php)
- [x] Standardise test group recipes: lint-php, lint-css, lint-js, test + add fix recipe - [x] Standardise test group recipes: lint-php, lint-css, lint-js, test + add fix recipe + make lint pass clean
File diff suppressed because it is too large Load Diff
+49 -36
View File
@@ -5,72 +5,85 @@
============================================================ */ ============================================================ */
/* Full-height flex layout: header → main → (optional footer) */ /* Full-height flex layout: header → main → (optional footer) */
html, body { html,
height: 100%; body {
overflow: hidden; height: 100%;
overflow: hidden;
} }
/* ── Discreet scrollbars ─────────────────────────────────────────── */ /* ── Discreet scrollbars ─────────────────────────────────────────── */
/* WebKit browsers */ /* WebKit browsers */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 5px; width: 5px;
height: 5px; height: 5px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: transparent; background: transparent;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: var(--text-tertiary); background: var(--text-tertiary);
border-radius: 3px; border-radius: 3px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary); background: var(--text-secondary);
} }
/* Firefox */ /* Firefox */
* { * {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--text-tertiary) transparent; scrollbar-color: var(--text-tertiary) transparent;
} }
body { body {
font-family: var(--font-body); font-family: var(--font-body);
font-weight: 300; font-weight: 300;
color: var(--text-primary); color: var(--text-primary);
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
rgba(0, 0, 0, 0) 92%, rgba(0, 0, 0, 0) 92%,
rgba(149, 87, 181, 1) 100% rgba(149, 87, 181, 1) 100%
); );
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
main { main {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
main * { main * {
overflow-wrap: anywhere; overflow-wrap: anywhere;
word-break: break-word; word-break: break-word;
} }
/* Global heading scale — used by admin + public pages */ /* Global heading scale — used by admin + public pages */
h1 { font-size: var(--step-4); } h1 {
h2 { font-size: var(--step-3); } font-size: var(--step-4);
h3 { font-size: var(--step-2); } }
h4 { font-size: var(--step-1); } h2 {
h5 { font-size: var(--step-0); } font-size: var(--step-3);
h6 { font-size: var(--step--1); } }
h3 {
font-size: var(--step-2);
}
h4 {
font-size: var(--step-1);
}
h5 {
font-size: var(--step-0);
}
h6 {
font-size: var(--step--1);
}
:where(h1, h2, h3, h4, h5, h6) { :where(h1, h2, h3, h4, h5, h6) {
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 400; font-weight: 400;
margin: 0 0 var(--space-l) 0; margin: 0 0 var(--space-l) 0;
line-height: 1.15; line-height: 1.15;
} }
+66 -66
View File
@@ -4,80 +4,80 @@
============================================================ */ ============================================================ */
:root { :root {
/* Backgrounds */ /* Backgrounds */
--bg-primary: #ffffff; --bg-primary: #ffffff;
--bg-secondary: #f5f5f5; --bg-secondary: #f5f5f5;
--bg-tertiary: #e8e8e8; --bg-tertiary: #e8e8e8;
--bg-active: #d0d0d0; --bg-active: #d0d0d0;
/* Text */ /* Text */
--text-primary: #111111; --text-primary: #111111;
--text-secondary: #666666; --text-secondary: #666666;
--text-tertiary: #999999; --text-tertiary: #999999;
/* Borders */ /* Borders */
--border-primary: #dddddd; --border-primary: #dddddd;
--border-secondary: #cccccc; --border-secondary: #cccccc;
/* Border radius */ /* Border radius */
--radius: 10px; --radius: 10px;
/* Status */ /* Status */
--success: #5cd69d; --success: #5cd69d;
--error: #f25a5a; --error: #f25a5a;
--warning: #fbca51; --warning: #fbca51;
/* Accent */ /* Accent */
--accent-primary: #9557b5; --accent-primary: #9557b5;
--accent-secondary: #683d7f; --accent-secondary: #683d7f;
--accent-foreground: #ffffff; --accent-foreground: #ffffff;
--accent-muted: rgba(149, 87, 181, 0.12); --accent-muted: rgba(149, 87, 181, 0.12);
--accent-blue: #41adff; --accent-blue: #41adff;
--accent-green: #4caf50; --accent-green: #4caf50;
--accent-yellow: #f39c12; --accent-yellow: #f39c12;
--accent-red: #f25a5a; --accent-red: #f25a5a;
/* Gradient (header) */ /* Gradient (header) */
--gradient-1: #42963f; --gradient-1: #42963f;
--gradient-2: #65e478; --gradient-2: #65e478;
--gradient-3: #57abc7; --gradient-3: #57abc7;
--gradient-4: #db53ed; --gradient-4: #db53ed;
/* Header decorative */ /* Header decorative */
--header-gradient-fade: rgba(149, 87, 181, 0); --header-gradient-fade: rgba(149, 87, 181, 0);
--header-shadow-strong: rgba(119, 70, 145, 1); --header-shadow-strong: rgba(119, 70, 145, 1);
--header-shadow-soft: rgba(119, 70, 145, 0.8); --header-shadow-soft: rgba(119, 70, 145, 0.8);
--header-nav-active-border: rgba(255, 255, 255, 0.6); --header-nav-active-border: rgba(255, 255, 255, 0.6);
/* Search error block */ /* Search error block */
--search-error-bg: #fff0f0; --search-error-bg: #fff0f0;
--search-error-border: #cc0000; --search-error-border: #cc0000;
--search-error-color: #cc0000; --search-error-color: #cc0000;
/* System page — log/config syntax highlight */ /* System page — log/config syntax highlight */
--sys-syntax-comment: #999999; --sys-syntax-comment: #999999;
--sys-syntax-directive: #1a6fb5; --sys-syntax-directive: #1a6fb5;
--sys-syntax-block: #7a2fa0; --sys-syntax-block: #7a2fa0;
--sys-syntax-value: #a05c00; --sys-syntax-value: #a05c00;
--sys-syntax-location: #1a7a6b; --sys-syntax-location: #1a7a6b;
--sys-syntax-notice: #3a6ea8; --sys-syntax-notice: #3a6ea8;
--sys-syntax-crit: #c0392b; --sys-syntax-crit: #c0392b;
/* Muted alpha overlays — derived from semantic tokens */ /* Muted alpha overlays — derived from semantic tokens */
--success-muted-bg: rgba(92, 214, 157, 0.12); --success-muted-bg: rgba(92, 214, 157, 0.12);
--success-muted-border: rgba(92, 214, 157, 0.35); --success-muted-border: rgba(92, 214, 157, 0.35);
--warning-muted-bg: rgba(251, 202, 81, 0.12); --warning-muted-bg: rgba(251, 202, 81, 0.12);
--warning-muted-border: rgba(251, 202, 81, 0.35); --warning-muted-border: rgba(251, 202, 81, 0.35);
--error-muted-bg: rgba(242, 90, 90, 0.12); --error-muted-bg: rgba(242, 90, 90, 0.12);
--error-muted-border: rgba(242, 90, 90, 0.35); --error-muted-border: rgba(242, 90, 90, 0.35);
--blue-muted-bg: rgba(65, 173, 255, 0.12); --blue-muted-bg: rgba(65, 173, 255, 0.12);
--blue-muted-border: rgba(65, 173, 255, 0.3); --blue-muted-border: rgba(65, 173, 255, 0.3);
--blue-muted-bg-hover: rgba(65, 173, 255, 0.22); --blue-muted-bg-hover: rgba(65, 173, 255, 0.22);
--yellow-muted-bg: rgba(243, 156, 18, 0.12); --yellow-muted-bg: rgba(243, 156, 18, 0.12);
--yellow-muted-border: rgba(243, 156, 18, 0.3); --yellow-muted-border: rgba(243, 156, 18, 0.3);
--yellow-muted-bg-hover: rgba(243, 156, 18, 0.22); --yellow-muted-bg-hover: rgba(243, 156, 18, 0.22);
--green-muted-bg: rgba(76, 175, 80, 0.12); --green-muted-bg: rgba(76, 175, 80, 0.12);
--green-muted-border: rgba(76, 175, 80, 0.3); --green-muted-border: rgba(76, 175, 80, 0.3);
--green-muted-bg-hover: rgba(76, 175, 80, 0.22); --green-muted-bg-hover: rgba(76, 175, 80, 0.22);
--danger-border-muted: rgba(242, 90, 90, 0.35); --danger-border-muted: rgba(242, 90, 90, 0.35);
} }
+23 -23
View File
@@ -4,45 +4,45 @@
============================================================ */ ============================================================ */
.status-badge { .status-badge {
display: inline-block; display: inline-block;
padding: var(--space-3xs) var(--space-2xs); padding: var(--space-3xs) var(--space-2xs);
border-radius: var(--radius); border-radius: var(--radius);
font-size: var(--step--2); font-size: var(--step--2);
font-weight: 500; font-weight: 500;
letter-spacing: 0.04em; letter-spacing: 0.04em;
} }
.status-published { .status-published {
background: var(--green-muted-bg); background: var(--green-muted-bg);
color: var(--accent-green); color: var(--accent-green);
} }
.status-pending { .status-pending {
background: var(--warning-muted-bg); background: var(--warning-muted-bg);
color: var(--warning); color: var(--warning);
} }
.status-access { .status-access {
display: inline-block; display: inline-block;
font-size: var(--step--2); font-size: var(--step--2);
padding: var(--space-3xs) var(--space-3xs); padding: var(--space-3xs) var(--space-3xs);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--bg-tertiary); background: var(--bg-tertiary);
color: var(--text-secondary); color: var(--text-secondary);
letter-spacing: 0.03em; letter-spacing: 0.03em;
} }
.status-access--libre { .status-access--libre {
background: var(--green-muted-bg); background: var(--green-muted-bg);
color: var(--accent-green); color: var(--accent-green);
} }
.status-access--interne { .status-access--interne {
background: var(--blue-muted-bg); background: var(--blue-muted-bg);
color: var(--accent-blue); color: var(--accent-blue);
} }
.status-access--interdit { .status-access--interdit {
background: var(--error-muted-bg); background: var(--error-muted-bg);
color: var(--error); color: var(--error);
} }
+92 -69
View File
@@ -5,142 +5,165 @@
============================================================ */ ============================================================ */
.btn { .btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: var(--space-3xs); gap: var(--space-3xs);
padding: var(--space-xs); padding: var(--space-xs);
border-radius: var(--radius); border-radius: var(--radius);
font-size: var(--step--1); font-size: var(--step--1);
font-family: inherit; font-family: inherit;
font-weight: 500; font-weight: 500;
letter-spacing: 0.04em; letter-spacing: 0.04em;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
line-height: 1.3; line-height: 1.3;
border: none; border: none;
transition: background 0.15s, opacity 0.15s, box-shadow 0.15s, filter 0.15s; transition:
width: fit-content; background 0.15s,
opacity 0.15s,
box-shadow 0.15s,
filter 0.15s;
width: fit-content;
} }
.btn:hover { filter: brightness(0.92); } .btn:hover {
filter: brightness(0.92);
}
/* Primary: accent background, white text */ /* Primary: accent background, white text */
.btn--primary { .btn--primary {
background: var(--accent-primary); background: var(--accent-primary);
color: var(--accent-foreground); color: var(--accent-foreground);
border: 1px solid transparent; border: 1px solid transparent;
} }
.btn--primary:hover { .btn--primary:hover {
background: var(--accent-secondary); background: var(--accent-secondary);
filter: none; filter: none;
} }
/* Secondary: light background with border */ /* Secondary: light background with border */
.btn--secondary { .btn--secondary {
background: var(--bg-primary); background: var(--bg-primary);
color: var(--text-secondary); color: var(--text-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
} }
.btn--secondary:hover { .btn--secondary:hover {
border-color: var(--text-secondary); border-color: var(--text-secondary);
color: var(--text-primary); color: var(--text-primary);
filter: none; filter: none;
} }
/* Muted secondary: bg-secondary background */ /* Muted secondary: bg-secondary background */
.btn--muted { .btn--muted {
background: var(--bg-secondary); background: var(--bg-secondary);
color: var(--text-secondary); color: var(--text-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
} }
.btn--muted:hover { .btn--muted:hover {
background: var(--bg-tertiary); background: var(--bg-tertiary);
color: var(--text-primary); color: var(--text-primary);
filter: none; filter: none;
} }
/* Ghost: transparent bg, border, for links styled as buttons */ /* Ghost: transparent bg, border, for links styled as buttons */
.btn--ghost { .btn--ghost {
background: transparent; background: transparent;
color: var(--text-secondary); color: var(--text-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
} }
.btn--ghost:hover { .btn--ghost:hover {
border-color: var(--text-secondary); border-color: var(--text-secondary);
color: var(--text-primary); color: var(--text-primary);
filter: none; filter: none;
} }
/* Danger: red background */ /* Danger: red background */
.btn--danger { .btn--danger {
background: var(--accent-red); background: var(--accent-red);
color: var(--accent-foreground); color: var(--accent-foreground);
} }
.btn--danger:hover { filter: brightness(0.9); } .btn--danger:hover {
filter: brightness(0.9);
}
/* Warning: yellow background */ /* Warning: yellow background */
.btn--warning { .btn--warning {
background: var(--accent-yellow); background: var(--accent-yellow);
color: var(--text-primary); color: var(--text-primary);
} }
.btn--warning:hover { filter: brightness(0.9); } .btn--warning:hover {
filter: brightness(0.9);
}
/* Success: green background */ /* Success: green background */
.btn--success { .btn--success {
background: var(--accent-green); background: var(--accent-green);
color: var(--accent-foreground); color: var(--accent-foreground);
} }
.btn--success:hover { filter: brightness(0.9); } .btn--success:hover {
filter: brightness(0.9);
}
/* Small size modifier */ /* Small size modifier */
.btn--sm { .btn--sm {
padding: var(--space-2xs) var(--space-xs); padding: var(--space-2xs) var(--space-xs);
font-size: var(--step--2); font-size: var(--step--2);
} }
/* Large size modifier */ /* Large size modifier */
.btn--lg { .btn--lg {
padding: var(--space-s) var(--space-m); padding: var(--space-s) var(--space-m);
font-size: var(--step-0); font-size: var(--step-0);
} }
/* Semantic colour modifiers on muted base (for table/action buttons) */ /* Semantic colour modifiers on muted base (for table/action buttons) */
.btn--blue { .btn--blue {
background: var(--blue-muted-bg); background: var(--blue-muted-bg);
color: var(--accent-blue); color: var(--accent-blue);
border: 1px solid var(--blue-muted-border); border: 1px solid var(--blue-muted-border);
} }
.btn--blue:hover { background: var(--blue-muted-bg-hover); filter: none; } .btn--blue:hover {
background: var(--blue-muted-bg-hover);
filter: none;
}
.btn--yellow { .btn--yellow {
background: var(--yellow-muted-bg); background: var(--yellow-muted-bg);
color: var(--accent-yellow); color: var(--accent-yellow);
border: 1px solid var(--yellow-muted-border); border: 1px solid var(--yellow-muted-border);
} }
.btn--yellow:hover { background: var(--yellow-muted-bg-hover); filter: none; } .btn--yellow:hover {
background: var(--yellow-muted-bg-hover);
filter: none;
}
.btn--green { .btn--green {
background: var(--green-muted-bg); background: var(--green-muted-bg);
color: var(--accent-green); color: var(--accent-green);
border: 1px solid var(--green-muted-border); border: 1px solid var(--green-muted-border);
} }
.btn--green:hover { background: var(--green-muted-bg-hover); filter: none; } .btn--green:hover {
background: var(--green-muted-bg-hover);
filter: none;
}
.btn--red { .btn--red {
background: var(--error-muted-bg); background: var(--error-muted-bg);
color: var(--error); color: var(--error);
border: 1px solid var(--danger-border-muted); border: 1px solid var(--danger-border-muted);
} }
.btn--red:hover { filter: brightness(0.9); } .btn--red:hover {
filter: brightness(0.9);
}
+21 -19
View File
@@ -4,41 +4,43 @@
============================================================ */ ============================================================ */
details > summary { details > summary {
cursor: pointer; cursor: pointer;
list-style: none; list-style: none;
} }
details > summary::-webkit-details-marker { details > summary::-webkit-details-marker {
display: none; display: none;
} }
details { details {
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: var(--radius, 6px); border-radius: var(--radius, 6px);
background: var(--bg-secondary, var(--surface)); background: var(--bg-secondary, var(--surface));
overflow: hidden; overflow: hidden;
} }
details[open] { details[open] {
padding-bottom: var(--space-s); padding-bottom: var(--space-s);
} }
details > :not(summary) { details > :not(summary) {
padding-left: var(--space-s); padding-left: var(--space-s);
padding-right: var(--space-s); padding-right: var(--space-s);
} }
summary { summary {
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 600; font-weight: 600;
text-decoration: none; text-decoration: none;
color: var(--text-primary); color: var(--text-primary);
transition: color 0.15s, background 0.15s; transition:
padding: var(--space-s); color 0.15s,
cursor: pointer; background 0.15s;
padding: var(--space-s);
cursor: pointer;
} }
summary:hover { summary:hover {
color: var(--accent-primary); color: var(--accent-primary);
background: var(--hover-bg, rgba(0, 0, 0, 0.03)); background: var(--hover-bg, rgba(0, 0, 0, 0.03));
} }
+7 -7
View File
@@ -4,14 +4,14 @@
============================================================ */ ============================================================ */
dialog { dialog {
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--bg-primary); background: var(--bg-primary);
color: var(--text-primary); color: var(--text-primary);
padding: 0; padding: 0;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
} }
dialog::backdrop { dialog::backdrop {
background: rgba(0, 0, 0, 0.45); background: rgba(0, 0, 0, 0.45);
} }
+4 -4
View File
@@ -4,8 +4,8 @@
============================================================ */ ============================================================ */
:focus-visible { :focus-visible {
outline: none; outline: none;
box-shadow: 0 0 0 2px var(--accent-primary); box-shadow: 0 0 0 2px var(--accent-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-3xs) var(--space-xs); padding: var(--space-3xs) var(--space-xs);
} }
+71 -59
View File
@@ -7,107 +7,119 @@
/* ── Labels ─────────────────────────────────────────────────────────── */ /* ── Labels ─────────────────────────────────────────────────────────── */
label { label {
display: block; display: block;
margin-bottom: var(--space-3xs); margin-bottom: var(--space-3xs);
font-weight: 400; font-weight: 400;
} }
/* ── Text inputs, selects, textareas ────────────────────────────────── */ /* ── Text inputs, selects, textareas ────────────────────────────────── */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="color"]), input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not(
[type="hidden"]
):not([type="submit"]):not([type="button"]):not([type="reset"]):not(
[type="color"]
),
textarea { textarea {
font-family: inherit; font-family: inherit;
font-size: var(--step--1); font-size: var(--step--1);
padding: var(--space-2xs) var(--space-xs); padding: var(--space-2xs) var(--space-xs);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
background: transparent; background: transparent;
color: var(--text-primary); color: var(--text-primary);
transition: border-color 0.15s; transition: border-color 0.15s;
} }
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="color"]):focus, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not(
[type="hidden"]
):not([type="submit"]):not([type="button"]):not([type="reset"]):not(
[type="color"]
):focus,
textarea:focus { textarea:focus {
outline: none; outline: none;
border: 2px solid var(--accent-primary); border: 2px solid var(--accent-primary);
} }
input::placeholder, input::placeholder,
textarea::placeholder { textarea::placeholder {
color: var(--text-tertiary); color: var(--text-tertiary);
font-size: var(--step--1); font-size: var(--step--1);
} }
textarea { textarea {
resize: vertical; resize: vertical;
min-height: 80px; min-height: 80px;
line-height: 1.5; line-height: 1.5;
} }
/* ── Select ─────────────────────────────────────────────────────────── */ /* ── Select ─────────────────────────────────────────────────────────── */
select { select {
font-family: inherit; font-family: inherit;
font-size: var(--step--1); font-size: var(--step--1);
padding: var(--space-2xs) var(--space-xs); padding: var(--space-2xs) var(--space-xs);
padding-right: 1.75rem; padding-right: 1.75rem;
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
background: transparent; background: transparent;
color: var(--text-primary); color: var(--text-primary);
cursor: pointer; cursor: pointer;
background-image: url("/assets/icons/select-chevron.svg"); background-image: url("/assets/icons/select-chevron.svg");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 0.55rem center; background-position: right 0.55rem center;
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
transition: border-color 0.15s; transition: border-color 0.15s;
} }
select:focus { select:focus {
outline: none; outline: none;
border: 2px solid var(--accent-primary); border: 2px solid var(--accent-primary);
} }
/* ── Checkboxes & radios ────────────────────────────────────────────── */ /* ── Checkboxes & radios ────────────────────────────────────────────── */
input[type="checkbox"], input[type="checkbox"],
input[type="radio"] { input[type="radio"] {
accent-color: var(--accent-primary); accent-color: var(--accent-primary);
width: 16px; width: 16px;
height: 16px; height: 16px;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
margin: 0; margin: 0;
} }
input[type="radio"] { border-radius: 50%; } input[type="radio"] {
border-radius: 50%;
}
/* ── Fieldsets & legends ──────────────────────────────────────────────── */ /* ── Fieldsets & legends ──────────────────────────────────────────────── */
fieldset { fieldset {
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: 0 var(--space-m) var(--space-m) var(--space-m); padding: 0 var(--space-m) var(--space-m) var(--space-m);
margin: 0; margin: 0;
} }
fieldset > *:not(:last-child) { margin-bottom: var(--space-xs); } fieldset > *:not(:last-child) {
margin-bottom: var(--space-xs);
}
legend { legend {
font-size: var(--step--1); font-size: var(--step--1);
font-weight: 400; font-weight: 400;
letter-spacing: 0.04em; letter-spacing: 0.04em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-secondary); color: var(--text-secondary);
padding: 0 var(--space-2xs); padding: 0 var(--space-2xs);
} }
/* ── Small / help text ──────────────────────────────────────────────────── */ /* ── Small / help text ──────────────────────────────────────────────────── */
small { small {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-secondary); color: var(--text-secondary);
display: block; display: block;
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
} }
+138 -138
View File
@@ -6,81 +6,81 @@
/* ── Header bar ─────────────────────────────────────────────────────── */ /* ── Header bar ─────────────────────────────────────────────────────── */
header { header {
vertical-align: center; vertical-align: center;
flex-shrink: 0; flex-shrink: 0;
background: linear-gradient( background: linear-gradient(
0deg, 0deg,
rgba(192, 93, 225, 1) 0%, rgba(192, 93, 225, 1) 0%,
rgba(51, 191, 135, 1) 66%, rgba(51, 191, 135, 1) 66%,
rgba(60, 133, 108, 1) 100% rgba(60, 133, 108, 1) 100%
); );
} }
/* ── Navigation ────────────────────────────────────────────────────── */ /* ── Navigation ────────────────────────────────────────────────────── */
header nav { header nav {
padding: var(--space-s) var(--space-s); padding: var(--space-s) var(--space-s);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
font-size: var(--step-0); font-size: var(--step-0);
} }
header nav ul { header nav ul {
display: flex; display: flex;
gap: var(--space-l); gap: var(--space-l);
align-items: center; align-items: center;
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
header nav ul a { header nav ul a {
font-family: var(--font-display); font-family: var(--font-display);
letter-spacing: 0.12em; letter-spacing: 0.12em;
text-transform: uppercase; text-transform: uppercase;
color: var(--accent-foreground); color: var(--accent-foreground);
text-decoration: none; text-decoration: none;
padding: var(--space-3xs) var(--space-xs); padding: var(--space-3xs) var(--space-xs);
text-shadow: text-shadow:
0 0 16px var(--header-shadow-strong), 0 0 16px var(--header-shadow-strong),
0 0 32px var(--header-shadow-soft); 0 0 32px var(--header-shadow-soft);
transition: opacity 0.15s; transition: opacity 0.15s;
} }
header nav ul a:hover { header nav ul a:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
header nav ul a[aria-current="page"] { header nav ul a[aria-current="page"] {
color: var(--accent-primary); color: var(--accent-primary);
border-radius: 0; border-radius: 0;
border-bottom: 2px solid currentColor; border-bottom: 2px solid currentColor;
padding-bottom: 1px; padding-bottom: 1px;
} }
/* ── Logo ───────────────────────────────────────────────────────────── */ /* ── Logo ───────────────────────────────────────────────────────────── */
.nav-logo { .nav-logo {
text-decoration: none; text-decoration: none;
} }
.nav-left-links, .nav-left-links,
.nav-right-links { .nav-right-links {
display: flex; display: flex;
gap: var(--space-l); gap: var(--space-l);
align-items: center; align-items: center;
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.nav-top-row { .nav-top-row {
display: contents; display: contents;
} }
.nav-mobile-links { .nav-mobile-links {
display: none; display: none;
} }
/* ── Hamburger menu (pure CSS, checkbox trick) ──────────────────────── */ /* ── Hamburger menu (pure CSS, checkbox trick) ──────────────────────── */
@@ -94,132 +94,132 @@ header nav ul a[aria-current="page"] {
ul.nav-mobile-links ← full dropdown (hidden by default) */ ul.nav-mobile-links ← full dropdown (hidden by default) */
.menu-btn { .menu-btn {
position: absolute; position: absolute;
top: -9999px; top: -9999px;
left: -9999px; left: -9999px;
} }
.menu-icon { .menu-icon {
display: none; display: none;
cursor: pointer; cursor: pointer;
padding: var(--space-2xs) var(--space-s); padding: var(--space-2xs) var(--space-s);
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
/* Middle bar of the burger icon */ /* Middle bar of the burger icon */
.navicon { .navicon {
background: var(--accent-foreground); background: var(--accent-foreground);
display: block; display: block;
height: 2px; height: 2px;
width: 24px; width: 24px;
position: relative; position: relative;
transition: all 0.3s ease-out; transition: all 0.3s ease-out;
} }
.navicon::before, .navicon::before,
.navicon::after { .navicon::after {
content: ""; content: "";
background: var(--accent-foreground); background: var(--accent-foreground);
display: block; display: block;
height: 2px; height: 2px;
width: 100%; width: 100%;
position: absolute; position: absolute;
transition: all 0.3s ease-out; transition: all 0.3s ease-out;
} }
.navicon::before { .navicon::before {
top: -7px; top: -7px;
} }
.navicon::after { .navicon::after {
bottom: -7px; bottom: -7px;
} }
/* ── Mobile (≤ 640px) ───────────────────────────────────────────────── */ /* ── Mobile (≤ 640px) ───────────────────────────────────────────────── */
@media screen and (max-width: 640px) { @media screen and (max-width: 640px) {
header nav[aria-label="Navigation principale"] { header nav[aria-label="Navigation principale"] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
padding: 0; padding: 0;
} }
header nav[aria-label="Navigation principale"] .nav-top-row { header nav[aria-label="Navigation principale"] .nav-top-row {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: var(--space-s); padding: var(--space-s);
} }
header nav[aria-label="Navigation principale"] .nav-right-links { header nav[aria-label="Navigation principale"] .nav-right-links {
display: none; display: none;
} }
header nav[aria-label="Navigation principale"] .nav-left-links { header nav[aria-label="Navigation principale"] .nav-left-links {
display: flex; display: flex;
gap: 0; gap: 0;
} }
header nav[aria-label="Navigation principale"] header
.nav-left-links nav[aria-label="Navigation principale"]
li:not(:first-child) { .nav-left-links
display: none; li:not(:first-child) {
} display: none;
}
.menu-icon { .menu-icon {
display: flex; display: flex;
} }
header nav[aria-label="Navigation principale"] .nav-mobile-links { header nav[aria-label="Navigation principale"] .nav-mobile-links {
display: block; display: block;
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
transition: max-height 0.2s ease-out; transition: max-height 0.2s ease-out;
} }
.menu-btn:checked .menu-btn:checked
~ nav[aria-label="Navigation principale"] ~ nav[aria-label="Navigation principale"]
.nav-mobile-links { .nav-mobile-links {
max-height: 300px; max-height: 300px;
} }
header nav[aria-label="Navigation principale"] .nav-mobile-links li { header nav[aria-label="Navigation principale"] .nav-mobile-links li {
border-top: 1px solid var(--header-nav-active-border); border-top: 1px solid var(--header-nav-active-border);
text-align: left; text-align: left;
} }
header nav[aria-label="Navigation principale"] .nav-mobile-links li a { header nav[aria-label="Navigation principale"] .nav-mobile-links li a {
display: block; display: block;
width: 100%; width: 100%;
padding: var(--space-s) var(--space-s); padding: var(--space-s) var(--space-s);
text-align: left; text-align: left;
} }
/* Animate burger → X */ /* Animate burger → X */
.menu-btn:checked .menu-btn:checked ~ nav[aria-label="Navigation principale"]
~ nav[aria-label="Navigation principale"] .menu-icon
.menu-icon .navicon {
.navicon { background: transparent;
background: transparent; }
}
.menu-btn:checked .menu-btn:checked
~ nav[aria-label="Navigation principale"] ~ nav[aria-label="Navigation principale"]
.menu-icon .menu-icon
.navicon::before { .navicon::before {
transform: rotate(-45deg); transform: rotate(-45deg);
top: 0; top: 0;
} }
.menu-btn:checked .menu-btn:checked
~ nav[aria-label="Navigation principale"] ~ nav[aria-label="Navigation principale"]
.menu-icon .menu-icon
.navicon::after { .navicon::after {
transform: rotate(45deg); transform: rotate(45deg);
bottom: 0; bottom: 0;
} }
} }
+5 -5
View File
@@ -4,12 +4,12 @@
============================================================ */ ============================================================ */
a { a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
font-weight: 400; font-weight: 400;
} }
a:hover { a:hover {
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
} }
+3 -3
View File
@@ -4,10 +4,10 @@
============================================================ */ ============================================================ */
:where(video, audio, iframe) { :where(video, audio, iframe) {
border: 1px solid var(--bg-primary); border: 1px solid var(--bg-primary);
border-radius: 15px; border-radius: 15px;
} }
audio::-webkit-media-controls-enclosure { audio::-webkit-media-controls-enclosure {
border-radius: var(--radius); border-radius: var(--radius);
} }
+35 -33
View File
@@ -4,55 +4,57 @@
============================================================ */ ============================================================ */
.pagination-wrap { .pagination-wrap {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: var(--space-2xs); gap: var(--space-2xs);
padding: var(--space-m) 0 var(--space-2xs); padding: var(--space-m) 0 var(--space-2xs);
} }
.pagination-wrap ul { .pagination-wrap ul {
display: flex; display: flex;
gap: var(--space-3xs); gap: var(--space-3xs);
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.pagination-btn { .pagination-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 2.75rem; min-width: 2.75rem;
min-height: 2.75rem; min-height: 2.75rem;
padding: 0 var(--space-2xs); padding: 0 var(--space-2xs);
border: 1px solid var(--border-secondary); border: 1px solid var(--border-secondary);
border-radius: var(--radius); border-radius: var(--radius);
color: var(--text-primary); color: var(--text-primary);
font-size: var(--step--1); font-size: var(--step--1);
font-weight: 400; font-weight: 400;
text-decoration: none; text-decoration: none;
transition: border-color 0.15s, color 0.15s; transition:
border-color 0.15s,
color 0.15s;
} }
.pagination-btn:hover:not(.disabled) { .pagination-btn:hover:not(.disabled) {
border-color: var(--accent-primary); border-color: var(--accent-primary);
color: var(--accent-primary); color: var(--accent-primary);
} }
.pagination-btn.disabled { .pagination-btn.disabled {
opacity: 0.3; opacity: 0.3;
cursor: not-allowed; cursor: not-allowed;
pointer-events: none; pointer-events: none;
} }
.pagination-info { .pagination-info {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
padding: 0 var(--space-2xs); padding: 0 var(--space-2xs);
} }
.page-current { .page-current {
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
} }
+38 -32
View File
@@ -6,55 +6,61 @@
/* Public search bar icon (in partials/search-bar.php) */ /* Public search bar icon (in partials/search-bar.php) */
/* Scoped to forms that are NOT inside .header-search-wrap */ /* Scoped to forms that are NOT inside .header-search-wrap */
.search-bar-icon { .search-bar-icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
flex-shrink: 0; flex-shrink: 0;
fill: var(--accent-primary); fill: var(--accent-primary);
margin-right: var(--space-2xs); margin-right: var(--space-2xs);
} }
form[role="search"]:not(.header-search-form) { form[role="search"]:not(.header-search-form) {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.header-search-wrap { .header-search-wrap {
padding: 0; padding: 0;
flex-shrink: 0; flex-shrink: 0;
background: linear-gradient(180deg, rgba(192, 93, 225, 1) 0%, rgba(255, 255, 255, 1) 100%); background: linear-gradient(
180deg,
rgba(192, 93, 225, 1) 0%,
rgba(255, 255, 255, 1) 100%
);
} }
.header-search-form { width: 100%; } .header-search-form {
width: 100%;
}
.header-search-input-wrap { .header-search-input-wrap {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.header-search-icon { .header-search-icon {
position: absolute; position: absolute;
left: var(--space-s); left: var(--space-s);
width: 18px; width: 18px;
height: 18px; height: 18px;
fill: var(--accent-primary); fill: var(--accent-primary);
pointer-events: none; pointer-events: none;
} }
.header-search-input-wrap input { .header-search-input-wrap input {
width: 100%; width: 100%;
padding: var(--space-2xs) var(--space-s) !important; padding: var(--space-2xs) var(--space-s);
padding-left: calc(18px + var(--space-l)) !important; padding-left: calc(18px + var(--space-l));
border: 1px solid var(--accent-primary) !important; border: 1px solid var(--accent-primary);
border-radius: var(--radius) !important; border-radius: var(--radius);
background: var(--bg-primary) !important; background: var(--bg-primary);
font-size: var(--step-0) !important; font-size: var(--step-0);
color: var(--text-primary) !important; color: var(--text-primary);
font-family: inherit !important; font-family: inherit;
} }
.header-search-input-wrap input::placeholder { .header-search-input-wrap input::placeholder {
color: var(--accent-primary) !important; color: var(--accent-primary);
font-family: var(--font-body); font-family: var(--font-body);
font-weight: 300; font-weight: 300;
} }
+21 -21
View File
@@ -4,39 +4,39 @@
============================================================ */ ============================================================ */
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
font-size: var(--step--1); font-size: var(--step--1);
} }
th { th {
text-align: left; text-align: left;
font-size: var(--step--2); font-size: var(--step--2);
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
padding: var(--space-3xs) var(--space-xs); padding: var(--space-3xs) var(--space-xs);
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
color: var(--text-secondary); color: var(--text-secondary);
font-weight: 400; font-weight: 400;
white-space: nowrap; white-space: nowrap;
} }
td { td {
padding: var(--space-2xs) var(--space-xs); padding: var(--space-2xs) var(--space-xs);
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
vertical-align: top; vertical-align: top;
} }
/* Sticky header — border-collapse:collapse blocks position:sticky in Chrome. /* Sticky header — border-collapse:collapse blocks position:sticky in Chrome.
--sticky-top is set by JS when a bulk actions bar appears above the table. */ --sticky-top is set by JS when a bulk actions bar appears above the table. */
.admin-table--sticky { .admin-table--sticky {
border-collapse: separate; border-collapse: separate;
border-spacing: 0; border-spacing: 0;
} }
.admin-table--sticky thead th { .admin-table--sticky thead th {
position: sticky; position: sticky;
top: var(--sticky-top, 0px); top: var(--sticky-top, 0px);
z-index: 5; z-index: 5;
background: var(--bg-primary); background: var(--bg-primary);
} }
+52 -40
View File
@@ -4,68 +4,80 @@
============================================================ */ ============================================================ */
#toast-region { #toast-region {
position: fixed; position: fixed;
bottom: var(--space-l); bottom: var(--space-l);
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
z-index: 10000; z-index: 10000;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: var(--space-xs); gap: var(--space-xs);
pointer-events: none; pointer-events: none;
width: max-content; width: max-content;
max-width: min(560px, calc(100vw - 2 * var(--space-l))); max-width: min(560px, calc(100vw - 2 * var(--space-l)));
} }
.toast { .toast {
padding: var(--space-s) var(--space-m); padding: var(--space-s) var(--space-m);
border-radius: var(--radius); border-radius: var(--radius);
font-size: var(--step-0); font-size: var(--step-0);
border-left: 4px solid; border-left: 4px solid;
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25);
pointer-events: auto; pointer-events: auto;
/* enter then fade out — total visible ~6.35s */ /* enter then fade out — total visible ~6.35s */
animation: toast-enter 0.35s ease-out, animation:
toast-exit 0.5s ease-in 6s forwards; toast-enter 0.35s ease-out,
toast-exit 0.5s ease-in 6s forwards;
} }
.toast--error { .toast--error {
background: var(--bg-secondary); background: var(--bg-secondary);
border-color: var(--error); border-color: var(--error);
color: var(--text-primary); color: var(--text-primary);
} }
.toast--success { .toast--success {
background: var(--bg-secondary); background: var(--bg-secondary);
border-color: var(--success); border-color: var(--success);
color: var(--text-primary); color: var(--text-primary);
} }
.toast--warning { .toast--warning {
background: var(--bg-secondary); background: var(--bg-secondary);
border-color: var(--warning); border-color: var(--warning);
color: var(--text-primary); color: var(--text-primary);
animation: toast-enter 0.35s ease-out; animation: toast-enter 0.35s ease-out;
} }
.toast--warning a { .toast--warning a {
color: inherit; color: inherit;
font-weight: 400; font-weight: 400;
text-decoration: none; text-decoration: none;
transition: color 0.15s; transition: color 0.15s;
} }
.toast--warning a:hover { .toast--warning a:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
@keyframes toast-enter { @keyframes toast-enter {
from { opacity: 0; transform: translateY(12px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
@keyframes toast-exit { @keyframes toast-exit {
from { opacity: 1; } from {
to { opacity: 0; pointer-events: none; } opacity: 1;
}
to {
opacity: 0;
pointer-events: none;
}
} }
+73 -73
View File
@@ -8,138 +8,138 @@
/* ── Shared list styles (.toc-list) ─────────────────────────────────────── */ /* ── Shared list styles (.toc-list) ─────────────────────────────────────── */
.toc-list, .toc-list,
.toc ul { .toc ul {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-3xs); gap: var(--space-3xs);
} }
.toc-list a, .toc-list a,
.toc ul a { .toc ul a {
display: block; display: block;
font-family: var(--font-body); font-family: var(--font-body);
font-size: var(--step-0); font-size: var(--step-0);
font-weight: 300; font-weight: 300;
color: var(--text-primary); color: var(--text-primary);
text-decoration: none; text-decoration: none;
padding: var(--space-3xs) 0; padding: var(--space-3xs) 0;
transition: color 0.15s; transition: color 0.15s;
} }
.toc-list a:hover { .toc-list a:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
.toc-list a.toc-active, .toc-list a.toc-active,
.toc ul a.toc-active { .toc ul a.toc-active {
color: var(--accent-primary); color: var(--accent-primary);
font-weight: 400; font-weight: 400;
} }
/* ── Public TOC (details/summary block) ─────────────────────────────────── */ /* ── Public TOC (details/summary block) ─────────────────────────────────── */
.toc { .toc {
border: none; border: none;
border-radius: 0; border-radius: 0;
background: transparent; background: transparent;
overflow: visible; overflow: visible;
} }
.toc[open] { .toc[open] {
padding-bottom: 0; padding-bottom: 0;
} }
.toc > :not(summary) { .toc > :not(summary) {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
} }
.toc summary { .toc summary {
font-family: var(--font-display); font-family: var(--font-display);
font-size: var(--step-1); font-size: var(--step-1);
font-weight: 400; font-weight: 400;
color: var(--text-primary); color: var(--text-primary);
padding: 0 0 var(--space-2xs) 0; padding: 0 0 var(--space-2xs) 0;
border-bottom: 1px solid var(--text-primary); border-bottom: 1px solid var(--text-primary);
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-2xs); gap: var(--space-2xs);
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
list-style: none; list-style: none;
} }
.toc summary::-webkit-details-marker { .toc summary::-webkit-details-marker {
display: none; display: none;
} }
.toc summary:hover { .toc summary:hover {
color: inherit; color: inherit;
background: transparent; background: transparent;
} }
/* Caret icon inside summary — visible only on mobile */ /* Caret icon inside summary — visible only on mobile */
.toc-caret { .toc-caret {
flex-shrink: 0; flex-shrink: 0;
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
.toc[open] > summary .toc-caret { .toc[open] > summary .toc-caret {
transform: rotate(180deg); transform: rotate(180deg);
} }
/* Sidebar links (about page — custom links below TOC) */ /* Sidebar links (about page — custom links below TOC) */
.toc-sidebar-link:first-of-type { .toc-sidebar-link:first-of-type {
padding-top: var(--space-s); padding-top: var(--space-s);
border-top: 1px solid var(--border-primary); border-top: 1px solid var(--border-primary);
} }
.toc-sidebar-link + .toc-sidebar-link { .toc-sidebar-link + .toc-sidebar-link {
padding-top: var(--space-xs); padding-top: var(--space-xs);
} }
.toc-sidebar-link a { .toc-sidebar-link a {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
transition: opacity 0.15s; transition: opacity 0.15s;
} }
.toc-sidebar-link a:hover { .toc-sidebar-link a:hover {
opacity: 0.8; opacity: 0.8;
} }
/* ── Desktop: sticky sidebar with independent scroll ──────────────────── */ /* ── Desktop: sticky sidebar with independent scroll ──────────────────── */
@media (min-width: 768px) { @media (min-width: 768px) {
.toc { .toc {
position: sticky; position: sticky;
top: var(--space-l); top: var(--space-l);
grid-column: 1; grid-column: 1;
max-height: calc(100vh - var(--space-xl) - var(--space-2xl)); max-height: calc(100vh - var(--space-xl) - var(--space-2xl));
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain; overscroll-behavior: contain;
scrollbar-width: thin; scrollbar-width: thin;
} }
.toc summary { .toc summary {
pointer-events: none; pointer-events: none;
} }
.toc-caret { .toc-caret {
display: none; display: none;
} }
} }
/* ── Mobile: collapsible TOC ────────────────────────────────────────────── */ /* ── Mobile: collapsible TOC ────────────────────────────────────────────── */
@media (max-width: 767px) { @media (max-width: 767px) {
.toc { .toc {
position: static; position: static;
grid-column: 1; grid-column: 1;
} }
.toc summary { .toc summary {
cursor: pointer; cursor: pointer;
list-style: revert; list-style: revert;
} }
} }
+140 -140
View File
@@ -5,52 +5,52 @@
/* Override inherited padding on main-content */ /* Override inherited padding on main-content */
#main-content { #main-content {
padding-top: 0; padding-top: 0;
} }
.page-content { .page-content {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
scroll-behavior: smooth; scroll-behavior: smooth;
padding: var(--space-xl) var(--space-l) var(--space-2xl); padding: var(--space-xl) var(--space-l) var(--space-2xl);
display: grid; display: grid;
grid-template-columns: 180px 1fr; grid-template-columns: 180px 1fr;
gap: var(--space-2xl); gap: var(--space-2xl);
align-items: start; align-items: start;
} }
/* ── TOC styles → components/toc.css (shared with admin) ───────────────── */ /* ── TOC styles → components/toc.css (shared with admin) ───────────────── */
/* Grid column positioning for content pages with sidebar TOC */ /* Grid column positioning for content pages with sidebar TOC */
@media (min-width: 768px) { @media (min-width: 768px) {
.page-content > article { .page-content > article {
grid-column: 2; grid-column: 2;
min-width: 0; min-width: 0;
max-width: 100%; max-width: 100%;
} }
} }
@media (max-width: 767px) { @media (max-width: 767px) {
.page-content { .page-content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: var(--space-l); gap: var(--space-l);
padding: var(--space-m) var(--space-s) var(--space-xl); padding: var(--space-m) var(--space-s) var(--space-xl);
} }
.page-content > article { .page-content > article {
grid-column: 1; grid-column: 1;
} }
.toc { .toc {
margin-top: var(--space-s); margin-top: var(--space-s);
} }
} }
/* ── First content child aligns with TOC heading top ───────────────────── */ /* ── First content child aligns with TOC heading top ───────────────────── */
.page-content > article > :first-child { .page-content > article > :first-child {
margin-top: 2.2rem; margin-top: 2.2rem;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -58,107 +58,107 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
.page-content > article { .page-content > article {
display: block; display: block;
max-width: 100%; max-width: 100%;
font-family: var(--font-body); font-family: var(--font-body);
font-size: var(--step-0); font-size: var(--step-0);
line-height: 1.6; line-height: 1.6;
color: var(--text-primary); color: var(--text-primary);
font-weight: 300; font-weight: 300;
padding-bottom: var(--space-xl); padding-bottom: var(--space-xl);
} }
.page-content > article * { .page-content > article * {
max-width: 100%; max-width: 100%;
overflow-wrap: anywhere; overflow-wrap: anywhere;
word-break: break-word; word-break: break-word;
} }
.page-content > article p { .page-content > article p {
margin: 0 0 1em 0; margin: 0 0 1em 0;
} }
.page-content > article p:last-child { .page-content > article p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.page-content > article :where(h1, h2, h3) { .page-content > article :where(h1, h2, h3) {
margin: 1.5em 0 0.5em 0; margin: 1.5em 0 0.5em 0;
} }
.page-content > article a { .page-content > article a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
font-weight: 700; font-weight: 700;
} }
.page-content > article a:hover { .page-content > article a:hover {
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
} }
.page-content > article ul, .page-content > article ul,
.page-content > article ol { .page-content > article ol {
padding-left: var(--space-m); padding-left: var(--space-m);
margin-bottom: var(--space-s); margin-bottom: var(--space-s);
} }
.page-content > article li { .page-content > article li {
margin-bottom: 0.3em; margin-bottom: 0.3em;
} }
.page-content > article strong { .page-content > article strong {
font-weight: 700; font-weight: 700;
} }
.page-content > article em { .page-content > article em {
font-style: italic; font-style: italic;
} }
.page-content > article :where(pre, pre code, code) { .page-content > article :where(pre, pre code, code) {
display: block; display: block;
max-width: 100%; max-width: 100%;
overflow-x: auto; overflow-x: auto;
overflow-wrap: normal; overflow-wrap: normal;
word-break: normal; word-break: normal;
font-family: "Courier New", Courier, monospace; font-family: "Courier New", Courier, monospace;
font-size: 0.88em; font-size: 0.88em;
background: color-mix(in srgb, var(--bg-tertiary) 50%, transparent); background: color-mix(in srgb, var(--bg-tertiary) 50%, transparent);
padding: 0.5em 0.75em; padding: 0.5em 0.75em;
border-radius: var(--radius); border-radius: var(--radius);
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
} }
#apropos-intro *, #apropos-intro *,
#apropos-contacts *, #apropos-contacts *,
#apropos-credits * { #apropos-credits * {
overflow-wrap: anywhere; overflow-wrap: anywhere;
word-break: break-word; word-break: break-word;
max-width: 100%; max-width: 100%;
} }
/* Section separators (about page only) */ /* Section separators (about page only) */
.page-content > article > section { .page-content > article > section {
padding-bottom: var(--space-xl); padding-bottom: var(--space-xl);
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
margin-bottom: var(--space-xl); margin-bottom: var(--space-xl);
} }
.page-content > article > section:last-child { .page-content > article > section:last-child {
border-bottom: none; border-bottom: none;
margin-bottom: 0; margin-bottom: 0;
padding-bottom: var(--space-xl); padding-bottom: var(--space-xl);
} }
/* Scroll margin so anchor links account for the sticky header */ /* Scroll margin so anchor links account for the sticky header */
.page-content > article :where(h1, h2, h3) { .page-content > article :where(h1, h2, h3) {
scroll-margin-top: var(--space-l); scroll-margin-top: var(--space-l);
} }
/* Hide CommonMark heading permalink anchors */ /* Hide CommonMark heading permalink anchors */
.heading-permalink { .heading-permalink {
display: none; display: none;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -166,12 +166,12 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
.content-section-title { .content-section-title {
font-family: var(--font-display); font-family: var(--font-display);
font-size: var(--step-3); font-size: var(--step-3);
font-weight: 400; font-weight: 400;
color: var(--text-primary); color: var(--text-primary);
margin: 0 0 var(--space-m) 0; margin: 0 0 var(--space-m) 0;
line-height: 1.1; line-height: 1.1;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -179,48 +179,48 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
.apropos-contacts-grid { .apropos-contacts-grid {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0; gap: 0;
} }
.apropos-contact-card { .apropos-contact-card {
font-style: normal; font-style: normal;
padding: var(--space-s) 0; padding: var(--space-s) 0;
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
} }
.apropos-contact-card:first-child { .apropos-contact-card:first-child {
border-top: 1px solid var(--border-primary); border-top: 1px solid var(--border-primary);
} }
.apropos-contact-card strong { .apropos-contact-card strong {
display: block; display: block;
font-size: var(--step-0); font-size: var(--step-0);
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: var(--space-3xs); margin-bottom: var(--space-3xs);
} }
.apropos-contact-card span { .apropos-contact-card span {
display: block; display: block;
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
line-height: 1.4; line-height: 1.4;
margin-bottom: var(--space-3xs); margin-bottom: var(--space-3xs);
} }
.apropos-contact-card a { .apropos-contact-card a {
font-size: var(--step--1); font-size: var(--step--1);
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
font-weight: 700; font-weight: 700;
transition: color 0.15s; transition: color 0.15s;
} }
.apropos-contact-card a:hover { .apropos-contact-card a:hover {
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -228,39 +228,39 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
.apropos-credits-list { .apropos-credits-list {
margin: 0; margin: 0;
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0; gap: 0;
} }
.apropos-credit-row { .apropos-credit-row {
display: grid; display: grid;
grid-template-columns: 1fr 1.6fr; grid-template-columns: 1fr 1.6fr;
gap: var(--space-s); gap: var(--space-s);
padding: var(--space-s) 0; padding: var(--space-s) 0;
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
align-items: baseline; align-items: baseline;
} }
.apropos-credit-row:first-child { .apropos-credit-row:first-child {
border-top: 1px solid var(--border-primary); border-top: 1px solid var(--border-primary);
} }
.apropos-credits-list dt { .apropos-credits-list dt {
font-size: var(--step--2); font-size: var(--step--2);
font-weight: 600; font-weight: 600;
letter-spacing: 0.04em; letter-spacing: 0.04em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-tertiary); color: var(--text-tertiary);
} }
.apropos-credits-list dd { .apropos-credits-list dd {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-primary); color: var(--text-primary);
margin: 0; margin: 0;
line-height: 1.5; line-height: 1.5;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -268,7 +268,7 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
.apropos-single { .apropos-single {
max-width: 720px; max-width: 720px;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -276,20 +276,20 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@media (max-width: 600px) { @media (max-width: 600px) {
.page-content { .page-content {
padding: var(--space-m) var(--space-s) var(--space-xl); padding: var(--space-m) var(--space-s) var(--space-xl);
} }
.page-content > article { .page-content > article {
font-size: var(--step-0); font-size: var(--step-0);
} }
.content-section-title { .content-section-title {
font-size: var(--step-2); font-size: var(--step-2);
} }
.apropos-credit-row { .apropos-credit-row {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: var(--space-3xs); gap: var(--space-3xs);
} }
} }
+124 -122
View File
@@ -3,245 +3,247 @@
============================================================ */ ============================================================ */
.access-req-stats { .access-req-stats {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: var(--space-m); gap: var(--space-m);
margin-bottom: var(--space-l); margin-bottom: var(--space-l);
} }
.access-req-stat-card { .access-req-stat-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: var(--space-m); padding: var(--space-m);
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
} }
.access-req-stat-number { .access-req-stat-number {
font-size: var(--step-2); font-size: var(--step-2);
font-weight: 700; font-weight: 700;
color: var(--text-primary); color: var(--text-primary);
line-height: 1; line-height: 1;
} }
.access-req-stat-label { .access-req-stat-label {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
} }
/* Tab navigation */ /* Tab navigation */
.access-req-tabs { .access-req-tabs {
display: flex; display: flex;
gap: var(--space-xs); gap: var(--space-xs);
margin-bottom: var(--space-l); margin-bottom: var(--space-l);
border-bottom: 2px solid var(--border-primary); border-bottom: 2px solid var(--border-primary);
} }
.access-req-tab { .access-req-tab {
padding: var(--space-2xs) var(--space-s); padding: var(--space-2xs) var(--space-s);
color: var(--text-secondary); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
margin-bottom: -2px; margin-bottom: -2px;
transition: color 0.2s, border-color 0.2s; transition:
color 0.2s,
border-color 0.2s;
} }
.access-req-tab:hover { .access-req-tab:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
.access-req-tab.active { .access-req-tab.active {
color: var(--accent-primary); color: var(--accent-primary);
border-bottom-color: var(--accent-primary); border-bottom-color: var(--accent-primary);
font-weight: 600; font-weight: 600;
} }
/* Empty state */ /* Empty state */
.access-req-empty { .access-req-empty {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-xl); padding: var(--space-xl);
text-align: center; text-align: center;
color: var(--text-secondary); color: var(--text-secondary);
} }
/* Request list */ /* Request list */
.access-req-list { .access-req-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-m); gap: var(--space-m);
} }
.access-req-card { .access-req-card {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-m); padding: var(--space-m);
transition: box-shadow 0.2s; transition: box-shadow 0.2s;
} }
.access-req-card:hover { .access-req-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
.access-req-card__header { .access-req-card__header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
gap: var(--space-m); gap: var(--space-m);
margin-bottom: var(--space-s); margin-bottom: var(--space-s);
} }
.access-req-card__thesis h3 { .access-req-card__thesis h3 {
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
margin: 0 0 var(--space-3xs) 0; margin: 0 0 var(--space-3xs) 0;
line-height: 1.3; line-height: 1.3;
} }
.access-req-card__authors { .access-req-card__authors {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
margin: 0; margin: 0;
} }
.access-req-card__meta { .access-req-card__meta {
flex-shrink: 0; flex-shrink: 0;
} }
.access-req-badge { .access-req-badge {
display: inline-block; display: inline-block;
padding: var(--space-3xs) var(--space-2xs); padding: var(--space-3xs) var(--space-2xs);
border-radius: 12px; border-radius: 12px;
font-size: var(--step--2); font-size: var(--step--2);
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
.access-req-badge--pending { .access-req-badge--pending {
background: #fef3c7; background: #fef3c7;
color: #92400e; color: #92400e;
} }
.access-req-badge--approved { .access-req-badge--approved {
background: #d1fae5; background: #d1fae5;
color: #065f46; color: #065f46;
} }
.access-req-badge--rejected { .access-req-badge--rejected {
background: #fee2e2; background: #fee2e2;
color: #991b1b; color: #991b1b;
} }
.access-req-card__body { .access-req-card__body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-s); gap: var(--space-s);
} }
.access-req-card__info { .access-req-card__info {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--space-m); gap: var(--space-m);
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
} }
.access-req-card__info strong { .access-req-card__info strong {
color: var(--text-primary); color: var(--text-primary);
margin-right: var(--space-3xs); margin-right: var(--space-3xs);
} }
.access-req-card__info a { .access-req-card__info a {
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
} }
.access-req-card__info a:hover { .access-req-card__info a:hover {
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
} }
.access-req-card__justification, .access-req-card__justification,
.access-req-card__admin-notes { .access-req-card__admin-notes {
background: var(--bg-primary); background: var(--bg-primary);
padding: var(--space-s); padding: var(--space-s);
border-radius: var(--radius); border-radius: var(--radius);
font-size: var(--step--1); font-size: var(--step--1);
line-height: 1.6; line-height: 1.6;
} }
.access-req-card__justification strong, .access-req-card__justification strong,
.access-req-card__admin-notes strong { .access-req-card__admin-notes strong {
display: block; display: block;
margin-bottom: var(--space-3xs); margin-bottom: var(--space-3xs);
color: var(--text-primary); color: var(--text-primary);
} }
.access-req-card__justification p, .access-req-card__justification p,
.access-req-card__admin-notes p { .access-req-card__admin-notes p {
margin: 0; margin: 0;
} }
.access-req-card__actions { .access-req-card__actions {
display: flex; display: flex;
gap: var(--space-s); gap: var(--space-s);
margin-top: var(--space-s); margin-top: var(--space-s);
} }
.access-req-btn { .access-req-btn {
/* deprecated alias for .btn; kept for backward-compat */ /* deprecated alias for .btn; kept for backward-compat */
} }
.access-req-btn--approve { .access-req-btn--approve {
/* deprecated alias for .btn--primary */ /* deprecated alias for .btn--primary */
} }
.access-req-btn--reject { .access-req-btn--reject {
/* deprecated alias for .btn--danger */ /* deprecated alias for .btn--danger */
} }
/* Pagination */ /* Pagination */
.access-req-pagination { .access-req-pagination {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: var(--space-m); gap: var(--space-m);
margin-top: var(--space-l); margin-top: var(--space-l);
padding: var(--space-s) 0; padding: var(--space-s) 0;
} }
.access-req-pagination__link { .access-req-pagination__link {
padding: var(--space-2xs) var(--space-s); padding: var(--space-2xs) var(--space-s);
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
transition: color 0.2s; transition: color 0.2s;
} }
.access-req-pagination__link:hover { .access-req-pagination__link:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
.access-req-pagination__info { .access-req-pagination__info {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
} }
/* Dialog textarea/label enhancements */ /* Dialog textarea/label enhancements */
.admin-dialog textarea { .admin-dialog textarea {
width: 100%; width: 100%;
padding: var(--space-2xs); padding: var(--space-2xs);
margin: var(--space-s) 0; margin: var(--space-s) 0;
} }
.admin-dialog label { .admin-dialog label {
display: block; display: block;
font-weight: 400; font-weight: 400;
margin-bottom: var(--space-3xs); margin-bottom: var(--space-3xs);
} }
+155 -155
View File
@@ -7,293 +7,293 @@
/* ── Recap files table ─────────────────────────────────────────── */ /* ── Recap files table ─────────────────────────────────────────── */
.recap-files-table { .recap-files-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
font-size: var(--step--1); font-size: var(--step--1);
} }
.recap-files-table thead th { .recap-files-table thead th {
text-align: left; text-align: left;
font-weight: 600; font-weight: 600;
color: var(--text-secondary); color: var(--text-secondary);
font-size: var(--step--2); font-size: var(--step--2);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
padding: var(--space-xs) var(--space-s); padding: var(--space-xs) var(--space-s);
border-bottom: 2px solid var(--border-primary); border-bottom: 2px solid var(--border-primary);
} }
.recap-files-table tbody td { .recap-files-table tbody td {
padding: var(--space-xs) var(--space-s); padding: var(--space-xs) var(--space-s);
border-bottom: 1px solid var(--border-secondary); border-bottom: 1px solid var(--border-secondary);
vertical-align: middle; vertical-align: middle;
} }
.recap-files-table tbody tr:last-child td { .recap-files-table tbody tr:last-child td {
border-bottom: none; border-bottom: none;
} }
.recap-files-icon { .recap-files-icon {
width: 1px; width: 1px;
white-space: nowrap; white-space: nowrap;
} }
.recap-files-icon-emoji { .recap-files-icon-emoji {
font-size: 1.3rem; font-size: 1.3rem;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 2.2rem; width: 2.2rem;
height: 2.2rem; height: 2.2rem;
background: var(--accent-muted); background: var(--accent-muted);
border-radius: var(--radius); border-radius: var(--radius);
} }
.recap-files-thumb { .recap-files-thumb {
max-width: 80px; max-width: 80px;
max-height: 60px; max-height: 60px;
object-fit: cover; object-fit: cover;
border-radius: var(--radius); border-radius: var(--radius);
} }
.recap-files-name { .recap-files-name {
font-weight: 500; font-weight: 500;
} }
.recap-files-name a { .recap-files-name a {
color: var(--text-primary); color: var(--text-primary);
text-decoration: none; text-decoration: none;
} }
.recap-files-name a:hover { .recap-files-name a:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
.recap-files-peertube-id { .recap-files-peertube-id {
display: block; display: block;
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-tertiary); color: var(--text-tertiary);
font-family: monospace; font-family: monospace;
} }
.recap-files-label { .recap-files-label {
display: block; display: block;
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-secondary); color: var(--text-secondary);
font-style: italic; font-style: italic;
} }
.recap-files-type { .recap-files-type {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-tertiary); color: var(--text-tertiary);
white-space: nowrap; white-space: nowrap;
} }
.recap-files-size { .recap-files-size {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-tertiary); color: var(--text-tertiary);
white-space: nowrap; white-space: nowrap;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.recap-files-date { .recap-files-date {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-tertiary); color: var(--text-tertiary);
white-space: nowrap; white-space: nowrap;
} }
/* ── File browser (relink) ──────────────────────────────────────── */ /* ── File browser (relink) ──────────────────────────────────────── */
.file-browser-trigger { .file-browser-trigger {
margin-top: var(--space-2xs); margin-top: var(--space-2xs);
font-size: var(--step--2); font-size: var(--step--2);
} }
.relink-modal { .relink-modal {
width: min(90vw, 700px); width: min(90vw, 700px);
max-height: 80vh; max-height: 80vh;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--bg-primary); background: var(--bg-primary);
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
color: var(--text-primary); color: var(--text-primary);
} }
.relink-modal::backdrop { .relink-modal::backdrop {
background: rgba(0,0,0,0.5); background: rgba(0, 0, 0, 0.5);
} }
.relink-modal-header { .relink-modal-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: var(--space-s) var(--space-m); padding: var(--space-s) var(--space-m);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
background: var(--bg-secondary); background: var(--bg-secondary);
} }
.relink-modal-header h3 { .relink-modal-header h3 {
margin: 0; margin: 0;
font-size: var(--step-0); font-size: var(--step-0);
} }
.relink-modal-footer { .relink-modal-footer {
padding: var(--space-xs) var(--space-m); padding: var(--space-xs) var(--space-m);
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
background: var(--bg-secondary); background: var(--bg-secondary);
} }
#relink-modal-body { #relink-modal-body {
padding: var(--space-s) var(--space-m); padding: var(--space-s) var(--space-m);
overflow-y: auto; overflow-y: auto;
max-height: 60vh; max-height: 60vh;
} }
/* ── File browser tree ─────────────────────────────────────────── */ /* ── File browser tree ─────────────────────────────────────────── */
.file-browser { .file-browser {
font-size: var(--step--1); font-size: var(--step--1);
} }
.file-browser-hint, .file-browser-hint,
.file-browser-empty, .file-browser-empty,
.file-browser-loading, .file-browser-loading,
.file-browser-error { .file-browser-error {
color: var(--text-secondary); color: var(--text-secondary);
text-align: center; text-align: center;
padding: var(--space-m); padding: var(--space-m);
} }
.file-browser-error { .file-browser-error {
color: var(--error); color: var(--error);
} }
.file-browser-breadcrumb { .file-browser-breadcrumb {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-2xs); gap: var(--space-2xs);
flex-wrap: wrap; flex-wrap: wrap;
padding-bottom: var(--space-s); padding-bottom: var(--space-s);
margin-bottom: var(--space-s); margin-bottom: var(--space-s);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
font-size: var(--step--2); font-size: var(--step--2);
} }
.file-browser-breadcrumb a { .file-browser-breadcrumb a {
color: var(--accent-blue, var(--link)); color: var(--accent-blue, var(--link));
text-decoration: none; text-decoration: none;
} }
.file-browser-breadcrumb a:hover { .file-browser-breadcrumb a:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
.file-browser-sep { .file-browser-sep {
color: var(--text-tertiary); color: var(--text-tertiary);
} }
.file-browser-list { .file-browser-list {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.file-browser-entry { .file-browser-entry {
border-bottom: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-subtle);
} }
.file-browser-entry a, .file-browser-entry a,
.file-browser-select-btn { .file-browser-select-btn {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-xs); gap: var(--space-xs);
width: 100%; width: 100%;
padding: var(--space-xs) var(--space-2xs); padding: var(--space-xs) var(--space-2xs);
text-decoration: none; text-decoration: none;
color: var(--text-primary); color: var(--text-primary);
background: none; background: none;
border: none; border: none;
cursor: pointer; cursor: pointer;
font-size: var(--step--1); font-size: var(--step--1);
text-align: left; text-align: left;
transition: background 0.15s; transition: background 0.15s;
} }
.file-browser-entry a:hover, .file-browser-entry a:hover,
.file-browser-select-btn:hover { .file-browser-select-btn:hover {
background: var(--bg-secondary); background: var(--bg-secondary);
} }
.file-browser-icon { .file-browser-icon {
flex-shrink: 0; flex-shrink: 0;
font-size: var(--step-0); font-size: var(--step-0);
} }
.file-browser-name { .file-browser-name {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.file-browser-size { .file-browser-size {
flex-shrink: 0; flex-shrink: 0;
color: var(--text-tertiary); color: var(--text-tertiary);
font-size: var(--step--2); font-size: var(--step--2);
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.file-browser-file .file-browser-select-btn { .file-browser-file .file-browser-select-btn {
color: var(--accent-green, var(--success)); color: var(--accent-green, var(--success));
} }
.file-browser-file .file-browser-select-btn:hover { .file-browser-file .file-browser-select-btn:hover {
background: var(--bg-secondary); background: var(--bg-secondary);
} }
/* ── Full-page editor (contenus-edit overtype) ─────────────────────── */ /* ── Full-page editor (contenus-edit overtype) ─────────────────────── */
/* Make the main content area a flex column so the form fills remaining height */ /* Make the main content area a flex column so the form fills remaining height */
.admin-body .full-editor-page { .admin-body .full-editor-page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.admin-form--full-editor { .admin-form--full-editor {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
min-height: 0; min-height: 0;
} }
.admin-form--full-editor #editor { .admin-form--full-editor #editor {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.admin-form--full-editor #editor .n-container { .admin-form--full-editor #editor .n-container {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.admin-form--full-editor #editor .n-wrapper { .admin-form--full-editor #editor .n-wrapper {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
} }
.admin-form--full-editor #editor .n-input { .admin-form--full-editor #editor .n-input {
height: 100% !important; height: 100%;
overflow-y: auto !important; overflow-y: auto;
} }
.admin-form--full-editor #editor .n-preview { .admin-form--full-editor #editor .n-preview {
height: 100% !important; height: 100%;
overflow-y: auto !important; overflow-y: auto;
} }
.full-editor-toolbar { .full-editor-toolbar {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-s); gap: var(--space-s);
padding-bottom: var(--space-s); padding-bottom: var(--space-s);
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
margin-bottom: var(--space-s); margin-bottom: var(--space-s);
flex-shrink: 0; flex-shrink: 0;
} }
.full-editor-label { .full-editor-label {
font-size: var(--step--1); font-size: var(--step--1);
font-weight: 600; font-weight: 600;
color: var(--text-secondary); color: var(--text-secondary);
} }
.full-editor-toolbar .btn--primary { .full-editor-toolbar .btn--primary {
margin-left: auto; margin-left: auto;
} }
File diff suppressed because it is too large Load Diff
+172 -172
View File
@@ -5,267 +5,267 @@
/* Cards grid — scrollable main area */ /* Cards grid — scrollable main area */
.home-main { .home-main {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding: 0; padding: 0;
} }
.cards-container { .cards-container {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: var(--space-s); gap: var(--space-s);
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: var(--space-xs) var(--space-s) var(--space-m); padding: var(--space-xs) var(--space-s) var(--space-m);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.cards-container { .cards-container {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.cards-container { .cards-container {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
/* Each card = list item containing a block <a> link */ /* Each card = list item containing a block <a> link */
.card { .card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
cursor: pointer; cursor: pointer;
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--bg-primary); background: var(--bg-primary);
overflow: hidden; overflow: hidden;
transition: transition:
box-shadow 0.2s ease, box-shadow 0.2s ease,
transform 0.2s ease; transform 0.2s ease;
} }
.card:hover { .card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px); transform: translateY(-2px);
} }
.card > a { .card > a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
border-radius: inherit; border-radius: inherit;
} }
/* Media wrapper: <figure> for real images/video, <div class="card__media--gradient"> for placeholders */ /* Media wrapper: <figure> for real images/video, <div class="card__media--gradient"> for placeholders */
.home-body figure { .home-body figure {
width: 100%; width: 100%;
aspect-ratio: 4/3; aspect-ratio: 4 / 3;
overflow: hidden; overflow: hidden;
background: var(--bg-tertiary); background: var(--bg-tertiary);
position: relative; position: relative;
margin: 0; /* reset <figure> default margin */ margin: 0; /* reset <figure> default margin */
border-radius: 7px 7px 0 0; border-radius: 7px 7px 0 0;
} }
.home-body figure img, .home-body figure img,
.home-body figure video { .home-body figure video {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
display: block; display: block;
transition: transform 0.3s ease; transition: transform 0.3s ease;
} }
.card:hover figure img, .card:hover figure img,
.card:hover figure video { .card:hover figure video {
transform: scale(1.02); transform: scale(1.02);
} }
.card__media--placeholder { .card__media--placeholder {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(135deg, var(--bg-tertiary), var(--bg-active)); background: linear-gradient(135deg, var(--bg-tertiary), var(--bg-active));
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: var(--text-tertiary); color: var(--text-tertiary);
font-size: var(--step-3); font-size: var(--step-3);
} }
.card__media--gradient { .card__media--gradient {
width: 100%; width: 100%;
aspect-ratio: 4/3; aspect-ratio: 4 / 3;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: var(--space-s); padding: var(--space-s);
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
border-radius: 7px 7px 0 0; border-radius: 7px 7px 0 0;
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
rgba(60, 133, 108, 1) 0%, rgba(60, 133, 108, 1) 0%,
rgba(51, 191, 135, 1) 25%, rgba(51, 191, 135, 1) 25%,
rgba(192, 93, 225, 1) 75%, rgba(192, 93, 225, 1) 75%,
rgba(149, 87, 181, 1) 100% rgba(149, 87, 181, 1) 100%
); );
} }
.card__gradient-author { .card__gradient-author {
color: var(--accent-foreground); color: var(--accent-foreground);
font-size: var(--step--2); font-size: var(--step--2);
opacity: 0.85; opacity: 0.85;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
display: block; display: block;
} }
.card__gradient-title { .card__gradient-title {
color: var(--accent-foreground); color: var(--accent-foreground);
font-size: var(--step--1); font-size: var(--step--1);
font-weight: 600; font-weight: 600;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 3; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
} }
/* Section label */ /* Section label */
.home-section-label { .home-section-label {
font-style: normal; font-style: normal;
background: transparent; background: transparent;
color: var(--text-primary); color: var(--text-primary);
font-size: var(--step--2); font-size: var(--step--2);
font-weight: 600; font-weight: 600;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
padding: var(--space-s) var(--space-s) var(--space-xs); padding: var(--space-s) var(--space-s) var(--space-xs);
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
margin: 0; margin: 0;
} }
/* Card caption — <p> directly inside the card <a> link, below the media */ /* Card caption — <p> directly inside the card <a> link, below the media */
.home-body li > a > p { .home-body li > a > p {
padding: var(--space-2xs) var(--space-xs) var(--space-xs); padding: var(--space-2xs) var(--space-xs) var(--space-xs);
font-size: var(--step--1); font-size: var(--step--1);
line-height: 1.35; line-height: 1.35;
color: var(--text-primary); color: var(--text-primary);
margin: 0; margin: 0;
font-weight: 400; font-weight: 400;
border-top: 1px solid var(--border-primary); border-top: 1px solid var(--border-primary);
} }
/* Empty-state message shown when no cards exist */ /* Empty-state message shown when no cards exist */
.cards-empty { .cards-empty {
padding: var(--space-l); padding: var(--space-l);
color: var(--text-tertiary); color: var(--text-tertiary);
font-size: var(--step--1); font-size: var(--step--1);
list-style: none; list-style: none;
} }
/* Filter info */ /* Filter info */
.filter-info { .filter-info {
background: var(--accent-muted); background: var(--accent-muted);
color: var(--accent-secondary); color: var(--accent-secondary);
padding: var(--space-3xs) var(--space-s); padding: var(--space-3xs) var(--space-s);
font-size: var(--step--1); font-size: var(--step--1);
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-s); gap: var(--space-s);
flex-shrink: 0; flex-shrink: 0;
} }
.clear-filter { .clear-filter {
color: var(--accent-secondary); color: var(--accent-secondary);
text-decoration: none; text-decoration: none;
padding: var(--space-3xs) var(--space-2xs); padding: var(--space-3xs) var(--space-2xs);
background: var(--accent-muted); background: var(--accent-muted);
border-radius: var(--radius); border-radius: var(--radius);
font-size: var(--step--1); font-size: var(--step--1);
} }
.clear-filter:hover { .clear-filter:hover {
background: var(--accent-secondary); background: var(--accent-secondary);
color: var(--accent-foreground); color: var(--accent-foreground);
} }
/* Pagination */ /* Pagination */
.pagination-wrap { .pagination-wrap {
border-top: 1px solid var(--border-secondary); border-top: 1px solid var(--border-secondary);
background: var(--bg-primary); background: var(--bg-primary);
flex-shrink: 0; flex-shrink: 0;
} }
.pagination-wrap ul { .pagination-wrap ul {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: var(--space-2xs); gap: var(--space-2xs);
padding: var(--space-s); padding: var(--space-s);
list-style: none; list-style: none;
margin: 0; margin: 0;
} }
.pagination-btn { .pagination-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 2.75rem; min-width: 2.75rem;
min-height: 2.75rem; min-height: 2.75rem;
padding: 0 var(--space-2xs); padding: 0 var(--space-2xs);
border: 1px solid var(--border-secondary); border: 1px solid var(--border-secondary);
border-radius: var(--radius); border-radius: var(--radius);
color: var(--text-primary); color: var(--text-primary);
font-size: var(--step--1); font-size: var(--step--1);
text-decoration: none; text-decoration: none;
font-weight: 400; font-weight: 400;
transition: all 0.15s; transition: all 0.15s;
} }
.pagination-btn:hover:not(.disabled) { .pagination-btn:hover:not(.disabled) {
border-color: var(--accent-primary); border-color: var(--accent-primary);
color: var(--accent-primary); color: var(--accent-primary);
} }
.pagination-btn.disabled { .pagination-btn.disabled {
opacity: 0.3; opacity: 0.3;
cursor: not-allowed; cursor: not-allowed;
pointer-events: none; pointer-events: none;
} }
.pagination-info { .pagination-info {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
padding: 0 var(--space-2xs); padding: 0 var(--space-2xs);
} }
.page-current { .page-current {
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
} }
/* Suppress card hover scale for users who prefer reduced motion */ /* Suppress card hover scale for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.home-body figure img, .home-body figure img,
.home-body figure video { .home-body figure video {
transition: none; transition: none;
} }
.card:hover figure img, .card:hover figure img,
.card:hover figure video { .card:hover figure video {
transform: none; transform: none;
} }
.card__media--gradient { .card__media--gradient {
transition: none; transition: none;
} }
.card { .card {
transition: none; transition: none;
} }
.card:hover { .card:hover {
transform: none; transform: none;
} }
} }
File diff suppressed because it is too large Load Diff
+74 -45
View File
@@ -3,102 +3,131 @@
originally used). Do not modify this file. originally used). Do not modify this file.
============================================================ */ ============================================================ */
*, ::before, ::after { *,
box-sizing: border-box; ::before,
::after {
box-sizing: border-box;
} }
html { html {
font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; font-family:
line-height: 1.15; system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
-webkit-text-size-adjust: 100%; "Apple Color Emoji", "Segoe UI Emoji";
tab-size: 4; line-height: 1.15;
-webkit-text-size-adjust: 100%;
tab-size: 4;
} }
body { body {
margin: 0; margin: 0;
} }
b, strong { b,
font-weight: bolder; strong {
font-weight: bolder;
} }
code, kbd, pre, samp { code,
font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; kbd,
font-size: 1em; pre,
samp {
font-family:
ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 1em;
} }
small { small {
font-size: 80%; font-size: 80%;
} }
sub, sup { sub,
font-size: 75%; sup {
line-height: 0; font-size: 75%;
position: relative; line-height: 0;
vertical-align: baseline; position: relative;
vertical-align: baseline;
} }
sub { bottom: -0.25em; } sub {
sup { top: -0.5em; } bottom: -0.25em;
}
sup {
top: -0.5em;
}
table { table {
border-color: currentcolor; border-color: currentcolor;
} }
button, input, optgroup, select, textarea { button,
font-family: inherit; input,
font-size: 100%; optgroup,
line-height: 1.15; select,
margin: 0; textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
} }
[type=button], [type=reset], [type=submit], button { [type="button"],
-webkit-appearance: button; [type="reset"],
[type="submit"],
button {
-webkit-appearance: button;
} }
legend { legend {
padding: 0; padding: 0;
} }
progress { progress {
vertical-align: baseline; vertical-align: baseline;
} }
::-webkit-inner-spin-button, ::-webkit-inner-spin-button,
::-webkit-outer-spin-button { ::-webkit-outer-spin-button {
height: auto; height: auto;
} }
[type=search] { [type="search"] {
-webkit-appearance: textfield; -webkit-appearance: textfield;
outline-offset: -2px; outline-offset: -2px;
} }
::-webkit-search-decoration { ::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }
::-webkit-file-upload-button { ::-webkit-file-upload-button {
-webkit-appearance: button; -webkit-appearance: button;
font: inherit; font: inherit;
} }
summary { summary {
display: list-item; display: list-item;
} }
/* Additional minimal resets not in modern-normalize */ /* Additional minimal resets not in modern-normalize */
img, svg, video, canvas, audio, iframe, embed, object { img,
display: block; svg,
vertical-align: middle; video,
canvas,
audio,
iframe,
embed,
object {
display: block;
vertical-align: middle;
} }
img, video { img,
max-width: 100%; video {
height: auto; max-width: 100%;
height: auto;
} }
[hidden] { [hidden] {
display: none; display: none;
} }
+280 -233
View File
@@ -4,352 +4,399 @@
/* ── System page tabs ──────────────────────────────────────────────────── */ /* ── System page tabs ──────────────────────────────────────────────────── */
.sys-tabs { .sys-tabs {
display: flex; display: flex;
gap: 0; gap: 0;
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
margin-bottom: var(--space-m); margin-bottom: var(--space-m);
} }
.sys-tab { .sys-tab {
display: inline-block; display: inline-block;
padding: var(--space-3xs) var(--space-s); padding: var(--space-3xs) var(--space-s);
font-size: var(--step--1); font-size: var(--step--1);
font-weight: 500; font-weight: 500;
color: var(--text-secondary); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
margin-bottom: -1px; margin-bottom: -1px;
transition: color .15s, border-color .15s; transition:
color 0.15s,
border-color 0.15s;
} }
.sys-tab:hover { .sys-tab:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
.sys-tab.active { .sys-tab.active {
color: var(--accent-primary); color: var(--accent-primary);
border-bottom-color: var(--accent-primary); border-bottom-color: var(--accent-primary);
} }
/* ── Status section (always-visible panel above tabs) ─────────────────── */ /* ── Status section (always-visible panel above tabs) ─────────────────── */
.sys-status-section { .sys-status-section {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-s) var(--space-m); padding: var(--space-s) var(--space-m);
margin-bottom: var(--space-m); margin-bottom: var(--space-m);
} }
.sys-status-header { .sys-status-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: var(--space-s); margin-bottom: var(--space-s);
} }
.sys-status-toggle { .sys-status-toggle {
background: none; background: none;
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
color: var(--text-secondary); color: var(--text-secondary);
border-radius: var(--radius); border-radius: var(--radius);
font-size: var(--step--2); font-size: var(--step--2);
font-family: inherit; font-family: inherit;
padding: var(--space-3xs) var(--space-2xs); padding: var(--space-3xs) var(--space-2xs);
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
transition: color .15s, border-color .15s; transition:
color 0.15s,
border-color 0.15s;
} }
.sys-status-toggle:hover { .sys-status-toggle:hover {
color: var(--text-primary); color: var(--text-primary);
border-color: var(--border-secondary); border-color: var(--border-secondary);
} }
.sys-status-meta { .sys-status-meta {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: var(--space-m) var(--space-l); gap: var(--space-m) var(--space-l);
margin-top: var(--space-m); margin-top: var(--space-m);
padding-top: var(--space-m); padding-top: var(--space-m);
border-top: 1px solid var(--border-primary); border-top: 1px solid var(--border-primary);
} }
@media (max-width: 700px) { @media (max-width: 700px) {
.sys-status-meta { grid-template-columns: 1fr; } .sys-status-meta {
grid-template-columns: 1fr;
}
} }
/* ── Status cards ──────────────────────────────────────────────────────── */ /* ── Status cards ──────────────────────────────────────────────────────── */
.srv-grid { .srv-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: var(--space-s); gap: var(--space-s);
margin-bottom: var(--space-l); margin-bottom: var(--space-l);
} }
.srv-card { .srv-card {
background: var(--bg-primary); background: var(--bg-primary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-s) var(--space-m); padding: var(--space-s) var(--space-m);
} }
.srv-card__header { .srv-card__header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: var(--space-3xs); margin-bottom: var(--space-3xs);
} }
.srv-card__name { .srv-card__name {
font-size: var(--step--1); font-size: var(--step--1);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: .07em; letter-spacing: 0.07em;
color: var(--text-secondary); color: var(--text-secondary);
font-weight: 500; font-weight: 500;
} }
.srv-card__detail { .srv-card__detail {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-tertiary); color: var(--text-tertiary);
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
}
.status-ok {
color: var(--accent-green);
font-weight: 600;
font-size: var(--step--1);
}
.status-warn {
color: var(--warning);
font-weight: 600;
font-size: var(--step--1);
}
.status-err {
color: var(--error);
font-weight: 600;
font-size: var(--step--1);
}
.status-unknown {
color: var(--text-tertiary);
font-weight: 600;
font-size: var(--step--1);
} }
.status-ok { color: var(--accent-green); font-weight: 600; font-size: var(--step--1); }
.status-warn { color: var(--warning); font-weight: 600; font-size: var(--step--1); }
.status-err { color: var(--error); font-weight: 600; font-size: var(--step--1); }
.status-unknown { color: var(--text-tertiary); font-weight: 600; font-size: var(--step--1); }
.srv-section-title { .srv-section-title {
font-size: var(--step--1); font-size: var(--step--1);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: .1em; letter-spacing: 0.1em;
color: var(--text-secondary); color: var(--text-secondary);
border-bottom: 1px solid var(--border-primary); border-bottom: 1px solid var(--border-primary);
padding-bottom: var(--space-3xs); padding-bottom: var(--space-3xs);
margin: 0 0 var(--space-s); margin: 0 0 var(--space-s);
font-weight: 500; font-weight: 500;
} }
/* Compact variant: no border, no margin — used inside sys-status-header */ /* Compact variant: no border, no margin — used inside sys-status-header */
.srv-section-title--compact { .srv-section-title--compact {
margin: 0; margin: 0;
border: none; border: none;
padding: 0; padding: 0;
} }
/* Sub-section variant: tighter bottom margin — used for PHP/disk sub-headings */ /* Sub-section variant: tighter bottom margin — used for PHP/disk sub-headings */
.srv-section-title--sub { margin-bottom: var(--space-xs); } .srv-section-title--sub {
margin-bottom: var(--space-xs);
}
/* ── PHP info grid ─────────────────────────────────────────────────────── */ /* ── PHP info grid ─────────────────────────────────────────────────────── */
.php-grid { .php-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: var(--space-3xs) var(--space-xs); gap: var(--space-3xs) var(--space-xs);
margin-bottom: var(--space-l); margin-bottom: var(--space-l);
} }
/* Flush variant: no bottom margin — used inside sys-status-meta cell */ /* Flush variant: no bottom margin — used inside sys-status-meta cell */
.php-grid--flush { margin-bottom: 0; } .php-grid--flush {
margin-bottom: 0;
}
.php-item { .php-item {
background: var(--bg-primary); background: var(--bg-primary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-3xs) var(--space-xs); padding: var(--space-3xs) var(--space-xs);
} }
.php-item__key { .php-item__key {
font-size: var(--step--2); font-size: var(--step--2);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: .06em; letter-spacing: 0.06em;
color: var(--text-tertiary); color: var(--text-tertiary);
} }
.php-item__val { .php-item__val {
font-size: var(--step--1); font-size: var(--step--1);
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
color: var(--text-primary); color: var(--text-primary);
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
} }
/* ── Disk bar ──────────────────────────────────────────────────────────── */ /* ── Disk bar ──────────────────────────────────────────────────────────── */
.disk-bar-wrap { .disk-bar-wrap {
background: var(--border-primary); background: var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
height: 6px; height: 6px;
margin-top: var(--space-2xs); margin-top: var(--space-2xs);
overflow: hidden; overflow: hidden;
} }
.disk-bar { .disk-bar {
height: 100%; height: 100%;
border-radius: var(--radius); border-radius: var(--radius);
width: var(--disk-pct, 0%); width: var(--disk-pct, 0%);
background: var(--disk-color, var(--accent-green)); background: var(--disk-color, var(--accent-green));
transition: width .3s; transition: width 0.3s;
} }
.disk-stats { .disk-stats {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-secondary); color: var(--text-secondary);
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
} }
/* ── Tab panel loading state ───────────────────────────────────────────── */ /* ── Tab panel loading state ───────────────────────────────────────────── */
#sys-tab-panel { #sys-tab-panel {
min-height: 8rem; min-height: 8rem;
position: relative; position: relative;
} }
#sys-tab-panel.htmx-request { #sys-tab-panel.htmx-request {
opacity: 0.4; opacity: 0.4;
pointer-events: none; pointer-events: none;
transition: opacity 0.1s; transition: opacity 0.1s;
} }
#sys-tab-panel.htmx-request::after { #sys-tab-panel.htmx-request::after {
content: ''; content: "";
position: absolute; position: absolute;
inset: 0; inset: 0;
background: repeating-linear-gradient( background: repeating-linear-gradient(
-45deg, -45deg,
transparent, transparent,
transparent 6px, transparent 6px,
rgba(0,0,0,0.03) 6px, rgba(0, 0, 0, 0.03) 6px,
rgba(0,0,0,0.03) 12px rgba(0, 0, 0, 0.03) 12px
); );
border-radius: var(--radius); border-radius: var(--radius);
animation: sys-panel-shimmer 1s linear infinite; animation: sys-panel-shimmer 1s linear infinite;
background-size: 200% 200%; background-size: 200% 200%;
} }
@keyframes sys-panel-shimmer { @keyframes sys-panel-shimmer {
0% { background-position: 0 0; } 0% {
100% { background-position: 100% 100%; } background-position: 0 0;
}
100% {
background-position: 100% 100%;
}
} }
/* ── Log viewer ────────────────────────────────────────────────────────── */ /* ── Log viewer ────────────────────────────────────────────────────────── */
.log-toolbar { .log-toolbar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
gap: var(--space-2xs); gap: var(--space-2xs);
margin-bottom: var(--space-m); margin-bottom: var(--space-m);
} }
.log-toolbar label { .log-toolbar label {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
} }
.log-toolbar select { .log-toolbar select {
padding: var(--space-3xs) var(--space-xs); padding: var(--space-3xs) var(--space-xs);
}
.log-toolbar select:focus {
outline: 2px solid var(--accent-primary);
} }
.log-toolbar select:focus { outline: 2px solid var(--accent-primary); }
.log-meta { .log-meta {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-tertiary); color: var(--text-tertiary);
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
margin-bottom: var(--space-xs); margin-bottom: var(--space-xs);
display: flex; display: flex;
gap: var(--space-m); gap: var(--space-m);
flex-wrap: wrap; flex-wrap: wrap;
}
.log-meta span::before {
content: attr(data-label) ": ";
opacity: 0.6;
} }
.log-meta span::before { content: attr(data-label) ": "; opacity: .6; }
.log-unavailable { .log-unavailable {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-m); padding: var(--space-m);
color: var(--text-secondary); color: var(--text-secondary);
font-size: var(--step--1); font-size: var(--step--1);
} }
.log-unavail-path { .log-unavail-path {
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
font-size: var(--step--2); font-size: var(--step--2);
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
opacity: .7; opacity: 0.7;
} }
.log-unavail-dev { .log-unavail-dev {
margin-top: var(--space-2xs); margin-top: var(--space-2xs);
font-size: var(--step--2); font-size: var(--step--2);
opacity: .7; opacity: 0.7;
} }
.log-empty { .log-empty {
color: var(--text-secondary); color: var(--text-secondary);
font-size: var(--step--1); font-size: var(--step--1);
padding: var(--space-s) 0; padding: var(--space-s) 0;
} }
.log-output { .log-output {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-s); padding: var(--space-s);
overflow-x: auto; overflow-x: auto;
font-family: ui-monospace, "SFMono-Regular", Consolas, "Courier New", monospace; font-family:
font-size: var(--step--2); ui-monospace, "SFMono-Regular", Consolas, "Courier New", monospace;
line-height: 1.55; font-size: var(--step--2);
max-height: 62vh; line-height: 1.55;
overflow-y: auto; max-height: 62vh;
position: relative; overflow-y: auto;
position: relative;
} }
.log-line { .log-line {
display: block; display: block;
white-space: pre; white-space: pre;
padding: var(--space-3xs) var(--space-3xs); padding: var(--space-3xs) var(--space-3xs);
border-radius: var(--radius); border-radius: var(--radius);
color: var(--text-primary); color: var(--text-primary);
}
.log-line + .log-line {
border-top: 1px solid var(--border-primary);
}
.log-crit {
color: var(--sys-syntax-crit);
background: var(--error-muted-bg);
}
.log-error {
color: var(--error);
}
.log-warn {
color: var(--warning);
}
.log-notice {
color: var(--sys-syntax-notice);
} }
.log-line + .log-line { border-top: 1px solid var(--border-primary); }
.log-crit { color: var(--sys-syntax-crit); background: var(--error-muted-bg); }
.log-error { color: var(--error); }
.log-warn { color: var(--warning); }
.log-notice { color: var(--sys-syntax-notice); }
.log-line::before { .log-line::before {
content: attr(data-n); content: attr(data-n);
display: inline-block; display: inline-block;
min-width: 4ch; min-width: 4ch;
margin-right: var(--space-xs); margin-right: var(--space-xs);
opacity: .3; opacity: 0.3;
text-align: right; text-align: right;
user-select: none; user-select: none;
} }
.log-count-badge { .log-count-badge {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
border-radius: var(--radius); border-radius: var(--radius);
font-size: var(--step--2); font-size: var(--step--2);
padding: var(--space-3xs) var(--space-2xs); padding: var(--space-3xs) var(--space-2xs);
color: var(--text-secondary); color: var(--text-secondary);
font-family: ui-monospace, monospace; font-family: ui-monospace, monospace;
} }
.log-copy-btn { .log-copy-btn {
/* uses .btn--secondary .btn--sm but with absolute positioning overrides */ /* uses .btn--secondary .btn--sm but with absolute positioning overrides */
position: absolute; position: absolute;
top: var(--space-2xs); top: var(--space-2xs);
right: var(--space-2xs); right: var(--space-2xs);
z-index: 2; z-index: 2;
} }
.log-copy-btn:hover { .log-copy-btn:hover {
color: var(--text-primary); color: var(--text-primary);
border-color: var(--accent-primary); border-color: var(--accent-primary);
} }
.log-copy-btn.copied { .log-copy-btn.copied {
color: var(--accent-green); color: var(--accent-green);
border-color: var(--accent-green); border-color: var(--accent-green);
} }
.sys-refresh-note { .sys-refresh-note {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-secondary); color: var(--text-secondary);
margin-bottom: var(--space-m); margin-bottom: var(--space-m);
} }
.sys-refresh-note a { .sys-refresh-note a {
color: var(--accent-primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
}
.sys-refresh-note a:hover {
color: var(--accent-primary);
} }
.sys-refresh-note a:hover { color: var(--accent-primary); }
/* ── Cache freshness badges ────────────────────────────────────────────── */ /* ── Cache freshness badges ────────────────────────────────────────────── */
.sys-cache-badge { .sys-cache-badge {
display: inline-block; display: inline-block;
font-size: var(--step--2); font-size: var(--step--2);
font-weight: 400; font-weight: 400;
font-family: ui-monospace, monospace; font-family: ui-monospace, monospace;
padding: var(--space-3xs) var(--space-3xs); padding: var(--space-3xs) var(--space-3xs);
border-radius: var(--radius); border-radius: var(--radius);
margin-left: var(--space-xs); margin-left: var(--space-xs);
vertical-align: middle; vertical-align: middle;
line-height: 1.6; line-height: 1.6;
} }
.sys-cache-badge--hit { .sys-cache-badge--hit {
background: var(--warning-muted-bg); background: var(--warning-muted-bg);
color: var(--warning); color: var(--warning);
border: 1px solid var(--warning-muted-border); border: 1px solid var(--warning-muted-border);
} }
.sys-cache-badge--miss { .sys-cache-badge--miss {
background: var(--success-muted-bg); background: var(--success-muted-bg);
color: var(--success); color: var(--success);
border: 1px solid var(--success-muted-border); border: 1px solid var(--success-muted-border);
} }
+141 -141
View File
@@ -4,292 +4,292 @@
============================================================ */ ============================================================ */
.tfe-main { .tfe-main {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow-y: auto; overflow-y: auto;
padding: var(--space-l) var(--space-m) var(--space-xl); padding: var(--space-l) var(--space-m) var(--space-xl);
} }
/* Stacked article layout */ /* Stacked article layout */
.tfe-layout { .tfe-layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-xl); gap: var(--space-xl);
width: 100%; width: 100%;
} }
/* Row 1: Author above Title */ /* Row 1: Author above Title */
.tfe-header-row { .tfe-header-row {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-3xs); gap: var(--space-3xs);
} }
/* Row 2: Two columns — meta left, synopsis right */ /* Row 2: Two columns — meta left, synopsis right */
.tfe-content-row { .tfe-content-row {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: var(--space-xl); gap: var(--space-xl);
align-items: start; align-items: start;
} }
/* Row 3: Files flex container */ /* Row 3: Files flex container */
.tfe-files { .tfe-files {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--space-m); gap: var(--space-m);
} }
.tfe-file-item { .tfe-file-item {
flex: 1 1 300px; flex: 1 1 300px;
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
border-radius: var(--radius); border-radius: var(--radius);
} }
.tfe-file-item img { .tfe-file-item img {
width: 100%; width: 100%;
height: auto; height: auto;
display: block; display: block;
border-radius: var(--radius); border-radius: var(--radius);
user-select: none; user-select: none;
-webkit-user-drag: none; -webkit-user-drag: none;
} }
.tfe-file-item embed, .tfe-file-item embed,
.tfe-file-item video { .tfe-file-item video {
width: 100%; width: 100%;
display: block; display: block;
border: none; border: none;
} }
.tfe-file-item video { .tfe-file-item video {
max-height: 500px; max-height: 500px;
} }
.tfe-file-item embed { .tfe-file-item embed {
height: clamp(300px, 80vh, 700px); height: clamp(300px, 80vh, 700px);
} }
.tfe-file-item figcaption { .tfe-file-item figcaption {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-secondary); color: var(--text-secondary);
margin: var(--space-3xs) 0 0; margin: var(--space-3xs) 0 0;
font-style: italic; font-style: italic;
} }
/* Synopsis column wrapper (context note + synopsis) */ /* Synopsis column wrapper (context note + synopsis) */
.tfe-synopsis-column { .tfe-synopsis-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-xs); gap: var(--space-xs);
} }
/* Contextual note above synopsis */ /* Contextual note above synopsis */
.tfe-context-note { .tfe-context-note {
font-style: italic; font-style: italic;
font-size: var(--step--1); font-size: var(--step--1);
line-height: 1.6; line-height: 1.6;
color: var(--text-secondary); color: var(--text-secondary);
margin: 0; margin: 0;
padding: var(--space-xs) var(--space-s); padding: var(--space-xs) var(--space-s);
background: color-mix(in srgb, var(--accent-primary) 10%, var(--bg-primary)); background: color-mix(in srgb, var(--accent-primary) 10%, var(--bg-primary));
border-radius: var(--radius); border-radius: var(--radius);
} }
.tfe-synopsis-empty { .tfe-synopsis-empty {
/* placeholder to maintain grid column */ /* placeholder to maintain grid column */
} }
/* Author (p) — inline with title */ /* Author (p) — inline with title */
.tfe-author { .tfe-author {
font-family: var(--font-display); font-family: var(--font-display);
font-size: var(--step-1); font-size: var(--step-1);
font-weight: 400; font-weight: 400;
color: var(--text-primary); color: var(--text-primary);
margin: 0; margin: 0;
line-height: 1.3; line-height: 1.3;
} }
/* Title (h1) — inherits global scale from base.css */ /* Title (h1) — inherits global scale from base.css */
.tfe-title { .tfe-title {
margin: 0; margin: 0;
} }
/* Metadata block */ /* Metadata block */
.tfe-meta { .tfe-meta {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-3xs); gap: var(--space-3xs);
font-size: var(--step--1); font-size: var(--step--1);
line-height: 1.4; line-height: 1.4;
margin: 0; margin: 0;
} }
/* Each metadata line */ /* Each metadata line */
.tfe-meta-item { .tfe-meta-item {
margin: 0; margin: 0;
font-weight: 700; font-weight: 700;
} }
.tfe-meta-label { .tfe-meta-label {
font-weight: 300; font-weight: 300;
} }
.tfe-meta-item a { .tfe-meta-item a {
color: inherit; color: inherit;
font-weight: 700; font-weight: 700;
text-decoration: none; text-decoration: none;
} }
.tfe-meta-item a:hover { .tfe-meta-item a:hover {
color: var(--accent-primary); color: var(--accent-primary);
} }
/* Note field: align label to top, value in italics */ /* Note field: align label to top, value in italics */
.tfe-meta-item.tfe-meta-note { .tfe-meta-item.tfe-meta-note {
align-items: start; align-items: start;
} }
.tfe-note-value { .tfe-note-value {
font-style: italic; font-style: italic;
} }
/* Synopsis paragraph */ /* Synopsis paragraph */
.tfe-synopsis-text { .tfe-synopsis-text {
font-size: var(--step--1); font-size: var(--step--1);
line-height: 1.7; line-height: 1.7;
color: var(--text-primary); color: var(--text-primary);
margin: 0; margin: 0;
} }
/* Audio player */ /* Audio player */
.tfe-audio { .tfe-audio {
width: 100%; width: 100%;
margin: 0; margin: 0;
display: block; display: block;
} }
/* "Not available" and "no files" notices */ /* "Not available" and "no files" notices */
.tfe-restricted, .tfe-restricted,
.tfe-no-files { .tfe-no-files {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
font-style: italic; font-style: italic;
padding: var(--space-s) 0; padding: var(--space-s) 0;
margin: 0; margin: 0;
} }
/* ============================================================ /* ============================================================
RESTRICTED ACCESS UI RESTRICTED ACCESS UI
============================================================ */ ============================================================ */
.tfe-restricted-access { .tfe-restricted-access {
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--space-m); padding: var(--space-m);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-m); gap: var(--space-m);
} }
.tfe-restricted-message { .tfe-restricted-message {
font-size: var(--step--1); font-size: var(--step--1);
color: var(--text-secondary); color: var(--text-secondary);
line-height: 1.6; line-height: 1.6;
margin: 0; margin: 0;
} }
.tfe-restricted-message strong { .tfe-restricted-message strong {
color: var(--text-primary); color: var(--text-primary);
display: block; display: block;
margin-bottom: var(--space-3xs); margin-bottom: var(--space-3xs);
font-size: var(--step-0); font-size: var(--step-0);
} }
.tfe-access-request-form { .tfe-access-request-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-s); gap: var(--space-s);
} }
.tfe-access-request-form .form-group { .tfe-access-request-form .form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-3xs); gap: var(--space-3xs);
} }
.tfe-access-request-form label { .tfe-access-request-form label {
font-size: var(--step--1); font-size: var(--step--1);
font-weight: 400; font-weight: 400;
color: var(--text-primary); color: var(--text-primary);
} }
.tfe-access-request-form input[type="email"], .tfe-access-request-form input[type="email"],
.tfe-access-request-form textarea { .tfe-access-request-form textarea {
padding: var(--space-2xs) var(--space-3xs); padding: var(--space-2xs) var(--space-3xs);
} }
.tfe-btn-request-access { .tfe-btn-request-access {
/* deprecated alias for .btn--primary; kept for backward-compat */ /* deprecated alias for .btn--primary; kept for backward-compat */
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
} }
.tfe-btn-request-access:hover:not(:disabled) { .tfe-btn-request-access:hover:not(:disabled) {
/* handled by .btn--primary */ /* handled by .btn--primary */
} }
.tfe-btn-request-access:disabled { .tfe-btn-request-access:disabled {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
.tfe-access-message { .tfe-access-message {
font-size: var(--step--1); font-size: var(--step--1);
padding: var(--space-2xs); padding: var(--space-2xs);
border-radius: var(--radius); border-radius: var(--radius);
margin-top: var(--space-3xs); margin-top: var(--space-3xs);
} }
.tfe-access-success { .tfe-access-success {
background: #f0fff4; background: #f0fff4;
border: 1px solid #48bb78; border: 1px solid #48bb78;
color: #22543d; color: #22543d;
} }
.tfe-access-error { .tfe-access-error {
background: #fff5f5; background: #fff5f5;
border: 1px solid #fc8181; border: 1px solid #fc8181;
color: #742a2a; color: #742a2a;
} }
.tfe-access-request-form input.input-error { .tfe-access-request-form input.input-error {
border-color: #fc8181; border-color: #fc8181;
outline-color: #fc8181; outline-color: #fc8181;
} }
/* Responsive */ /* Responsive */
@media (max-width: 900px) { @media (max-width: 900px) {
.tfe-content-row { .tfe-content-row {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: var(--space-l); gap: var(--space-l);
} }
.tfe-file-item { .tfe-file-item {
flex: 1 1 100%; flex: 1 1 100%;
} }
.tfe-title { .tfe-title {
font-size: var(--step-2); font-size: var(--step-2);
} }
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.tfe-main { .tfe-main {
padding: var(--space-m) var(--space-s) var(--space-l); padding: var(--space-m) var(--space-s) var(--space-l);
} }
.tfe-title { .tfe-title {
font-size: var(--step-1); font-size: var(--step-1);
} }
} }
+58 -58
View File
@@ -7,85 +7,85 @@
/* ── Font faces ─────────────────────────────────────────────────────── */ /* ── Font faces ─────────────────────────────────────────────────────── */
@font-face { @font-face {
font-family: "Ductus"; font-family: "Ductus";
src: url("../fonts/DuctusRegular.otf") format("opentype"); src: url("../fonts/DuctusRegular.otf") format("opentype");
font-style: normal; font-style: normal;
font-weight: 398; font-weight: 398;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "BBBDMSans"; font-family: "BBBDMSans";
src: url("../fonts/BBBDMSans-Light.otf") format("opentype"); src: url("../fonts/BBBDMSans-Light.otf") format("opentype");
font-style: normal; font-style: normal;
font-weight: 298; font-weight: 298;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "BBBDMSans"; font-family: "BBBDMSans";
src: url("/assets/fonts/BBBDMSans-Regular.otf") format("opentype"); src: url("/assets/fonts/BBBDMSans-Regular.otf") format("opentype");
font-style: normal; font-style: normal;
font-weight: 398; font-weight: 398;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "BBBDMSans"; font-family: "BBBDMSans";
src: url("../fonts/BBBDMSans-Medium.otf") format("opentype"); src: url("../fonts/BBBDMSans-Medium.otf") format("opentype");
font-style: normal; font-style: normal;
font-weight: 498; font-weight: 498;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "BBBDMSans"; font-family: "BBBDMSans";
src: url("../fonts/BBBDMSans-Bold.otf") format("opentype"); src: url("../fonts/BBBDMSans-Bold.otf") format("opentype");
font-style: normal; font-style: normal;
font-weight: 698; font-weight: 698;
font-display: swap; font-display: swap;
} }
/* ── Named font-size scale ──────────────────────────────────────────── */ /* ── Named font-size scale ──────────────────────────────────────────── */
/* @link https://utopia.fyi/type/calculator?c=360,18,1.2,1240,20,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */ /* @link https://utopia.fyi/type/calculator?c=360,18,1.2,1240,20,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
:root { :root {
--step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem); --step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
--step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem); --step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
--step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem); --step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
--step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem); --step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
--step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem); --step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
--step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem); --step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
--step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem); --step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
--step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem); --step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
/* ── Named spacing scale ─────────────────────────────────────────── */ /* ── Named spacing scale ─────────────────────────────────────────── */
/* @link https://utopia.fyi/space/calculator?c=360,18,1.2,1240,20,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */ /* @link https://utopia.fyi/space/calculator?c=360,18,1.2,1240,20,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
--space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem); --space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
--space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem); --space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
--space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem); --space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
--space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem); --space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
--space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem); --space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
--space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem); --space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
--space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem); --space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
--space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem); --space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
--space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem); --space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
/* One-up pairs */ /* One-up pairs */
--space-3xs-2xs: clamp(0.3125rem, 0.1847rem + 0.5682vw, 0.625rem); --space-3xs-2xs: clamp(0.3125rem, 0.1847rem + 0.5682vw, 0.625rem);
--space-2xs-xs: clamp(0.5625rem, 0.4091rem + 0.6818vw, 0.9375rem); --space-2xs-xs: clamp(0.5625rem, 0.4091rem + 0.6818vw, 0.9375rem);
--space-xs-s: clamp(0.875rem, 0.7216rem + 0.6818vw, 1.25rem); --space-xs-s: clamp(0.875rem, 0.7216rem + 0.6818vw, 1.25rem);
--space-s-m: clamp(1.125rem, 0.8182rem + 1.3636vw, 1.875rem); --space-s-m: clamp(1.125rem, 0.8182rem + 1.3636vw, 1.875rem);
--space-m-l: clamp(1.6875rem, 1.3551rem + 1.4773vw, 2.5rem); --space-m-l: clamp(1.6875rem, 1.3551rem + 1.4773vw, 2.5rem);
--space-l-xl: clamp(2.25rem, 1.6364rem + 2.7273vw, 3.75rem); --space-l-xl: clamp(2.25rem, 1.6364rem + 2.7273vw, 3.75rem);
--space-xl-2xl: clamp(3.375rem, 2.7102rem + 2.9545vw, 5rem); --space-xl-2xl: clamp(3.375rem, 2.7102rem + 2.9545vw, 5rem);
--space-2xl-3xl: clamp(4.5rem, 3.2727rem + 5.4545vw, 7.5rem); --space-2xl-3xl: clamp(4.5rem, 3.2727rem + 5.4545vw, 7.5rem);
/* Custom pairs */ /* Custom pairs */
--space-s-l: clamp(1.125rem, 0.5625rem + 2.5vw, 2.5rem); --space-s-l: clamp(1.125rem, 0.5625rem + 2.5vw, 2.5rem);
/* Font families */ /* Font families */
--font-body: "BBBDMSans", sans-serif; --font-body: "BBBDMSans", sans-serif;
--font-display: "Ductus", sans-serif; --font-display: "Ductus", sans-serif;
} }
+29 -29
View File
@@ -5,47 +5,47 @@
/* Visually-hidden but screen-reader-accessible */ /* Visually-hidden but screen-reader-accessible */
.sr-only { .sr-only {
position: absolute; position: absolute;
width: 1px; width: 1px;
height: 1px; height: 1px;
padding: 0; padding: 0;
margin: -1px; margin: -1px;
overflow: hidden; overflow: hidden;
clip: rect(0, 0, 0, 0); clip: rect(0, 0, 0, 0);
white-space: nowrap; white-space: nowrap;
border: 0; border: 0;
} }
/* Skip-to-content link (visible only on keyboard focus) */ /* Skip-to-content link (visible only on keyboard focus) */
.skip-link { .skip-link {
position: absolute; position: absolute;
top: -999px; top: -999px;
left: 1rem; left: 1rem;
z-index: 9999; z-index: 9999;
padding: var(--space-2xs) var(--space-s); padding: var(--space-2xs) var(--space-s);
background: var(--accent-primary); background: var(--accent-primary);
color: var(--accent-foreground); color: var(--accent-foreground);
font-size: var(--step--1); font-size: var(--step--1);
font-weight: 600; font-weight: 600;
text-decoration: none; text-decoration: none;
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
} }
.skip-link:focus { .skip-link:focus {
top: 0; top: 0;
} }
.skip-link:hover { .skip-link:hover {
color: var(--accent-primary); color: var(--accent-primary);
background: var(--accent-foreground); background: var(--accent-foreground);
} }
/* Respect user motion preferences */ /* Respect user motion preferences */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
*, *,
*::before, *::before,
*::after { *::after {
transition-duration: 0.01ms !important; transition-duration: 0.01ms;
animation-duration: 0.01ms !important; animation-duration: 0.01ms;
} }
} }
@@ -4,43 +4,45 @@
* Provides: dialog openers, clipboard copy, password dialog. * Provides: dialog openers, clipboard copy, password dialog.
*/ */
(() => { (() => {
var createBtn = document.getElementById('open-create-dialog'); var createBtn = document.getElementById("open-create-dialog");
if (createBtn) { if (createBtn) {
createBtn.addEventListener('click', () => { createBtn.addEventListener("click", () => {
document.getElementById('create-dialog').showModal(); document.getElementById("create-dialog").showModal();
}); });
} }
})(); })();
var _pendingDeleteLinkId = null; var _pendingDeleteLinkId = null;
function openDeleteLinkDialog(id) { function _openDeleteLinkDialog(id) {
_pendingDeleteLinkId = id; _pendingDeleteLinkId = id;
document.getElementById('delete-link-dialog').showModal(); document.getElementById("delete-link-dialog").showModal();
} }
function _executeDeleteLink() { function _executeDeleteLink() {
var form = document.getElementById('delete-link-form-' + _pendingDeleteLinkId); var form = document.getElementById(
if (form) form.submit(); `delete-link-form-${_pendingDeleteLinkId}`,
);
if (form) form.submit();
} }
function copyUrl(id) { function _copyUrl(id) {
var input = document.getElementById('url-' + id); var input = document.getElementById(`url-${id}`);
navigator.clipboard.writeText(input.value).then(() => { navigator.clipboard.writeText(input.value).then(() => {
var btn = event.target.closest('button'); var btn = event.target.closest("button");
var orig = btn.textContent; var orig = btn.textContent;
btn.textContent = '✓ Copié'; btn.textContent = "✓ Copié";
setTimeout(() => { setTimeout(() => {
btn.textContent = orig; btn.textContent = orig;
}, 1200); }, 1200);
}); });
} }
function openPasswordDialog(id, hasPassword) { function _openPasswordDialog(id, hasPassword) {
document.getElementById('password-link-id').value = id; document.getElementById("password-link-id").value = id;
var info = document.getElementById('password-current-info'); var info = document.getElementById("password-current-info");
info.textContent = hasPassword info.textContent = hasPassword
? 'Un mot de passe est actuellement configuré. Entrez-en un nouveau ou laissez vide pour le supprimer.' ? "Un mot de passe est actuellement configuré. Entrez-en un nouveau ou laissez vide pour le supprimer."
: 'Aucun mot de passe configuré.'; : "Aucun mot de passe configuré.";
document.getElementById('password-dialog').showModal(); document.getElementById("password-dialog").showModal();
} }
+78 -72
View File
@@ -7,99 +7,105 @@
* <meta name="acces-new-link-slug" content="..."> * <meta name="acces-new-link-slug" content="...">
*/ */
(() => { (() => {
var baseUrlMeta = document.querySelector('meta[name="acces-base-url"]'); var baseUrlMeta = document.querySelector('meta[name="acces-base-url"]');
var passwordMeta = document.querySelector('meta[name="acces-new-link-password"]'); var passwordMeta = document.querySelector(
var slugMeta = document.querySelector('meta[name="acces-new-link-slug"]'); 'meta[name="acces-new-link-password"]',
);
var slugMeta = document.querySelector('meta[name="acces-new-link-slug"]');
// Show result dialog after redirect (new link created) // Show result dialog after redirect (new link created)
if (passwordMeta && slugMeta && passwordMeta.content && slugMeta.content) { if (passwordMeta && slugMeta && passwordMeta.content && slugMeta.content) {
document.getElementById('create-result-password').value = passwordMeta.content; document.getElementById("create-result-password").value =
document.getElementById('create-result-url').value = passwordMeta.content;
(baseUrlMeta ? baseUrlMeta.content : '') + '/partage/' + slugMeta.content; document.getElementById("create-result-url").value =
document.getElementById('create-result-dialog').showModal(); `${baseUrlMeta ? baseUrlMeta.content : ""}/partage/${slugMeta.content}`;
} document.getElementById("create-result-dialog").showModal();
}
// Create dialog opener // Create dialog opener
var createBtn = document.getElementById('open-create-dialog'); var createBtn = document.getElementById("open-create-dialog");
if (createBtn) { if (createBtn) {
createBtn.addEventListener('click', () => { createBtn.addEventListener("click", () => {
document.getElementById('create-dialog').showModal(); document.getElementById("create-dialog").showModal();
}); });
} }
})(); })();
function copyUrl(id) { function _copyUrl(id) {
var input = document.getElementById('url-' + id); var input = document.getElementById(`url-${id}`);
navigator.clipboard.writeText(input.value).then(() => { navigator.clipboard.writeText(input.value).then(() => {
var btn = event.target.closest('button'); var btn = event.target.closest("button");
if (btn) { if (btn) {
var orig = btn.getAttribute('title') || ''; const orig = btn.getAttribute("title") || "";
btn.setAttribute('title', '✓ Copié'); btn.setAttribute("title", "✓ Copié");
setTimeout(() => { setTimeout(() => {
btn.setAttribute('title', orig); btn.setAttribute("title", orig);
}, 1200); }, 1200);
} }
}); });
} }
function copyUrlFrom(el) { function _copyUrlFrom(el) {
navigator.clipboard.writeText(el.value).then(() => { navigator.clipboard.writeText(el.value).then(() => {
var btn = el.nextElementSibling; const btn = el.nextElementSibling;
if (btn) { if (btn) {
var orig = btn.textContent; const orig = btn.textContent;
btn.textContent = '✓ Copié'; btn.textContent = "✓ Copié";
setTimeout(() => { setTimeout(() => {
btn.textContent = orig; btn.textContent = orig;
}, 1200); }, 1200);
} }
}); });
} }
function copyTextToClipboard(text) { function _copyTextToClipboard(text) {
navigator.clipboard.writeText(text) navigator.clipboard
.then(() => { .writeText(text)
var btn = event && event.target ? event.target.closest('button') : null; .then(() => {
if (btn) { const btn = event?.target ? event.target.closest("button") : null;
var orig = btn.getAttribute('title') || ''; if (btn) {
btn.setAttribute('title', '✓ Copié'); const orig = btn.getAttribute("title") || "";
setTimeout(() => { btn.setAttribute("title", "✓ Copié");
btn.setAttribute('title', orig); setTimeout(() => {
}, 1200); btn.setAttribute("title", orig);
} }, 1200);
}) }
.catch(() => {}); })
.catch(() => {});
} }
function openEditDialog(id, name, hasPassword, expiresVal) { function _openEditDialog(id, name, _hasPassword, expiresVal) {
document.getElementById('edit-link-id').value = id; document.getElementById("edit-link-id").value = id;
document.getElementById('edit-name').value = name || ''; document.getElementById("edit-name").value = name || "";
document.getElementById('edit-expires').value = expiresVal || ''; document.getElementById("edit-expires").value = expiresVal || "";
document.getElementById('edit-dialog').showModal(); document.getElementById("edit-dialog").showModal();
} }
function openApproveDialog(requestId) { function _openApproveDialog(requestId) {
document.getElementById('approve-request-id').value = requestId; document.getElementById("approve-request-id").value = requestId;
document.getElementById('approve-dialog').showModal(); document.getElementById("approve-dialog").showModal();
} }
function openRejectDialog(requestId) { function _openRejectDialog(requestId) {
document.getElementById('reject-request-id').value = requestId; document.getElementById("reject-request-id").value = requestId;
document.getElementById('reject-dialog').showModal(); document.getElementById("reject-dialog").showModal();
} }
var _pendingArchiveLinkId = null; var _pendingArchiveLinkId = null;
function openArchiveLinkDialog(id) { function _openArchiveLinkDialog(id) {
_pendingArchiveLinkId = id; _pendingArchiveLinkId = id;
document.getElementById('archive-link-dialog').showModal(); document.getElementById("archive-link-dialog").showModal();
} }
function _executeArchiveLink() { function _executeArchiveLink() {
var form = document.getElementById('archive-link-form-' + _pendingArchiveLinkId); var form = document.getElementById(
if (form) form.submit(); `archive-link-form-${_pendingArchiveLinkId}`,
);
if (form) form.submit();
} }
function openDeleteArchivedLinkDialog(id) { function _openDeleteArchivedLinkDialog(id) {
document.getElementById('delete-archived-link-id').value = id; document.getElementById("delete-archived-link-id").value = id;
document.getElementById('delete-archived-link-dialog').showModal(); document.getElementById("delete-archived-link-dialog").showModal();
} }
+60 -55
View File
@@ -5,67 +5,72 @@
* All functions are attached to `window` for onclick handlers in PHP templates. * All functions are attached to `window` for onclick handlers in PHP templates.
*/ */
(() => { (() => {
function cleanupToggleAll(src, group) { function cleanupToggleAll(src, group) {
const selector = group === 'filepond' const selector =
? 'input[name="filepond_dirs[]"]' group === "filepond"
: 'input[name="trash_files[]"]'; ? 'input[name="filepond_dirs[]"]'
document.querySelectorAll(selector).forEach((cb) => { : 'input[name="trash_files[]"]';
cb.checked = src.checked; document.querySelectorAll(selector).forEach((cb) => {
}); cb.checked = src.checked;
cleanupUpdateBulk(); });
} cleanupUpdateBulk();
}
function cleanupUpdateBulk() { function cleanupUpdateBulk() {
const bar = document.getElementById('cleanup-bulk-actions'); const bar = document.getElementById("cleanup-bulk-actions");
const countEl = document.getElementById('cleanup-selected-count'); const countEl = document.getElementById("cleanup-selected-count");
if (!bar || !countEl) return; if (!bar || !countEl) return;
const n = document.querySelectorAll( const n = document.querySelectorAll(
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked' 'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked',
).length; ).length;
countEl.textContent = n; countEl.textContent = n;
bar.style.display = n > 0 ? 'flex' : 'none'; bar.style.display = n > 0 ? "flex" : "none";
} }
function cleanupBulkDelete() { function cleanupBulkDelete() {
const form = document.getElementById('cleanup-bulk-form'); const form = document.getElementById("cleanup-bulk-form");
const container = document.getElementById('cleanup-bulk-checkboxes'); const container = document.getElementById("cleanup-bulk-checkboxes");
if (!form || !container) return; if (!form || !container) return;
// Populate hidden inputs from checked checkboxes // Populate hidden inputs from checked checkboxes
container.innerHTML = ''; container.innerHTML = "";
document.querySelectorAll( document
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked' .querySelectorAll(
).forEach((cb) => { 'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked',
const inp = document.createElement('input'); )
inp.type = 'hidden'; .forEach((cb) => {
inp.name = cb.name; // "filepond_dirs[]" or "trash_files[]" const inp = document.createElement("input");
inp.value = cb.value; inp.type = "hidden";
container.appendChild(inp); inp.name = cb.name; // "filepond_dirs[]" or "trash_files[]"
}); inp.value = cb.value;
container.appendChild(inp);
});
// Submit via HTMX // Submit via HTMX
htmx.trigger(form, 'submit'); htmx.trigger(form, "submit");
} }
function reattachListeners() { function reattachListeners() {
document.querySelectorAll( document
'input[name="filepond_dirs[]"], input[name="trash_files[]"]' .querySelectorAll(
).forEach((cb) => { 'input[name="filepond_dirs[]"], input[name="trash_files[]"]',
cb.addEventListener('change', cleanupUpdateBulk); )
}); .forEach((cb) => {
cleanupUpdateBulk(); cb.addEventListener("change", cleanupUpdateBulk);
} });
cleanupUpdateBulk();
}
document.addEventListener('htmx:afterSwap', (evt) => { document.addEventListener("htmx:afterSwap", (evt) => {
// Only reattach if the swap target is our cleanup wrapper // Only reattach if the swap target is our cleanup wrapper
if (evt.detail.target.id === 'tmp-cleanup-stats-wrapper') { if (evt.detail.target.id === "tmp-cleanup-stats-wrapper") {
reattachListeners(); reattachListeners();
} }
}); });
// Export to global scope for onclick handlers in HTML // Export to global scope for onclick handlers in HTML
window.cleanupToggleAll = cleanupToggleAll; window.cleanupToggleAll = cleanupToggleAll;
window.cleanupUpdateBulk = cleanupUpdateBulk; window.cleanupUpdateBulk = cleanupUpdateBulk;
window.cleanupBulkDelete = cleanupBulkDelete; window.cleanupBulkDelete = cleanupBulkDelete;
})(); })();
+74 -73
View File
@@ -5,87 +5,88 @@
* Expects templates with ids `entry-template-f-{key}` and `group-template-f-{key}`. * Expects templates with ids `entry-template-f-{key}` and `group-template-f-{key}`.
*/ */
(() => { (() => {
var form = document.querySelector('form[data-apropos-key]'); var form = document.querySelector("form[data-apropos-key]");
if (!form) return; if (!form) return;
var key = form.dataset.aproposKey; var key = form.dataset.aproposKey;
var groupCount = parseInt(form.dataset.aproposGroupCount, 10) || 0; var groupCount = parseInt(form.dataset.aproposGroupCount, 10) || 0;
var entryTpl = document.getElementById('entry-template-f-' + key).innerHTML; var entryTpl = document.getElementById(`entry-template-f-${key}`).innerHTML;
var groupTpl = document.getElementById('group-template-f-' + key).innerHTML; var groupTpl = document.getElementById(`group-template-f-${key}`).innerHTML;
function reindexGroups() { function reindexGroups() {
var fieldsets = form.querySelectorAll('fieldset.apropos-group'); var fieldsets = form.querySelectorAll("fieldset.apropos-group");
groupCount = fieldsets.length; groupCount = fieldsets.length;
fieldsets.forEach((fs, i) => { fieldsets.forEach((fs, i) => {
var newIdx = i; var newIdx = i;
var legend = fs.querySelector('legend'); var legend = fs.querySelector("legend");
if (legend) legend.textContent = 'Contact ' + (newIdx + 1); if (legend) legend.textContent = `Contact ${newIdx + 1}`;
fs.querySelectorAll('input').forEach((inp) => { fs.querySelectorAll("input").forEach((inp) => {
if (inp.name) { if (inp.name) {
inp.name = inp.name.replace(/groups\[\d+\]/, 'groups[' + newIdx + ']'); inp.name = inp.name.replace(/groups\[\d+\]/, `groups[${newIdx}]`);
} }
if (inp.id) { if (inp.id) {
inp.id = inp.id.replace( inp.id = inp.id.replace(
/(group_f_contacts_|entry_f_contacts_)\d+/, /(group_f_contacts_|entry_f_contacts_)\d+/,
'$1' + newIdx `$1${newIdx}`,
); );
} }
}); });
fs.querySelectorAll('label[for]').forEach((lbl) => { fs.querySelectorAll("label[for]").forEach((lbl) => {
lbl.setAttribute( lbl.setAttribute(
'for', "for",
lbl.getAttribute('for').replace( lbl
/(group_f_contacts_|entry_f_contacts_)\d+/, .getAttribute("for")
'$1' + newIdx .replace(/(group_f_contacts_|entry_f_contacts_)\d+/, `$1${newIdx}`),
) );
); });
});
var addBtn = fs.querySelector('.add-entry-btn-f'); var addBtn = fs.querySelector(".add-entry-btn-f");
if (addBtn) addBtn.dataset.group = newIdx; if (addBtn) addBtn.dataset.group = newIdx;
}); });
} }
function bindAddEntry(btn) { function bindAddEntry(btn) {
btn.addEventListener('click', function () { btn.addEventListener("click", function () {
var gi = parseInt(this.dataset.group); var gi = parseInt(this.dataset.group, 10);
var fieldset = this.closest('fieldset'); var fieldset = this.closest("fieldset");
var entryCount = fieldset.querySelectorAll('.apropos-entry').length; var entryCount = fieldset.querySelectorAll(".apropos-entry").length;
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount); var html = entryTpl
this.insertAdjacentHTML('beforebegin', html); .replaceAll("{{gi}}", gi)
}); .replaceAll("{{ei}}", entryCount);
} this.insertAdjacentHTML("beforebegin", html);
});
}
function bindDeleteGroup(btn) { function bindDeleteGroup(btn) {
btn.addEventListener('click', function () { btn.addEventListener("click", function () {
var fieldset = this.closest('fieldset'); var fieldset = this.closest("fieldset");
if (fieldset) { if (fieldset) {
fieldset.remove(); fieldset.remove();
reindexGroups(); reindexGroups();
} }
}); });
} }
// Bind existing buttons // Bind existing buttons
form.querySelectorAll('.add-entry-btn-f').forEach(bindAddEntry); form.querySelectorAll(".add-entry-btn-f").forEach(bindAddEntry);
form.querySelectorAll('.delete-group-btn-f').forEach(bindDeleteGroup); form.querySelectorAll(".delete-group-btn-f").forEach(bindDeleteGroup);
form.querySelector('.add-group-btn-f').addEventListener('click', function () { form.querySelector(".add-group-btn-f").addEventListener("click", function () {
groupCount++; groupCount++;
var html = groupTpl.replaceAll('{{gi}}', groupCount); var html = groupTpl.replaceAll("{{gi}}", groupCount);
this.insertAdjacentHTML('beforebegin', html); this.insertAdjacentHTML("beforebegin", html);
var newGroup = this.previousElementSibling; var newGroup = this.previousElementSibling;
if (newGroup && newGroup.classList.contains('apropos-group')) { if (newGroup?.classList.contains("apropos-group")) {
var addBtn = newGroup.querySelector('.add-entry-btn-f'); var addBtn = newGroup.querySelector(".add-entry-btn-f");
if (addBtn) { if (addBtn) {
addBtn.dataset.group = groupCount; addBtn.dataset.group = groupCount;
bindAddEntry(addBtn); bindAddEntry(addBtn);
} }
var delBtn = newGroup.querySelector('.delete-group-btn-f'); var delBtn = newGroup.querySelector(".delete-group-btn-f");
if (delBtn) bindDeleteGroup(delBtn); if (delBtn) bindDeleteGroup(delBtn);
} }
}); });
})(); })();
@@ -7,111 +7,126 @@
*/ */
var _languesPendingForm = null; var _languesPendingForm = null;
function languesConfirmDelete(btn, name) { function _languesConfirmDelete(btn, name) {
_languesPendingForm = btn.closest('form'); _languesPendingForm = btn.closest("form");
document.getElementById('langues-delete-name').textContent = name; document.getElementById("langues-delete-name").textContent = name;
document.getElementById('langues-delete-dialog').showModal(); document.getElementById("langues-delete-dialog").showModal();
} }
function languesSubmitPending() { function _languesSubmitPending() {
if (_languesPendingForm) _languesPendingForm.submit(); if (_languesPendingForm) _languesPendingForm.submit();
} }
function languesToggleAll(src) { function _languesToggleAll(src) {
document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => { document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => {
cb.checked = src.checked; cb.checked = src.checked;
}); });
languesUpdateBulk(); languesUpdateBulk();
} }
function languesUpdateBulk() { function languesUpdateBulk() {
var n = document.querySelectorAll('input[name="selected_langs[]"]:checked').length; var n = document.querySelectorAll(
document.getElementById('langues-selected-count').textContent = n; 'input[name="selected_langs[]"]:checked',
var bar = document.getElementById('langues-bulk-actions'); ).length;
var wrap = document.getElementById('langues-table-wrap'); document.getElementById("langues-selected-count").textContent = n;
var visible = n > 1; var bar = document.getElementById("langues-bulk-actions");
bar.style.display = visible ? 'flex' : 'none'; var wrap = document.getElementById("langues-table-wrap");
if (visible) { var visible = n > 1;
requestAnimationFrame(() => { bar.style.display = visible ? "flex" : "none";
wrap.style.setProperty('--sticky-top', bar.offsetHeight + 'px'); if (visible) {
}); requestAnimationFrame(() => {
} else { wrap.style.setProperty("--sticky-top", `${bar.offsetHeight}px`);
wrap.style.setProperty('--sticky-top', '0px'); });
} } else {
wrap.style.setProperty("--sticky-top", "0px");
}
} }
function languesCancelSelection() { function _languesCancelSelection() {
document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => { document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => {
cb.checked = false; cb.checked = false;
}); });
languesUpdateBulk(); languesUpdateBulk();
} }
function languesConfirmBulkDelete() { function _languesConfirmBulkDelete() {
var checked = document.querySelectorAll('input[name="selected_langs[]"]:checked'); var checked = document.querySelectorAll(
if (checked.length < 1) return; 'input[name="selected_langs[]"]:checked',
document.getElementById('langues-bulk-delete-count').textContent = checked.length; );
document.getElementById('langues-bulk-delete-dialog').showModal(); if (checked.length < 1) return;
document.getElementById("langues-bulk-delete-count").textContent =
checked.length;
document.getElementById("langues-bulk-delete-dialog").showModal();
} }
function languesExecBulkDelete() { function _languesExecBulkDelete() {
var container = document.getElementById('langues-bulk-checkboxes'); var container = document.getElementById("langues-bulk-checkboxes");
container.innerHTML = ''; container.innerHTML = "";
document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach((cb) => { document
var inp = document.createElement('input'); .querySelectorAll('input[name="selected_langs[]"]:checked')
inp.type = 'hidden'; .forEach((cb) => {
inp.name = 'selected_langs[]'; var inp = document.createElement("input");
inp.value = cb.value; inp.type = "hidden";
container.appendChild(inp); inp.name = "selected_langs[]";
}); inp.value = cb.value;
document.getElementById('langues-bulk-form').querySelector('input[name="action"]').value = container.appendChild(inp);
'delete_bulk'; });
document.getElementById('langues-bulk-delete-dialog').close(); document
document.getElementById('langues-bulk-form').submit(); .getElementById("langues-bulk-form")
.querySelector('input[name="action"]').value = "delete_bulk";
document.getElementById("langues-bulk-delete-dialog").close();
document.getElementById("langues-bulk-form").submit();
} }
function languesConfirmBulkMerge() { function _languesConfirmBulkMerge() {
var checked = document.querySelectorAll('input[name="selected_langs[]"]:checked'); var checked = document.querySelectorAll(
if (checked.length < 2) return; 'input[name="selected_langs[]"]:checked',
document.getElementById('langues-bulk-merge-count').textContent = checked.length; );
var sel = document.getElementById('langues-bulk-merge-target-select'); if (checked.length < 2) return;
sel.innerHTML = '<option value="">— Choisir la destination —</option>'; document.getElementById("langues-bulk-merge-count").textContent =
checked.forEach((cb) => { checked.length;
var tr = cb.closest('tr'); var sel = document.getElementById("langues-bulk-merge-target-select");
sel.innerHTML += sel.innerHTML = '<option value="">— Choisir la destination —</option>';
'<option value="' + checked.forEach((cb) => {
cb.value + var tr = cb.closest("tr");
'">' + sel.innerHTML +=
tr.querySelector('td:nth-child(2)').textContent.trim() + '<option value="' +
'</option>'; cb.value +
}); '">' +
document.getElementById('langues-bulk-merge-dialog').showModal(); tr.querySelector("td:nth-child(2)").textContent.trim() +
"</option>";
});
document.getElementById("langues-bulk-merge-dialog").showModal();
} }
function languesExecBulkMerge() { function _languesExecBulkMerge() {
var targetId = document.getElementById('langues-bulk-merge-target-select').value; var targetId = document.getElementById(
if (!targetId) return; "langues-bulk-merge-target-select",
document.getElementById('langues-bulk-target').value = targetId; ).value;
var container = document.getElementById('langues-bulk-checkboxes'); if (!targetId) return;
container.innerHTML = ''; document.getElementById("langues-bulk-target").value = targetId;
document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach((cb) => { var container = document.getElementById("langues-bulk-checkboxes");
var inp = document.createElement('input'); container.innerHTML = "";
inp.type = 'hidden'; document
inp.name = 'selected_langs[]'; .querySelectorAll('input[name="selected_langs[]"]:checked')
inp.value = cb.value; .forEach((cb) => {
container.appendChild(inp); var inp = document.createElement("input");
}); inp.type = "hidden";
document.getElementById('langues-bulk-merge-dialog').close(); inp.name = "selected_langs[]";
document.getElementById('langues-bulk-form').submit(); inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById("langues-bulk-merge-dialog").close();
document.getElementById("langues-bulk-form").submit();
} }
document.addEventListener('htmx:afterSwap', (evt) => { document.addEventListener("htmx:afterSwap", (evt) => {
if (evt.target.id === 'langues-table-wrap') { if (evt.target.id === "langues-table-wrap") {
document document
.querySelectorAll('input[name="selected_langs[]"]') .querySelectorAll('input[name="selected_langs[]"]')
.forEach((cb) => { .forEach((cb) => {
cb.addEventListener('change', languesUpdateBulk); cb.addEventListener("change", languesUpdateBulk);
}); });
languesUpdateBulk(); languesUpdateBulk();
} }
}); });
@@ -7,111 +7,124 @@
*/ */
var _motsclesPendingForm = null; var _motsclesPendingForm = null;
function motsclesConfirmDelete(btn, name) { function _motsclesConfirmDelete(btn, name) {
_motsclesPendingForm = btn.closest('form'); _motsclesPendingForm = btn.closest("form");
document.getElementById('motscles-delete-name').textContent = name; document.getElementById("motscles-delete-name").textContent = name;
document.getElementById('motscles-delete-dialog').showModal(); document.getElementById("motscles-delete-dialog").showModal();
} }
function motsclesSubmitPending() { function _motsclesSubmitPending() {
if (_motsclesPendingForm) _motsclesPendingForm.submit(); if (_motsclesPendingForm) _motsclesPendingForm.submit();
} }
function motsclesToggleAll(src) { function _motsclesToggleAll(src) {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => { document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.checked = src.checked; cb.checked = src.checked;
}); });
motsclesUpdateBulk(); motsclesUpdateBulk();
} }
function motsclesUpdateBulk() { function motsclesUpdateBulk() {
var n = document.querySelectorAll('input[name="selected_tags[]"]:checked').length; var n = document.querySelectorAll(
document.getElementById('motscles-selected-count').textContent = n; 'input[name="selected_tags[]"]:checked',
var bar = document.getElementById('motscles-bulk-actions'); ).length;
var wrap = document.getElementById('motscles-table-wrap'); document.getElementById("motscles-selected-count").textContent = n;
var visible = n > 1; var bar = document.getElementById("motscles-bulk-actions");
bar.style.display = visible ? 'flex' : 'none'; var wrap = document.getElementById("motscles-table-wrap");
if (visible) { var visible = n > 1;
requestAnimationFrame(() => { bar.style.display = visible ? "flex" : "none";
wrap.style.setProperty('--sticky-top', bar.offsetHeight + 'px'); if (visible) {
}); requestAnimationFrame(() => {
} else { wrap.style.setProperty("--sticky-top", `${bar.offsetHeight}px`);
wrap.style.setProperty('--sticky-top', '0px'); });
} } else {
wrap.style.setProperty("--sticky-top", "0px");
}
} }
function motsclesConfirmBulkMerge() { function _motsclesConfirmBulkMerge() {
var checked = document.querySelectorAll('input[name="selected_tags[]"]:checked'); var checked = document.querySelectorAll(
if (checked.length < 2) return; 'input[name="selected_tags[]"]:checked',
document.getElementById('motscles-bulk-merge-count').textContent = checked.length; );
var sel = document.getElementById('motscles-bulk-merge-target-select'); if (checked.length < 2) return;
sel.innerHTML = '<option value="">— Choisir la destination —</option>'; document.getElementById("motscles-bulk-merge-count").textContent =
checked.forEach((cb) => { checked.length;
var tr = cb.closest('tr'); var sel = document.getElementById("motscles-bulk-merge-target-select");
sel.innerHTML += sel.innerHTML = '<option value="">— Choisir la destination —</option>';
'<option value="' + checked.forEach((cb) => {
cb.value + var tr = cb.closest("tr");
'">' + sel.innerHTML +=
tr.querySelector('td:nth-child(2)').textContent.trim() + '<option value="' +
'</option>'; cb.value +
}); '">' +
document.getElementById('motscles-bulk-merge-dialog').showModal(); tr.querySelector("td:nth-child(2)").textContent.trim() +
"</option>";
});
document.getElementById("motscles-bulk-merge-dialog").showModal();
} }
function motsclesExecBulkMerge() { function _motsclesExecBulkMerge() {
var targetId = document.getElementById('motscles-bulk-merge-target-select').value; var targetId = document.getElementById(
if (!targetId) return; "motscles-bulk-merge-target-select",
document.getElementById('motscles-bulk-target').value = targetId; ).value;
var container = document.getElementById('motscles-bulk-checkboxes'); if (!targetId) return;
container.innerHTML = ''; document.getElementById("motscles-bulk-target").value = targetId;
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach((cb) => { var container = document.getElementById("motscles-bulk-checkboxes");
var inp = document.createElement('input'); container.innerHTML = "";
inp.type = 'hidden'; document
inp.name = 'selected_tags[]'; .querySelectorAll('input[name="selected_tags[]"]:checked')
inp.value = cb.value; .forEach((cb) => {
container.appendChild(inp); var inp = document.createElement("input");
}); inp.type = "hidden";
document.getElementById('motscles-bulk-merge-dialog').close(); inp.name = "selected_tags[]";
document.getElementById('motscles-bulk-form').submit(); inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById("motscles-bulk-merge-dialog").close();
document.getElementById("motscles-bulk-form").submit();
} }
function motsclesCancelSelection() { function _motsclesCancelSelection() {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => { document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.checked = false; cb.checked = false;
}); });
motsclesUpdateBulk(); motsclesUpdateBulk();
} }
function motsclesConfirmBulkDelete() { function _motsclesConfirmBulkDelete() {
var checked = document.querySelectorAll('input[name="selected_tags[]"]:checked'); var checked = document.querySelectorAll(
if (checked.length < 1) return; 'input[name="selected_tags[]"]:checked',
document.getElementById('motscles-bulk-delete-count').textContent = checked.length; );
document.getElementById('motscles-bulk-delete-dialog').showModal(); if (checked.length < 1) return;
document.getElementById("motscles-bulk-delete-count").textContent =
checked.length;
document.getElementById("motscles-bulk-delete-dialog").showModal();
} }
function motsclesExecBulkDelete() { function _motsclesExecBulkDelete() {
var container = document.getElementById('motscles-bulk-checkboxes'); var container = document.getElementById("motscles-bulk-checkboxes");
container.innerHTML = ''; container.innerHTML = "";
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach((cb) => { document
var inp = document.createElement('input'); .querySelectorAll('input[name="selected_tags[]"]:checked')
inp.type = 'hidden'; .forEach((cb) => {
inp.name = 'selected_tags[]'; var inp = document.createElement("input");
inp.value = cb.value; inp.type = "hidden";
container.appendChild(inp); inp.name = "selected_tags[]";
}); inp.value = cb.value;
document.getElementById('motscles-bulk-form').querySelector('input[name="action"]').value = container.appendChild(inp);
'delete_bulk'; });
document.getElementById('motscles-bulk-delete-dialog').close(); document
document.getElementById('motscles-bulk-form').submit(); .getElementById("motscles-bulk-form")
.querySelector('input[name="action"]').value = "delete_bulk";
document.getElementById("motscles-bulk-delete-dialog").close();
document.getElementById("motscles-bulk-form").submit();
} }
document.addEventListener('htmx:afterSwap', (evt) => { document.addEventListener("htmx:afterSwap", (evt) => {
if (evt.target.id === 'motscles-table-wrap') { if (evt.target.id === "motscles-table-wrap") {
document document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
.querySelectorAll('input[name="selected_tags[]"]') cb.addEventListener("change", motsclesUpdateBulk);
.forEach((cb) => { });
cb.addEventListener('change', motsclesUpdateBulk); motsclesUpdateBulk();
}); }
motsclesUpdateBulk();
}
}); });
@@ -3,12 +3,12 @@
* *
* Provides: openApproveDialog, openRejectDialog. * Provides: openApproveDialog, openRejectDialog.
*/ */
function openApproveDialog(requestId) { function _openApproveDialog(requestId) {
document.getElementById('approve-request-id').value = requestId; document.getElementById("approve-request-id").value = requestId;
document.getElementById('approve-dialog').showModal(); document.getElementById("approve-dialog").showModal();
} }
function openRejectDialog(requestId) { function _openRejectDialog(requestId) {
document.getElementById('reject-request-id').value = requestId; document.getElementById("reject-request-id").value = requestId;
document.getElementById('reject-dialog').showModal(); document.getElementById("reject-dialog").showModal();
} }
+104 -99
View File
@@ -5,113 +5,118 @@
* confirmExport, confirmExportFiles, confirmDelete. * confirmExport, confirmExportFiles, confirmDelete.
*/ */
(() => { (() => {
function toggleAll(src) { function toggleAll(src) {
document.querySelectorAll('input[name="selected_theses[]"]').forEach((cb) => { document
cb.checked = src.checked; .querySelectorAll('input[name="selected_theses[]"]')
}); .forEach((cb) => {
updateBulk(); cb.checked = src.checked;
} });
updateBulk();
}
function updateBulk() { function updateBulk() {
var b = document.getElementById('bulk-actions'); var b = document.getElementById("bulk-actions");
var c = document.getElementById('selected-count'); var c = document.getElementById("selected-count");
if (!b || !c) return; // only on thesis index page if (!b || !c) return; // only on thesis index page
var n = document.querySelectorAll('input[name="selected_theses[]"]:checked').length; var n = document.querySelectorAll(
c.textContent = n; 'input[name="selected_theses[]"]:checked',
b.style.display = n > 0 ? 'flex' : 'none'; ).length;
document.getElementById('admin-table-wrap').style.setProperty( c.textContent = n;
'--sticky-top', b.style.display = n > 0 ? "flex" : "none";
n > 0 ? b.offsetHeight + 'px' : '0px' document
); .getElementById("admin-table-wrap")
} .style.setProperty("--sticky-top", n > 0 ? `${b.offsetHeight}px` : "0px");
}
function getSelectedIds() { function getSelectedIds() {
return Array.from( return Array.from(
document.querySelectorAll('input[name="selected_theses[]"]:checked') document.querySelectorAll('input[name="selected_theses[]"]:checked'),
).map((cb) => cb.value); ).map((cb) => cb.value);
} }
function confirmBulk(act) { function confirmBulk(act) {
var noSel = document.getElementById('no-selection-dialog'); var noSel = document.getElementById("no-selection-dialog");
if (!noSel) return; // only on thesis index page if (!noSel) return; // only on thesis index page
var ids = getSelectedIds(); var ids = getSelectedIds();
if (!ids.length) { if (!ids.length) {
noSel.showModal(); noSel.showModal();
return; return;
} }
var n = ids.length; var n = ids.length;
document.getElementById('bulk-action-input').value = act; document.getElementById("bulk-action-input").value = act;
if (act === 'delete') { if (act === "delete") {
document.getElementById('bulk-delete-count').textContent = n; document.getElementById("bulk-delete-count").textContent = n;
document.getElementById('bulk-delete-dialog').showModal(); document.getElementById("bulk-delete-dialog").showModal();
} else { } else {
document.getElementById('bulk-confirm-word').textContent = document.getElementById("bulk-confirm-word").textContent =
act === 'publish' ? 'Publier' : 'Dépublier'; act === "publish" ? "Publier" : "Dépublier";
document.getElementById('bulk-confirm-count').textContent = n; document.getElementById("bulk-confirm-count").textContent = n;
document.getElementById('bulk-confirm-dialog').showModal(); document.getElementById("bulk-confirm-dialog").showModal();
} }
} }
function execBulk() { function execBulk() {
var a = document.getElementById('bulk-action-input').value; var a = document.getElementById("bulk-action-input").value;
var f = document.getElementById('bulk-form'); var f = document.getElementById("bulk-form");
f.action = a === 'delete' ? 'actions/delete.php' : 'actions/publish.php'; f.action = a === "delete" ? "actions/delete.php" : "actions/publish.php";
var c = document.getElementById('bulk-checkboxes'); var c = document.getElementById("bulk-checkboxes");
c.innerHTML = ''; c.innerHTML = "";
getSelectedIds().forEach((id) => { getSelectedIds().forEach((id) => {
var inp = document.createElement('input'); var inp = document.createElement("input");
inp.type = 'hidden'; inp.type = "hidden";
inp.name = 'selected_theses[]'; inp.name = "selected_theses[]";
inp.value = id; inp.value = id;
c.appendChild(inp); c.appendChild(inp);
}); });
f.submit(); f.submit();
} }
function confirmExport() { function confirmExport() {
var ids = getSelectedIds(); var ids = getSelectedIds();
if (!ids.length) { if (!ids.length) {
document.getElementById('no-selection-dialog').showModal(); document.getElementById("no-selection-dialog").showModal();
return; return;
} }
window.location.href = '/admin/actions/export.php?csv=1&ids=' + ids.join(','); window.location.href = `/admin/actions/export.php?csv=1&ids=${ids.join(",")}`;
} }
function confirmExportFiles() { function confirmExportFiles() {
var ids = getSelectedIds(); var ids = getSelectedIds();
if (!ids.length) { if (!ids.length) {
document.getElementById('no-selection-dialog').showModal(); document.getElementById("no-selection-dialog").showModal();
return; return;
} }
window.location.href = '/admin/actions/export.php?files=1&ids=' + ids.join(','); window.location.href = `/admin/actions/export.php?files=1&ids=${ids.join(",")}`;
} }
function confirmDelete(id, title) { function confirmDelete(id, title) {
var dialog = document.getElementById('delete-thesis-dialog'); var dialog = document.getElementById("delete-thesis-dialog");
if (!dialog) return; // only on thesis index page if (!dialog) return; // only on thesis index page
document.getElementById('delete-thesis-title').textContent = title; document.getElementById("delete-thesis-title").textContent = title;
dialog.showModal(); dialog.showModal();
document.getElementById('delete-dialog-confirm').onclick = () => { document.getElementById("delete-dialog-confirm").onclick = () => {
document.getElementById('delete-form-' + id).submit(); document.getElementById(`delete-form-${id}`).submit();
}; };
} }
function reattachListeners() { function reattachListeners() {
document.querySelectorAll('input[name="selected_theses[]"]').forEach((cb) => { document
cb.addEventListener('change', updateBulk); .querySelectorAll('input[name="selected_theses[]"]')
}); .forEach((cb) => {
updateBulk(); cb.addEventListener("change", updateBulk);
} });
updateBulk();
}
document.addEventListener('DOMContentLoaded', reattachListeners); document.addEventListener("DOMContentLoaded", reattachListeners);
document.addEventListener('htmx:afterSwap', reattachListeners); document.addEventListener("htmx:afterSwap", reattachListeners);
// Export to global scope for onclick handlers in HTML // Export to global scope for onclick handlers in HTML
window.toggleAll = toggleAll; window.toggleAll = toggleAll;
window.updateBulk = updateBulk; window.updateBulk = updateBulk;
window.confirmBulk = confirmBulk; window.confirmBulk = confirmBulk;
window.execBulk = execBulk; window.execBulk = execBulk;
window.confirmExport = confirmExport; window.confirmExport = confirmExport;
window.confirmExportFiles = confirmExportFiles; window.confirmExportFiles = confirmExportFiles;
window.confirmDelete = confirmDelete; window.confirmDelete = confirmDelete;
})(); })();
+58 -53
View File
@@ -10,71 +10,76 @@
*/ */
var _pendingTagForm = null; var _pendingTagForm = null;
function tagsToggleAll(src) { function _tagsToggleAll(src) {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => { document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.checked = src.checked; cb.checked = src.checked;
}); });
tagsUpdateBulk(); tagsUpdateBulk();
} }
function tagsUpdateBulk() { function tagsUpdateBulk() {
var n = document.querySelectorAll('input[name="selected_tags[]"]:checked').length; var n = document.querySelectorAll(
document.getElementById('tags-selected-count').textContent = n; 'input[name="selected_tags[]"]:checked',
document.getElementById('tags-bulk-actions').style.display = n > 1 ? 'flex' : 'none'; ).length;
document.getElementById("tags-selected-count").textContent = n;
document.getElementById("tags-bulk-actions").style.display =
n > 1 ? "flex" : "none";
} }
function tagsConfirmBulkMerge() { function _tagsConfirmBulkMerge() {
var checked = document.querySelectorAll('input[name="selected_tags[]"]:checked'); var checked = document.querySelectorAll(
if (checked.length < 2) return; 'input[name="selected_tags[]"]:checked',
document.getElementById('bulk-merge-count').textContent = checked.length; );
var sel = document.getElementById('bulk-merge-target-select'); if (checked.length < 2) return;
sel.innerHTML = '<option value="">— Choisir la destination —</option>'; document.getElementById("bulk-merge-count").textContent = checked.length;
checked.forEach((cb) => { var sel = document.getElementById("bulk-merge-target-select");
var tr = cb.closest('tr'); sel.innerHTML = '<option value="">— Choisir la destination —</option>';
sel.innerHTML += checked.forEach((cb) => {
'<option value="' + var tr = cb.closest("tr");
cb.value + sel.innerHTML +=
'">' + '<option value="' +
tr.querySelector('.tag-name-cell').textContent.trim() + cb.value +
'</option>'; '">' +
}); tr.querySelector(".tag-name-cell").textContent.trim() +
document.getElementById('bulk-merge-dialog').showModal(); "</option>";
});
document.getElementById("bulk-merge-dialog").showModal();
} }
function tagsExecBulkMerge() { function _tagsExecBulkMerge() {
var targetId = document.getElementById('bulk-merge-target-select').value; var targetId = document.getElementById("bulk-merge-target-select").value;
if (!targetId) return; if (!targetId) return;
document.getElementById('tags-bulk-target').value = targetId; document.getElementById("tags-bulk-target").value = targetId;
var container = document.getElementById('tags-bulk-checkboxes'); var container = document.getElementById("tags-bulk-checkboxes");
container.innerHTML = ''; container.innerHTML = "";
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach((cb) => { document
var inp = document.createElement('input'); .querySelectorAll('input[name="selected_tags[]"]:checked')
inp.type = 'hidden'; .forEach((cb) => {
inp.name = 'selected_tags[]'; var inp = document.createElement("input");
inp.value = cb.value; inp.type = "hidden";
container.appendChild(inp); inp.name = "selected_tags[]";
}); inp.value = cb.value;
document.getElementById('bulk-merge-dialog').close(); container.appendChild(inp);
document.getElementById('tags-bulk-form').submit(); });
document.getElementById("bulk-merge-dialog").close();
document.getElementById("tags-bulk-form").submit();
} }
function tagsConfirmDelete(btn, name) { function _tagsConfirmDelete(btn, name) {
_pendingTagForm = btn.closest('form'); _pendingTagForm = btn.closest("form");
document.getElementById('delete-tag-name').textContent = name; document.getElementById("delete-tag-name").textContent = name;
document.getElementById('delete-tag-dialog').showModal(); document.getElementById("delete-tag-dialog").showModal();
} }
function _submitPendingTagForm() { function _submitPendingTagForm() {
if (_pendingTagForm) _pendingTagForm.submit(); if (_pendingTagForm) _pendingTagForm.submit();
} }
document.addEventListener('htmx:afterSwap', (evt) => { document.addEventListener("htmx:afterSwap", (evt) => {
if (evt.target.id === 'tags-table-wrap') { if (evt.target.id === "tags-table-wrap") {
document document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
.querySelectorAll('input[name="selected_tags[]"]') cb.addEventListener("change", tagsUpdateBulk);
.forEach((cb) => { });
cb.addEventListener('change', tagsUpdateBulk); tagsUpdateBulk();
}); }
tagsUpdateBulk();
}
}); });
+91 -90
View File
@@ -10,108 +10,109 @@
* Guarded: only runs once. * Guarded: only runs once.
*/ */
(() => { (() => {
if (window.__adminTocBuilt) return; if (window.__adminTocBuilt) return;
window.__adminTocBuilt = true; window.__adminTocBuilt = true;
function build() { function build() {
var main = document.getElementById('main-content'); var main = document.getElementById("main-content");
var toc = document.getElementById('admin-toc'); var toc = document.getElementById("admin-toc");
var list = document.getElementById('admin-toc-list'); var list = document.getElementById("admin-toc-list");
if (!main || !toc || !list) return; if (!main || !toc || !list) return;
// Guard against double population // Guard against double population
if (list.children.length > 0) return; if (list.children.length > 0) return;
var sections = main.querySelectorAll('section[aria-labelledby]'); var sections = main.querySelectorAll("section[aria-labelledby]");
if (sections.length < 2) { if (sections.length < 2) {
toc.hidden = true; toc.hidden = true;
return; return;
} }
var items = []; var items = [];
sections.forEach((sec) => { sections.forEach((sec) => {
var headingId = sec.getAttribute('aria-labelledby'); var headingId = sec.getAttribute("aria-labelledby");
var heading = document.getElementById(headingId); var heading = document.getElementById(headingId);
if (!heading) return; if (!heading) return;
if (!sec.id) sec.id = headingId; if (!sec.id) sec.id = headingId;
var a = document.createElement('a'); var a = document.createElement("a");
a.href = '#' + sec.id; a.href = `#${sec.id}`;
a.textContent = heading.textContent.trim(); a.textContent = heading.textContent.trim();
var li = document.createElement('li'); var li = document.createElement("li");
li.appendChild(a); li.appendChild(a);
list.appendChild(li); list.appendChild(li);
items.push({ section: sec, link: a }); items.push({ section: sec, link: a });
}); });
// Generate a spread of thresholds for smooth IntersectionObserver firing. // Generate a spread of thresholds for smooth IntersectionObserver firing.
function buildThresholds() { function buildThresholds() {
var t = []; var t = [];
for (var i = 0; i <= 20; i++) t.push(i / 20); for (var i = 0; i <= 20; i++) t.push(i / 20);
return t; return t;
} }
var observer = new IntersectionObserver( var observer = new IntersectionObserver(
() => { () => {
// Active section = whose top is closest to the midpoint of the // Active section = whose top is closest to the midpoint of the
// viewport from above. At page bottom, the last section wins // viewport from above. At page bottom, the last section wins
// even if its heading can't reach 50%. // even if its heading can't reach 50%.
var line = window.innerHeight * 0.5; var line = window.innerHeight * 0.5;
var best = null; var best = null;
var bestDist = Infinity; var bestDist = Infinity;
// Pass 1: sections whose top is above/at the line // Pass 1: sections whose top is above/at the line
items.forEach((item) => { items.forEach((item) => {
var top = item.section.getBoundingClientRect().top; var top = item.section.getBoundingClientRect().top;
if (top <= line && (line - top) < bestDist) { if (top <= line && line - top < bestDist) {
bestDist = line - top; bestDist = line - top;
best = item; best = item;
} }
}); });
// Pass 2: no section above the line — pick closest from below // Pass 2: no section above the line — pick closest from below
if (!best) { if (!best) {
items.forEach((item) => { items.forEach((item) => {
var top = item.section.getBoundingClientRect().top; var top = item.section.getBoundingClientRect().top;
var dist = top - line; var dist = top - line;
if (dist >= 0 && dist < bestDist) { if (dist >= 0 && dist < bestDist) {
bestDist = dist; bestDist = dist;
best = item; best = item;
} }
}); });
} }
// Pass 3: at page bottom, if the last section is visible but // Pass 3: at page bottom, if the last section is visible but
// its heading never crossed 50%, promote it anyway. // its heading never crossed 50%, promote it anyway.
if (best) { if (best) {
var lastItem = items[items.length - 1]; var lastItem = items[items.length - 1];
if (lastItem !== best) { if (lastItem !== best) {
var lastTop = lastItem.section.getBoundingClientRect().top; var lastTop = lastItem.section.getBoundingClientRect().top;
var atBottom = (window.innerHeight + window.scrollY + 5) var atBottom =
>= document.documentElement.scrollHeight; window.innerHeight + window.scrollY + 5 >=
if (atBottom && lastTop > line && lastTop < window.innerHeight) { document.documentElement.scrollHeight;
best = lastItem; if (atBottom && lastTop > line && lastTop < window.innerHeight) {
} best = lastItem;
} }
} }
}
if (!best && items.length > 0) best = items[0]; if (!best && items.length > 0) best = items[0];
items.forEach((item) => { items.forEach((item) => {
item.link.classList.toggle('toc-active', item === best); item.link.classList.toggle("toc-active", item === best);
}); });
}, },
{ threshold: buildThresholds() } { threshold: buildThresholds() },
); );
items.forEach((item) => { items.forEach((item) => {
observer.observe(item.section); observer.observe(item.section);
}); });
} }
if (document.readyState === 'loading') { if (document.readyState === "loading") {
document.addEventListener('DOMContentLoaded', build); document.addEventListener("DOMContentLoaded", build);
} else { } else {
build(); build();
} }
})(); })();
@@ -78,5 +78,3 @@ document.body.addEventListener("htmx:beforeRequest", (e) => {
status.className = "autosave-status autosave-status--saving"; status.className = "autosave-status autosave-status--saving";
} }
}); });
@@ -172,7 +172,7 @@
* Name: queue_order[<queueType>] for the pipe-separated order. * Name: queue_order[<queueType>] for the pipe-separated order.
*/ */
function syncOrderInput(queueType, pond) { function syncOrderInput(queueType, pond) {
if (!pond || !pond.element) return; if (!pond?.element) return;
var form = pond.element.closest("form"); var form = pond.element.closest("form");
if (!form) return; if (!form) return;
@@ -724,7 +724,9 @@
// Find the label for the TFE input (its parent group's <label>) // Find the label for the TFE input (its parent group's <label>)
var fieldGroup = tfeInput.closest(".admin-files-fieldgroup"); var fieldGroup = tfeInput.closest(".admin-files-fieldgroup");
var label = fieldGroup ? fieldGroup.querySelector("label[for='tfe-files-input']") : null; var label = fieldGroup
? fieldGroup.querySelector("label[for='tfe-files-input']")
: null;
if (checkedAny) { if (checkedAny) {
tfeInput.removeAttribute("required"); tfeInput.removeAttribute("required");
@@ -992,12 +994,12 @@
? data.error ? data.error
: typeof data === "string" : typeof data === "string"
? data ? data
: "Erreur " + status; : `Erreur ${status}`;
if (bodyEl) if (bodyEl)
bodyEl.innerHTML = '<p class="file-browser-error">Erreur : ' + msg + '</p>'; bodyEl.innerHTML = `<p class="file-browser-error">Erreur : ${msg}</p>`;
return; return;
} }
console.log("[pt-relink] success | new_id=" + data.id); console.log(`[pt-relink] success | new_id=${data.id}`);
var input = document.querySelector( var input = document.querySelector(
'.tfe-file-picker[data-queue-type="tfe"]', '.tfe-file-picker[data-queue-type="tfe"]',
@@ -1008,8 +1010,7 @@
var block = document.getElementById("format-fichiers-block"); var block = document.getElementById("format-fichiers-block");
if (block && window.htmx) { if (block && window.htmx) {
var url = "/admin/fragments/fichiers.php"; var url = "/admin/fragments/fichiers.php";
if (thesisId) if (thesisId) url += `?_thesis_id=${encodeURIComponent(thesisId)}`;
url += "?_thesis_id=" + encodeURIComponent(thesisId);
htmx.ajax("GET", url, { htmx.ajax("GET", url, {
target: "#format-fichiers-block", target: "#format-fichiers-block",
swap: "outerHTML", swap: "outerHTML",
@@ -1050,8 +1051,7 @@
.catch((err) => { .catch((err) => {
console.error("[pt-relink] fetch error", err); console.error("[pt-relink] fetch error", err);
if (bodyEl) if (bodyEl)
bodyEl.innerHTML = bodyEl.innerHTML = '<p class="file-browser-error">Erreur réseau.</p>';
'<p class="file-browser-error">Erreur réseau.</p>';
}); });
}; };
})(); })();
@@ -5,22 +5,22 @@
* receives a single integer (total minutes). * receives a single integer (total minutes).
*/ */
(() => { (() => {
var form = document.querySelector('form.admin-form'); var form = document.querySelector("form.admin-form");
if (!form) return; if (!form) return;
form.addEventListener('submit', function() { form.addEventListener("submit", () => {
var h = parseInt(document.getElementById('duration_h')?.value, 10) || 0; var h = parseInt(document.getElementById("duration_h")?.value, 10) || 0;
var m = parseInt(document.getElementById('duration_m')?.value, 10) || 0; var m = parseInt(document.getElementById("duration_m")?.value, 10) || 0;
var total = h * 60 + m; var total = h * 60 + m;
var hidden = document.getElementById('duration_minutes_hidden'); var hidden = document.getElementById("duration_minutes_hidden");
if (!hidden) { if (!hidden) {
hidden = document.createElement('input'); hidden = document.createElement("input");
hidden.type = 'hidden'; hidden.type = "hidden";
hidden.name = 'duration_minutes'; hidden.name = "duration_minutes";
hidden.id = 'duration_minutes_hidden'; hidden.id = "duration_minutes_hidden";
form.appendChild(hidden); form.appendChild(hidden);
} }
hidden.value = total > 0 ? String(total) : ''; hidden.value = total > 0 ? String(total) : "";
}); });
})(); })();
+58 -52
View File
@@ -5,62 +5,68 @@
* Reads `data-admin-mode` (0/1) from the jury <fieldset>. * Reads `data-admin-mode` (0/1) from the jury <fieldset>.
*/ */
(() => { (() => {
// ── Dynamic row add/remove ────────────────────────────────────────────── // ── Dynamic row add/remove ──────────────────────────────────────────────
window.addJuryRow = (listId, inputName, roleLabel) => { window.addJuryRow = (listId, inputName, roleLabel) => {
var list = document.getElementById(listId); var list = document.getElementById(listId);
if (!list) return; if (!list) return;
var n = list.querySelectorAll('.admin-jury-entry').length + 1; var n = list.querySelectorAll(".admin-jury-entry").length + 1;
var div = document.createElement('div'); var div = document.createElement("div");
div.className = 'admin-jury-entry'; div.className = "admin-jury-entry";
div.innerHTML = div.innerHTML =
'<input type="text" name="' + '<input type="text" name="' +
inputName + inputName +
'" placeholder="Nom" autocomplete="off" aria-label="' + '" placeholder="Nom" autocomplete="off" aria-label="' +
roleLabel + roleLabel +
' ' + " " +
n + n +
' \u2014 nom">' + ' \u2014 nom">' +
'<button type="button" class="btn btn--sm btn--ghost admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer">' + '<button type="button" class="btn btn--sm btn--ghost admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer">' +
'<span aria-hidden="true">\u2715</span></button>'; '<span aria-hidden="true">\u2715</span></button>';
list.appendChild(div); list.appendChild(div);
}; };
window.removeJuryRow = (btn) => { window.removeJuryRow = (btn) => {
var entry = btn.closest('.admin-jury-entry'); var entry = btn.closest(".admin-jury-entry");
if (entry) entry.remove(); if (entry) entry.remove();
}; };
// ── ULB toggle ───────────────────────────────────────────────────────── // ── ULB toggle ─────────────────────────────────────────────────────────
var juryFieldset = document.querySelector('fieldset[data-admin-mode]'); var juryFieldset = document.querySelector("fieldset[data-admin-mode]");
var adminMode = juryFieldset ? juryFieldset.getAttribute('data-admin-mode') === '1' : false; var adminMode = juryFieldset
? juryFieldset.getAttribute("data-admin-mode") === "1"
: false;
try { try {
var finalitySelect = document.querySelector('select[name="finality"]'); var finalitySelect = document.querySelector('select[name="finality"]');
var ulbRow = document.getElementById('jury-promoteur-ulb-row'); var ulbRow = document.getElementById("jury-promoteur-ulb-row");
if (!finalitySelect || !ulbRow) return; if (!finalitySelect || !ulbRow) return;
var ulbAsterisk = document.getElementById('jury-ulb-asterisk'); var ulbAsterisk = document.getElementById("jury-ulb-asterisk");
function isApprofondiSelected() { function isApprofondiSelected() {
var opt = finalitySelect.options[finalitySelect.selectedIndex]; var opt = finalitySelect.options[finalitySelect.selectedIndex];
if (!opt) return false; if (!opt) return false;
return (opt.textContent || opt.text || '').toLowerCase().includes('approfondi'); return (opt.textContent || opt.text || "")
} .toLowerCase()
.includes("approfondi");
}
function toggleUlb() { function toggleUlb() {
var show = isApprofondiSelected(); var show = isApprofondiSelected();
ulbRow.style.display = show ? '' : 'none'; ulbRow.style.display = show ? "" : "none";
if (ulbAsterisk) ulbAsterisk.style.display = show ? '' : 'none'; if (ulbAsterisk) ulbAsterisk.style.display = show ? "" : "none";
var inputs = ulbRow.querySelectorAll('input[name="jury_promoteur_ulb_name[]"]'); var inputs = ulbRow.querySelectorAll(
inputs.forEach((inp, idx) => { 'input[name="jury_promoteur_ulb_name[]"]',
inp.required = adminMode ? false : show && idx === 0; );
inp.disabled = !show; inputs.forEach((inp, idx) => {
if (!show) inp.value = ''; inp.required = adminMode ? false : show && idx === 0;
}); inp.disabled = !show;
} if (!show) inp.value = "";
});
}
finalitySelect.addEventListener('change', toggleUlb); finalitySelect.addEventListener("change", toggleUlb);
toggleUlb(); toggleUlb();
} catch (e) { } catch (e) {
console.error('jury ULB toggle:', e); console.error("jury ULB toggle:", e);
} }
})(); })();
@@ -5,28 +5,28 @@
* Reads the container id from data-search-container-id on the pill-search div. * Reads the container id from data-search-container-id on the pill-search div.
*/ */
(() => { (() => {
var pillDiv = document.querySelector('[data-search-container-id]'); var pillDiv = document.querySelector("[data-search-container-id]");
if (!pillDiv) return; if (!pillDiv) return;
var containerId = pillDiv.getAttribute('data-search-container-id'); var containerId = pillDiv.getAttribute("data-search-container-id");
var container = document.getElementById(containerId); var container = document.getElementById(containerId);
if (!container) return; if (!container) return;
var pills = container.querySelector('.tag-search-pills'); var pills = container.querySelector(".tag-search-pills");
if (!pills) return; if (!pills) return;
function check() { function check() {
var asteriskEl = document.getElementById('languages-required-asterisk'); var asteriskEl = document.getElementById("languages-required-asterisk");
if (!asteriskEl) return; if (!asteriskEl) return;
var n = pills.querySelectorAll('.tag-pill').length; var n = pills.querySelectorAll(".tag-pill").length;
var checkboxes = document.querySelectorAll( var checkboxes = document.querySelectorAll(
'#languages-fieldset input[type="checkbox"]:checked' '#languages-fieldset input[type="checkbox"]:checked',
); );
asteriskEl.innerHTML = asteriskEl.innerHTML =
n === 0 && checkboxes.length === 0 n === 0 && checkboxes.length === 0
? ' <span class="asterisk">*</span>' ? ' <span class="asterisk">*</span>'
: ''; : "";
} }
var observer = new MutationObserver(check); var observer = new MutationObserver(check);
observer.observe(pills, { childList: true }); observer.observe(pills, { childList: true });
check(); check();
})(); })();
+29 -26
View File
@@ -8,32 +8,35 @@
* Loaded on all admin pages via footer.php. * Loaded on all admin pages via footer.php.
*/ */
(() => { (() => {
// Toast accessibility — auto-focus warning toasts // Toast accessibility — auto-focus warning toasts
document.body.addEventListener('htmx:afterSettle', (e) => { document.body.addEventListener("htmx:afterSettle", (e) => {
if (e.target && e.target.id === 'toast-region') { if (e.target && e.target.id === "toast-region") {
var warn = e.target.querySelector('.toast--warning'); var warn = e.target.querySelector(".toast--warning");
if (warn) { if (warn) {
warn.setAttribute('tabindex', '-1'); warn.setAttribute("tabindex", "-1");
warn.focus(); warn.focus();
} }
} }
}); });
// Markdown cheatsheet: remove stale dialogs before a new one arrives // Markdown cheatsheet: remove stale dialogs before a new one arrives
document.body.addEventListener('htmx:beforeRequest', (e) => { document.body.addEventListener("htmx:beforeRequest", (e) => {
if ( if (
e.detail.requestConfig && e.detail.requestConfig &&
e.detail.requestConfig.path === '/admin/markdown-cheatsheet-fragment.php' e.detail.requestConfig.path === "/admin/markdown-cheatsheet-fragment.php"
) { ) {
var old = document.getElementById('md-cheatsheet-dialog'); var old = document.getElementById("md-cheatsheet-dialog");
if (old) old.remove(); if (old) old.remove();
} }
}); });
// Markdown cheatsheet: close on backdrop (dialog element) click // Markdown cheatsheet: close on backdrop (dialog element) click
document.body.addEventListener('click', (e) => { document.body.addEventListener("click", (e) => {
if (e.target.tagName === 'DIALOG' && e.target.id === 'md-cheatsheet-dialog') { if (
e.target.close(); e.target.tagName === "DIALOG" &&
} e.target.id === "md-cheatsheet-dialog"
}); ) {
e.target.close();
}
});
})(); })();
@@ -5,85 +5,79 @@
* On desktop, all panels close when crossing the breakpoint. * On desktop, all panels close when crossing the breakpoint.
*/ */
(() => { (() => {
var INDEX_SEL = '#repertoire-index'; var INDEX_SEL = "#repertoire-index";
var ACCORDION_SEL = '.rep-accordion'; var ACCORDION_SEL = ".rep-accordion";
var TOGGLE_SEL = '.rep-accordion__toggle'; var TOGGLE_SEL = ".rep-accordion__toggle";
var PANEL_SEL = '.rep-accordion__panel'; var PANEL_SEL = ".rep-accordion__panel";
function isMobile() { function isMobile() {
return window.matchMedia('(max-width: 1025px)').matches; return window.matchMedia("(max-width: 1025px)").matches;
} }
function initAccordions(root) { function initAccordions(root) {
if (!isMobile()) return; if (!isMobile()) return;
var toggles = root.querySelectorAll(TOGGLE_SEL); var toggles = root.querySelectorAll(TOGGLE_SEL);
toggles.forEach((btn) => { toggles.forEach((btn) => {
// Skip students column — always visible, not an accordion // Skip students column — always visible, not an accordion
if (btn.closest('[data-col="students"]')) return; if (btn.closest('[data-col="students"]')) return;
if (btn._accordionBound) return; if (btn._accordionBound) return;
btn._accordionBound = true; btn._accordionBound = true;
btn.addEventListener('click', () => { btn.addEventListener("click", () => {
var section = btn.closest(ACCORDION_SEL); var section = btn.closest(ACCORDION_SEL);
var panel = section.querySelector(PANEL_SEL); var panel = section.querySelector(PANEL_SEL);
var isOpen = btn.getAttribute('aria-expanded') === 'true'; var isOpen = btn.getAttribute("aria-expanded") === "true";
// Close all others (except students column) // Close all others (except students column)
root.querySelectorAll(ACCORDION_SEL).forEach((s) => { root.querySelectorAll(ACCORDION_SEL).forEach((s) => {
if (s.dataset.col === 'students') return; if (s.dataset.col === "students") return;
var p = s.querySelector(PANEL_SEL); var p = s.querySelector(PANEL_SEL);
var t = s.querySelector(TOGGLE_SEL); var t = s.querySelector(TOGGLE_SEL);
if (s !== section) { if (s !== section) {
t.setAttribute('aria-expanded', 'false'); t.setAttribute("aria-expanded", "false");
p.classList.remove('is-open'); p.classList.remove("is-open");
} }
}); });
// Toggle this one // Toggle this one
var nowOpen = !isOpen; var nowOpen = !isOpen;
btn.setAttribute('aria-expanded', nowOpen ? 'true' : 'false'); btn.setAttribute("aria-expanded", nowOpen ? "true" : "false");
if (nowOpen) { if (nowOpen) {
panel.classList.add('is-open'); panel.classList.add("is-open");
} else { } else {
panel.classList.remove('is-open'); panel.classList.remove("is-open");
} }
}); });
}); });
} }
// Initial bind // Initial bind
initAccordions(document); initAccordions(document);
// Re-bind after HTMX swaps (use live DOM since e.detail.target may be detached) // Re-bind after HTMX swaps (use live DOM since e.detail.target may be detached)
document.body.addEventListener('htmx:afterSwap', (e) => { document.body.addEventListener("htmx:afterSwap", (e) => {
if ( if (e.detail.target?.matches?.(INDEX_SEL)) {
e.detail.target && var liveIndex = document.querySelector(INDEX_SEL);
e.detail.target.matches && if (liveIndex) initAccordions(liveIndex);
e.detail.target.matches(INDEX_SEL) }
) { });
var liveIndex = document.querySelector(INDEX_SEL);
if (liveIndex) initAccordions(liveIndex);
}
});
// Re-bind on resize crossing the breakpoint // Re-bind on resize crossing the breakpoint
var wasMobile = isMobile(); var wasMobile = isMobile();
window.addEventListener('resize', () => { window.addEventListener("resize", () => {
var nowMobile = isMobile(); var nowMobile = isMobile();
if (nowMobile !== wasMobile) { if (nowMobile !== wasMobile) {
wasMobile = nowMobile; wasMobile = nowMobile;
if (!nowMobile) { if (!nowMobile) {
// Switching to desktop — close all panels // Switching to desktop — close all panels
document document
.querySelectorAll(INDEX_SEL + ' ' + TOGGLE_SEL) .querySelectorAll(`${INDEX_SEL} ${TOGGLE_SEL}`)
.forEach((btn) => { .forEach((btn) => {
btn.setAttribute('aria-expanded', 'false'); btn.setAttribute("aria-expanded", "false");
}); });
document document.querySelectorAll(`${INDEX_SEL} ${PANEL_SEL}`).forEach((p) => {
.querySelectorAll(INDEX_SEL + ' ' + PANEL_SEL) p.classList.remove("is-open");
.forEach((p) => { });
p.classList.remove('is-open'); }
}); }
} });
}
});
})(); })();
@@ -8,63 +8,55 @@
* DOM replacement. * DOM replacement.
*/ */
(() => { (() => {
var INDEX_SEL = '#repertoire-index'; var INDEX_SEL = "#repertoire-index";
var scrollSnapshots = {}; var scrollSnapshots = {};
function snapshot() { function snapshot() {
var index = document.querySelector(INDEX_SEL); var index = document.querySelector(INDEX_SEL);
if (!index) return; if (!index) return;
scrollSnapshots = {}; scrollSnapshots = {};
index.querySelectorAll('.repertoire-col[data-col] > ul').forEach((ul) => { index.querySelectorAll(".repertoire-col[data-col] > ul").forEach((ul) => {
var col = ul.closest('.repertoire-col'); var col = ul.closest(".repertoire-col");
if (!col) return; if (!col) return;
var key = col.getAttribute('data-col'); var key = col.getAttribute("data-col");
scrollSnapshots[key] = ul.scrollTop; scrollSnapshots[key] = ul.scrollTop;
}); });
} }
function restore() { function restore() {
var index = document.querySelector(INDEX_SEL); var index = document.querySelector(INDEX_SEL);
if (!index) return; if (!index) return;
index.querySelectorAll('.repertoire-col[data-col] > ul').forEach((ul) => { index.querySelectorAll(".repertoire-col[data-col] > ul").forEach((ul) => {
var col = ul.closest('.repertoire-col'); var col = ul.closest(".repertoire-col");
if (!col) return; if (!col) return;
var key = col.getAttribute('data-col'); var key = col.getAttribute("data-col");
if (scrollSnapshots[key] !== undefined) { if (scrollSnapshots[key] !== undefined) {
ul.scrollTop = scrollSnapshots[key]; ul.scrollTop = scrollSnapshots[key];
} }
}); });
} }
// Capture scroll positions before the outgoing element is replaced // Capture scroll positions before the outgoing element is replaced
document.body.addEventListener('htmx:beforeSwap', (e) => { document.body.addEventListener("htmx:beforeSwap", (e) => {
if ( if (e.detail.target?.matches?.(INDEX_SEL)) {
e.detail.target && snapshot();
e.detail.target.matches && }
e.detail.target.matches(INDEX_SEL) });
) {
snapshot();
}
});
// Restore after the new content is in the DOM // Restore after the new content is in the DOM
document.body.addEventListener('htmx:afterSwap', (e) => { document.body.addEventListener("htmx:afterSwap", (e) => {
if ( if (e.detail.target?.matches?.(INDEX_SEL)) {
e.detail.target && // Use requestAnimationFrame to ensure layout has settled
e.detail.target.matches && requestAnimationFrame(() => {
e.detail.target.matches(INDEX_SEL) restore();
) { });
// Use requestAnimationFrame to ensure layout has settled }
requestAnimationFrame(() => { });
restore();
});
}
});
// Also restore on history navigation (browser back/forward) // Also restore on history navigation (browser back/forward)
document.body.addEventListener('htmx:historyRestore', () => { document.body.addEventListener("htmx:historyRestore", () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
restore(); restore();
}); });
}); });
})(); })();
@@ -5,53 +5,53 @@
* with `data-student-name` attribute. * with `data-student-name` attribute.
*/ */
(() => { (() => {
var popover = document.getElementById('student-popover'); var popover = document.getElementById("student-popover");
var currentAnchor = null; var currentAnchor = null;
function position(anchor) { function position(anchor) {
var r = anchor.getBoundingClientRect(); var r = anchor.getBoundingClientRect();
var left = r.right + window.scrollX + 12; var left = r.right + window.scrollX + 12;
var top = r.top + window.scrollY; var top = r.top + window.scrollY;
if (left + 300 > window.innerWidth + window.scrollX) { if (left + 300 > window.innerWidth + window.scrollX) {
left = r.left + window.scrollX - 312; left = r.left + window.scrollX - 312;
} }
popover.style.left = left + 'px'; popover.style.left = `${left}px`;
popover.style.top = top + 'px'; popover.style.top = `${top}px`;
} }
document.body.addEventListener( document.body.addEventListener(
'mouseenter', "mouseenter",
(e) => { (e) => {
var a = e.target.closest('[data-student-name]'); var a = e.target.closest("[data-student-name]");
if (!a) return; if (!a) return;
currentAnchor = a; currentAnchor = a;
}, },
true true,
); );
document.body.addEventListener('htmx:afterSwap', (e) => { document.body.addEventListener("htmx:afterSwap", (e) => {
if (e.detail.target !== popover) return; if (e.detail.target !== popover) return;
if (currentAnchor) position(currentAnchor); if (currentAnchor) position(currentAnchor);
popover.hidden = false; popover.hidden = false;
}); });
document.body.addEventListener( document.body.addEventListener(
'mouseleave', "mouseleave",
(e) => { (e) => {
if ( if (
!e.target.closest('[data-student-name]') && !e.target.closest("[data-student-name]") &&
!e.target.closest('#student-popover') !e.target.closest("#student-popover")
) )
return; return;
setTimeout(() => { setTimeout(() => {
if ( if (
!document.querySelector('[data-student-name]:hover') && !document.querySelector("[data-student-name]:hover") &&
!document.querySelector('#student-popover:hover') !document.querySelector("#student-popover:hover")
) { ) {
popover.hidden = true; popover.hidden = true;
} }
}, 120); }, 120);
}, },
true true,
); );
})(); })();
@@ -4,42 +4,45 @@
* Operates on #sidebar-links-form and #sidebar-link-tpl. * Operates on #sidebar-links-form and #sidebar-link-tpl.
*/ */
(() => { (() => {
var form = document.getElementById('sidebar-links-form'); var form = document.getElementById("sidebar-links-form");
var tpl = document.getElementById('sidebar-link-tpl'); var tpl = document.getElementById("sidebar-link-tpl");
if (!form || !tpl) return; if (!form || !tpl) return;
var tplHtml = tpl.innerHTML; var tplHtml = tpl.innerHTML;
function reindexLinks() { function reindexLinks() {
var rows = form.querySelectorAll('.sidebar-link-row'); var rows = form.querySelectorAll(".sidebar-link-row");
rows.forEach((row, i) => { rows.forEach((row, i) => {
row.querySelectorAll('input').forEach((inp) => { row.querySelectorAll("input").forEach((inp) => {
if (inp.name) { if (inp.name) {
inp.name = inp.name.replace(/links\[\d+\]/, 'links[' + i + ']'); inp.name = inp.name.replace(/links\[\d+\]/, `links[${i}]`);
} }
if (inp.id) { if (inp.id) {
inp.id = inp.id.replace(/sl_\d+/, 'sl_' + i); inp.id = inp.id.replace(/sl_\d+/, `sl_${i}`);
} }
}); });
row.querySelectorAll('label[for]').forEach((lbl) => { row.querySelectorAll("label[for]").forEach((lbl) => {
lbl.setAttribute('for', lbl.getAttribute('for').replace(/sl_\d+/, 'sl_' + i)); lbl.setAttribute(
}); "for",
}); lbl.getAttribute("for").replace(/sl_\d+/, `sl_${i}`),
} );
});
});
}
// Event delegation for remove buttons // Event delegation for remove buttons
form.addEventListener('click', (e) => { form.addEventListener("click", (e) => {
if (!e.target.closest('.remove-sidebar-link-btn')) return; if (!e.target.closest(".remove-sidebar-link-btn")) return;
e.preventDefault(); e.preventDefault();
e.target.closest('.sidebar-link-row').remove(); e.target.closest(".sidebar-link-row").remove();
reindexLinks(); reindexLinks();
}); });
// Add button // Add button
var addBtn = document.getElementById('add-sidebar-link-btn'); var addBtn = document.getElementById("add-sidebar-link-btn");
addBtn.addEventListener('click', function () { addBtn.addEventListener("click", function () {
var rows = form.querySelectorAll('.sidebar-link-row'); var rows = form.querySelectorAll(".sidebar-link-row");
var idx = rows.length; var idx = rows.length;
var html = tplHtml.split('{{li}}').join(idx); var html = tplHtml.split("{{li}}").join(idx);
this.insertAdjacentHTML('beforebegin', html); this.insertAdjacentHTML("beforebegin", html);
}); });
})(); })();
+7 -7
View File
@@ -3,11 +3,11 @@
* error. Reads the field id from data-smtp-error-field on the SMTP form. * error. Reads the field id from data-smtp-error-field on the SMTP form.
*/ */
(() => { (() => {
var form = document.querySelector('form[data-smtp-error-field]'); var form = document.querySelector("form[data-smtp-error-field]");
if (!form) return; if (!form) return;
var fieldId = form.getAttribute('data-smtp-error-field'); var fieldId = form.getAttribute("data-smtp-error-field");
var el = fieldId ? document.getElementById(fieldId) : null; var el = fieldId ? document.getElementById(fieldId) : null;
if (!el) return; if (!el) return;
el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.scrollIntoView({ behavior: "smooth", block: "center" });
el.focus(); el.focus();
})(); })();
-3
View File
@@ -47,9 +47,6 @@ class FormBootstrap
if (is_array($formData[$key])) { if (is_array($formData[$key])) {
return $formData[$key]; return $formData[$key];
} }
if ($formData[$key] === null) {
return $default;
}
return htmlspecialchars((string)$formData[$key]); return htmlspecialchars((string)$formData[$key]);
}; };
} }
+7 -1
View File
@@ -24,7 +24,13 @@
"linter": { "linter": {
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true "recommended": true,
"style": {
"noDescendingSpecificity": "off"
},
"correctness": {
"noInnerDeclarations": "off"
}
} }
}, },
"formatter": { "formatter": {
+1 -1
View File
@@ -470,7 +470,7 @@ lint:
[group('test')] [group('test')]
fix: fix:
# Auto-fix: biome (JS/CSS formatting + lint) + php-cs-fixer (PHP coding standards) # Auto-fix: biome (JS/CSS formatting + lint) + php-cs-fixer (PHP coding standards)
@npx biome check --write app/public/assets/css/ app/public/assets/js/app/ scripts/ @npx biome check --write --unsafe app/public/assets/css/ app/public/assets/js/app/ scripts/
@vendor/bin/php-cs-fixer fix --no-interaction @vendor/bin/php-cs-fixer fix --no-interaction
# ============================================================================ # ============================================================================
+12
View File
@@ -6,6 +6,18 @@ parameters:
count: 1 count: 1
path: app/src/Controllers/SearchController.php path: app/src/Controllers/SearchController.php
-
message: '#^Offset ''duration_minutes'' on array\{\}\|array\{license_id\: int\|null, access_type_id\: int\|null, context_note\: string, contact_visible\: int\|null\} on left side of \?\? does not exist\.$#'
identifier: nullCoalesce.offset
count: 1
path: app/src/Controllers/ThesisEditController.php
-
message: '#^Offset ''duration_pages'' on array\{\}\|array\{license_id\: int\|null, access_type_id\: int\|null, context_note\: string, contact_visible\: int\|null\} on left side of \?\? does not exist\.$#'
identifier: nullCoalesce.offset
count: 1
path: app/src/Controllers/ThesisEditController.php
- -
message: '#^Property Dispatcher\:\:\$queryParams is never read, only written\.$#' message: '#^Property Dispatcher\:\:\$queryParams is never read, only written\.$#'
identifier: property.onlyWritten identifier: property.onlyWritten
+4 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Build CSS bundles with Lightning CSS. * Build CSS bundles with Lightning CSS.
* *
@@ -13,10 +14,10 @@
* Output: app/public/assets/dist/ * Output: app/public/assets/dist/
*/ */
import { bundleAsync } from "lightningcss"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { dirname, resolve } from "node:path";
import { resolve, dirname, basename } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { bundleAsync } from "lightningcss";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, ".."); const root = resolve(__dirname, "..");
+4 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Build JS bundles: one self-contained file per entry point. * Build JS bundles: one self-contained file per entry point.
* *
@@ -6,10 +7,10 @@
* Output: app/public/assets/dist/{admin,public,form,partage}.min.js * Output: app/public/assets/dist/{admin,public,form,partage}.min.js
*/ */
import { rolldown } from "rolldown"; import { mkdirSync, writeFileSync } from "node:fs";
import { resolve, dirname } from "node:path"; import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { writeFileSync, mkdirSync } from "node:fs"; import { rolldown } from "rolldown";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, ".."); const root = resolve(__dirname, "..");
+1 -1
View File
@@ -12,7 +12,7 @@
import { execSync } from "node:child_process"; import { execSync } from "node:child_process";
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import { resolve, dirname } from "node:path"; import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
+2 -2
View File
@@ -6,8 +6,8 @@
* Staleness: any source file newer than the oldest dist file. * Staleness: any source file newer than the oldest dist file.
*/ */
import { statSync, existsSync, readdirSync } from "node:fs"; import { existsSync, readdirSync, statSync } from "node:fs";
import { resolve, dirname } from "node:path"; import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));