commit ccf32dcece365471834128934f2edb56064c84eb Author: Valentin Le Moign Date: Tue May 12 23:33:46 2026 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f271996 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# --- Dépendances Composer --- +vendor/ + +# --- Artefacts SASS --- +.sass-cache/ +*.css.map + +# --- Node --- +node_modules/ + +# --- OS / éditeur --- +.DS_Store +Thumbs.db +*.swp +*~ +.idea/ +.vscode/ diff --git a/404.php b/404.php new file mode 100644 index 0000000..2563540 --- /dev/null +++ b/404.php @@ -0,0 +1,3 @@ + true` sur les args de `WP_Query`. Filtre de plage : `'thalim_event_date_filter' => ['from' => …, 'to' => …]`. + +### Admin très customisée +- Onglets FR/EN sur l'éditeur de corps de post (`body` natif WP + `body_en` Pods) +- Renommage « Article » → « Annonce » dans toute l'UI +- Visibilité conditionnelle des metaboxes Pods + groupement des axes thématiques par période +- Restauration automatique des champs en cas d'erreur de validation Pods (transient + JS) +- Restrictions des contributeurs (édition limitée aux posts où ils figurent comme membre) +- Dashboard et menu Outils masqués pour les non-admins +- Voir `CLAUDE.md` côté stack pour le détail + +### AJAX et infinite scroll +Les pages d'archives utilisent un système d'infinite scroll AJAX (`wp_ajax_load_more_posts`) avec recherche Relevanssi, filtres par axe / date / taxonomie / catégorie, et override de langue côté serveur. + +## Compilation des styles + +Les fichiers SCSS dans `scss/` sont compilés **manuellement** vers `css/`. + +## Structure + +``` +. +├── functions.php # setup, i18n, contexte Twig, AJAX, filtres de requête, mods admin (≈1400 lignes) +├── index.php, single.php … # templates PHP qui chargent les Twig correspondants +├── templates/ # templates Twig (base.twig = layout, autres l'étendent) +│ └── partials/ # header, footer, post-card, agenda-card, search-panel… +├── scss/ → css/ # sources SASS → CSS compilé (commité) +├── js/ # scripts frontend + adminDashboardMods.js +├── inc/ # helpers PHP par contexte : +│ ├── single-helpers.php # résolution champs Pods d'un post +│ ├── author-helpers.php # profil membre + posts liés +│ ├── membres-helpers.php # page /membres (groupes par rôle) +│ ├── post-card-helpers.php # données pour les cards +│ ├── pods-conditional-required.php # patch validation Pods +│ ├── pods-save-error-handler.php # restauration des champs en cas d'erreur +│ ├── post-title-required.php # titre obligatoire +│ └── admin-users-filter.php # filtre Statut sur /wp-admin/users.php +├── assets/ # fonts, images, logo-shapes (SVG) +├── composer.json # dépendance : timber/timber ^2.3 +└── vendor/ # Composer (gitignoré, à reconstruire après clone) +``` + +## Architecture détaillée + +Voir le fichier `CLAUDE.md` à la racine du repo [`thalim-stack`](https://figureslibres.io/valentin_le_moign/thalim-stack) pour la documentation exhaustive (conventions Pods, customisations admin, restrictions de contenu, helpers, rewrite rules, etc.). diff --git a/assets/fonts/Gelasio-Regular.woff2 b/assets/fonts/Gelasio-Regular.woff2 new file mode 100644 index 0000000..e0e8bbe Binary files /dev/null and b/assets/fonts/Gelasio-Regular.woff2 differ diff --git a/assets/fonts/NewsCycle-Regular.woff2 b/assets/fonts/NewsCycle-Regular.woff2 new file mode 100644 index 0000000..ba6713c Binary files /dev/null and b/assets/fonts/NewsCycle-Regular.woff2 differ diff --git a/assets/images/cnrs.png b/assets/images/cnrs.png new file mode 100644 index 0000000..96209ab Binary files /dev/null and b/assets/images/cnrs.png differ diff --git a/assets/images/ens.png b/assets/images/ens.png new file mode 100644 index 0000000..eb16a20 Binary files /dev/null and b/assets/images/ens.png differ diff --git a/assets/images/sorbonne.png b/assets/images/sorbonne.png new file mode 100644 index 0000000..8b8d2d3 Binary files /dev/null and b/assets/images/sorbonne.png differ diff --git a/assets/images/thalim-logo.png b/assets/images/thalim-logo.png new file mode 100644 index 0000000..4249d77 Binary files /dev/null and b/assets/images/thalim-logo.png differ diff --git a/assets/images/thalim-logo.svg b/assets/images/thalim-logo.svg new file mode 100644 index 0000000..3884da5 --- /dev/null +++ b/assets/images/thalim-logo.svg @@ -0,0 +1,87 @@ + + + + diff --git a/assets/logo-shapes/fillshape.svg b/assets/logo-shapes/fillshape.svg new file mode 100644 index 0000000..c3d78c7 --- /dev/null +++ b/assets/logo-shapes/fillshape.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/logo-shapes/shape1.svg b/assets/logo-shapes/shape1.svg new file mode 100644 index 0000000..8aea71b --- /dev/null +++ b/assets/logo-shapes/shape1.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/logo-shapes/shape2.svg b/assets/logo-shapes/shape2.svg new file mode 100644 index 0000000..ef961b5 --- /dev/null +++ b/assets/logo-shapes/shape2.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/logo-shapes/shape3.svg b/assets/logo-shapes/shape3.svg new file mode 100644 index 0000000..bd1559d --- /dev/null +++ b/assets/logo-shapes/shape3.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/logo-shapes/shape4.svg b/assets/logo-shapes/shape4.svg new file mode 100644 index 0000000..635a6ff --- /dev/null +++ b/assets/logo-shapes/shape4.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/logo-shapes/shape5.svg b/assets/logo-shapes/shape5.svg new file mode 100644 index 0000000..b058c9b --- /dev/null +++ b/assets/logo-shapes/shape5.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/author.php b/author.php new file mode 100644 index 0000000..db748b5 --- /dev/null +++ b/author.php @@ -0,0 +1,16 @@ +set_404(); + status_header(404); + return; +} + +$context['author'] = thalim_get_author_data($user->ID); +$context['author_posts'] = thalim_get_author_posts_by_category($user->ID); +$context['author_edit_link'] = current_user_can('edit_user', $user->ID) ? get_edit_user_link($user->ID) : ''; + +Timber::render('author.twig', $context); diff --git a/category.php b/category.php new file mode 100644 index 0000000..152c61d --- /dev/null +++ b/category.php @@ -0,0 +1,272 @@ +parent) { + $parent_cat = get_category($category->parent); + $context['parent_slug'] = $parent_cat->slug; + $context['active_rubrique'] = $parent_cat->term_id; +} else { + $context['parent_slug'] = $category->slug; + $context['active_rubrique'] = $category->term_id; +} + +// Read filter query params +$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0; +$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : ''; +$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : ''; + +$context['active_axe'] = $active_axe; +$context['active_date_from'] = $active_date_from; +$context['active_date_to'] = $active_date_to; + +// Build query param string to preserve across filter links +$filter_query = http_build_query(array_filter([ + 'axe' => $active_axe ?: null, + 'date_from' => $active_date_from ?: null, + 'date_to' => $active_date_to ?: null, +])); +$context['filter_query'] = $filter_query; + +// Build extra query args for axe/date filtering +$extra_query_args = []; +if ($active_axe) { + $extra_query_args['meta_query'] = [[ + 'key' => 'axes_thematiques', + 'value' => $active_axe, + 'type' => 'NUMERIC', + ]]; +} +if ($active_date_from || $active_date_to) { + $extra_query_args['thalim_event_date_filter'] = ['from' => $active_date_from, 'to' => $active_date_to]; +} + +// Build parent categories for filter bar (with links) +$all_cats = get_categories([ + 'taxonomy' => 'category', + 'hide_empty' => false, + 'exclude' => $excluded_ids, +]); + +$filter_parents = []; +foreach ($all_cats as $cat) { + if ($cat->parent == 0) { + $link = get_category_link($cat->term_id); + if ($filter_query) $link .= '?' . $filter_query; + $filter_parents[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => $link, + ]; + } +} +$context['filter_parents'] = $filter_parents; + +// Children of active rubrique for catégorie filter (with links) +$active_rubrique_id = $context['active_rubrique']; +$is_direct = (bool) get_query_var('thalim_direct_posts'); +$lang = thalim_current_language(); + +$filter_categories = []; +foreach ($all_cats as $cat) { + if ($cat->parent == $active_rubrique_id) { + $link = get_category_link($cat->term_id); + if ($filter_query) $link .= '?' . $filter_query; + $filter_categories[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => $link, + ]; + } +} + +// Add "Autres" entry if the active rubrique has posts directly assigned to it +if ($is_direct) { + $has_direct_posts = true; +} else { + $direct_check = new WP_Query([ + 'post_type' => 'post', + 'posts_per_page' => 1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'lang' => '', + 'tax_query' => [[ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$active_rubrique_id], + 'include_children' => false, + ]], + ]); + $has_direct_posts = $direct_check->have_posts(); +} +if ($has_direct_posts && !empty($filter_categories)) { + $autres_link = trailingslashit(get_category_link($active_rubrique_id)) . 'autres/'; + if ($filter_query) $autres_link .= '?' . $filter_query; + $filter_categories[] = [ + 'id' => 'autres', + 'name' => $lang === 'en' ? 'Other' : 'Autres', + 'slug' => 'autres', + 'link' => $autres_link, + ]; +} + +$context['filter_categories'] = $filter_categories; +$context['active_category_id'] = $is_direct ? 'autres' : $category->term_id; + +// Axes thématiques for filter dropdown +$axes_groups = thalim_get_axes_filter_groups(); +$current_axes = $axes_groups[0]['terms'] ?? []; +$context['filter_axes'] = $current_axes; +$context['axe_stay_on_page'] = true; + +// Fetch posts for initial display +$children = get_categories([ + 'parent' => $category->term_id, + 'taxonomy' => 'category', + 'hide_empty' => true, + 'exclude' => $excluded_ids, +]); + +// Ordre personnalisé des sous-catégories (term_id => position). +// Les termes absents du tableau sont placés en dernier (position 999). +$subcategory_order = [ + // Publications et productions (parent: 4) + 15 => 0, // Ouvrages + 16 => 1, // Articles + 65 => 2, // Revues et collections + 17 => 3, // Multimédia + // Activités (parent: 3) + 11 => 0, // Séminaires + 10 => 1, // Colloques et journées d'études + 13 => 2, // Communications + 14 => 3, // Soutenances +]; +usort($children, function($a, $b) use ($subcategory_order) { + $pos_a = $subcategory_order[$a->term_id] ?? 999; + $pos_b = $subcategory_order[$b->term_id] ?? 999; + return $pos_a - $pos_b; +}); + +$context['category_id'] = $category->term_id; +$context['agenda_include_children'] = ( ! $is_direct && ! empty( $children ) ) ? 1 : 0; + +// Helper: move pinned posts to the front (same logic as homepage diaporamas) +$sort_with_pinned = function ( $posts ) { + $today = date( 'Y-m-d' ); + $pinned = []; + $normal = []; + foreach ( $posts as $post ) { + $epingle = get_post_meta( $post->ID, 'epingler_dans_la_categorie', true ); + $fin = get_post_meta( $post->ID, 'date_de_fin_depinglage', true ); + $active = $epingle == '1' && ( empty( $fin ) || $fin === '0000-00-00' || $fin >= $today ); + if ( $active ) { $pinned[] = $post; } else { $normal[] = $post; } + } + return array_merge( $pinned, $normal ); +}; + +if (!$is_direct && !empty($children)) { + $context['is_parent'] = true; + $context['subcategories'] = []; + + foreach ($children as $child) { + $query_args = array_merge([ + 'post_type' => 'post', + 'tax_query' => [[ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$child->term_id], + 'include_children' => false, + ]], + 'posts_per_page' => 3, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'thalim_event_date_order' => true, + ], $extra_query_args); + $posts = $sort_with_pinned( Timber::get_posts($query_args) ); + $context['cards'] += thalim_get_cards_data($posts); + $context['subcategories'][] = [ + 'term' => Timber::get_term($child), + 'posts' => $posts, + ]; + } + + // Fetch posts directly in the parent category (no child category assigned) + if ($has_direct_posts) { + $direct_query_args = array_merge([ + 'post_type' => 'post', + 'tax_query' => [[ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$category->term_id], + 'include_children' => false, + ]], + 'posts_per_page' => 3, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'thalim_event_date_order' => true, + ], $extra_query_args); + $direct_posts = $sort_with_pinned( Timber::get_posts($direct_query_args) ); + if (!empty($direct_posts)) { + $context['cards'] += thalim_get_cards_data($direct_posts); + $context['direct_posts'] = $direct_posts; + $autres_link = trailingslashit(get_category_link($category->term_id)) . 'autres/'; + if ($filter_query) $autres_link .= '?' . $filter_query; + $context['autres_link'] = $autres_link; + } + } +} else { + $context['is_parent'] = false; + $context['is_direct'] = $is_direct; + $query_args = array_merge([ + 'post_type' => 'post', + 'tax_query' => [[ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$category->term_id], + 'include_children' => false, + ]], + 'posts_per_page' => 12, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'thalim_event_date_order' => true, + ], $extra_query_args); + $posts = $sort_with_pinned( Timber::get_posts($query_args) ); + $context['cards'] = thalim_get_cards_data($posts); + $context['posts'] = $posts; +} + +// View mode toggle (?view=agenda) +$view_mode = ( isset( $_GET['view'] ) && $_GET['view'] === 'agenda' ) ? 'agenda' : 'grid'; +$context['view_mode'] = $view_mode; + +// Toggle URL (used as href fallback on the button) +$toggle_base = get_category_link( $category->term_id ); +$toggle_params = array_filter([ + 'axe' => $active_axe ?: null, + 'date_from' => $active_date_from ?: null, + 'date_to' => $active_date_to ?: null, +]); +if ( $view_mode === 'grid' ) { + $toggle_params['view'] = 'agenda'; +} +// When toggling back to grid we omit ?view entirely +$context['agenda_toggle_url'] = add_query_arg( $toggle_params, $toggle_base ); + +// Custom Pods presentation fields +$cat_lang = thalim_current_language(); +$pres_fr = get_term_meta( $category->term_id, 'presentation', true ) ?: ''; +$pres_en = get_term_meta( $category->term_id, 'presentation_en', true ) ?: ''; +$context['term_presentation'] = wpautop( ( $cat_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr ); + +Timber::render('category.twig', $context); diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..1f128f1 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "timber/timber": "^2.3" + } +} diff --git a/composer.lock b/composer.lock new file mode 100755 index 0000000..cf0e951 --- /dev/null +++ b/composer.lock @@ -0,0 +1,434 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "977b0c3760f52f4b6ad4694d7ac704ba", + "packages": [ + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "timber/timber", + "version": "v2.3.3", + "source": { + "type": "git", + "url": "https://github.com/timber/timber.git", + "reference": "7a87ac27c0b9deedffe419388b63a0c95d8798ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/timber/timber/zipball/7a87ac27c0b9deedffe419388b63a0c95d8798ca", + "reference": "7a87ac27c0b9deedffe419388b63a0c95d8798ca", + "shasum": "" + }, + "require": { + "php": "^8.1", + "twig/twig": "^3.19" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.28", + "php-parallel-lint/php-parallel-lint": "^1.3", + "php-stubs/wp-cli-stubs": "^2.0", + "phpro/grumphp": "^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9.0", + "rector/rector": "^2.0", + "squizlabs/php_codesniffer": "^3.0", + "symplify/easy-coding-standard": "^12", + "szepeviktor/phpstan-wordpress": "^2", + "twig/cache-extra": "^3.17", + "wpackagist-plugin/advanced-custom-fields": "^6.0", + "wpackagist-plugin/co-authors-plus": "^3.6", + "yoast/wp-test-utils": "^1.2" + }, + "suggest": { + "php-coveralls/php-coveralls": "^2.0 for code coverage", + "twig/cache-extra": "For using the cache tag in Twig" + }, + "type": "library", + "autoload": { + "psr-4": { + "Timber\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Erik van der Bas", + "email": "erik@basedonline.nl", + "homepage": "https://basedonline.nl" + }, + { + "name": "Lukas Gächter", + "email": "lukas.gaechter@mind.ch", + "homepage": "https://www.mind.ch" + }, + { + "name": "Nicolas Lemoine", + "email": "nico@n5s.dev", + "homepage": "https://n5s.dev" + }, + { + "name": "Jared Novack", + "email": "jared@upstatement.com", + "homepage": "https://upstatement.com" + }, + { + "name": "Timber Community", + "homepage": "https://github.com/timber/timber" + } + ], + "description": "Create WordPress themes with beautiful OOP code and the Twig Template Engine", + "homepage": "https://timber.upstatement.com", + "keywords": [ + "templating", + "themes", + "timber", + "twig", + "wordpress" + ], + "support": { + "docs": "https://timber.github.io/docs/", + "issues": "https://github.com/timber/timber/issues", + "source": "https://github.com/timber/timber" + }, + "funding": [ + { + "url": "https://github.com/timber", + "type": "github" + }, + { + "url": "https://opencollective.com/timber", + "type": "open_collective" + } + ], + "time": "2025-09-24T14:07:33+00:00" + }, + { + "name": "twig/twig", + "version": "v3.22.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "4509984193026de413baf4ba80f68590a7f2c51d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/4509984193026de413baf4ba80f68590a7f2c51d", + "reference": "4509984193026de413baf4ba80f68590a7f2c51d", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.22.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2025-10-29T15:56:47+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/css/admin.css b/css/admin.css new file mode 100644 index 0000000..b84105e --- /dev/null +++ b/css/admin.css @@ -0,0 +1,923 @@ +/* Fade in transition for post edit and profile pages */ +body.post-php #wpbody, +body.post-new-php #wpbody, +body.profile-php #wpbody, +body.user-edit-php #wpbody, +body.user-new-php #wpbody { + opacity: 0; + transition: opacity 0.3s ease; +} + +body.post-php.admin-mods-ready #wpbody, +body.post-new-php.admin-mods-ready #wpbody, +body.profile-php.admin-mods-ready #wpbody, +body.user-edit-php.admin-mods-ready #wpbody, +body.user-new-php.admin-mods-ready #wpbody { + opacity: 1; +} + +/* Post edit pages: hide elements */ +body.post-php #advanced-sortables, +body.post-new-php #advanced-sortables, +body.post-php #preview-action, +body.post-new-php #preview-action, +body.post-php #visibility, +body.post-new-php #visibility, +body.post-php #add_pod_button, +body.post-new-php #add_pod_button, +body.post-php #wp-content-editor-tools > .wp-editor-tabs, +body.post-new-php #wp-content-editor-tools > .wp-editor-tabs, +body.post-php #mceu_0, +body.post-new-php #mceu_0, +body.post-php #mceu_6, +body.post-new-php #mceu_6, +body.post-php #mceu_7, +body.post-new-php #mceu_7, +body.post-php #mceu_8, +body.post-new-php #mceu_8, +body.post-php #mceu_10, +body.post-new-php #mceu_10, +body.post-php #mceu_11, +body.post-new-php #mceu_11, +body.post-php #postexcerpt, +body.post-new-php #postexcerpt, +body.post-php #postcustom, +body.post-new-php #postcustom, +body.post-php #slugdiv, +body.post-new-php #slugdiv, +body.post-php #authordiv, +body.post-new-php #authordiv, +body.post-php #screen-meta-links, +body.post-new-php #screen-meta-links, +body.post-php .handle-order-higher, +body.post-new-php .handle-order-higher, +body.post-php .handle-order-lower, +body.post-new-php .handle-order-lower, +body.post-php #pods-meta-seances-seminaire .pods-field-wrapper > div:first-of-type, +body.post-new-php #pods-meta-seances-seminaire .pods-field-wrapper > div:first-of-type, +body.post-php #tagsdiv-post_tag, +body.post-new-php #tagsdiv-post_tag, +body.post-php #wp-content-media-buttons, +body.post-new-php #wp-content-media-buttons, +body.post-php #members-cp, +body.post-new-php #members-cp { + display: none !important; +} + +body.post-php .postbox-header .hndle, +body.post-new-php .postbox-header .hndle { + pointer-events: none; + cursor: default; + overflow: visible; + justify-content: start; +} + +body.post-php #pods-form-ui-pods-meta-categorie option[value="31"], +body.post-new-php #pods-form-ui-pods-meta-categorie option[value="31"] { + display: none; +} + +body.post-php #pods-form-ui-pods-meta-categorie option:disabled, +body.post-new-php #pods-form-ui-pods-meta-categorie option:disabled { + color: #a7aaad; +} + +/* Documents joints: margin-top to separate it from the body editor section */ +body.post-php #pods-meta-documents-joints, +body.post-new-php #pods-meta-documents-joints { + margin-top: 20px; +} + +/* Pods iframe modal: hide elements */ +body.pods-modal-window #ml_box, +body.pods-modal-window #pods-meta-liens-externes, +body.pods-modal-window #pods-meta-documents-joints { + display: none; +} + +/* Axes thématiques checkbox group headers */ +body.post-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label, +body.post-new-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label { + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #646970; + background-color: #f0f0f1; + padding: 0.4rem 0.5rem; + cursor: default; + pointer-events: none; + border-top: 1px solid #dcdcde; + margin-top: 0.4rem; + width: 100%; +} +body.post-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label:first-child, +body.post-new-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label:first-child { + border-top: none; + margin-top: 0; +} + +/* Remove WP's JS-injected padding-top on the editor wrap (compensation for sticky toolbar) */ +body.post-php #wp-content-editor-tools, +body.post-new-php #wp-content-editor-tools { + padding-top: 0 !important; +} + +body.post-php .pods-dfv-container-wysiwyg, +body.post-new-php .pods-dfv-container-wysiwyg { + max-width: unset !important; +} + +body.post-php .pods-tinymce-reinit, +body.post-new-php .pods-tinymce-reinit { + display: none; +} + +/* Keep statusbar in layout so TinyMCE's reposition() can read its getBoundingClientRect(). + display:none would return top=0, making mceStatusbarTop=windowHeight → editorHeight<0 → toolbar always hidden. */ +body.post-php .pods-dfv-container-wysiwyg .mce-statusbar, +body.post-new-php .pods-dfv-container-wysiwyg .mce-statusbar { + opacity: 0; + pointer-events: none; + user-select: none; +} + +body.post-php #pods-form-ui-pods-meta-body-en_ifr, +body.post-new-php #pods-form-ui-pods-meta-body-en_ifr { + min-height: 334px !important; +} + +body.post-php #content_ifr, +body.post-new-php #content_ifr, +body.post-php #pods-form-ui-pods-meta-body-en_ifr, +body.post-new-php #pods-form-ui-pods-meta-body-en_ifr { + cursor: text; +} + +/* Pods body-en: hide Visuel/Code tabs and same toolbar buttons removed from native editor + (paragraph format, alignment ×3, "Lire la suite", toggle-advanced-toolbar). + Uses aria-label selectors — more stable than MCE numeric IDs used for the native editor. */ +body.post-php #pods-meta-body-en .wp-editor-tabs, +body.post-new-php #pods-meta-body-en .wp-editor-tabs, +body.post-php #pods-meta-body-en .mce-listbox.mce-fixed-width, +body.post-new-php #pods-meta-body-en .mce-listbox.mce-fixed-width, +body.post-php #pods-meta-body-en [aria-label*="Aligner"], +body.post-new-php #pods-meta-body-en [aria-label*="Aligner"], +body.post-php #pods-meta-body-en [aria-label*="Centrer"], +body.post-new-php #pods-meta-body-en [aria-label*="Centrer"], +body.post-php #pods-meta-body-en [aria-label*="Lire la suite"], +body.post-new-php #pods-meta-body-en [aria-label*="Lire la suite"], +body.post-php #pods-meta-body-en [aria-label*="Permuter la barre"], +body.post-new-php #pods-meta-body-en [aria-label*="Permuter la barre"] { + display: none !important; +} + +/* Référence bibliographique: hide tabs, media buttons, status bar, + and all toolbar buttons except Italic (only formatting allowed). */ +#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .wp-editor-tabs, +#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .wp-media-buttons, +#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-statusbar { + display: none !important; +} +#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-toolbar .mce-btn { + display: none !important; +} +#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-toolbar .mce-btn:has(.mce-i-italic), +#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-toolbar .mce-btn:has(.mce-i-bold) { + display: inline-block !important; +} + +/* Profile / user-edit: fixed submit button */ +body.profile-php #your-profile p.submit::before, +body.user-edit-php #your-profile p.submit::before { + content: "Pensez à sauvegarder les modifications"; + display: block; + font-size: 11px; + color: #646970; + margin-bottom: 6px; +} + +body.profile-php #your-profile p.submit, +body.user-edit-php #your-profile p.submit { + position: fixed; + bottom: 0; + right: 0; + margin: 0; + padding: 12px 16px; + background: #f0f0f1; + border-top: 1px solid #c3c4c7; + border-left: 1px solid #c3c4c7; + z-index: 100; +} + +/* Profile / user-edit / user-new / taxonomy term pages: TinyMCE editor mods (mirrors post-edit treatment) */ +body.profile-php .wp-editor-tabs, +body.user-edit-php .wp-editor-tabs, +body.user-new-php .wp-editor-tabs, +body.edit-tags-php .wp-editor-tabs, +body.term-php .wp-editor-tabs, +body.profile-php .mce-listbox.mce-fixed-width, +body.user-edit-php .mce-listbox.mce-fixed-width, +body.user-new-php .mce-listbox.mce-fixed-width, +body.edit-tags-php .mce-listbox.mce-fixed-width, +body.term-php .mce-listbox.mce-fixed-width, +body.profile-php [aria-label*="Aligner"], +body.user-edit-php [aria-label*="Aligner"], +body.user-new-php [aria-label*="Aligner"], +body.edit-tags-php [aria-label*="Aligner"], +body.term-php [aria-label*="Aligner"], +body.profile-php [aria-label*="Centrer"], +body.user-edit-php [aria-label*="Centrer"], +body.user-new-php [aria-label*="Centrer"], +body.edit-tags-php [aria-label*="Centrer"], +body.term-php [aria-label*="Centrer"], +body.profile-php [aria-label*="Lire la suite"], +body.user-edit-php [aria-label*="Lire la suite"], +body.user-new-php [aria-label*="Lire la suite"], +body.edit-tags-php [aria-label*="Lire la suite"], +body.term-php [aria-label*="Lire la suite"], +body.profile-php [aria-label*="Permuter la barre"], +body.user-edit-php [aria-label*="Permuter la barre"], +body.user-new-php [aria-label*="Permuter la barre"], +body.edit-tags-php [aria-label*="Permuter la barre"], +body.term-php [aria-label*="Permuter la barre"], +body.profile-php .wp-media-buttons, +body.user-edit-php .wp-media-buttons, +body.user-new-php .wp-media-buttons, +body.edit-tags-php .wp-media-buttons, +body.term-php .wp-media-buttons, +body.profile-php .pods-tinymce-reinit, +body.user-edit-php .pods-tinymce-reinit, +body.user-new-php .pods-tinymce-reinit, +body.edit-tags-php .pods-tinymce-reinit, +body.term-php .pods-tinymce-reinit { + display: none !important; +} + +body.profile-php .wp-editor-tools, +body.user-edit-php .wp-editor-tools, +body.user-new-php .wp-editor-tools, +body.edit-tags-php .wp-editor-tools, +body.term-php .wp-editor-tools { + padding-top: 0 !important; +} + +body.profile-php .pods-dfv-container-wysiwyg, +body.user-edit-php .pods-dfv-container-wysiwyg, +body.user-new-php .pods-dfv-container-wysiwyg, +body.edit-tags-php .pods-dfv-container-wysiwyg, +body.term-php .pods-dfv-container-wysiwyg { + max-width: unset !important; +} + +body.profile-php .wp-editor-container iframe, +body.user-edit-php .wp-editor-container iframe, +body.user-new-php .wp-editor-container iframe, +body.edit-tags-php .wp-editor-container iframe, +body.term-php .wp-editor-container iframe { + cursor: text; +} + +/* Fade-in transition for tab panels (opacity is set to 0 by JS before reveal, then cleared) */ +body.post-php #postdivrich, +body.post-new-php #postdivrich, +body.post-php #pods-meta-body-en, +body.post-new-php #pods-meta-body-en { + transition: opacity 0.15s ease; +} + +/* Body FR/EN language tabs */ +body.post-php .body-lang-tabs, +body.post-new-php .body-lang-tabs { + display: flex; + gap: 0; + margin-bottom: -1px; + position: relative; + z-index: 1; +} + +body.post-php .body-lang-tab, +body.post-new-php .body-lang-tab { + padding: 5px 14px; + background: #f0f0f1; + border: 1px solid #c3c4c7; + border-bottom-color: #c3c4c7; + cursor: pointer; + font-size: 13px; + line-height: 1.5; + color: #50575e; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +body.post-php .body-lang-tab.is-active, +body.post-new-php .body-lang-tab.is-active { + background: #fff; + border-bottom-color: #fff; + color: #1d2327; + font-weight: 600; +} + +/* Hide postbox headers for both body editors — tabs replace them */ +body.post-php #postdivrich .postbox-header, +body.post-new-php #postdivrich .postbox-header, +body.post-php #pods-meta-body-en .postbox-header, +body.post-new-php #pods-meta-body-en .postbox-header { + display: none !important; +} + +/* Strip Pods table layout from body-en — make it full width like native editor */ +body.post-php #pods-meta-body-en .inside, +body.post-new-php #pods-meta-body-en .inside { + margin: 0; + padding: 0; +} + +body.post-php #pods-meta-body-en .form-table, +body.post-new-php #pods-meta-body-en .form-table, +body.post-php #pods-meta-body-en .form-table tbody, +body.post-new-php #pods-meta-body-en .form-table tbody, +body.post-php #pods-meta-body-en .form-table tr, +body.post-new-php #pods-meta-body-en .form-table tr, +body.post-php #pods-meta-body-en .form-table td, +body.post-new-php #pods-meta-body-en .form-table td { + display: block; + width: 100%; + padding: 0; + margin: 0; +} + +body.post-php #pods-meta-body-en .form-table th, +body.post-new-php #pods-meta-body-en .form-table th { + display: none; +} + +body.post-php #pods-meta-body-en .pods-submittable-fields, +body.post-new-php #pods-meta-body-en .pods-submittable-fields, +body.post-php #pods-meta-body-en .pods-dfv-container, +body.post-new-php #pods-meta-body-en .pods-dfv-container { + padding: 0; +} + +/* Allow TinyMCE floating panels (link popover, inline toolbar) to escape the Pods + container chain — any overflow:hidden in Pods DFV elements would clip them */ +body.post-php #pods-meta-body-en .inside, +body.post-new-php #pods-meta-body-en .inside, +body.post-php #pods-meta-body-en .pods-submittable-fields, +body.post-new-php #pods-meta-body-en .pods-submittable-fields, +body.post-php #pods-meta-body-en .pods-dfv-field, +body.post-new-php #pods-meta-body-en .pods-dfv-field, +body.post-php #pods-meta-body-en .pods-field-option, +body.post-new-php #pods-meta-body-en .pods-field-option, +body.post-php #pods-meta-body-en .pods-field-option__field, +body.post-new-php #pods-meta-body-en .pods-field-option__field, +body.post-php #pods-meta-body-en .pods-dfv-container-wysiwyg, +body.post-new-php #pods-meta-body-en .pods-dfv-container-wysiwyg, +body.post-php #pods-meta-body-en .pods-field-wrapper, +body.post-new-php #pods-meta-body-en .pods-field-wrapper, +body.post-php #pods-meta-body-en .wp-editor-wrap, +body.post-new-php #pods-meta-body-en .wp-editor-wrap, +body.post-php #pods-meta-body-en .wp-editor-container, +body.post-new-php #pods-meta-body-en .wp-editor-container { + overflow: visible !important; +} + +/* Info popovers */ +.thalim-info-wrapper { + position: relative; + display: inline-flex; + align-items: center; + margin-left: 6px; + vertical-align: middle; +} + +.thalim-info-btn { + display: inline-flex; + align-items: center; + justify-content: center; + background: none; + border: none; + padding: 2px; + cursor: pointer; + color: #646970; + line-height: 1; + border-radius: 50%; + transition: color 0.15s, background 0.15s; + pointer-events: auto; +} + +.thalim-info-btn:hover, +.thalim-info-btn:focus { + color: #2271b1; + background: #f0f6fc; + outline: none; +} + +.thalim-info-popover { + display: none; + position: fixed; + transform: translateX(-50%); + z-index: 9999; + background: #fff; + border: 1px solid #c3c4c7; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); + padding: 10px 12px; + min-width: 220px; + max-width: 320px; + font-size: 12px; + font-weight: normal; + line-height: 1.5; + color: #3c434a; + white-space: normal; + text-align: left; + pointer-events: auto; +} + +.thalim-info-popover.is-open { + display: block; +} + +.thalim-info-popover p { + margin: 0 0 4px; +} + +.thalim-info-popover p:last-child { + margin-bottom: 0; +} + +/* Translate popovers */ +.thalim-translate-btn { + display: inline-flex; + align-items: center; + justify-content: center; + background: none; + border: none; + padding: 2px; + cursor: pointer; + color: #2a7a4f; + line-height: 1; + border-radius: 50%; + transition: color 0.15s, background 0.15s; + pointer-events: auto; +} + +.thalim-translate-btn:hover, +.thalim-translate-btn:focus { + color: #1d5c3a; + background: #edfaf3; + outline: none; +} + +.thalim-translate-popover { + border-color: #b2dfcc; +} + +/* Required field indicator */ +abbr.required::after { + content: " Champ nécessaire"; + font-size: 0.72rem; + font-weight: normal; + font-style: normal; + color: #646970; + text-decoration: none; +} + +/* Profile pages: hide sections */ +body.user-new-php #simple-local-avatar-section, +body.profile-php .user-admin-color-wrap, +body.user-edit-php .user-admin-color-wrap, +body.user-new-php .user-admin-color-wrap, +body.profile-php .user-admin-bar-front-wrap, +body.user-edit-php .user-admin-bar-front-wrap, +body.user-new-php .user-admin-bar-front-wrap, +body.profile-php .user-nickname-wrap, +body.user-edit-php .user-nickname-wrap, +body.user-new-php .user-nickname-wrap, +body.profile-php .user-display-name-wrap, +body.user-edit-php .user-display-name-wrap, +body.user-new-php .user-display-name-wrap, +body.profile-php .user-profile-picture, +body.user-edit-php .user-profile-picture, +body.user-new-php .user-profile-picture, +body.profile-php .ratings-row, +body.user-edit-php .ratings-row, +body.user-new-php .ratings-row, +body.profile-php .application-passwords, +body.user-edit-php .application-passwords, +body.user-new-php .application-passwords, +body.profile-php .user-comment-shortcuts-wrap, +body.user-edit-php .user-comment-shortcuts-wrap, +body.user-new-php .user-comment-shortcuts-wrap, +body.profile-php .form-table:has(.user-description-wrap), +body.user-edit-php .form-table:has(.user-description-wrap), +body.user-new-php .form-table:has(.user-description-wrap) { + display: none !important; +} + +body.profile-php .form-table, +body.user-edit-php .form-table, +body.user-new-php .form-table { + display: block; +} + +body.profile-php .form-table tbody, +body.user-edit-php .form-table tbody, +body.user-new-php .form-table tbody { + display: grid; + grid-template-columns: 1fr; + background: #fff; + border: 1px solid #c3c4c7; + padding: 8px 16px; + margin-bottom: 16px; +} + +body.profile-php .form-table tbody tr, +body.user-edit-php .form-table tbody tr, +body.user-new-php .form-table tbody tr { + display: flex; + flex-direction: column; + grid-column: 1 / -1; +} + +body.profile-php .form-table tbody tr th, +body.user-edit-php .form-table tbody tr th, +body.user-new-php .form-table tbody tr th, +body.profile-php .form-table tbody tr td, +body.user-edit-php .form-table tbody tr td, +body.user-new-php .form-table tbody tr td { + display: block; + width: 100%; + padding: 0 0 4px; +} + +@media (min-width: 768px) { + /* Side-by-side section pairs */ + body.profile-php .profile-section-row, + body.user-edit-php .profile-section-row, + body.user-new-php .profile-section-row { + display: flex; + gap: 24px; + align-items: flex-start; + } + + body.profile-php .profile-section-col, + body.user-edit-php .profile-section-col, + body.user-new-php .profile-section-col { + flex: 1; + min-width: 0; + } + + /* Tables inside a col are already at 50% width — keep fields single-column */ + body.profile-php .profile-section-col .form-table tbody, + body.user-edit-php .profile-section-col .form-table tbody, + body.user-new-php .profile-section-col .form-table tbody { + grid-template-columns: 1fr; + } + + + body.profile-php .form-table tbody, + body.user-edit-php .form-table tbody, + body.user-new-php .form-table tbody { + grid-template-columns: 1fr 1fr; + gap: 0 15px; + &:has(.pods-form-ui-row-name-role-1) { + grid-template-columns: 1fr 1fr 1fr; + } + } + + body.profile-php .form-table tbody .user-user-login-wrap, + body.user-edit-php .form-table tbody .user-user-login-wrap, + body.user-new-php .form-table tbody .user-user-login-wrap, + body.profile-php .form-table tbody .user-first-name-wrap, + body.user-edit-php .form-table tbody .user-first-name-wrap, + body.user-new-php .form-table tbody .user-first-name-wrap, + body.profile-php .form-table tbody .user-email-wrap, + body.user-edit-php .form-table tbody .user-email-wrap, + body.user-new-php .form-table tbody .user-email-wrap, + body.profile-php .form-table tbody .pods-form-ui-row-name-lien-externe-1, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-lien-externe-1, + body.user-new-php .form-table tbody .pods-form-ui-row-name-lien-externe-1, + body.profile-php .form-table tbody .pods-form-ui-row-name-lien-externe-2, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-lien-externe-2, + body.user-new-php .form-table tbody .pods-form-ui-row-name-lien-externe-2, + body.profile-php .form-table tbody .pods-form-ui-row-name-lien-externe-3, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-lien-externe-3, + body.user-new-php .form-table tbody .pods-form-ui-row-name-lien-externe-3, + body.profile-php .form-table tbody .pods-form-ui-row-name-role-1, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-role-1, + body.user-new-php .form-table tbody .pods-form-ui-row-name-role-1, + body.profile-php .form-table tbody .pods-form-ui-row-name-role-2, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-role-2, + body.user-new-php .form-table tbody .pods-form-ui-row-name-role-2, + body.profile-php .form-table tbody .pods-form-ui-row-name-role-3, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-role-3, + body.user-new-php .form-table tbody .pods-form-ui-row-name-role-3, + body.profile-php .form-table tbody .pods-form-ui-row-name-affiliation, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-affiliation, + body.user-new-php .form-table tbody .pods-form-ui-row-name-affiliation, + body.profile-php .form-table tbody .pods-form-ui-row-name-titre-de-these, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-de-these, + body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-de-these, + body.profile-php .form-table tbody .pods-form-ui-row-name-directeur-de-these-thalim, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-directeur-de-these-thalim, + body.user-new-php .form-table tbody .pods-form-ui-row-name-directeur-de-these-thalim, + body.profile-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours, + body.user-new-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours, + body.profile-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches, + body.user-new-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches, + body.profile-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these, + body.user-new-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these, + body.profile-php .form-table tbody .pods-form-ui-row-name-biographie, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-biographie, + body.user-new-php .form-table tbody .pods-form-ui-row-name-biographie { + grid-column: 1; + } + + body.profile-php .form-table tbody .user-role-wrap, + body.user-edit-php .form-table tbody .user-role-wrap, + body.user-new-php .form-table tbody .user-role-wrap, + body.profile-php .form-table tbody .user-last-name-wrap, + body.user-edit-php .form-table tbody .user-last-name-wrap, + body.user-new-php .form-table tbody .user-last-name-wrap, + body.profile-php .form-table tbody .user-url-wrap, + body.user-edit-php .form-table tbody .user-url-wrap, + body.user-new-php .form-table tbody .user-url-wrap, + body.profile-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-1, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-1, + body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-1, + body.profile-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-2, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-2, + body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-2, + body.profile-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-3, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-3, + body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-3, + body.profile-php .form-table tbody .pods-form-ui-row-name-complement-de-role-1, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-complement-de-role-1, + body.user-new-php .form-table tbody .pods-form-ui-row-name-complement-de-role-1, + body.profile-php .form-table tbody .pods-form-ui-row-name-complement-de-role-2, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-complement-de-role-2, + body.user-new-php .form-table tbody .pods-form-ui-row-name-complement-de-role-2, + body.profile-php .form-table tbody .pods-form-ui-row-name-complement-de-role-3, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-complement-de-role-3, + body.user-new-php .form-table tbody .pods-form-ui-row-name-complement-de-role-3, + body.profile-php .form-table tbody .pods-form-ui-row-name-affiliation-autre, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-affiliation-autre, + body.user-new-php .form-table tbody .pods-form-ui-row-name-affiliation-autre, + body.profile-php .form-table tbody .pods-form-ui-row-name-autre-directeur-de-these, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-autre-directeur-de-these, + body.user-new-php .form-table tbody .pods-form-ui-row-name-autre-directeur-de-these, + body.profile-php .form-table tbody .pods-form-ui-row-name-date-de-soutenance, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-date-de-soutenance, + body.user-new-php .form-table tbody .pods-form-ui-row-name-date-de-soutenance, + body.profile-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours-en, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours-en, + body.user-new-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours-en, + body.profile-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches-en, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches-en, + body.user-new-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches-en, + body.profile-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these-en, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these-en, + body.user-new-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these-en, + body.profile-php .form-table tbody .pods-form-ui-row-name-biographie-en, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-biographie-en, + body.user-new-php .form-table tbody .pods-form-ui-row-name-biographie-en { + grid-column: 2; + } + + body.profile-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-1, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-1, + body.user-new-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-1, + body.profile-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-2, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-2, + body.user-new-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-2, + body.profile-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-3, + body.user-edit-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-3, + body.user-new-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-3 { + grid-column: 3; + } + + body.profile-php .user-user-login-wrap span.description, + body.user-edit-php .user-user-login-wrap span.description, + body.user-new-php .user-user-login-wrap span.description { + display: block; + } +} + + +/* Profile FR/EN language overlay tabs */ +body.profile-php .profile-lang-tabs, +body.user-edit-php .profile-lang-tabs, +body.user-new-php .profile-lang-tabs { + display: flex; + gap: 0; + margin-bottom: -1px; + position: relative; + z-index: 1; +} +body.profile-php .profile-lang-tab, +body.user-edit-php .profile-lang-tab, +body.user-new-php .profile-lang-tab { + padding: 5px 14px; + background: #f0f0f1; + border: 1px solid #c3c4c7; + cursor: pointer; + font-size: 13px; + line-height: 1.5; + color: #50575e; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} +body.profile-php .profile-lang-tab.is-active, +body.user-edit-php .profile-lang-tab.is-active, +body.user-new-php .profile-lang-tab.is-active { + background: #fff; + border-bottom-color: #fff; + color: #1d2327; + font-weight: 600; +} +body.profile-php .form-table tbody .profile-lang-tabs-row td, +body.user-edit-php .form-table tbody .profile-lang-tabs-row td, +body.user-new-php .form-table tbody .profile-lang-tabs-row td { + padding: 0; + grid-column: 1 / -1; +} + + +/* Membres: 2-column explicit grid + Row 1: Fonction (col 1 only) + Row 2: Membres | Autrepersonnes + Row 3: full-width separator (injected ) + Row 4: Autre fonction (col 1 only) + Row 5: Autre membres | Autre autrepersonnes +*/ +body.post-php #pods-meta-membres .form-table, +body.post-new-php #pods-meta-membres .form-table { + display: block; +} + +body.post-php #pods-meta-membres .form-table tbody, +body.post-new-php #pods-meta-membres .form-table tbody { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 16px; +} + +body.post-php #pods-meta-membres .form-table tr, +body.post-new-php #pods-meta-membres .form-table tr { + display: flex; + flex-direction: column; + padding: 6px 0; +} + +body.post-php #pods-meta-membres .form-table th, +body.post-new-php #pods-meta-membres .form-table th, +body.post-php #pods-meta-membres .form-table td, +body.post-new-php #pods-meta-membres .form-table td { + display: block; + width: 100%; + padding: 0 0 4px; +} + +body.post-php #pods-meta-membres .form-table input[type="text"], +body.post-new-php #pods-meta-membres .form-table input[type="text"] { + width: 100%; + box-sizing: border-box; + max-width: none; +} + +/* Row 1: Fonction — left column only */ +body.post-php #pods-meta-membres [class*="pods-form-ui-row-name-fonction-"], +body.post-new-php #pods-meta-membres [class*="pods-form-ui-row-name-fonction-"] { + grid-column: 1; + grid-row: 1; +} + +/* Row 2: Membres (left) | Autrepersonnes (right) */ +body.post-php #pods-meta-membres .pods-form-ui-row-name-membres, +body.post-new-php #pods-meta-membres .pods-form-ui-row-name-membres { + grid-column: 1; + grid-row: 2; +} + +body.post-php #pods-meta-membres .pods-form-ui-row-name-autrepersonnes, +body.post-new-php #pods-meta-membres .pods-form-ui-row-name-autrepersonnes { + grid-column: 2; + grid-row: 2; +} + +/* Row 3: full-width separator */ +body.post-php #pods-meta-membres .membres-grid-separator, +body.post-new-php #pods-meta-membres .membres-grid-separator { + grid-column: 1 / -1; + grid-row: 3; + border-top: 1px solid #dcdcde; + height: 0; + padding: 0; + margin: 4px 0; +} + +/* Row 4: Autre fonction — left column only */ +body.post-php #pods-meta-membres [class*="pods-form-ui-row-name-autre-fonction-"], +body.post-new-php #pods-meta-membres [class*="pods-form-ui-row-name-autre-fonction-"] { + grid-column: 1; + grid-row: 4; +} + +/* Row 5: Autre membres (left) | Autre autrepersonnes (right) */ +body.post-php #pods-meta-membres .pods-form-ui-row-name-autre-membres, +body.post-new-php #pods-meta-membres .pods-form-ui-row-name-autre-membres { + grid-column: 1; + grid-row: 5; +} + +body.post-php #pods-meta-membres .pods-form-ui-row-name-autre-autrepersonnes, +body.post-new-php #pods-meta-membres .pods-form-ui-row-name-autre-autrepersonnes { + grid-column: 2; + grid-row: 5; +} + +/* Liens externes: two-column layout — URL field and title field side by side per link */ +body.post-php #pods-meta-liens-externes .form-table, +body.post-new-php #pods-meta-liens-externes .form-table { + display: block; +} + +body.post-php #pods-meta-liens-externes .form-table tbody, +body.post-new-php #pods-meta-liens-externes .form-table tbody { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 16px; +} + +body.post-php #pods-meta-liens-externes .form-table tr, +body.post-new-php #pods-meta-liens-externes .form-table tr { + display: flex; + flex-direction: column; + padding: 6px 0; +} + +body.post-php #pods-meta-liens-externes .form-table th, +body.post-new-php #pods-meta-liens-externes .form-table th, +body.post-php #pods-meta-liens-externes .form-table td, +body.post-new-php #pods-meta-liens-externes .form-table td { + display: block; + width: 100%; + padding: 0 0 4px; +} + +body.post-php #pods-meta-liens-externes .form-table input[type="text"], +body.post-new-php #pods-meta-liens-externes .form-table input[type="text"] { + width: 100%; + box-sizing: border-box; + max-width: none; +} + +/* Visual separator between link pairs 2 and 3 */ +body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-2, +body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-2, +body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-2, +body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-2, +body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-3, +body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-3, +body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-3, +body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-3 { + border-top: 1px solid #dcdcde; + padding-top: 8px; +} + +/* Dates: date de début and date de fin side by side */ +body.post-php #pods-meta-dates .form-table, +body.post-new-php #pods-meta-dates .form-table { + display: block; +} + +body.post-php #pods-meta-dates .form-table tbody, +body.post-new-php #pods-meta-dates .form-table tbody { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 16px; +} + +body.post-php #pods-meta-dates .form-table tr, +body.post-new-php #pods-meta-dates .form-table tr { + display: flex; + flex-direction: column; + padding: 6px 0; +} + +body.post-php #pods-meta-dates .form-table th, +body.post-new-php #pods-meta-dates .form-table th, +body.post-php #pods-meta-dates .form-table td, +body.post-new-php #pods-meta-dates .form-table td { + display: block; + width: 100%; + padding: 0 0 4px; +} + +/* Taxonomy pages — hide unused description field */ +.edit-tags-php .term-description-wrap, +.term-php .term-description-wrap { + display: none; +} diff --git a/css/style.css b/css/style.css new file mode 100755 index 0000000..a28209b --- /dev/null +++ b/css/style.css @@ -0,0 +1,2901 @@ +@charset "UTF-8"; +*, *:before, *:after { + box-sizing: border-box; } + +html, body, div, span, object, iframe, figure, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, code, em, img, small, strike, strong, sub, sup, tt, b, u, i, ol, ul, li, fieldset, form, label, table, caption, tbody, tfoot, thead, tr, th, td, main, canvas, embed, footer, header, nav, section, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + text-size-adjust: none; } + +footer, header, nav, section, main { + display: block; } + +body { + line-height: 1; } + +ol, ul { + list-style: none; } + +blockquote, q { + quotes: none; } + +blockquote:before, blockquote:after, q:before, q:after { + content: ''; + content: none; } + +table { + border-collapse: collapse; + border-spacing: 0; } + +input { + -webkit-appearance: none; + border-radius: 0; } + +#wpadminbar { + position: fixed !important; } + +@font-face { + font-family: 'Gelasio'; + src: url("../assets/fonts/Gelasio-Regular.woff2") format("woff2"); + font-weight: normal; + font-style: normal; } +@font-face { + font-family: 'NewsCycle'; + src: url("../assets/fonts/NewsCycle-Regular.woff2") format("woff2"); + font-weight: normal; + font-style: normal; } +body { + font-family: "NewsCycle", sans-serif; } + +h1, h2, h3, h4, h5, h6 { + font-family: "Gelasio", serif; } + +a, +a:active { + color: #1a1a1a; + transition: color 0.2s ease-out; } + +a:hover { + color: #3e3e3e; } + +p { + line-height: 1.2; } + +.link-button { + display: inline-flex; + background-color: #eeeeee; + color: #1a1a1a; + padding: 0.6rem 0.7rem; + font-size: 0.9rem; + transition: background-color 0.3s ease-out; + text-decoration: none; + justify-content: center; + align-items: center; + word-break: break-all; + max-width: 100%; } + .link-button > i { + margin-right: 0.6rem; } + .link-button:hover { + background-color: #cccccc; } + @media (min-width: 768px) { + .link-button { + font-size: unset; + padding: 0.6rem 1rem; } } + .link-button--wrap-word { + word-break: normal; + overflow-wrap: break-word; } + +body { + display: flex; + flex-direction: column; + height: 100vh; + background-color: #eeeeee; } + +main { + width: 100vw; + display: flex; + flex-direction: column; + align-items: center; } + @media (min-width: 768px) { + main { + margin-top: 12vh; } } + +.container { + display: flex; + align-items: center; + justify-content: start; + flex-direction: column; + position: relative; + z-index: 1; + padding: 4vh 5vw; + max-width: 1640px; + width: 100vw; } + +.full-block { + width: 100%; + background-color: white; + padding: 2.5vh 5vw; + position: relative; + padding-bottom: 6vh; } + @media (min-width: 768px) { + .full-block { + padding: 3vh 3vw; + padding-bottom: 8vh; } } + .full-block::after { + content: ''; + display: block; + position: absolute; + height: 30px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; } + +body > +header { + display: flex; + flex-direction: column-reverse; + justify-content: space-between; + transition: height 0.3s ease-out; + background-color: white; + width: 100%; + position: relative; + z-index: 5; } + @media (min-width: 768px) { + body > + header { + position: fixed; + height: 12vh; + min-height: 100px; + max-height: 130px; + flex-direction: row; } } + +.header-left { + display: flex; + height: 6rem; + margin-top: 3rem; } + @media (min-width: 768px) { + .header-left { + margin-top: unset; + height: 100%; } } + +.main-logo-container { + display: inline-block; + background-color: #eeeeee; + height: 100%; } + @media (min-width: 768px) { + .main-logo-container { + flex-direction: row; } } + +.main-logo { + display: inline-block; + padding: 0.3rem 0.6rem; + height: 100%; + background: linear-gradient(to bottom, #eeeeee 60%, #f7ff29 100%); + background-position: bottom 0px left 0px; + background-repeat: no-repeat; + cursor: pointer; + transition: background 0.3s ease-out, padding 0.2s ease-out; } + .main-logo:hover { + background-position: bottom -10px left 0px; } + @media (min-width: 768px) { + .main-logo { + padding: 1.5rem 2rem; } } + .main-logo > img, + .main-logo > a > img { + height: 100%; + transform: scale(1); + transition: transform 0.2s ease-out; } + .main-logo:hover > img, .main-logo:hover > a > img { + transform: scale(1.05); } + +.description { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + gap: 0.3rem; + margin-left: 1.2rem; + opacity: 1; + transition: opacity 0.2s ease-out; } + .description > div:first-of-type { + text-transform: uppercase; + font-size: 0.8rem; + background-color: #eeeeee; + padding: 0.2rem; } + .description > div:last-of-type { + font-size: 0.8rem; } + .description > div:last-of-type > sup { + font-size: 0.6rem; + vertical-align: super; } + +.header-right { + display: flex; + align-items: center; + gap: 2rem; + justify-content: space-between; + position: fixed; + width: 100%; + background-color: white; + height: 3rem; } + @media (min-width: 768px) { + .header-right { + padding-left: unset; + height: unset; + position: relative; + width: unset; } } + .header-right.scrolled .secondary-logo-container { + max-width: 40vw; } + .header-right.scrolled .secondary-logo-container .main-logo:hover { + background-position: unset; } + .header-right.scrolled .secondary-logo-container .main-logo:hover img { + transform: unset; } + +.secondary-logo-container { + height: 100%; + max-width: 0; + overflow: hidden; + transition: max-width 0.6s ease-out; } + @media (min-width: 768px) { + .secondary-logo-container { + display: none; } } + +.lang-switch { + text-transform: uppercase; + font-size: 0.8rem; + transition: font-size 0.2s ease-out; } + .lang-switch > ul { + display: flex; + gap: 1rem; } + .lang-switch > ul > li:not(.active) > a { + text-decoration: none; } + +.search-button { + margin-left: auto; } + .search-button > div { + background-color: #eeeeee; + padding: 0.5rem; + border-radius: 5rem; + min-width: 2rem; + aspect-ratio: 1 / 1; + display: flex; + justify-content: center; + align-items: center; + transition: background-color 0.3s ease-out; + cursor: pointer; } + .search-button > div:hover { + background-color: #cccccc; } + +.menu-toggle { + display: inline-block; + background-color: #eeeeee; + width: fit-content; + height: 100%; } + .menu-toggle > div { + height: 100%; + display: flex; + padding: 0.5rem; + flex-direction: column; + align-items: center; + justify-content: space-between; + gap: 0.3rem; + background: linear-gradient(to bottom, #eeeeee 60%, #f7ff29 100%); + background-position: bottom 0px left 0px; + background-repeat: no-repeat; + cursor: pointer; + transition: background 0.3s ease-out, padding 0.2s ease-out; } + .menu-toggle > div:hover { + background-position: bottom -10px left 0px; } + @media (min-width: 768px) { + .menu-toggle > div { + gap: unset; + padding: 2rem; } } + .menu-toggle > div > div { + font-size: 1rem; + transform: scale(1); + transition: transform 0.2s ease-out, font-size 0.2s ease-out; } + @media (min-width: 768px) { + .menu-toggle > div > div { + font-size: 2rem; } } + .menu-toggle > div > p { + font-family: NewsCycle; + text-transform: uppercase; + transform: scale(1); + font-size: 0.8rem; + transition: transform 0.2s ease-out, font-size 0.2s ease-out; } + @media (min-width: 768px) { + .menu-toggle > div > p { + font-size: 1rem; } } + .menu-toggle:hover > div > div, .menu-toggle:hover > div > p { + transform: scale(0.9); } + +.search-panel { + position: absolute; + right: 0; + width: 100%; + background-color: white; + z-index: 4; + border-top: 2px solid #eeeeee; + transition: top 0.4s ease-out, opacity 0.2s ease-out; + opacity: 0; + pointer-events: none; } + @media (min-width: 768px) { + .search-panel { + position: fixed; + width: 33.333%; } } + .search-panel.active { + opacity: 1; + pointer-events: all; } + .search-panel__inner { + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; } + .search-panel__title { + font-family: "NewsCycle", sans-serif; + font-size: 1.1rem; + text-transform: uppercase; + display: inline-block; + align-self: start; + position: relative; + line-height: 1.6; } + .search-panel__title::after { + content: ''; + display: block; + position: absolute; + height: 30px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + .search-panel__desc { + font-family: "NewsCycle", sans-serif; + color: #3e3e3e; } + .search-panel__input-wrap { + position: relative; } + .search-panel__icon-btn { + position: absolute; + right: 0.8rem; + top: 50%; + transform: translateY(-50%); + color: #3e3e3e; + background: none; + border: none; + padding: 0; + cursor: pointer; + line-height: 1; } + .search-panel__icon-btn:hover { + color: #1a1a1a; } + .search-panel__input { + width: 100%; + border: none; + padding: 0.6rem 2.5rem 0.6rem 0.8rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + outline: none; + background-color: #eeeeee; } + .search-panel__input::placeholder { + color: #3e3e3e; + text-transform: uppercase; } + .search-panel__input:focus { + border-color: #cccccc; } + .search-panel__submit { + display: block; + margin-left: auto; + background-color: #eeeeee; + padding: 0.5rem 1rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + text-transform: uppercase; + cursor: pointer; + margin-top: 1rem; + border: none; } + .search-panel__submit:hover { + background-color: #cccccc; } + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #cccccc; + opacity: 0; + z-index: 3; + pointer-events: none; + transition: opacity 0.3s ease-out; } + .overlay.active { + opacity: 0.6; + pointer-events: all; } + +.main-menu { + overflow-y: scroll; + left: 0; + width: 100%; + background-color: white; + z-index: 4; + padding-bottom: 4vh; + transition: top 0.4s ease-out, opacity 0.2s ease-out; + border-top: 2px solid #eeeeee; + position: absolute; + opacity: 0; } + @media (min-width: 768px) { + .main-menu { + overflow-y: unset; + position: fixed; } } + .main-menu::after { + content: ''; + display: block; + position: absolute; + height: 30px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease-out 0.3s; } + @media (min-width: 768px) { + .main-menu::after { + opacity: 1; } } + .main-menu.active { + opacity: 1; } + .main-menu.active::after { + position: fixed; + opacity: 1; } + @media (min-width: 768px) { + .main-menu.active::after { + position: absolute; } } + +.menu-navigation-container > ul, +.menu-navigation-en-container > ul { + display: grid; + gap: 3rem; + padding: 2rem 1.5rem; + max-width: 1400px; + margin: 0 auto; } + @media (min-width: 768px) { + .menu-navigation-container > ul, + .menu-navigation-en-container > ul { + padding: 3rem 4rem; + grid-template-columns: repeat(3, auto); } } + @media (min-width: 1024px) { + .menu-navigation-container > ul, + .menu-navigation-en-container > ul { + grid-template-columns: repeat(5, auto); } } + .menu-navigation-container > ul > li, + .menu-navigation-en-container > ul > li { + display: flex; + flex-direction: column; + gap: 0.8rem; + align-items: start; } + .menu-navigation-container > ul > li > a, + .menu-navigation-en-container > ul > li > a { + text-transform: uppercase; + text-decoration: none; + position: relative; + padding-bottom: 0.8rem; + margin-bottom: 0.5rem; } + .menu-navigation-container > ul > li > a::after, + .menu-navigation-en-container > ul > li > a::after { + content: ''; + display: block; + position: absolute; + height: 8px; + width: 100%; + bottom: 0; + left: 0; + z-index: 2; } + .menu-navigation-container > ul > li:nth-of-type(1) a:hover, + .menu-navigation-en-container > ul > li:nth-of-type(1) a:hover { + color: #e0775d; } + .menu-navigation-container > ul > li:nth-of-type(1) > a::after, + .menu-navigation-en-container > ul > li:nth-of-type(1) > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #e0775d 100%); } + .menu-navigation-container > ul > li:nth-of-type(1) > ul.sub-menu > li:first-of-type, + .menu-navigation-en-container > ul > li:nth-of-type(1) > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid #e0775d; + padding-bottom: 0.8rem; } + .menu-navigation-container > ul > li:nth-of-type(2) a:hover, + .menu-navigation-en-container > ul > li:nth-of-type(2) a:hover { + color: #7cc0c6; } + .menu-navigation-container > ul > li:nth-of-type(2) > a::after, + .menu-navigation-en-container > ul > li:nth-of-type(2) > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #7cc0c6 100%); } + .menu-navigation-container > ul > li:nth-of-type(2) > ul.sub-menu > li:first-of-type, + .menu-navigation-en-container > ul > li:nth-of-type(2) > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid #7cc0c6; + padding-bottom: 0.8rem; } + .menu-navigation-container > ul > li:nth-of-type(3) a:hover, + .menu-navigation-en-container > ul > li:nth-of-type(3) a:hover { + color: #46ae51; } + .menu-navigation-container > ul > li:nth-of-type(3) > a::after, + .menu-navigation-en-container > ul > li:nth-of-type(3) > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #46ae51 100%); } + .menu-navigation-container > ul > li:nth-of-type(3) > ul.sub-menu > li:first-of-type, + .menu-navigation-en-container > ul > li:nth-of-type(3) > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid #46ae51; + padding-bottom: 0.8rem; } + .menu-navigation-container > ul > li:nth-of-type(4) a:hover, + .menu-navigation-en-container > ul > li:nth-of-type(4) a:hover { + color: #e05680; } + .menu-navigation-container > ul > li:nth-of-type(4) > a::after, + .menu-navigation-en-container > ul > li:nth-of-type(4) > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #e05680 100%); } + .menu-navigation-container > ul > li:nth-of-type(4) > ul.sub-menu > li:first-of-type, + .menu-navigation-en-container > ul > li:nth-of-type(4) > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid #e05680; + padding-bottom: 0.8rem; } + .menu-navigation-container > ul > li:nth-of-type(5) a:hover, + .menu-navigation-en-container > ul > li:nth-of-type(5) a:hover { + color: #bb8dd9; } + .menu-navigation-container > ul > li:nth-of-type(5) > a::after, + .menu-navigation-en-container > ul > li:nth-of-type(5) > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #bb8dd9 100%); } + .menu-navigation-container > ul > li:nth-of-type(5) > ul.sub-menu > li:first-of-type, + .menu-navigation-en-container > ul > li:nth-of-type(5) > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid #bb8dd9; + padding-bottom: 0.8rem; } + .menu-navigation-container > ul > li > ul.sub-menu, + .menu-navigation-en-container > ul > li > ul.sub-menu { + display: flex; + flex-direction: column; + gap: 0.8rem; } + .menu-navigation-container > ul > li > ul.sub-menu > li > a, + .menu-navigation-en-container > ul > li > ul.sub-menu > li > a { + text-decoration: none; } + +.nav-axes-item .nav-axes-trigger { + background: none; + border: none; + padding: 0; + cursor: pointer; + font-family: "NewsCycle", sans-serif; + font-size: inherit; + color: inherit; + display: flex; + align-items: center; + gap: 0.3rem; } + .nav-axes-item .nav-axes-trigger i { + transition: transform 0.2s ease; } +.nav-axes-item.is-open .nav-axes-trigger i { + transform: rotate(180deg); } +.nav-axes-item .nav-axes-list { + display: none; + flex-direction: column; + gap: 0.8rem; + padding-top: 0.8rem; } + .nav-axes-item .nav-axes-list li a { + font-size: 0.8rem; + padding-left: 0.5rem; + text-decoration: none; } +.nav-axes-item.is-open .nav-axes-list { + display: flex; } + +.menu-navigation-container > ul > li:nth-of-type(1) .nav-axes-trigger:hover, +.menu-navigation-en-container > ul > li:nth-of-type(1) .nav-axes-trigger:hover { + color: #e0775d; } + +footer { + background-color: white; + width: 100%; + padding: 1rem 2rem; + margin-top: auto; } + +.footer-content { + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + gap: 2rem; } + @media (min-width: 768px) { + .footer-content { + gap: unset; + flex-direction: row; } } + +.footer-nav ul#menu-footer, +.footer-nav ul#menu-footer-en { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.5rem 2rem; } + @media (min-width: 768px) { + .footer-nav ul#menu-footer, + .footer-nav ul#menu-footer-en { + justify-content: start; } } + .footer-nav ul#menu-footer li a, + .footer-nav ul#menu-footer-en li a { + text-transform: uppercase; + font-size: 0.75rem; } + +.footer-logos { + display: inline-flex; + height: 3rem; + gap: 1rem; + padding: 0.5rem; + align-items: start; } + .footer-logos > a { + height: 100%; } + .footer-logos > a > img { + height: 100%; } + +.hero-header { + display: flex; + flex-direction: column-reverse; + position: relative; } + @media (min-width: 768px) { + .hero-header { + flex-direction: row; } } + +.hero-logos { + position: absolute; + top: 2.5vh; + display: inline-flex; + height: 3rem; + gap: 1rem; + padding: 0.5rem; + align-items: start; + background-color: #eeeeee; } + @media (min-width: 768px) { + .hero-logos { + position: unset; + top: unset; } } + .hero-logos > a { + height: 100%; } + .hero-logos > a > img { + height: 100%; } + .hero-logos > a:nth-of-type(2) { + mix-blend-mode: darken; } + +.color-changer { + transition: color 0.3s ease-out; } + +.hero-presentation { + font-family: "Gelasio", serif; + font-size: 1.6rem; + line-height: 1.1; } + @media (min-width: 768px) { + .hero-presentation { + margin-top: 2rem; + font-size: 2.6rem; } } + +.hero-presentation-detail { + margin-top: 1.5rem; + width: 90%; } + +.hero-content > .link-button { + margin-top: 2.5rem; } + +@media (min-width: 768px) { + .hero-content { + width: 75%; } } + +#sketch { + z-index: 0; + position: relative; + display: block; + height: 200px; } + @media (min-width: 768px) { + #sketch { + height: unset; + width: 25%; + margin-top: 0 !important; } } + +.floating-shape { + position: absolute; + top: 0; + left: 0; + transform-origin: top left; + will-change: transform; + pointer-events: none; } + .floating-shape svg { + overflow: visible; } + .floating-shape path, .floating-shape polyline, .floating-shape polygon, .floating-shape line, .floating-shape circle, .floating-shape ellipse, .floating-shape rect { + transition: fill-opacity 0.5s ease-in-out; } + +.thalim-text { + position: absolute; + pointer-events: none; + transition: opacity 0.3s ease-out; + display: flex; + gap: 0; + z-index: 20; + font-family: 'NewsCycle', sans-serif; + font-size: 26px; } + @media (min-width: 768px) { + .thalim-text { + font-size: 48px; } } + +.swiper-section { + margin-top: 3rem; + position: relative; } + .swiper-section .section-title { + font-family: NewsCycle; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; } + .swiper-section .section-title::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + .swiper-section .swiper_content_controls { + display: flex; + gap: 0.8rem; + align-items: center; + margin-top: 2rem; } + .swiper-section .swiper_content_controls .swiper { + overflow: hidden; + flex: 1; } + .swiper-section .swiper_content_controls .swiper-button-prev, + .swiper-section .swiper_content_controls .swiper-button-next { + position: static; + width: 2.4rem; + height: 2.4rem; + aspect-ratio: 1; + margin: 0; + background-color: #eeeeee; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: #1a1a1a; + transition: background-color 0.2s ease-out; + cursor: pointer; + flex-shrink: 0; } + .swiper-section .swiper_content_controls .swiper-button-prev::after, + .swiper-section .swiper_content_controls .swiper-button-next::after { + display: none; } + .swiper-section .swiper_content_controls .swiper-button-prev:hover, + .swiper-section .swiper_content_controls .swiper-button-next:hover { + background-color: #cccccc; } + .swiper-section .swiper_content_controls .swiper-button-prev i, + .swiper-section .swiper_content_controls .swiper-button-next i { + font-size: 1.1rem; + line-height: 1; } + .swiper-section .button-annonces { + display: flex; + justify-content: center; + margin-top: 1.2rem; } + +.message-agenda-section { + display: flex; + flex-direction: column; + width: 100%; + gap: 2rem; + margin-top: 3rem; } + @media (min-width: 768px) { + .message-agenda-section { + flex-direction: row; } } + +.message-du-labo, +.agenda { + position: relative; + padding: 2.5vh 5vw; + padding-bottom: 6vh; + background-color: white; + display: flex; + flex-direction: column; + align-items: flex-start; } + .message-du-labo::after, + .agenda::after { + content: ''; + display: block; + position: absolute; + height: 30px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; } + .message-du-labo .section-title, + .agenda .section-title { + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 3rem; } + .message-du-labo .section-title::after, + .agenda .section-title::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + @media (min-width: 768px) { + .message-du-labo, + .agenda { + padding: 3vh 3vw; + padding-bottom: 8vh; } } + +@media (min-width: 768px) { + .message-du-labo { + flex: 4; } } + +.messages-list { + width: 100%; } + @media (min-width: 768px) { + .messages-list { + flex: 1; + overflow: hidden; } } + +@media (min-width: 768px) { + .agenda { + flex: 3; } } + +.message-date { + font-size: 0.75rem; + color: #3e3e3e; + display: block; + margin-bottom: 0.3rem; } + +.message-item + .message-item { + margin-top: 1.5rem; + padding-top: 1.5rem; + border-top: 1px solid #eeeeee; } + +.message-content { + margin-bottom: 1.5rem; + padding-right: 2rem; + position: relative; } + .message-content > p { + margin: 0.7rem 0; } + .message-content p { + margin-bottom: 1rem; + line-height: 1.6; } + .message-content p strong { + font-weight: bold; } + .message-content p em { + font-style: italic; } + .message-content ul, .message-content ol { + line-height: 1.6; + padding-left: 0.8rem; } + .message-content ul { + list-style: inside "· "; } + .message-content ol { + list-style: inside decimal; } + .message-content blockquote { + padding-left: 1rem; + margin-left: 1.5rem; + border-left: solid 1px #eeeeee; } + @media (min-width: 768px) { + .message-content { + overflow: hidden; } + .message-content.is-overflowing::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 8rem; + background: linear-gradient(to bottom, transparent 0%, white 70%); + pointer-events: none; } } + +.button-messages, +.button-agenda { + align-self: center; + margin-top: auto; } + +.message-read-more { + display: none; + position: absolute; + bottom: 1rem; + left: 0; + z-index: 1; + font-size: 0.85rem; + text-decoration: none; + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; } + .is-overflowing .message-read-more { + display: inline-block; } + +.agenda-content { + width: 100%; } + .agenda-content .agenda-item { + display: flex; + align-items: center; + gap: 1.5rem; + transform: scale(1); + transition: transform 0.2s ease-out; + margin-bottom: 2rem; + text-decoration: none; + color: inherit; } + .agenda-content .agenda-item:hover { + transform: scale(0.97); } + .agenda-content .date-container { + background-color: #eeeeee; + text-transform: uppercase; + font-family: "NewsCycle", sans-serif; + text-align: center; + padding: 0.5rem; + position: relative; + flex-shrink: 0; + min-width: 3rem; } + .agenda-content .date-container > p { + position: relative; + z-index: 1; } + .agenda-content .date-container > p:first-of-type { + font-size: 1.3rem; } + .agenda-content .date-container > p:last-of-type { + margin-bottom: 3px; } + .agenda-content .date-container::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, #eeeeee 0%, #7cc0c6 100%); + z-index: 0; } + .agenda-content .event-content { + padding-bottom: 1rem; + border-bottom: 1px solid #7cc0c6; + flex: 1; } + .agenda-content .event-content .meta { + font-family: "NewsCycle", sans-serif; + display: flex; + gap: 1rem; + padding-bottom: 0.5rem; + text-transform: uppercase; + font-size: 0.8rem; + flex-wrap: wrap; + opacity: 0.7; } + .agenda-content .event-content .event-title { + font-family: "Gelasio", serif; + font-size: 1.1rem; } + +.keyword-cloud { + margin-top: 4rem; + margin-bottom: 3rem; + width: 100%; } + @media (min-width: 768px) { + .keyword-cloud { + margin-bottom: 0; } } + .keyword-cloud .section-title { + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; } + .keyword-cloud .section-title::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + +#keyword-container { + position: relative; + width: 100%; + min-height: 120px; } + +.keyword { + position: absolute; + font-family: "NewsCycle", sans-serif; + font-size: 0.7rem; + text-transform: uppercase; + white-space: nowrap; + text-decoration: none; + color: #1a1a1a; + opacity: 0; + cursor: pointer; } + @media (min-width: 768px) { + .keyword { + font-size: 0.95rem; } } + .keyword.keyword--visible { + animation: keywordFadeIn 0.7s ease-out forwards; } + +@keyframes keywordFadeIn { + from { + opacity: 0; + transform: scale(0.92); } + to { + opacity: 1; + transform: scale(1); } } +.quick-links { + position: fixed; + display: block; + background-color: #fcfcfc; + right: 0; + top: 35vh; + z-index: 3; + font-family: "NewsCycle", sans-serif; + max-width: 2.2rem; + overflow: hidden; + transition: max-width 0.9s ease-out, top 0.2s ease; + text-decoration: none; + z-index: 10; } + @media (min-width: 768px) { + .quick-links { + right: 2vw; } } + @media (min-width: 1024px) { + .quick-links { + right: 4vw; } } + .quick-links > ul { + display: flex; + flex-direction: column; + align-items: end; + gap: 0.8rem; + padding: 0.8rem 0.6rem; } + .quick-links > ul > li > a { + display: flex; + align-items: center; + gap: 0.8rem; + text-decoration: none; + white-space: nowrap; } + .quick-links > ul > li > a:hover { + font-weight: bold; } + .quick-links:hover { + max-width: 40vw; } + .quick-links::after { + content: ""; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 0%, #f7ff29 100%); + z-index: 2; } + +.post-card { + padding-bottom: 0.8rem; + border-bottom: solid 1px; } + .post-card.gradient--le-laboratoire { + border-color: #e0775d; } + .post-card.gradient--le-laboratoire .gradient-container { + background: linear-gradient(to bottom, #eeeeee 60%, #e0775d); } + .post-card.gradient--manifestations-scientifiques { + border-color: #7cc0c6; } + .post-card.gradient--manifestations-scientifiques .gradient-container { + background: linear-gradient(to bottom, #eeeeee 60%, #7cc0c6); } + .post-card.gradient--publications-et-productions { + border-color: #46ae51; } + .post-card.gradient--publications-et-productions .gradient-container { + background: linear-gradient(to bottom, #eeeeee 60%, #46ae51); } + .post-card.gradient--mediation-scientifique { + border-color: #e05680; } + .post-card.gradient--mediation-scientifique .gradient-container { + background: linear-gradient(to bottom, #eeeeee 60%, #e05680); } + .post-card.gradient--ressources { + border-color: #bb8dd9; } + .post-card.gradient--ressources .gradient-container { + background: linear-gradient(to bottom, #eeeeee 60%, #bb8dd9); } + .post-card:hover .gradient-container img, .post-card:hover .gradient-container h2 { + transform: scale(0.98); } + .post-card .gradient-container { + height: 25vh; + padding: 0.7rem; + display: flex; + align-items: center; + justify-content: center; + text-decoration: unset; } + .post-card .gradient-container img { + max-height: 100%; + transition: transform 0.2s ease-out; + transform: scale(1); + max-width: 100%; } + .post-card .gradient-container h2 { + font-family: Gelasio; + font-size: 1.7rem; + line-height: 1.1; + padding: 1.5rem; + transition: transform 0.2s ease-out; + transform: scale(1); + text-decoration: unset; } + .post-card .gradient-container.text-only { + font-family: Gelasio; + font-size: 1.7rem; + padding: 1.5rem; } + .post-card .gradient-container.text-only p { + transition: transform 0.2s ease-out; + transform: scale(1); } + .post-card .contextual-infos { + text-transform: uppercase; + font-size: 0.8rem; + display: flex; + justify-content: space-between; + margin-top: 0.6rem; + line-height: 1.3; } + .post-card .contextual-infos .authors a { + text-decoration: none; } + .post-card .contextual-infos .authors > span:not(:last-of-type)::after { + content: ", "; } + .post-card .contextual-infos .date-category { + display: flex; + flex-direction: column; + align-items: flex-end; + text-align: right; } + .post-card .contextual-infos .date-category a { + text-decoration: none; } + .post-card .title-bottom { + font-size: 1.2rem; + margin-top: 0.8rem; + line-height: 1.2; } + .post-card .title-bottom a { + text-decoration: none; } + +.category-archive { + margin-top: 0; } + .category-archive .category-header-top { + display: flex; + justify-content: space-between; + flex-direction: column; + align-items: start; } + .category-archive .category-header-top .breadcrumb { + font-size: 0.85rem; + margin-bottom: 1.5rem; + text-transform: uppercase; + line-height: 1.3; } + .category-archive .category-header-top .breadcrumb__separator { + margin: 0 0.4rem; } + @media (min-width: 768px) { + .category-archive .category-header-top { + flex-direction: row; } } + .category-archive h1 { + font-family: Gelasio; + font-weight: normal; + font-size: 1.8rem; + position: relative; + margin-top: 2rem; + display: inline-block; } + .category-archive h1::after { + content: ''; + display: block; + position: absolute; + height: 6px; + width: 100%; + bottom: -1.2rem; + left: 0; + z-index: 2; } + @media (min-width: 768px) { + .category-archive h1 { + font-size: 2.6rem; } + .category-archive h1::after { + bottom: -0.4rem; } } + .category-archive .category--le-laboratoire h1::after { + background: linear-gradient(to bottom, transparent 0%, #e0775d 50%); } + .category-archive .category--manifestations-scientifiques h1::after { + background: linear-gradient(to bottom, transparent 0%, #7cc0c6 50%); } + .category-archive .category--publications-et-productions h1::after { + background: linear-gradient(to bottom, transparent 0%, #46ae51 50%); } + .category-archive .category--mediation-scientifique h1::after { + background: linear-gradient(to bottom, transparent 0%, #e05680 50%); } + .category-archive .category--ressources h1::after { + background: linear-gradient(to bottom, transparent 0%, #bb8dd9 50%); } + +.breadcrumb a:not(.breadcrumb__cat) { + text-decoration: underline; + text-decoration-color: #cccccc; + text-underline-offset: 3px; } + +.category--le-laboratoire .breadcrumb__cat, +.category--le-laboratoire .breadcrumb__current { + text-decoration: underline; + text-decoration-color: #e0775d; + text-underline-offset: 3px; } + +.category--manifestations-scientifiques .breadcrumb__cat, +.category--manifestations-scientifiques .breadcrumb__current { + text-decoration: underline; + text-decoration-color: #7cc0c6; + text-underline-offset: 3px; } + +.category--publications-et-productions .breadcrumb__cat, +.category--publications-et-productions .breadcrumb__current { + text-decoration: underline; + text-decoration-color: #46ae51; + text-underline-offset: 3px; } + +.category--mediation-scientifique .breadcrumb__cat, +.category--mediation-scientifique .breadcrumb__current { + text-decoration: underline; + text-decoration-color: #e05680; + text-underline-offset: 3px; } + +.category--ressources .breadcrumb__cat, +.category--ressources .breadcrumb__current { + text-decoration: underline; + text-decoration-color: #bb8dd9; + text-underline-offset: 3px; } + +.category-archive .taxonomy-description { + font-family: "NewsCycle", sans-serif; + margin-top: 2rem; + line-height: 1.6; + max-width: 70ch; } + .category-archive .taxonomy-description a { + text-decoration: underline; } + .category-archive .taxonomy-description p { + margin-bottom: 1rem; + line-height: 1.6; } + .category-archive .taxonomy-description p strong { + font-weight: bold; } + .category-archive .taxonomy-description p em { + font-style: italic; } + .category-archive .taxonomy-description p:first-child { + margin-top: 0 !important; } + .category-archive .taxonomy-description ul, .category-archive .taxonomy-description ol { + line-height: 1.6; + padding-left: 0.8rem; } + .category-archive .taxonomy-description ul { + list-style: inside "· "; } + .category-archive .taxonomy-description ol { + list-style: inside decimal; } + .category-archive .taxonomy-description blockquote { + padding-left: 1rem; + margin-left: 1.5rem; + border-left: solid 1px #eeeeee; } +.category-archive .subcategory-section { + margin-top: 5rem; } + .category-archive .subcategory-section:first-of-type { + margin-top: 2rem; } + .category-archive .subcategory-section .subcategory-section__title { + font-family: NewsCycle; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + font-size: 1.2rem; } + .category-archive .subcategory-section .subcategory-section__title::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + .category-archive .subcategory-section .post-grid { + display: grid; + align-items: start; + grid-template-columns: 1fr; + gap: 2rem; + width: 100%; } + .category-archive .subcategory-section .post-grid .post-card { + min-width: 0; } + @media (min-width: 768px) { + .category-archive .subcategory-section .post-grid { + grid-template-columns: repeat(2, 1fr); } } + @media (min-width: 1024px) { + .category-archive .subcategory-section .post-grid { + grid-template-columns: repeat(3, 1fr); } } + .category-archive .subcategory-section .category-section-footer { + margin-top: 3rem; + display: flex; + justify-content: center; } + +/* +.category-header { + width: 100%; + margin-bottom: 3vh; + + &__back { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-size: 0.9rem; + text-decoration: none; + margin-bottom: 1rem; + + i { + font-size: 1.1rem; + } + } + + &__title { + font-size: 2rem; + margin-bottom: 0.5rem; + + @media ($tablet) { + font-size: 2.5rem; + } + } + + &__description { + color: $less-dark-gray; + } +} + +// Sub-category sections on parent category page +.subcategory-section { + width: 100%; + margin-bottom: 4vh; + + &__header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 2vh; + border-bottom: 2px solid $publications; + padding-bottom: 0.5rem; + } + + &__title { + font-size: 1.5rem; + + a { + text-decoration: none; + } + } + + &__see-all { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-size: 0.9rem; + text-decoration: none; + white-space: nowrap; + } + + &__empty { + color: $less-less-light-gray; + font-style: italic; + } +} + +// Post grid +.post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + width: 100%; + + @media ($tablet) { + grid-template-columns: repeat(2, 1fr); + } + + @media ($desktop) { + grid-template-columns: repeat(3, 1fr); + } +} + +// Post card +.post-card { + background-color: white; + display: flex; + flex-direction: column; + overflow: hidden; + + &__image { + width: 100%; + aspect-ratio: 4 / 3; + overflow: hidden; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + &__content { + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.4rem; + } + + &__title { + font-size: 1.1rem; + line-height: 1.3; + + a { + text-decoration: none; + } + } + + &__subtitle { + font-size: 0.9rem; + color: $less-dark-gray; + font-style: italic; + } + + &__date { + font-size: 0.8rem; + color: $less-less-light-gray; + } + + &__authors { + font-size: 0.9rem; + display: flex; + flex-wrap: wrap; + gap: 0.2rem; + + .post-card__author { + &:not(:last-child)::after { + content: ','; + } + } + + &--external { + color: $less-dark-gray; + } + } + + &__role, + &__publisher, + &__journal { + font-size: 0.85rem; + color: $less-dark-gray; + } + + &__axes, + &__tags { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + } + + &__axe, + &__tag { + font-size: 0.75rem; + background-color: $light-gray; + padding: 0.15rem 0.5rem; + } + + &__axe { + border-left: 2px solid $publications; + } + + &__links { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.3rem; + } + + &__link { + font-size: 0.8rem; + + &--hal { + color: $publications; + } + } +} + +// Infinite scroll spinner +.scroll-spinner { + display: flex; + justify-content: center; + align-items: center; + gap: 0.4rem; + padding: 2rem 0; + + &__dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: $publications; + animation: scroll-spinner-bounce 1.2s infinite ease-in-out; + + &:nth-child(2) { + animation-delay: 0.2s; + } + + &:nth-child(3) { + animation-delay: 0.4s; + } + } +} + +@keyframes scroll-spinner-bounce { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.4; + } + 40% { + transform: scale(1); + opacity: 1; + } +} +*/ +.agenda-view-container { + display: none; + margin-top: 3rem; } + .agenda-view-container.is-active { + display: block; } + +.agenda-view-title { + font-family: NewsCycle; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + font-size: 1.2rem; } + .agenda-view-title::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + +.agenda-swiper-wrap { + display: flex; + align-items: center; + gap: 0.8rem; + margin-top: 2rem; } + +.agenda-swiper { + flex: 1; + overflow: hidden; } + +.agenda-swiper-prev, +.agenda-swiper-next { + flex-shrink: 0; + background: none; + border: none; + cursor: pointer; + font-size: 1.2rem; + padding: 0.4rem; + color: #1a1a1a; + line-height: 1; } + .agenda-swiper-prev:hover, + .agenda-swiper-next:hover { + color: black; } + .agenda-swiper-prev.swiper-button-disabled, + .agenda-swiper-next.swiper-button-disabled { + opacity: 0.25; + pointer-events: none; } + +.agenda-card { + display: flex; + flex-direction: column; + text-decoration: none; + color: inherit; + padding-bottom: 1.5rem; + border-bottom: 1px solid #eeeeee; + height: 100%; + position: relative; + transition: transform 0.2s ease-out; + margin-right: 0 !important; } + .agenda-card::before { + content: ''; + position: absolute; + top: 1.7rem; + left: 0; + right: 0; + height: 1px; + background-color: #cccccc; + z-index: 0; + width: calc(100% + 20px); } + @media (min-width: 768px) { + .agenda-card::before { + width: calc(100% + 24px); } } + @media (min-width: 1024px) { + .agenda-card::before { + width: calc(100% + 32px); } } + .agenda-card:hover { + transform: scale(0.98); } + .agenda-card__dates { + display: flex; + align-items: flex-start; + gap: 0.5rem; + margin-bottom: 1rem; } + .agenda-card__body { + flex: 1; } + .agenda-card__meta { + display: flex; + flex-wrap: wrap; + gap: 0.3rem 0.8rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.75rem; + text-transform: uppercase; + opacity: 0.65; + margin-bottom: 0.5rem; } + .agenda-card__title { + font-family: "Gelasio", serif; + font-size: 1rem; + line-height: 1.35; } + +.category--manifestations-scientifiques .agenda-card { + border-bottom-color: #7cc0c6; } + +.category--le-laboratoire .agenda-card { + border-bottom-color: #e0775d; } + +.category--publications-et-productions .agenda-card { + border-bottom-color: #46ae51; } + +.category--mediation-scientifique .agenda-card { + border-bottom-color: #e05680; } + +.category--ressources .agenda-card { + border-bottom-color: #bb8dd9; } + +.category--manifestations-scientifiques .agenda-date-box { + background: linear-gradient(to bottom, #eeeeee 60%, #7cc0c6); } + +.category--le-laboratoire .agenda-date-box { + background: linear-gradient(to bottom, #eeeeee 60%, #e0775d); } + +.category--publications-et-productions .agenda-date-box { + background: linear-gradient(to bottom, #eeeeee 60%, #46ae51); } + +.category--mediation-scientifique .agenda-date-box { + background: linear-gradient(to bottom, #eeeeee 60%, #e05680); } + +.category--ressources .agenda-date-box { + background: linear-gradient(to bottom, #eeeeee 60%, #bb8dd9); } + +.agenda-date-box { + position: relative; + z-index: 1; + background-color: #eeeeee; + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + text-align: center; + padding: 0.4rem 0.55rem; + flex-shrink: 0; + min-width: 2.6rem; } + .agenda-date-box .agenda-date-day { + display: block; + font-size: 1.3rem; + line-height: 1; } + .agenda-date-box .agenda-date-month { + display: block; + font-size: 0.65rem; + margin-top: 2px; } + .agenda-date-box .agenda-date-year { + display: block; + font-size: 0.6rem; + opacity: 0.65; } + .agenda-date-box sup { + font-size: 0.7rem; } + +.agenda-date-arrow { + font-size: 0.85rem; + opacity: 0.5; } + +.filters-bar { + margin-top: 2.5rem; + display: flex; + align-items: center; + gap: 0.8rem; + flex-wrap: wrap; + position: relative; + padding-bottom: 0.6rem; } + .filters-bar::after { + content: ''; + position: absolute; + bottom: 0; + left: -5vw; + width: calc(100% + 10vw); + height: 1px; + background-color: #eeeeee; } + @media (min-width: 768px) { + .filters-bar::after { + left: -3vw; + width: calc(100% + 6vw); } } + +.filters-toggle-btn { + display: flex; + align-items: center; + gap: 0.5rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.9rem; + text-transform: uppercase; + background-color: #eeeeee; + border: unset; + cursor: pointer; + padding: 0.4rem 0.8rem; + color: #1a1a1a; + flex-shrink: 0; } + .filters-toggle-btn .filters-chevron { + transition: transform 0.2s; } + .filters-toggle-btn.is-open .filters-chevron { + transform: rotate(180deg); } + .filters-toggle-btn:hover { + background-color: #cccccc; } + +.filters-active-chips { + display: flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; } + +.filter-chip { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.8rem; + text-transform: uppercase; + background-color: #eeeeee; + padding: 0.3rem 0.5rem; + text-decoration: none; + color: #1a1a1a; + border: none; + cursor: pointer; } + .filter-chip i { + font-size: 0.7rem; + opacity: 0.5; } + .filter-chip:hover { + background-color: #cccccc; } + .filter-chip:hover i { + opacity: 1; } + +.category-filters { + font-size: 0.9rem; + font-family: "NewsCycle", sans-serif; + margin-top: 0; + display: none; + padding: 2rem 5vw; + padding-bottom: 0.5rem !important; + margin-left: -5vw; + width: calc(100% + 10vw); + justify-content: space-between; + background-color: #fcfcfc; + border-top: solid 1px #eeeeee; + border-bottom: solid 1px #eeeeee; + flex-direction: column; } + .category-filters.is-open { + display: flex; } + .category-filters > div { + margin-top: 1.2rem; + margin-bottom: 1.2rem; + flex: auto; } + .category-filters > div.filtre-rubrique { + flex: 2; } + .category-filters > div.filtre-categorie { + flex: 1; } + .category-filters > div.filtre-date { + flex: 1; } + .category-filters > div.filtre-axe { + flex: 1; } + .category-filters > div:first-of-type { + margin-top: 0; + padding-left: 0; } + .category-filters > div:last-of-type { + margin-bottom: 0; } + .category-filters > div:not(:last-of-type) { + padding-bottom: 1.8rem; + border-bottom: solid 1px #cccccc; } + .category-filters > div .filter-section-header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 2rem; } + .category-filters > div .section-title { + text-transform: uppercase; + position: relative; + display: inline-block; } + .category-filters > div .section-title::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + .category-filters > div ul { + display: flex; + justify-content: start; + flex-wrap: wrap; + align-items: start; + gap: 0.5rem 0.2rem; + list-style: none; + margin: 0; + padding: 0; } + .category-filters > div ul li { + font-size: 1rem; + cursor: pointer; + padding: 0.4rem 0.6rem; } + .category-filters > div ul li a { + text-decoration: none; } + .category-filters > div ul li:hover, .category-filters > div ul li.is-active { + background-color: #cccccc; } + .category-filters .filter-dd { + position: relative; + margin-top: 0; + display: inline-block; } + .category-filters .filter-dd .dd-title { + padding: 0.3rem 0.5rem; + display: flex; + align-items: center; + gap: 0.4rem; + cursor: pointer; } + .category-filters .filter-dd .dd-title > p, + .category-filters .filter-dd .dd-title > i { + font-size: 0.9rem; } + .category-filters .filter-dd .dd-title > i { + transition: transform 0.2s; } + .category-filters .filter-dd.is-open .dd-title > i { + transform: rotate(180deg); } + .category-filters .filter-dd.is-active .dd-title { + background-color: #cccccc; } + .category-filters .filter-dd .dd-content { + position: absolute; + padding: 0; + z-index: 5; + background: white; + border: 1px solid #cccccc; + min-width: 100%; } + .category-filters .filter-dd .dd-content ul { + gap: 0.3rem; + padding: 0.5rem; } + .category-filters .filter-dd .dd-content ul li { + padding: 0.4rem; + white-space: nowrap; + background-color: #eeeeee; } + .category-filters .filter-dd .dd-content#filter-axe-popover { + right: auto; + left: 0; + min-width: 280px; + max-width: min(420px, 90vw); } + .category-filters .filter-dd .dd-content#filter-axe-popover ul { + flex-direction: column; + padding: 0; + gap: 0; } + .category-filters .filter-dd .dd-content#filter-axe-popover ul li { + width: 100%; + margin: 0; + white-space: normal; } + .category-filters .filter-dd .dd-content#filter-axe-popover ul li:nth-of-type(odd) { + background-color: white; } + .category-filters .filter-dd .dd-content#filter-axe-popover ul li:first-of-type { + background-color: #cccccc; } + .category-filters .filter-dd .dd-content#filter-axe-popover ul li.dd-axe-group-label { + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #3e3e3e; + background-color: #eeeeee !important; + padding: 0.5rem 0.4rem; + cursor: default; + pointer-events: none; + border-top: solid 1px #cccccc; } + .category-filters .filter-dd .dd-content#filter-axe-popover ul li.dd-axe-group-label:first-child { + margin-top: 0; } + .category-filters .filter-dd .dd-date-fields { + padding: 0.6rem; + padding-top: 1rem; + display: flex; + flex-direction: column; + gap: 0.4rem; + border-top: 1px solid #cccccc; } + .category-filters .filter-dd .dd-date-fields label { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.85rem; + text-transform: uppercase; } + .category-filters .filter-dd .dd-date-fields input[type="date"] { + padding: 0.2rem 0.4rem; + border: 1px solid #cccccc; + font-size: 0.85rem; + font-family: "NewsCycle", sans-serif; } + .category-filters .filter-dd .dd-date-apply { + margin-top: 0.6rem; + width: 100%; + padding: 0.3rem; + border: none; + font-size: 0.85rem; + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + cursor: pointer; } + .category-filters .membres-search-input.is-active { + background-color: #cccccc; } + .category-filters .date-reset-link { + display: block; + font-size: 0.75rem; + text-transform: uppercase; + text-decoration: none; + color: inherit; + opacity: 0.6; + padding: 0 0.5rem 0.4rem; } + .category-filters .date-reset-link:hover { + opacity: 1; } + @media (min-width: 768px) { + .category-filters { + padding: 2rem 3vw; + margin-left: -3vw; + width: calc(100% + 6vw); + flex-direction: row; } + .category-filters > div { + margin-top: 0; + margin-bottom: 0; + padding-left: 1.5rem; } + .category-filters > div:not(:last-of-type) { + padding-right: 0.8rem; + border-bottom: unset; + border-right: solid 1px #cccccc; } } + +.article { + margin-top: 0; + width: 100%; } + .article .category-header-top { + display: flex; + flex-direction: column; + align-items: start; + justify-content: space-between; } + .article .category-header-top .breadcrumb { + font-size: 0.85rem; + margin-bottom: 1.5rem; + text-transform: uppercase; + line-height: 1.3; } + .article .category-header-top .breadcrumb__separator { + margin: 0 0.4rem; } + @media (min-width: 768px) { + .article .category-header-top { + flex-direction: row; } } + .article h2 { + font-family: Gelasio; + font-weight: normal; + font-size: 1.8rem; + position: relative; + display: inline-block; + margin-top: 2rem; + margin-bottom: 2rem; } + .article h2::after { + content: ''; + display: block; + position: absolute; + height: 5px; + width: 100%; + left: 0; + z-index: 2; } + .article h2 p { + line-height: 1.3; } + .article h2 p:last-of-type { + margin-top: 0.3rem; + margin-bottom: 0.3rem; } + .article h2 p + p { + font-size: 1.6rem; } + @media (min-width: 768px) { + .article h2 p { + font-size: 2.2rem !important; } + .article h2 p + p { + font-size: 1.6rem !important; } + .article h2::after { + bottom: -0.4rem; } } + .article.category--le-laboratoire h2::after { + background: linear-gradient(to bottom, transparent 0%, #e0775d 30%); } + .article.category--manifestations-scientifiques h2::after { + background: linear-gradient(to bottom, transparent 0%, #7cc0c6 30%); } + .article.category--publications-et-productions h2::after { + background: linear-gradient(to bottom, transparent 0%, #46ae51 30%); } + .article.category--mediation-scientifique h2::after { + background: linear-gradient(to bottom, transparent 0%, #e05680 30%); } + .article.category--ressources h2::after { + background: linear-gradient(to bottom, transparent 0%, #bb8dd9 30%); } + .article .article-type { + display: inline-block; + margin-top: 2rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + text-transform: uppercase; + background-color: #eeeeee; + padding: 0.2rem 0.6rem; } + .article .maj { + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem !important; + color: #3e3e3e; + margin-top: 1rem; + margin-bottom: 2.5rem; + text-transform: uppercase; } + .article .imgs { + width: 100%; + margin-bottom: 3rem; } + .article .imgs figure { + width: 100%; } + .article .imgs figure img { + width: 100%; + height: auto; } + .article .imgs figure figcaption { + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + color: #3e3e3e; + margin-top: 0.5rem; + font-style: italic; } + @media (min-width: 768px) { + .article .imgs figure { + width: 50%; } } + .article .imgs--swiper { + display: flex; + align-items: center; + gap: 0.5rem; } + @media (min-width: 768px) { + .article .imgs--swiper { + width: 50%; } } + .article .imgs--swiper .swiper { + flex: 1; + min-width: 0; } + .article .imgs--swiper figure { + width: 100%; } + .article .imgs--swiper .swiper-pagination { + position: static; + margin-top: 0.5rem; + text-align: center; } + .article .imgs--swiper .swiper-pagination-bullet-active { + background-color: #3e3e3e !important; } + .article .article-content { + display: flex; + flex-direction: column-reverse; + gap: 3rem; } + @media (min-width: 768px) { + .article .article-content { + flex-direction: row; } } + .article .sidebar { + background-color: #fcfcfc; } + @media (min-width: 768px) { + .article .sidebar { + width: 25%; + padding: 0.8rem; + flex-shrink: 0; } } + .article .sidebar .sidebar-container { + position: sticky; + top: 6rem; + display: flex; + flex-direction: column; + gap: 1.5rem; } + .article .sidebar .sidebar-section { + display: flex; + flex-direction: column; + gap: 0.8rem; + line-height: 1.4; } + .article .sidebar .sidebar-section p { + margin: 0; } + .article .sidebar .sidebar-section.reference-bibliographique { + display: inline-block; } + .article .sidebar .sidebar-section.reference-bibliographique em, .article .sidebar .sidebar-section.reference-bibliographique i { + font-style: italic; } + .article .sidebar .sidebar-section.reference-bibliographique strong { + font-weight: bold; } + .article .sidebar p { + font-family: "NewsCycle", sans-serif; } + .article .sidebar .link-button { + width: fit-content; } + @media (min-width: 768px) { + .article .sidebar .imgs--portrait { + width: calc(100% + 1.6rem); + margin-left: -0.8rem; + margin-right: -0.8rem; } } + .article .sidebar .imgs--portrait .sidebar-portrait { + width: 100%; } + .article .sidebar .sidebar-portrait { + width: 100%; } + .article .sidebar .sidebar-portrait img { + width: 100%; + height: auto; } + .article .sidebar .sidebar-portrait figcaption { + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + color: #3e3e3e; + margin-top: 0.5rem; + font-style: italic; } + .article .main-content-text { + margin-top: 1rem; + flex: 1; + min-height: unset; + /* Affichage posts newsletter */ } + .article .main-content-text:has(table[role=presentation]) p:not(table[role=presentation] p):not(.maj) { + display: none; } + .article .main-content-text:has(table[role=presentation]) table[role=presentation] p { + margin: unset; } + .article .main-content-text:has(table[role=presentation]) table td { + vertical-align: top; } + .article .main-content-text:has(table[role=presentation]) table br { + display: none; } + .article .main-content-text > *:not(.article-field) { + font-size: 1.25rem; } + .article .main-content-text a { + text-decoration: underline; } + .article .main-content-text p { + margin-bottom: 1rem; + line-height: 1.4; } + .article .main-content-text p strong { + font-weight: bold; } + .article .main-content-text p em { + font-style: italic; } + .article .main-content-text p:first-child { + margin-top: 0 !important; } + .article .main-content-text ul, .article .main-content-text ol { + line-height: 1.4; + padding-left: 0.8rem; } + .article .main-content-text ul { + list-style: inside "· "; } + .article .main-content-text ol { + list-style: inside decimal; } + .article .main-content-text blockquote { + padding-left: 1rem; + margin-left: 1.5rem; + border-left: solid 1px #eeeeee; } + .article .main-content-text p:first-of-type + .mots-cles { + margin-top: 1rem; } + .article .main-content-text .article-field { + font-size: 0.9rem !important; + line-height: 1.4 !important; + margin-bottom: 1.5rem !important; } + .article .main-content-text .article-field i { + font-style: italic; } + .article .main-content-text .mots-cles { + margin-top: 4rem; } + .article .main-content-text .mots-cles ~ .mots-cles { + margin-top: 1rem; } + .article .main-content-text .canal-u-embeds, + .article .main-content-text .youtube-embeds { + margin-top: 2rem; + display: flex; + flex-direction: column; + gap: 1.5rem; } + .article .main-content-text .canal-u-embed, + .article .main-content-text .video-embed { + position: relative; + width: 100%; + padding-bottom: 56.25%; + height: 0; } + .article .main-content-text .canal-u-embed iframe, + .article .main-content-text .video-embed iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; } + .article .inline-title { + text-transform: uppercase; + position: relative; } + .article .inline-title::after { + content: ''; + width: 100%; + height: 10px; + bottom: -1px; + left: 0; + position: absolute; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); } + .article .related-posts, + .article .seances-section { + margin-top: 5rem; } + .article .related-posts h3, + .article .seances-section h3 { + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; } + .article .related-posts h3::after, + .article .seances-section h3::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + .article .related-posts .post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; } + .article .related-posts .post-grid .post-card { + min-width: 0; } + .article .related-posts .post-grid .post-card a { + text-decoration: none; } + .article .related-posts .post-grid .post-card a h2::after { + display: none; } + @media (min-width: 768px) { + .article .related-posts .post-grid { + grid-template-columns: repeat(2, 1fr); } } + .article .seances-list { + display: flex; + flex-direction: column; + gap: 1.5rem; } + .article .seance-header { + display: flex; + align-items: stretch; + gap: 1.2rem; + padding: 0.1rem; + background-color: #eeeeee; + cursor: pointer; + transition: background-color 0.15s; } + .article .seance-header:hover { + background-color: #cccccc; } + @media (min-width: 768px) { + .article .seance-header { + padding: 0.8rem; } } + .article .seance-date { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 3rem; + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + line-height: 1.2; + padding: 0.4rem 0; + background: linear-gradient(to bottom, #fcfcfc 60%, #7cc0c6); } + .article .seance-date__day { + font-size: 1.4rem; } + .article .seance-date__month { + font-size: 0.85rem; } + .article .seance-date__year { + font-size: 0.75rem; } + .article .seance-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.2rem; } + .article .seance-info .seance-title { + font-family: "Gelasio", serif; + font-size: 1.15rem; + line-height: 1.2; + text-decoration: none; + padding: 0.6rem 0; } + @media (min-width: 768px) { + .article .seance-info .seance-title { + padding: unset; } } + .article .seance-info .seance-intervenants { + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + margin-top: 0.1rem; + color: black; } + .article .seance-info .seance-intervenants a { + text-decoration: none; } + .article .seance-info .seance-intervenants a:hover { + text-decoration: underline; } + .article .seance-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + align-self: center; + margin-right: 0.5rem; } + .article .seance-item.is-open .seance-chevron { + transform: rotate(180deg); } + .article .seance-content { + padding: 1rem 1rem 1rem 1rem; + background-color: #fcfcfc; + font-family: "NewsCycle", sans-serif; + font-size: 0.9rem; + line-height: 1.4; } + .article .seance-content .seance-content-infos { + display: flex; + width: 100%; + justify-content: space-between; + margin-bottom: 1rem; } + .article .seance-content .seance-content-infos > .seance-content-lieu { + text-align: right; } + .article .seance-content .seance-content-infos > .seance-content-lieu > p { + margin: 0; } + .article .seance-content p { + margin-bottom: 0.6rem; } + .article .seance-content .seance-images { + margin-top: 1.5rem; + margin-bottom: 0; } + @media (min-width: 768px) { + .article .seance-content .seance-images figure { + width: 50%; } } + .article .seance-content .seance-extras { + display: flex; + flex-direction: column; + align-items: start; + gap: 0.8rem; + margin-top: 1.5rem; } + .article .seance-content .seance-related { + margin-top: 1.5rem; } + .article .seance-content .seance-related h4 { + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + margin-bottom: 1rem; } + .article .seance-content .seance-related .post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; } + .article .seance-content .seance-related .post-grid .post-card { + min-width: 0; } + .article .seance-content .seance-related .post-grid .post-card a { + text-decoration: none; } + .article .seance-content .seance-related .post-grid .post-card a h2::after { + display: none; } + @media (min-width: 768px) { + .article .seance-content .seance-related .post-grid { + grid-template-columns: repeat(2, 1fr); } } + @media (min-width: 768px) { + .article .seance-content { + padding: 1rem 1rem 1rem 5.5rem; } } + +.author-header { + display: flex; + gap: 2rem; + align-items: flex-start; + margin: 2rem 0; } + +.author-avatar { + flex-shrink: 0; } + .author-avatar img { + width: 140px; + height: 140px; + object-fit: cover; } + @media (min-width: 768px) { + .author-avatar img { + width: 180px; + height: 180px; } } + +.author-identity h2 { + margin-top: 0; } +.author-identity + .author-bio { + margin-top: 2rem; } + +.author-role { + font-family: "NewsCycle", sans-serif; + margin-top: 0.4rem; + opacity: 0.85; + line-height: 1.4; } + +.author-bio { + margin-bottom: 1.5rem; + line-height: 1.6; } + .author-bio > p { + margin: 0.8rem 0; } + .author-bio hr { + display: none; } + .author-bio h3 { + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; } + +.author-resume-these { + margin-top: 1rem; + line-height: 1.6; } + .author-resume-these > p { + margin: 0.8rem 0; } + +.author-cat-footer { + display: flex; + justify-content: center; + margin-top: 1.5rem; + padding-top: 1rem; + border-top: 1px solid #eeeeee; } + +.author-titre-these { + font-family: "Gelasio", serif; + font-size: 1.4rem; + line-height: 1.3 !important; + margin-bottom: 0.8rem; } + +.these-inline-title { + text-transform: uppercase; } + +.domaines-autres > p:first-of-type, +.recherches-en-cours > p:first-of-type { + margin-top: 0.4rem; } + +.author-posts-section { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 5rem; } + +.author-posts-header { + display: flex; + align-items: center; + gap: 1.2rem; + padding: 0.8rem; + background-color: #eeeeee; + cursor: pointer; + transition: background-color 0.15s; + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; } + .author-posts-header:hover { + background-color: #cccccc; } + +.author-posts-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + margin-left: auto; } + .author-posts-item.is-open .author-posts-chevron { + transform: rotate(180deg); } + +.author-posts-content { + padding: 1rem; + background-color: #fcfcfc; + position: relative; + padding-bottom: 3rem; } + .author-posts-content::after { + content: ''; + display: block; + position: absolute; + height: 30px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; } + .author-posts-content article a { + text-decoration: none !important; } + .author-posts-content article a h2 { + margin-top: 0 !important; } + +.author-post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + width: 100%; } + .author-post-grid h2::after { + display: none; } + @media (min-width: 768px) { + .author-post-grid { + grid-template-columns: repeat(2, 1fr); } } + +.membres-section { + margin-top: 4rem; + display: flex; + flex-direction: column; + gap: 1rem; } + +.membres-item.is-open { + position: relative; + padding-bottom: 1rem; } + .membres-item.is-open::after { + content: ''; + display: block; + position: absolute; + height: 30px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; } + +.membres-header { + display: flex; + align-items: center; + gap: 1.2rem; + padding: 0.8rem; + background-color: #eeeeee; + cursor: pointer; + transition: background-color 0.15s; + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; } + .membres-header:hover { + background-color: #cccccc; } + +.membres-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + margin-left: auto; } + .membres-item.is-open .membres-chevron { + transform: rotate(180deg); } + +.membres-sort-chevron { + font-size: 0.8rem; + opacity: 0.3; + transition: transform 0.2s, opacity 0.15s; + vertical-align: middle; + margin-left: 0.3rem; } + +.membres-content { + padding: 1rem; + background-color: #fcfcfc; + font-family: "NewsCycle", sans-serif; + font-size: 0.9rem; + line-height: 1.6; } + +.membres-table { + width: 100%; + table-layout: fixed; + border-collapse: collapse; + font-size: 0.9rem; } + .membres-table th { + width: 33.333%; + text-align: left; + text-transform: uppercase; + font-family: "NewsCycle", sans-serif; + font-weight: normal; + font-size: 0.8rem; + padding: 0.5rem 1.5rem 0.5rem 0.5rem; + background-color: #eeeeee; + cursor: pointer; + user-select: none; + white-space: nowrap; } + .membres-table th:hover .membres-sort-chevron { + opacity: 0.7; } + .membres-table th.sort-asc .membres-sort-chevron { + opacity: 1; + transform: rotate(180deg); } + .membres-table th.sort-desc .membres-sort-chevron { + opacity: 1; + transform: rotate(0deg); } + .membres-table tbody tr { + cursor: pointer; + transition: background-color 0.15s; + background-color: white; } + .membres-table tbody tr.is-even-row { + background-color: #eeeeee; } + .membres-table tbody tr:last-child { + border-bottom: none; } + .membres-table tbody tr:hover { + background-color: #cccccc; } + .membres-table td { + padding: 0.6rem 1.5rem 0.6rem 0.5rem; + vertical-align: top; + line-height: 1.4; } + .membres-table td:first-child { + white-space: nowrap; } + .membres-table td a { + text-decoration: none; } + +#membre-popover { + position: fixed; + z-index: 9999; + background: white; + pointer-events: none; + max-width: 380px; + min-width: 180px; + padding: 1rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + opacity: 0; + transition: opacity 0.1s; + border: solid 1px #eeeeee; } + #membre-popover.is-visible { + opacity: 1; } + #membre-popover .membre-popover-inner { + display: flex; + gap: 1rem; + align-items: flex-start; } + #membre-popover .membre-popover-pic { + width: 80px; + height: 80px; + object-fit: cover; + object-position: top; + flex-shrink: 0; } + #membre-popover .membre-popover-name { + font-size: 1.1rem; + font-weight: normal; + margin: 0 0 0.3rem; } + #membre-popover .membre-popover-status { + text-transform: uppercase; + color: #3e3e3e; + margin: 0 0 0.6rem; } + #membre-popover .membre-popover-domaines { + font-size: 0.8rem; + margin: 0 0 0.3rem; + line-height: 1.5; } + #membre-popover .membre-popover-autres { + font-size: 0.8rem; + margin: 0; + line-height: 1.5; + color: #3e3e3e; } + +.filtre-role { + flex: 2; } + +.filtre-recherche { + flex: 1; } + +.membres-search-input { + width: 100%; + border: none; + padding: 0.6rem 0.8rem; + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + background-color: #eeeeee; + outline: none; } + .membres-search-input::placeholder { + color: #3e3e3e; } + .membres-search-input:focus { + background-color: #cccccc; } + @media (min-width: 1024px) { + .membres-search-input { + width: 50%; } } + +.labo-images { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + margin-bottom: 3rem; } + +.labo-image { + flex: 0 0 auto; + width: 100%; + margin-top: 2rem; } + .labo-image img { + width: 100%; + height: auto; + display: block; } + .labo-image figcaption { + font-family: "NewsCycle", sans-serif; + font-size: 0.85rem; + color: #3e3e3e; + margin-top: 0.5rem; } + @media (min-width: 768px) { + .labo-image { + width: calc(50% - 0.75rem); } } + +.labo-section { + margin-top: 5rem; } + .labo-section > h3 { + font-family: "NewsCycle", sans-serif; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; } + .labo-section > h3::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; + bottom: -10px; } + +.labo-dropdowns { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 5rem; } + .labo-section .labo-dropdowns { + margin-top: 0; } + +.labo-dropdown-item.is-open { + position: relative; + padding-bottom: 1rem; } + .labo-dropdown-item.is-open::after { + content: ''; + display: block; + position: absolute; + height: 30px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 50%, #f7ff29 100%); + z-index: 2; + pointer-events: none; } + +.labo-dropdown-header { + display: flex; + align-items: center; + gap: 1.2rem; + padding: 0.8rem; + background-color: #eeeeee; + cursor: pointer; + transition: background-color 0.15s; + font-family: "NewsCycle", sans-serif; + font-size: inherit; + font-weight: normal; + text-transform: uppercase; + margin: 0; } + .labo-dropdown-header:hover { + background-color: #cccccc; } + +.labo-dropdown-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + margin-left: auto; } + .labo-dropdown-item.is-open .labo-dropdown-chevron { + transform: rotate(180deg); } + +.labo-dropdown-content { + padding: 1rem 1rem 1rem 1rem; + background-color: #fcfcfc; + font-family: "NewsCycle", sans-serif; + font-size: 0.9rem; + line-height: 1.6; } + .labo-dropdown-content p { + margin-bottom: 0.6rem; } + .labo-dropdown-content p + ul { + margin-top: -1rem; } + .labo-dropdown-content ul, .labo-dropdown-content ol { + padding-left: 1.2rem; + margin-bottom: 0.6rem; } + +.labo-axes-list { + list-style: none; + padding: 0; + margin: 0; } + .labo-axes-list li { + padding: 0.5rem 0; + border-bottom: 1px solid #eeeeee; } + .labo-axes-list li:last-child { + border-bottom: none; } + .labo-axes-list li a { + text-decoration: none; } + .labo-axes-list li a:hover { + text-decoration: underline; } + +.labo-bibliotheques { + font-family: "NewsCycle", sans-serif; + font-size: 0.9rem; + line-height: 1.6; } + .labo-bibliotheques p { + margin-bottom: 0.8rem; } + .labo-bibliotheques p + ul { + margin-top: -1rem; + margin-bottom: 0.6rem; } + .labo-bibliotheques a { + text-decoration: underline; } + +.programme-description { + margin-bottom: 1.5rem; } + .programme-description p { + margin-bottom: 0.6rem; } + .programme-description ul, .programme-description ol { + padding-left: 1.2rem; } + .programme-description a { + text-decoration: underline; } + +.programme-link { + padding-top: 0.5rem; + border-top: 1px solid #eeeeee; } + +.search-page-form { + margin-top: 2rem; + margin-bottom: 3rem; } + @media (min-width: 1024px) { + .search-page-form { + width: 50%; } } + .search-page-form .search-panel__desc { + margin-bottom: 1.3rem; } + +.search-page-form + #category-filters { + margin-top: 0; } + +.author-results { + margin-bottom: 3rem; } + .author-results__title { + font-family: "NewsCycle", sans-serif; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 1.2rem; + color: #3e3e3e; } + +.taxonomy-results { + margin-bottom: 3rem; } + .taxonomy-results__title { + font-family: "NewsCycle", sans-serif; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 1.2rem; + color: #3e3e3e; } + .taxonomy-results__list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + gap: 0.6rem; } + .taxonomy-results__link { + display: block; + padding: 0.5rem 1rem; + border: 1px solid #eeeeee; + text-decoration: none; + transition: border-color 0.15s; } + .taxonomy-results__link:hover { + border-color: #1a1a1a; } + .taxonomy-results__name { + font-family: "Gelasio", serif; + font-size: 0.95rem; + line-height: 1.3; } + .taxonomy-results__meta { + display: block; + font-family: "NewsCycle", sans-serif; + font-size: 0.7rem; + text-transform: uppercase; + color: #3e3e3e; + margin-top: 0.15rem; } + +.author-cards-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; } + @media (min-width: 768px) { + .author-cards-grid { + grid-template-columns: repeat(3, 1fr); } } + @media (min-width: 1024px) { + .author-cards-grid { + grid-template-columns: repeat(6, 1fr); } } + +.author-card { + border-bottom: solid 1px #e0775d; + padding-bottom: 0.4rem; } + .author-card__visual { + display: flex; + align-items: center; + justify-content: center; + height: 14vh; + padding: 0.7rem; + background-color: #f7dcd5; + overflow: hidden; + text-decoration: none; } + .author-card__visual img { + max-height: 100%; + max-width: 100%; + width: auto; + height: auto; + transition: transform 0.2s ease-out; } + .author-card__visual:hover img { + transform: scale(0.98); } + .author-card__initials { + font-family: "Gelasio", serif; + font-size: 2rem; + color: #e0775d; + user-select: none; } + .author-card__info { + padding-top: 0.5rem; } + .author-card__name { + font-family: "Gelasio", serif; + font-size: 1rem; + font-weight: normal; + line-height: 1.2; + margin-bottom: 0.3rem; } + .author-card__name a { + text-decoration: none; } + .author-card__role, .author-card__affiliation { + font-family: "NewsCycle", sans-serif; + font-size: 0.75rem; + text-transform: uppercase; + line-height: 1.3; + color: #3e3e3e; + margin: 0; } + +/* +Theme Name: Thalim +Author: Valentin Le Moign +Version: 1.0 +*/ + +/*# sourceMappingURL=style.css.map */ diff --git a/functions.php b/functions.php new file mode 100755 index 0000000..2be3aea --- /dev/null +++ b/functions.php @@ -0,0 +1,1375 @@ +user_email ) ) ); + $response = wp_remote_head( + 'https://www.gravatar.com/avatar/' . $hash . '?d=404', + [ 'timeout' => 3 ] + ); + + if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) { + $url = 'https://www.gravatar.com/avatar/' . $hash . '?s=300'; + set_transient( $cache_key, $url, WEEK_IN_SECONDS ); + return $url; + } + + // No Gravatar — cache the negative result too + set_transient( $cache_key, '', WEEK_IN_SECONDS ); + return ''; +} + +function thalim_en_url( string $url ): string { + if ( thalim_current_language() !== 'en' ) return $url; + $home = rtrim( home_url(), '/' ); + $path = substr( $url, strlen( $home ) ); + if ( str_starts_with( $path, '/en/' ) || $path === '/en' ) return $url; + return $home . '/en' . $path; +} + +// Auto-prefix all WP-generated internal URLs in EN mode. +// Safe on admin: THALIM_ORIGINAL_URI starts with /wp-admin/ there, +// so thalim_current_language() returns 'fr' and thalim_en_url() is a no-op. +add_filter( 'term_link', 'thalim_en_url' ); // get_term_link(), get_category_link(), get_tag_link() +add_filter( 'post_link', 'thalim_en_url' ); // get_permalink() on regular posts +add_filter( 'page_link', 'thalim_en_url' ); // get_permalink() on pages +add_filter( 'post_type_link', 'thalim_en_url' ); // get_permalink() on CPTs +add_filter( 'author_link', 'thalim_en_url' ); // get_author_posts_url() + +// Return the translated category name if viewing in EN and titre_anglais is set. +// Accepts a WP_Term, Timber\Term, term_id (int), or a name string + term_id. +function thalim_cat_name( $cat, string $lang = null ): string { + if ( $lang === null ) $lang = thalim_current_language(); + if ( is_object( $cat ) ) { + $term_id = $cat->term_id ?? ( $cat->id ?? 0 ); + $fallback = $cat->name; + } elseif ( is_numeric( $cat ) ) { + $term_id = (int) $cat; + $term = get_term( $term_id, 'category' ); + $fallback = $term && ! is_wp_error( $term ) ? $term->name : (string) $cat; + } else { + return (string) $cat; + } + if ( $lang !== 'en' ) return $fallback; + $en = get_term_meta( $term_id, 'titre_anglais', true ); + return ( $en !== '' && $en !== false ) ? $en : $fallback; +} + +// Register bilingual and en_url as Twig filters +add_filter( 'timber/twig', function ( $twig ) { + $twig->addFilter( new \Twig\TwigFilter( 'bilingual', 'thalim_bilingual' ) ); + $twig->addFilter( new \Twig\TwigFilter( 'en_url', 'thalim_en_url' ) ); + $twig->addFilter( new \Twig\TwigFilter( 'cat_name', 'thalim_cat_name' ) ); + return $twig; +} ); + +// Language switcher data (replaces pll_the_languages) +// Output matches the structure header.twig expects: slug, url, current_lang +function thalim_language_switcher(): array { + $uri = THALIM_ORIGINAL_URI; + $path = parse_url( $uri, PHP_URL_PATH ) ?? '/'; + $query = ( $q = parse_url( $uri, PHP_URL_QUERY ) ) ? '?' . $q : ''; + $is_en = thalim_current_language() === 'en'; + + $fr_path = $is_en ? ( substr( $path, 3 ) ?: '/' ) : $path; + $en_path = $is_en ? $path : '/en' . $path; + + return [ + 'fr' => [ 'slug' => 'fr', 'url' => home_url( $fr_path ) . $query, 'current_lang' => ! $is_en ], + 'en' => [ 'slug' => 'en', 'url' => home_url( $en_path ) . $query, 'current_lang' => $is_en ], + ]; +} + +function theme_enqueue_assets() { + wp_enqueue_style( + 'main-styles', + get_template_directory_uri() . '/css/style.css', + [], + filemtime(get_template_directory() . '/css/style.css') + ); + + wp_enqueue_style( + 'iconoir', + 'https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css', + [], + null + ); + + wp_enqueue_script( + 'overlay', + get_template_directory_uri() . '/js/overlay.js', + [], + filemtime(get_template_directory() . '/js/overlay.js'), + true + ); + + if (is_front_page() || is_404()) { + wp_enqueue_script( + 'animatedLogo', + get_template_directory_uri() . '/js/animatedLogo.js', + [], + filemtime(get_template_directory() . '/js/animatedLogo.js'), + true + ); + wp_add_inline_script( 'animatedLogo', 'var themeDirURI = ' . wp_json_encode( get_template_directory_uri() ) . ';', 'before' ); + } + + if ( is_category() ) { + wp_enqueue_style( 'swiper', 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css', [], null ); + wp_enqueue_script( 'swiper', 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js', [], null, true ); + wp_enqueue_script( 'agendaView', get_template_directory_uri() . '/js/agendaView.js', ['swiper'], filemtime( get_template_directory() . '/js/agendaView.js' ), true ); + wp_localize_script( 'agendaView', 'agendaViewData', [ + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'load_more_posts' ), + 'lang' => thalim_current_language(), + ]); + } + + if (is_front_page()) { + wp_enqueue_script( + 'coloredWordsHero', + get_template_directory_uri() . '/js/coloredWordsHero.js', + [], + filemtime(get_template_directory() . '/js/coloredWordsHero.js'), + true + ); + + wp_enqueue_style( + 'swiper', + 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css', + [], + null + ); + + wp_enqueue_script( + 'swiper', + 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js', + [], + null, + true + ); + + wp_enqueue_script( + 'annoncesSwiper', + get_template_directory_uri() . '/js/annoncesSwiper.js', + ['swiper'], + filemtime(get_template_directory() . '/js/annoncesSwiper.js'), + true + ); + + wp_enqueue_script( + 'messageLabo', + get_template_directory_uri() . '/js/messageLabo.js', + [], + filemtime(get_template_directory() . '/js/messageLabo.js'), + true + ); + + wp_enqueue_script( + 'keywordCloud', + get_template_directory_uri() . '/js/keywordCloud.js', + [], + filemtime(get_template_directory() . '/js/keywordCloud.js'), + true + ); + + wp_enqueue_script( + 'quickLinks', + get_template_directory_uri() . '/js/quickLinks.js', + [], + filemtime(get_template_directory() . '/js/quickLinks.js'), + true + ); + + $kw_tags = get_terms([ + 'taxonomy' => 'post_tag', + 'hide_empty' => true, + 'orderby' => 'name', + 'order' => 'ASC', + 'lang' => '', + ]); + if (!is_wp_error($kw_tags) && !empty($kw_tags)) { + $kw_lang = thalim_current_language(); + $kw_tags = array_filter($kw_tags, function ($tag) { + return !get_term_meta($tag->term_id, 'ne_pas_afficher_dans_le_nuage', true); + }); + wp_localize_script('keywordCloud', 'thalimTags', array_values(array_map(function ($tag) use ($kw_lang) { + return ['name' => html_entity_decode(thalim_bilingual($tag->name, $kw_lang), ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'url' => get_term_link($tag)]; + }, $kw_tags))); + } + } + + + wp_enqueue_script( + 'navAxesToggle', + get_template_directory_uri() . '/js/navAxesToggle.js', + [], + filemtime(get_template_directory() . '/js/navAxesToggle.js'), + true + ); + + wp_enqueue_script( + 'stickyHeader', + get_template_directory_uri() . '/js/stickyHeader.js', + [], + filemtime(get_template_directory() . '/js/stickyHeader.js'), + true + ); + + wp_enqueue_script( + 'frenchTypography', + get_template_directory_uri() . '/js/frenchTypography.js', + [], + filemtime(get_template_directory() . '/js/frenchTypography.js'), + true + ); + + if (is_page('membres')) { + wp_enqueue_script( + 'membresFilters', + get_template_directory_uri() . '/js/membresFilters.js', + [], + filemtime(get_template_directory() . '/js/membresFilters.js'), + true + ); + wp_enqueue_script( + 'membresPopover', + get_template_directory_uri() . '/js/membresPopover.js', + [], + filemtime(get_template_directory() . '/js/membresPopover.js'), + true + ); + } + + if (is_single() || is_author() || is_page('membres') || is_page('le-laboratoire') || is_page('programmes-de-recherche')) { + wp_enqueue_script( + 'seanceToggle', + get_template_directory_uri() . '/js/seanceToggle.js', + [], + filemtime(get_template_directory() . '/js/seanceToggle.js'), + true + ); + } + + if (is_single()) { + wp_enqueue_style( + 'swiper', + 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css', + [], + null + ); + + wp_enqueue_script( + 'swiper', + 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js', + [], + null, + true + ); + + wp_enqueue_script( + 'imageSwiper', + get_template_directory_uri() . '/js/imageSwiper.js', + ['swiper'], + filemtime(get_template_directory() . '/js/imageSwiper.js'), + true + ); + } + + wp_enqueue_script( + 'fitPostCardTitle', + get_template_directory_uri() . '/js/fitPostCardTitle.js', + ['frenchTypography'], + filemtime(get_template_directory() . '/js/fitPostCardTitle.js'), + true + ); + + $is_archive_page = is_category() || is_tax() || is_tag() || is_page(['annonces', 'announcements']) || is_search(); + + if ($is_archive_page) { + wp_enqueue_script( + 'infiniteScroll', + get_template_directory_uri() . '/js/infiniteScroll.js', + [], + filemtime(get_template_directory() . '/js/infiniteScroll.js'), + true + ); + wp_localize_script('infiniteScroll', 'infiniteScrollData', [ + 'ajaxUrl' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('load_more_posts'), + 'lang' => thalim_current_language(), + ]); + } + + if ($is_archive_page) { + wp_enqueue_script( + 'categoryFilters', + get_template_directory_uri() . '/js/categoryFilters.js', + [], + filemtime(get_template_directory() . '/js/categoryFilters.js'), + true + ); + } + + // wp_enqueue_style('wp-block-library'); +} +add_action('wp_enqueue_scripts', 'theme_enqueue_assets'); + +// Auto-sync display_name from first_name + last_name on every profile save +add_action( 'profile_update', 'thalim_sync_display_name' ); +add_action( 'user_register', 'thalim_sync_display_name' ); +add_action( 'pods_api_post_save_pod_item_user', function( $pieces ) { + $user_id = isset( $pieces['id'] ) ? intval( $pieces['id'] ) : 0; + if ( $user_id ) thalim_sync_display_name( $user_id ); +} ); + +function thalim_sync_display_name( int $user_id ): void { + $first = get_user_meta( $user_id, 'first_name', true ); + $last = get_user_meta( $user_id, 'last_name', true ); + $name = trim( "$first $last" ); + if ( ! $name ) return; + // Bypass the filter to avoid infinite loop + remove_action( 'profile_update', 'thalim_sync_display_name' ); + wp_update_user( [ 'ID' => $user_id, 'display_name' => $name ] ); + add_action( 'profile_update', 'thalim_sync_display_name' ); +} + +add_action( 'admin_enqueue_scripts', 'enqueue_admin_js' ); + +function enqueue_admin_js(){ + wp_enqueue_style( + 'adminDashboardStyles', + get_template_directory_uri() . '/css/admin.css', + [], + filemtime(get_template_directory() . '/css/admin.css') + ); + + wp_enqueue_script( + 'adminDashboardMods', + get_template_directory_uri() . '/js/adminDashboardMods.js', + [], + filemtime(get_template_directory() . '/js/adminDashboardMods.js'), + true + ); + $axes_groups = thalim_get_axes_filter_groups(); + if ( current_user_can( 'contributor' ) && ! current_user_can( 'edit_others_posts' ) ) { + $axes_groups = array_slice( $axes_groups, 0, 1 ); + } + wp_localize_script( 'adminDashboardMods', 'thalimAxesGroups', $axes_groups ); + + // [DÉSACTIVÉ] adminFormRestore — décommenter pour réactiver + // $screen = get_current_screen(); + // if ( $screen && 'post' === $screen->base ) { + // wp_enqueue_script( + // 'adminFormRestore', + // get_template_directory_uri() . '/js/adminFormRestore.js', + // [ 'jquery' ], + // filemtime(get_template_directory() . '/js/adminFormRestore.js'), + // true + // ); + // } +} +add_theme_support('title-tag'); + +// Apply bilingual split to the browser tab title + translate category names +add_filter('document_title_parts', function ($title_parts) { + if (!empty($title_parts['title'])) { + $title_parts['title'] = thalim_bilingual($title_parts['title']); + // On category archives, replace the title with the translated category name + if ( is_category() ) { + $cat = get_queried_object(); + if ( $cat ) { + $title_parts['title'] = thalim_cat_name( $cat ); + } + } + } + return $title_parts; +}); + +function add_to_context($context) { + $current_lang = thalim_current_language(); + + // menus + $menu_slug = ($current_lang === 'en') ? 'Navigation-en' : 'Navigation'; + $context['menu'] = Timber::get_menu($menu_slug); + + $footer_menu_slug = ($current_lang === 'en') ? 'Footer-en' : 'Footer'; + $context['footer_menu'] = Timber::get_menu($footer_menu_slug); + + // contenus généraux (single post, bilingual) + $gc_posts = Timber::get_posts([ + 'post_type' => 'contenu_general', + 'posts_per_page' => 1, + 'orderby' => 'ID', + 'order' => 'ASC', + ]); + $gc_post = $gc_posts[0] ?? null; + if ( $gc_post ) { + $context['gc'] = [ + 'umr' => thalim_bilingual( $gc_post->umr ?: '', $current_lang ), + 'thalim' => thalim_bilingual( $gc_post->thalim ?: '', $current_lang ), + 'siecles' => thalim_bilingual( $gc_post->siecles ?: '', $current_lang ), + 'presentation' => ( $current_lang === 'en' && $gc_post->presentation_en ) ? $gc_post->presentation_en : $gc_post->presentation, + 'presentation_detail' => ( $current_lang === 'en' && $gc_post->presentation_detail_en ) ? $gc_post->presentation_detail_en : $gc_post->presentation_detail, + ]; + } else { + $context['gc'] = []; + } + + $context['current_language'] = $current_lang; + + // Axes thématiques courants (annee_fin >= current year) for navigation dropdown + $current_year = (int) date('Y'); + $all_axes = get_terms(['taxonomy' => 'axe_thematique', 'hide_empty' => false, 'orderby' => 'name']); + $axes_courants = []; + if (!is_wp_error($all_axes)) { + foreach ($all_axes as $axe) { + $fin = (int) get_term_meta($axe->term_id, 'annee_fin', true); + if ($fin >= $current_year) { + $link = get_term_link($axe); + if (!is_wp_error($link)) { + $axes_courants[] = [ + 'name' => thalim_bilingual($axe->name, $current_lang), + 'link' => $link, + 'ordre' => (int) get_term_meta($axe->term_id, 'ordre_daffichage', true), + ]; + } + } + } + usort($axes_courants, fn($a, $b) => $a['ordre'] <=> $b['ordre']); + } + $context['axes_courants'] = $axes_courants; + + // Annonces page URL (language-aware) + $annonces_page = get_page_by_path('annonces'); + $annonces_base = $annonces_page ? get_permalink($annonces_page->ID) : home_url('/annonces/'); + $context['annonces_url'] = $annonces_base; + + // Language switcher + $context['languages'] = thalim_language_switcher(); + + return $context; +} + +// Restrict Contributors to see only their own posts in admin, +// but also include posts where they appear in membres/autre_membres. +function restrict_contributor_posts( $query ) { + if ( ! is_admin() || ! $query->is_main_query() || current_user_can( 'edit_others_posts' ) ) { + return; + } + + global $user_ID, $wpdb; + + // Posts where the user is listed as membre or autre_membre. + $membre_ids = array_map( 'intval', (array) $wpdb->get_col( + $wpdb->prepare( + "SELECT DISTINCT post_id FROM {$wpdb->postmeta} + WHERE meta_key IN ('membres', 'autre_membres') AND meta_value = %s", + $user_ID + ) + ) ); + + if ( empty( $membre_ids ) ) { + // Fast path: no membre posts, use simple author filter. + $query->set( 'author', $user_ID ); + return; + } + + // Posts authored by this user. + $authored_ids = array_map( 'intval', (array) $wpdb->get_col( + $wpdb->prepare( + "SELECT ID FROM {$wpdb->posts} WHERE post_author = %d", + $user_ID + ) + ) ); + + $all_ids = array_unique( array_merge( $authored_ids, $membre_ids ) ); + + // post__in with [0] returns nothing when the combined set is empty. + $query->set( 'post__in', empty( $all_ids ) ? [ 0 ] : $all_ids ); +} +add_action( 'pre_get_posts', 'restrict_contributor_posts' ); + +/** + * Let contributors listed as membres/autre_membres on a post edit it. + * + * user_has_cap modifies capabilities only for this single check — + * it does not permanently alter the user's capability set. + */ +function thalim_membres_can_edit_post( $allcaps, $caps, $args, $user ) { + // Editors and above already have edit_others_posts — nothing to do. + if ( ! empty( $allcaps['edit_others_posts'] ) ) { + return $allcaps; + } + + if ( empty( $args[0] ) ) { + return $allcaps; + } + + $cap = $args[0]; + + // Meta caps that carry a post ID in $args[2] (e.g. wp-admin/post.php load). + $meta_caps_with_id = [ 'edit_post', 'edit_page' ]; + + // Primitive caps called during the admin save/publish flow *without* a + // post_id (e.g. wp-admin/includes/post.php:76 checks edit_others_posts + // directly when $post_author !== current user). We infer the post_id from + // the request so we can still authorize membres per-post. + $primitive_caps_in_save_flow = [ + 'edit_others_posts', + 'edit_others_pages', + 'edit_published_posts', + 'edit_published_pages', + 'publish_posts', + 'publish_pages', + ]; + + $post_id = 0; + if ( in_array( $cap, $meta_caps_with_id, true ) && ! empty( $args[2] ) ) { + $post_id = (int) $args[2]; + } elseif ( in_array( $cap, $primitive_caps_in_save_flow, true ) ) { + $post_id = (int) ( $_POST['post_ID'] ?? $_REQUEST['post'] ?? 0 ); + } + + if ( ! $post_id ) { + return $allcaps; + } + + $user_id = $user->ID; + + $membre_ids = array_map( + 'intval', + array_merge( + (array) get_post_meta( $post_id, 'membres', false ), + (array) get_post_meta( $post_id, 'autre_membres', false ) + ) + ); + + if ( in_array( $user_id, $membre_ids, true ) ) { + // Grant every primitive cap mapped for this check + // (e.g. edit_others_posts, edit_published_posts). + foreach ( $caps as $c ) { + $allcaps[ $c ] = true; + } + } + + return $allcaps; +} +add_filter( 'user_has_cap', 'thalim_membres_can_edit_post', 10, 4 ); + +// Prevent WP_Posts_List_Table from auto-redirecting contributors to the "Mine" +// view. The constructor sets $_GET['author'] = current_user_id() when the user +// lacks edit_others_posts and no other filter is active. Setting all_posts=1 +// before the list table is constructed short-circuits that condition. +add_action( 'load-edit.php', function () { + if ( current_user_can( 'edit_others_posts' ) ) { + return; + } + if ( + empty( $_REQUEST['post_status'] ) + && empty( $_REQUEST['all_posts'] ) + && empty( $_REQUEST['author'] ) + && empty( $_REQUEST['show_sticky'] ) + ) { + $_GET['all_posts'] = 1; + $_REQUEST['all_posts'] = 1; + } +} ); + +// Adjust post-status counts so contributors see only posts they can access +// (posts they authored + posts listed in membres/autre_membres), not all posts. +// The wp_count_posts filter runs even on cached values, so it won't pollute +// the shared cache. +add_filter( 'wp_count_posts', function ( $counts, $type, $perm ) { + if ( ! is_admin() || current_user_can( 'edit_others_posts' ) ) { + return $counts; + } + + global $wpdb; + $user_id = get_current_user_id(); + + $results = $wpdb->get_results( + $wpdb->prepare( + "SELECT post_status, COUNT(*) AS num_posts + FROM {$wpdb->posts} + WHERE post_type = %s + AND ID IN ( + SELECT ID FROM {$wpdb->posts} + WHERE post_author = %d AND post_type = %s + UNION + SELECT post_id FROM {$wpdb->postmeta} + WHERE meta_key IN ('membres', 'autre_membres') AND meta_value = %s + ) + GROUP BY post_status", + $type, + $user_id, + $type, + (string) $user_id + ), + ARRAY_A + ); + + $new_counts = array_fill_keys( get_post_stati(), 0 ); + foreach ( $results as $row ) { + $new_counts[ $row['post_status'] ] = (int) $row['num_posts']; + } + + return (object) $new_counts; +}, 10, 3 ); + +// Show Pods user meta fields on the Add New User page (user-new.php). +// Fires the same hook Pods listens to on profile.php / user-edit.php. +add_action('user_new_form', function($operation) { + if ($operation === 'add-existing-user') return; + $user = new WP_User(0); + do_action('show_user_profile', $user); +}); + +// Save Pods user meta fields when a new user is registered. +// Re-fires Pods' personal_options_update callback with the new user ID +// while $_POST still contains the submitted form data. +add_action('user_register', function($user_id) { + do_action('personal_options_update', $user_id); +}, 20); + +// Remove the Pods autocomplete 30-item cap for the "étiquettes" (post_tag) pick field +// so all tags are available in the dropdown, not just the first 30 alphabetically. +add_filter( 'pods_form_ui_field_pick_autocomplete_limit', function( $limit, $name ) { + if ( 'etiquettes' === $name ) { + return -1; + } + return $limit; +}, 10, 2 ); + +// Admin "Programmes de recherche" taxonomy list: +// - Filter by "Type de programme" (select injected via JS into the search form). +// - Add a "Type de programme" column to the list table. + +// Server-side filtering: read the GET param and add a meta_query on pre_get_terms. +add_action( 'pre_get_terms', function( $query ) { + if ( ! is_admin() ) { + return; + } + if ( ! in_array( 'programme_de_recherche', (array) $query->query_vars['taxonomy'], true ) ) { + return; + } + $type = isset( $_GET['type_de_programme'] ) ? sanitize_text_field( $_GET['type_de_programme'] ) : ''; + if ( '' === $type ) { + return; + } + $query->query_vars['meta_query'] = [ + [ + 'key' => 'type_de_programme', + 'value' => $type, + 'compare' => '=', + ], + ]; +} ); + +// Column: add "Type de programme" to the list table, replacing "Description". +add_filter( 'manage_edit-programme_de_recherche_columns', function( $columns ) { + unset( $columns['description'] ); + $columns['type_de_programme'] = __( 'Type de programme', 'thalim' ); + return $columns; +} ); + +add_filter( 'manage_programme_de_recherche_custom_column', function( $output, $column_name, $term_id ) { + if ( 'type_de_programme' !== $column_name ) { + return $output; + } + return esc_html( get_term_meta( (int) $term_id, 'type_de_programme', true ) ?: '—' ); +}, 10, 3 ); + +// Admin tag list: replace the "Description" column with the custom boolean field +// "ne_pas_afficher_dans_le_nuage" (Ne pas afficher dans le nuage de mots-clé). + +add_filter( 'manage_edit-post_tag_columns', function( $columns ) { + unset( $columns['description'] ); + $columns['nuage_exclus'] = __( 'Exclure du nuage', 'thalim' ); + return $columns; +} ); + +add_filter( 'manage_post_tag_custom_column', function( $output, $column_name, $term_id ) { + if ( 'nuage_exclus' !== $column_name ) { + return $output; + } + $val = get_term_meta( (int) $term_id, 'ne_pas_afficher_dans_le_nuage', true ); + return $val ? '✓' : '—'; +}, 10, 3 ); + +require_once __DIR__ . '/inc/admin-users-filter.php'; +require_once __DIR__ . '/inc/pods-conditional-required.php'; +require_once __DIR__ . '/inc/pods-save-error-handler.php'; +require_once __DIR__ . '/inc/post-title-required.php'; +require_once __DIR__ . '/inc/post-card-helpers.php'; +require_once __DIR__ . '/inc/single-helpers.php'; +require_once __DIR__ . '/inc/author-helpers.php'; + +// In admin post list, filter by exact category only (exclude subcategories) +function thalim_exact_category_filter( $query ) { + if ( is_admin() && $query->is_main_query() ) { + $cat = $query->get( 'cat' ); + if ( $cat ) { + $query->set( 'cat', '' ); + $query->set( 'tax_query', [ + [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [ (int) $cat ], + 'include_children' => false, + ], + ] ); + } + } +} +add_action( 'pre_get_posts', 'thalim_exact_category_filter' ); + +// ── "Vie du labo" (cat 9) — restricted to logged-in users ──── +add_action( 'pre_get_posts', function( $query ) { + if ( is_user_logged_in() ) return; + $excluded = $query->get( 'category__not_in' ); + if ( ! is_array( $excluded ) ) $excluded = $excluded ? [ $excluded ] : []; + if ( ! in_array( 9, $excluded ) ) { + $excluded[] = 9; + $query->set( 'category__not_in', $excluded ); + } +} ); + +add_action( 'template_redirect', function() { + if ( ! is_user_logged_in() && is_category( 9 ) ) { + wp_safe_redirect( home_url( '/' ) ); + exit; + } +} ); + +// Séance de séminaire (cat 12): redirect to parent séminaire with #seance-{ID} anchor +add_action( 'template_redirect', function() { + if ( ! is_single() ) return; + if ( ! has_category( 12 ) ) return; + + global $wpdb; + $seance_id = get_the_ID(); + $parent_id = $wpdb->get_var( $wpdb->prepare( + "SELECT pm.post_id FROM {$wpdb->postmeta} pm + JOIN {$wpdb->posts} p ON p.ID = pm.post_id + WHERE pm.meta_key = 'seances' AND pm.meta_value = %s + AND p.post_status = 'publish' + LIMIT 1", + (string) $seance_id + ) ); + + if ( $parent_id ) { + wp_redirect( get_permalink( (int) $parent_id ) . '#seance-' . $seance_id, 301 ); + exit; + } +} ); + +add_filter( 'wp_nav_menu_objects', function( $items, $args ) { + if ( is_user_logged_in() ) return $items; + return array_values( array_filter( $items, function( $item ) { + if ( $item->object === 'category' && (int) $item->object_id === 9 ) return false; + if ( strpos( $item->url, 'vie-du-labo' ) !== false ) return false; + return true; + } ) ); +}, 10, 2 ); + +// Rewrite rule for /category/{slug}/autres → posts directly in parent category +add_action('init', function() { + add_rewrite_rule( + 'category/([^/]+)/autres/?$', + 'index.php?category_name=$matches[1]&thalim_direct_posts=1', + 'top' + ); +}); +add_filter('query_vars', function($vars) { + $vars[] = 'thalim_direct_posts'; + return $vars; +}); + +// Admin bar customizations (front + back) +add_action('admin_bar_menu', function($wp_admin_bar) { + $wp_admin_bar->remove_node('wp-logo'); + $wp_admin_bar->remove_node('customize'); + foreach ($wp_admin_bar->get_nodes() as $node) { + if (empty($node->title) || stripos($node->title, 'article') === false) continue; + $node->title = preg_replace_callback('/article/i', function($m) { + $w = $m[0]; + if ($w === strtoupper($w)) return 'ANNONCE'; + if ($w[0] === strtoupper($w[0])) return 'Annonce'; + return 'annonce'; + }, $node->title); + $wp_admin_bar->add_node((array) $node); + } +}, 999); + +add_action('wp_before_admin_bar_render', function() { + global $wp_admin_bar; + $wp_admin_bar->remove_node('wpforms-menu'); +}); + +// Force visual (TinyMCE) editor for all users — our admin CSS hides the +// Visual/Code tabs, so if a user has "Disable the visual editor" checked +// in their profile they get stuck in code mode with no way to switch back. +add_filter( 'user_can_richedit', '__return_true' ); + +// Non-admins: hide dashboard and tools menu, redirect to posts list +add_action( 'admin_menu', function() { + if ( ! current_user_can( 'manage_options' ) ) { + remove_menu_page( 'tools.php' ); + remove_menu_page( 'index.php' ); + } +} ); + +// Redirect non-admins away from dashboard to their posts list +add_action( 'admin_init', function() { + if ( current_user_can( 'manage_options' ) ) return; + global $pagenow; + if ( $pagenow === 'index.php' ) { + wp_safe_redirect( admin_url( 'edit.php' ) ); + exit; + } +} ); + +// After login, send non-admins to posts list instead of dashboard +add_filter( 'login_redirect', function( $redirect_to, $requested, $user ) { + if ( ! is_wp_error( $user ) && ! $user->has_cap( 'manage_options' ) ) { + return admin_url( 'edit.php' ); + } + return $redirect_to; +}, 10, 3 ); + +// AJAX handler for infinite scroll on category pages +function thalim_load_more_posts() { + check_ajax_referer('load_more_posts', 'nonce'); + + $GLOBALS['thalim_lang_override'] = sanitize_key( $_POST['lang'] ?? 'fr' ); + + $page = intval($_POST['page']); + $category = isset($_POST['category']) ? intval($_POST['category']) : 0; + $axe = isset($_POST['axe']) ? intval($_POST['axe']) : 0; + $date_from = isset($_POST['date_from']) ? sanitize_text_field($_POST['date_from']) : ''; + $date_to = isset($_POST['date_to']) ? sanitize_text_field($_POST['date_to']) : ''; + $term_taxonomy = isset($_POST['taxonomy']) ? sanitize_key($_POST['taxonomy']) : ''; + $term_id = isset($_POST['term']) ? intval($_POST['term']) : 0; + $cat_filter = isset($_POST['filter_cat']) ? intval($_POST['filter_cat']) : 0; + $filter_autres = isset($_POST['filter_autres']) ? intval($_POST['filter_autres']) : 0; + $exclude_cats = isset($_POST['exclude_cats']) ? sanitize_text_field($_POST['exclude_cats']) : ''; + $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : ''; + + $query_args = [ + 'post_type' => 'post', + 'posts_per_page' => 12, + 'paged' => $page, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'thalim_event_date_order' => true, + ]; + if ($search) { + $query_args['s'] = $search; + $query_args['relevanssi'] = true; + $query_args['orderby'] = 'relevance'; + } + + // Build tax_query — may combine category page filter, taxonomy term, and cat filter + $tax_clauses = []; + if ($category) { + $tax_clauses[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$category], + 'include_children' => false, + ]; + } + if ($term_taxonomy && $term_id) { + $tax_clauses[] = [ + 'taxonomy' => $term_taxonomy, + 'field' => 'term_id', + 'terms' => [$term_id], + ]; + // Exclure les séances de séminaire sur les pages de taxonomie + $tax_clauses[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [12], + 'operator' => 'NOT IN', + ]; + } + if ($cat_filter) { + $tax_clauses[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$cat_filter], + 'include_children' => !$filter_autres, + ]; + } + if ($exclude_cats) { + $ids = array_filter(array_map('intval', explode(',', $exclude_cats))); + if (!empty($ids)) { + $tax_clauses[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => $ids, + 'operator' => 'NOT IN', + ]; + } + } + + if (!empty($tax_clauses)) { + $query_args['tax_query'] = count($tax_clauses) > 1 + ? array_merge(['relation' => 'AND'], $tax_clauses) + : $tax_clauses; + } + + if ($axe) { + $query_args['meta_query'] = [[ + 'key' => 'axes_thematiques', + 'value' => $axe, + 'type' => 'NUMERIC', + ]]; + } + + if ($date_from || $date_to) { + $query_args['thalim_event_date_filter'] = ['from' => $date_from, 'to' => $date_to]; + } + + // Exclude pinned posts on category pages to avoid duplicates (they already appear at the top) + if ($category) { + $today = date( 'Y-m-d' ); + $pinned_query = new WP_Query([ + 'post_type' => 'post', + 'posts_per_page' => -1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'lang' => '', + 'tax_query' => [[ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$category], + 'include_children' => false, + ]], + 'meta_query' => [[ + 'key' => 'epingler_dans_la_categorie', + 'value' => '1', + ]], + ]); + $pinned_ids = []; + foreach ( $pinned_query->posts as $pid ) { + $fin = get_post_meta( $pid, 'date_de_fin_depinglage', true ); + if ( empty( $fin ) || $fin === '0000-00-00' || $fin >= $today ) { + $pinned_ids[] = $pid; + } + } + if ( ! empty( $pinned_ids ) ) { + $query_args['post__not_in'] = $pinned_ids; + } + } + + $posts = Timber::get_posts($query_args); + + if (empty($posts)) { + wp_send_json_success(['html' => '']); + return; + } + + $cards = thalim_get_cards_data($posts); + + $html = ''; + foreach ($posts as $post) { + $html .= Timber::compile('partials/post-card.twig', [ + 'post' => $post, + 'card' => $cards[$post->ID], + 'show_category' => true, + 'type_only' => $category > 0, // category pages: type chip only; everywhere else (taxonomy, search, annonces): type or category name + ]); + } + + wp_send_json_success(['html' => $html]); +} +add_action('wp_ajax_load_more_posts', 'thalim_load_more_posts'); +add_action('wp_ajax_nopriv_load_more_posts', 'thalim_load_more_posts'); + +/** + * Build structured data for one agenda slider card. + */ +function thalim_get_agenda_card_data( $post_id, $lang = 'fr' ) { + $months_fr = ['jan.','fév.','mars','avr.','mai','juin','juil.','août','sept.','oct.','nov.','déc.']; + $months_en = ['Jan.','Feb.','Mar.','Apr.','May','Jun.','Jul.','Aug.','Sep.','Oct.','Nov.','Dec.']; + $months = $lang === 'en' ? $months_en : $months_fr; + + $raw_debut = get_post_meta( $post_id, 'date_de_debut', true ); + $raw_datetime = get_post_meta( $post_id, 'datetime', true ); + if ( $raw_debut && ! str_starts_with( $raw_debut, '0000' ) ) { + $ts = strtotime( $raw_debut ); + } elseif ( $raw_datetime && ! str_starts_with( $raw_datetime, '0000' ) ) { + $ts = strtotime( $raw_datetime ); + } else { + $ts = get_post_timestamp( $post_id ); + } + + $raw_fin = get_post_meta( $post_id, 'date_de_fin', true ); + $ts_debut = ( $raw_debut && ! str_starts_with( $raw_debut, '0000' ) ) ? strtotime( $raw_debut ) : 0; + $ts_fin = ( $raw_fin && ! str_starts_with( $raw_fin, '0000' ) ) ? strtotime( $raw_fin ) : 0; + + // Build date_label — same rules as single.twig sidebar + $fmt_debut = $ts_debut ? thalim_format_date( $raw_debut, $lang ) : ''; + $fmt_fin = $ts_fin ? thalim_format_date( $raw_fin, $lang ) : ''; + $fmt_dt = ( $raw_datetime && ! str_starts_with( $raw_datetime, '0000' ) ) + ? thalim_format_date( $raw_datetime, $lang ) : ''; + $h_debut = substr( get_post_meta( $post_id, 'heure_de_debut', true ) ?: '', 0, 5 ); + $h_fin = substr( get_post_meta( $post_id, 'heure_de_fin', true ) ?: '', 0, 5 ); + + if ( $fmt_debut || $fmt_fin ) { + if ( $ts_debut && $ts_fin && date( 'Y-m-d', $ts_debut ) === date( 'Y-m-d', $ts_fin ) ) { + // Same day + if ( $h_debut && $h_fin ) { + $date_label = ( $lang === 'en' ? 'On ' : 'Le ' ) . $fmt_debut + . ' ' . ( $lang === 'en' ? 'from ' : 'de ' ) . $h_debut + . ' ' . ( $lang === 'en' ? 'to ' : 'à ' ) . $h_fin; + } elseif ( $h_debut ) { + $date_label = $fmt_debut . ( $lang === 'en' ? ' at ' : ' à ' ) . $h_debut; + } else { + $date_label = $fmt_debut; + } + } elseif ( $fmt_debut && $fmt_fin ) { + $date_label = ( $lang === 'en' ? 'From ' : 'Du ' ) . $fmt_debut + . ' ' . ( $lang === 'en' ? 'to ' : 'au ' ) . $fmt_fin; + } elseif ( $fmt_debut ) { + $date_label = $h_debut + ? $fmt_debut . ( $lang === 'en' ? ' at ' : ' à ' ) . $h_debut + : $fmt_debut; + } else { + $date_label = ( $lang === 'en' ? 'Until ' : "Jusqu'au " ) . $fmt_fin; + } + } elseif ( $fmt_dt ) { + $date_label = $h_debut + ? $fmt_dt . ( $lang === 'en' ? ' at ' : ' à ' ) . $h_debut + : $fmt_dt; + } else { + $date_label = ''; + } + + $type_fields = [ + 'type_colloque_journee_d_etude', 'type_soutenance', 'type_evenement_culturel', + 'type_media', 'type_captation', 'type_revue_collection', 'type_autre', + ]; + $type_label = ''; + foreach ( $type_fields as $f ) { + $v = get_post_meta( $post_id, $f, true ); + if ( $v ) { $type_label = thalim_bilingual( $v, $lang ); break; } + } + if ( ! $type_label ) { + foreach ( get_the_category( $post_id ) as $cat ) { + if ( $cat->parent ) { $type_label = thalim_cat_name( $cat, $lang ); break; } + } + } + + $end_day = $end_month = $end_year = null; + if ( $ts_fin && date( 'Ymd', $ts_fin ) !== date( 'Ymd', $ts ) ) { + $end_day = (int) date( 'j', $ts_fin ); + $end_month = $months[ (int) date( 'n', $ts_fin ) - 1 ]; + $end_year = (int) date( 'Y', $ts_fin ); + } + + // Séance de séminaire (cat 12): link to parent séminaire at #seance-{ID} + $link = get_permalink( $post_id ); + if ( in_array( 12, wp_list_pluck( get_the_category( $post_id ), 'term_id' ) ) ) { + global $wpdb; + $parent_id = $wpdb->get_var( $wpdb->prepare( + "SELECT pm.post_id FROM {$wpdb->postmeta} pm + JOIN {$wpdb->posts} p ON p.ID = pm.post_id + WHERE pm.meta_key = 'seances' AND pm.meta_value = %s + AND p.post_status = 'publish' + LIMIT 1", + (string) $post_id + ) ); + if ( $parent_id ) { + $link = get_permalink( (int) $parent_id ) . '#seance-' . $post_id; + } + } + + return [ + 'day' => (int) date( 'j', $ts ), + 'month' => $months[ (int) date( 'n', $ts ) - 1 ], + 'year' => (int) date( 'Y', $ts ), + 'end_day' => $end_day, + 'end_month' => $end_month, + 'end_year' => $end_year, + 'type_label' => $type_label, + 'date_label' => $date_label, + 'lieu' => thalim_bilingual( get_post_meta( $post_id, 'lieu', true ) ?: '', $lang ), + 'link' => $link, + ]; +} + +function thalim_load_more_agenda() { + check_ajax_referer( 'load_more_posts', 'nonce' ); + + $lang = sanitize_key( $_POST['lang'] ?? 'fr' ); + $GLOBALS['thalim_lang_override'] = $lang; + $page = intval( $_POST['page'] ); + $category = isset( $_POST['category'] ) ? intval( $_POST['category'] ) : 0; + $axe = isset( $_POST['axe'] ) ? intval( $_POST['axe'] ) : 0; + $date_from = isset( $_POST['date_from'] ) ? sanitize_text_field( $_POST['date_from'] ) : ''; + $date_to = isset( $_POST['date_to'] ) ? sanitize_text_field( $_POST['date_to'] ) : ''; + $term_taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( $_POST['taxonomy'] ) : ''; + $term_id = isset( $_POST['term'] ) ? intval( $_POST['term'] ) : 0; + $cat_filter = isset( $_POST['filter_cat'] ) ? intval( $_POST['filter_cat'] ) : 0; + $filter_autres = isset( $_POST['filter_autres'] ) ? intval( $_POST['filter_autres'] ) : 0; + $exclude_cats = isset( $_POST['exclude_cats'] ) ? sanitize_text_field( $_POST['exclude_cats'] ) : ''; + + $query_args = [ + 'post_type' => 'post', + 'posts_per_page' => 12, + 'paged' => $page, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'thalim_event_date_order' => true, + ]; + + $include_children = ! empty( $_POST['include_children'] ); + + $tax_clauses = []; + if ( $category ) { + $tax_clauses[] = [ 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => [ $category ], 'include_children' => $include_children ]; + } + if ( $term_taxonomy && $term_id ) { + $tax_clauses[] = [ 'taxonomy' => $term_taxonomy, 'field' => 'term_id', 'terms' => [ $term_id ] ]; + $tax_clauses[] = [ 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => [ 12 ], 'operator' => 'NOT IN' ]; + } + if ( $cat_filter ) { + $tax_clauses[] = [ 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => [ $cat_filter ], 'include_children' => ! $filter_autres ]; + } + if ( $exclude_cats ) { + $ids = array_filter( array_map( 'intval', explode( ',', $exclude_cats ) ) ); + if ( ! empty( $ids ) ) { + $tax_clauses[] = [ 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => $ids, 'operator' => 'NOT IN' ]; + } + } + if ( ! empty( $tax_clauses ) ) { + $query_args['tax_query'] = count( $tax_clauses ) > 1 + ? array_merge( [ 'relation' => 'AND' ], $tax_clauses ) + : $tax_clauses; + } + if ( $axe ) { + $query_args['meta_query'] = [[ 'key' => 'axes_thematiques', 'value' => $axe, 'type' => 'NUMERIC' ]]; + } + if ( $date_from || $date_to ) { + $query_args['thalim_event_date_filter'] = [ 'from' => $date_from, 'to' => $date_to ]; + } + + // On first page, count future events to find today's anchor position + $today_offset = 0; + if ( (int) $page === 1 ) { + $offset_args = $query_args; + $offset_args['posts_per_page'] = 1; + $offset_args['no_found_rows'] = false; + $offset_args['paged'] = 1; + $today_str = current_time( 'Y-m-d' ); + $existing_filter = $offset_args['thalim_event_date_filter'] ?? []; + $offset_args['thalim_event_date_filter'] = array_merge( + $existing_filter, + [ 'from' => $today_str ] + ); + $future_query = new \WP_Query( $offset_args ); + $today_offset = (int) $future_query->found_posts; + } + + $posts = Timber::get_posts( $query_args ); + if ( empty( $posts ) ) { + $response = [ 'html' => '' ]; + if ( (int) $page === 1 ) $response['today_offset'] = $today_offset; + wp_send_json_success( $response ); + return; + } + + $html = ''; + foreach ( $posts as $post ) { + $data = thalim_get_agenda_card_data( $post->ID, $lang ); + $html .= Timber::compile( 'partials/agenda-card.twig', array_merge( $data, [ 'post' => $post ] ) ); + } + $response = [ 'html' => $html ]; + if ( (int) $page === 1 ) $response['today_offset'] = $today_offset; + wp_send_json_success( $response ); +} +add_action( 'wp_ajax_load_more_agenda', 'thalim_load_more_agenda' ); +add_action( 'wp_ajax_nopriv_load_more_agenda', 'thalim_load_more_agenda' ); + +// Event date ordering: LEFT JOIN + COALESCE(date_de_debut, post_date) +// Activated by adding 'thalim_event_date_order' => true to WP_Query args. +// Event date ordering: COALESCE(date_de_debut, datetime, post_date) +// Activated by adding 'thalim_event_date_order' => true to WP_Query args. +add_filter('posts_join', function ($join, $query) { + if (!$query->get('thalim_event_date_order') && !$query->get('thalim_event_date_filter')) return $join; + global $wpdb; + $join .= " LEFT JOIN {$wpdb->postmeta} AS thalim_ed" + . " ON (thalim_ed.post_id = {$wpdb->posts}.ID" + . " AND thalim_ed.meta_key = 'date_de_debut') "; + $join .= " LEFT JOIN {$wpdb->postmeta} AS thalim_dt" + . " ON (thalim_dt.post_id = {$wpdb->posts}.ID" + . " AND thalim_dt.meta_key = 'datetime') "; + return $join; +}, 10, 2); + +add_filter('posts_orderby', function ($orderby, $query) { + if (!$query->get('thalim_event_date_order')) return $orderby; + global $wpdb; + $valid = "IS NOT NULL AND %s != '' AND %s NOT LIKE '0000-00-00%%'"; + return "CASE" + . " WHEN thalim_ed.meta_value " . sprintf($valid, 'thalim_ed.meta_value', 'thalim_ed.meta_value') . " THEN thalim_ed.meta_value" + . " WHEN thalim_dt.meta_value " . sprintf($valid, 'thalim_dt.meta_value', 'thalim_dt.meta_value') . " THEN thalim_dt.meta_value" + . " ELSE {$wpdb->posts}.post_date" + . " END DESC"; +}, 10, 2); + +// Event date range filter: uses same CASE logic as ordering so date_de_debut/datetime take priority over post_date. +// Activated by adding 'thalim_event_date_filter' => ['from' => $date_from, 'to' => $date_to] to WP_Query args. +add_filter('posts_where', function ($where, $query) { + $filter = $query->get('thalim_event_date_filter'); + if (empty($filter) || (!isset($filter['from']) && !isset($filter['to']))) return $where; + global $wpdb; + + $effective = "CASE" + . " WHEN thalim_ed.meta_value IS NOT NULL AND thalim_ed.meta_value != '' AND thalim_ed.meta_value NOT LIKE '0000-00-00%' THEN thalim_ed.meta_value" + . " WHEN thalim_dt.meta_value IS NOT NULL AND thalim_dt.meta_value != '' AND thalim_dt.meta_value NOT LIKE '0000-00-00%' THEN thalim_dt.meta_value" + . " ELSE {$wpdb->posts}.post_date" + . " END"; + + if (!empty($filter['from'])) { + $from = $wpdb->prepare('%s', $filter['from']); + $where .= " AND ({$effective}) >= {$from}"; + } + if (!empty($filter['to'])) { + $to = $wpdb->prepare('%s', $filter['to'] . ' 23:59:59'); + $where .= " AND ({$effective}) <= {$to}"; + } + + return $where; +}, 10, 2); + +// ── Axes thématiques groupés pour les filtres ────────────────── +// Retourne un tableau de groupes triés par période (plus récent en premier, +// "passés" toujours en dernier). Chaque terme contient id, name, ordre. +function thalim_get_axes_filter_groups() { + $terms = get_terms( [ 'taxonomy' => 'axe_thematique', 'hide_empty' => false ] ); + $axes_map = []; + + foreach ( $terms as $term ) { + $debut = trim( get_term_meta( $term->term_id, 'annee_debut', true ) ); + $fin = trim( get_term_meta( $term->term_id, 'annee_fin', true ) ); + + if ( $debut && $fin ) { + $key = $debut . '-' . $fin; + $label = $debut . ' – ' . $fin; + } else { + $key = 'passes'; + $label = 'Axes antérieurs'; + } + + if ( ! isset( $axes_map[ $key ] ) ) { + $axes_map[ $key ] = [ 'label' => $label, 'debut' => intval( $debut ), 'terms' => [] ]; + } + + $ordre = trim( get_term_meta( $term->term_id, 'ordre_daffichage', true ) ); + $axes_map[ $key ]['terms'][] = [ + 'id' => $term->term_id, + 'name' => $term->name, + 'ordre' => $ordre !== '' ? intval( $ordre ) : null, + 'href' => get_term_link( $term ), + ]; + } + + // Tri des groupes : plus récent en premier, passés toujours en dernier + uasort( $axes_map, function ( $a, $b ) { + if ( $a['label'] === 'Axes antérieurs' ) return 1; + if ( $b['label'] === 'Axes antérieurs' ) return -1; + return $b['debut'] - $a['debut']; + } ); + + // Tri des termes dans chaque groupe : ordre_daffichage d'abord, puis alphabétique + foreach ( $axes_map as &$group ) { + usort( $group['terms'], function ( $a, $b ) { + $a_has = $a['ordre'] !== null; + $b_has = $b['ordre'] !== null; + if ( $a_has && $b_has ) return $a['ordre'] - $b['ordre']; + if ( $a_has ) return -1; + if ( $b_has ) return 1; + return strcmp( $a['name'], $b['name'] ); + } ); + } + unset( $group ); + + return array_values( $axes_map ); +} + diff --git a/inc/admin-users-filter.php b/inc/admin-users-filter.php new file mode 100644 index 0000000..78c7577 --- /dev/null +++ b/inc/admin-users-filter.php @@ -0,0 +1,48 @@ + 'role', 'hide_empty' => true, 'orderby' => 'name' ] ); + if ( is_wp_error( $terms ) || empty( $terms ) ) return; + + $selected = isset( $_GET['thalim_statut'] ) ? intval( $_GET['thalim_statut'] ) : 0; + ?> + + + + set( 'meta_query', [ + 'relation' => 'OR', + [ 'key' => 'role_1', 'value' => $term_id, 'compare' => '=' ], + [ 'key' => 'role_2', 'value' => $term_id, 'compare' => '=' ], + [ 'key' => 'role_3', 'value' => $term_id, 'compare' => '=' ], + ] ); +} ); diff --git a/inc/author-helpers.php b/inc/author-helpers.php new file mode 100644 index 0000000..53ddbbb --- /dev/null +++ b/inc/author-helpers.php @@ -0,0 +1,250 @@ +name; + } + } + + // --- Direction title (read from "Le laboratoire" page) --- + $labo_page = get_page_by_path('le-laboratoire'); + $directeur_id = $labo_page ? intval(get_post_meta($labo_page->ID, 'directeur', true)) : 0; + $adjoint_id = $labo_page ? intval(get_post_meta($labo_page->ID, 'directeur_adjoint', true)) : 0; + if ($user_id === $directeur_id) { + $role_label = 'Directeur' . ($role_label ? ', ' . $role_label : ''); + } elseif ($user_id === $adjoint_id) { + $role_label = 'Directeur adjoint' . ($role_label ? ', ' . $role_label : ''); + } + + // --- Domaines de recherches (multiple usermeta rows with post_tag IDs) --- + $domaine_ids = get_user_meta($user_id, 'domaines_de_recherches', false); + $domaines_tags = []; + foreach ($domaine_ids as $tag_id) { + if (!$tag_id) continue; + $term = get_term(intval($tag_id), 'post_tag'); + if ($term && !is_wp_error($term)) { + $link = get_term_link($term); + if (!is_wp_error($link)) { + $domaines_tags[] = ['name' => thalim_bilingual($term->name, $lang), 'url' => $link]; + } + } + } + + // --- Axes thématiques (multiple usermeta rows) --- + $axe_ids = get_user_meta($user_id, 'axes_thematiques', false); + $axes = []; + foreach ($axe_ids as $axe_id) { + $term = get_term(intval($axe_id), 'axe_thematique'); + if ($term && !is_wp_error($term)) { + $axes[] = [ + 'name' => thalim_bilingual($term->name, $lang), + 'url' => get_term_link($term), + ]; + } + } + + // --- External links (up to 4) --- + $liens_externes = []; + for ($i = 1; $i <= 4; $i++) { + $url = get_user_meta($user_id, 'lien_externe_' . $i, true); + if ($url) { + $titre = thalim_bilingual(get_user_meta($user_id, 'titre_du_lien_' . $i, true) ?: '', $lang); + if (!$titre) { + $host = parse_url($url, PHP_URL_HOST) ?: $url; + $parts = explode('.', $host); + $titre = count($parts) >= 2 ? implode('.', array_slice($parts, -2)) : $host; + } + $liens_externes[] = ['url' => $url, 'titre' => $titre]; + } + } + + // --- Documents (multiple usermeta rows with attachment IDs) --- + $doc_ids = get_user_meta($user_id, 'documents', false); + $documents = []; + foreach ($doc_ids as $doc_id) { + $url = wp_get_attachment_url(intval($doc_id)); + if ($url) { + $documents[] = [ + 'url' => $url, + 'title' => get_the_title($doc_id) ?: basename(get_attached_file($doc_id)), + ]; + } + } + + // --- Thesis director (THALIM member — stored as user ID) --- + $directeur_id = get_user_meta($user_id, 'directeur_de_these_thalim', true); + $directeur_thalim = null; + if ($directeur_id) { + $dir_user = get_userdata(intval($directeur_id)); + if ($dir_user) { + $directeur_thalim = [ + 'name' => $dir_user->display_name, + 'url' => get_author_posts_url(intval($directeur_id)), + ]; + } + } + + // --- Email visibility --- + $is_ancien = isset($role_term) && $role_term && $role_term->slug === 'anciens-membres'; + $show_email = !$is_ancien && get_user_meta($user_id, 'afficher_ladresse_mail_sur_le_profil', true); + + return [ + 'display_name' => $user->display_name, + 'avatar_url' => $avatar_url, + 'role_label' => $role_label, + 'role_complement' => thalim_bilingual(get_user_meta($user_id, 'complement_de_role_1', true) ?: '', $lang), + 'affiliation' => (function() use ($user_id, $lang) { + $v = get_user_meta($user_id, 'affiliation', true) ?: ''; + return strtolower($v) === 'autre' + ? thalim_bilingual(get_user_meta($user_id, 'affiliation_autre', true) ?: '', $lang) + : $v; + })(), + 'bio' => wpautop( make_clickable( get_user_meta($user_id, 'biographie', true) ?: '' ) ), + 'bio_en' => wpautop( make_clickable( get_user_meta($user_id, 'biographie_en', true) ?: '' ) ), + 'domaines_tags' => $domaines_tags, + 'domaines' => wpautop( make_clickable( get_user_meta($user_id, 'autres_domaines_de_recherches', true) ?: '' ) ), + 'domaines_en' => wpautop( make_clickable( get_user_meta($user_id, 'autres_domaines_de_recherches_en', true) ?: '' ) ), + 'recherches' => wpautop( get_user_meta($user_id, 'recherches_en_cours', true) ?: '' ), + 'recherches_en' => wpautop( get_user_meta($user_id, 'recherches_en_cours_en', true) ?: '' ), + 'axes' => $axes, + 'titre_these' => thalim_bilingual(get_user_meta($user_id, 'titre_de_these', true) ?: '', $lang), + 'date_soutenance' => get_user_meta($user_id, 'date_de_soutenance', true) ?: '', + 'directeur_thalim'=> $directeur_thalim, + 'autre_directeur' => get_user_meta($user_id, 'autre_directeur_de_these', true) ?: '', + 'resume_these' => wpautop( get_user_meta($user_id, 'resume_de_la_these', true) ?: '' ), + 'resume_these_en' => wpautop( get_user_meta($user_id, 'resume_de_la_these_en', true) ?: '' ), + 'email' => $show_email ? $user->user_email : '', + 'liens_externes' => $liens_externes, + 'documents' => $documents, + 'hal_publications_url' => (function() use ($user_id) { + $hal_id = get_user_meta($user_id, 'identifiant_hal', true) ?: ''; + return $hal_id + ? 'https://hal.science/search/index/?qa[authIdHal_s][]=' . rawurlencode($hal_id) . '&sort=publicationDate_tdate+desc' + : ''; + })(), + 'user_since' => date_i18n('d/m/Y', strtotime($user->user_registered)), + ]; +} + +/** + * Query all posts linked to a member and group them by primary category. + * Returns an array sorted by post count (descending). + */ +function thalim_get_author_posts_by_category($user_id) { + $excluded_cats = [12, 31]; // séances de séminaire, etc. + $lang = thalim_current_language(); + + $posts = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => -1, + 'meta_query' => [ + 'relation' => 'OR', + [ + 'key' => 'membres', + 'value' => $user_id, + ], + [ + 'key' => 'autre_membres', + 'value' => $user_id, + ], + ], + 'thalim_event_date_order' => true, + 'lang' => '', + ]); + + $groups = []; + + foreach ($posts as $post) { + $categories = wp_get_post_categories($post->ID, ['fields' => 'all']); + $primary_cat = null; + + foreach ($categories as $cat) { + if (in_array($cat->term_id, $excluded_cats)) continue; + $primary_cat = $cat; + break; + } + + if (!$primary_cat) continue; + + $cat_id = $primary_cat->term_id; + if (!isset($groups[$cat_id])) { + // A top-level category with subcategories → these posts are "Autres" + $is_autres = false; + if ($primary_cat->parent == 0) { + $subcats = get_categories(['parent' => $cat_id, 'hide_empty' => true, 'exclude' => $excluded_cats]); + $is_autres = !empty($subcats); + } + $groups[$cat_id] = [ + 'cat_id' => $cat_id, + 'cat_name' => $is_autres + ? ($lang === 'en' ? 'Other ' : 'Autres ') . thalim_cat_name($primary_cat, $lang) + : thalim_cat_name($primary_cat, $lang), + 'cat_url' => $is_autres + ? trailingslashit(get_category_link($cat_id)) . 'autres/' + : get_category_link($cat_id), + 'posts' => [], + ]; + } + $groups[$cat_id]['posts'][] = $post; + } + + // Séances de séminaire — dedicated group. Posts in cat 12 where the member + // is listed in `membres`/`autre_membres`. Cards use the parent séminaire + // permalink with a #seance-{ID} hash (see thalim_get_card_data). + $seances = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => -1, + 'category__in' => [12], + 'meta_query' => [ + 'relation' => 'OR', + [ 'key' => 'membres', 'value' => $user_id ], + [ 'key' => 'autre_membres', 'value' => $user_id ], + ], + 'thalim_event_date_order' => true, + 'lang' => '', + ]); + if (count($seances) > 0) { + $seance_cat = get_term(12, 'category'); + $groups[12] = [ + 'cat_id' => 12, + 'cat_name' => $seance_cat && !is_wp_error($seance_cat) + ? thalim_cat_name($seance_cat, $lang) + : ($lang === 'en' ? 'Seminar sessions' : 'Séances de séminaire'), + 'cat_url' => get_category_link(12), + 'posts' => $seances, + ]; + } + + // Resolve card data and sort by count descending + foreach ($groups as &$group) { + $group['cards'] = thalim_get_cards_data($group['posts']); + } + unset($group); + + uasort($groups, function($a, $b) { + $oa = (int) get_term_meta($a['cat_id'], 'ordre_profil', true) ?: 999; + $ob = (int) get_term_meta($b['cat_id'], 'ordre_profil', true) ?: 999; + return $oa !== $ob + ? $oa <=> $ob + : count($b['posts']) <=> count($a['posts']); + }); + + return array_values($groups); +} diff --git a/inc/membres-helpers.php b/inc/membres-helpers.php new file mode 100644 index 0000000..f420e90 --- /dev/null +++ b/inc/membres-helpers.php @@ -0,0 +1,244 @@ + variants to \n and strips any remaining HTML tags. + */ +function thalim_sanitize_domaines( $raw ) { + // Normalise all
variants (including \r before them) to a newline + $text = preg_replace( '/\r?/i', "\n", $raw ); + // Strip any remaining HTML tags + $text = strip_tags( $text ); + // Clean up excess blank lines / whitespace + $text = preg_replace( "/\n{3,}/", "\n\n", trim( $text ) ); + return $text; +} + +/** + * Build the display data array for a single user. + */ +function thalim_build_membre_data( $user ) { + $lang = thalim_current_language(); + $status_parts = []; + $role_names = []; + for ( $n = 1; $n <= 3; $n++ ) { + $role_id = get_user_meta( $user->ID, 'role_' . $n, true ); + if ( ! $role_id ) continue; + $term = get_term( intval( $role_id ), 'role' ); + if ( ! $term || is_wp_error( $term ) ) continue; + $role_names[] = $term->name; + $override = thalim_bilingual( get_user_meta( $user->ID, 'affichage_du_statut_' . $n, true ) ?: '', $lang ); + if ( $override ) { + $status_parts[] = $override; + } else { + $entry = $term->name; + $complement = thalim_bilingual( get_user_meta( $user->ID, 'complement_de_role_' . $n, true ) ?: '', $lang ); + if ( $complement ) $entry .= ' ' . $complement; + $status_parts[] = $entry; + } + } + + // Avatar (Simple Local Avatar with Gravatar fallback) + $avatar_url = thalim_get_user_avatar_url( $user->ID ); + + // Domaines de recherches: multiple usermeta rows, each is a post_tag term ID + $domaine_ids = get_user_meta( $user->ID, 'domaines_de_recherches', false ); + $domaines = []; + foreach ( $domaine_ids as $term_id ) { + $term = get_term( intval( $term_id ), 'post_tag' ); + if ( $term && ! is_wp_error( $term ) ) { + $domaines[] = $term->name; + } + } + + return [ + 'display_name' => $user->display_name, + 'sort_key' => thalim_get_sort_key( $user->ID, $user->display_name ), + 'url' => get_author_posts_url( $user->ID ), + 'status' => implode( ', ', $status_parts ), + 'affiliation' => (function() use ($user) { + $v = get_user_meta( $user->ID, 'affiliation', true ) ?: ''; + return strtolower( $v ) === 'autre' + ? ( get_user_meta( $user->ID, 'affiliation_autre', true ) ?: '' ) + : $v; + })(), + 'role_names' => $role_names, + 'avatar_url' => $avatar_url, + 'domaines' => $domaines, + 'autres_domaines' => thalim_sanitize_domaines( get_user_meta( $user->ID, 'autres_domaines_de_recherches', true ) ?: '' ), + ]; +} + +/** + * Return all role taxonomy terms that are in use, sorted by name. + */ +function thalim_get_role_terms() { + $terms = get_terms( [ 'taxonomy' => 'role', 'hide_empty' => true, 'orderby' => 'name' ] ); + if ( is_wp_error( $terms ) ) return []; + return array_values( array_map( + fn( $t ) => [ 'id' => $t->term_id, 'name' => $t->name ], + array_filter( $terms, fn( $t ) => ! in_array( mb_strtolower( $t->name, 'UTF-8' ), [ 'archive', 'à ranger' ], true ) ) + ) ); +} + +/** + * Return all role term_ids set for a user (role_1, role_2, role_3). + */ +function thalim_get_user_role_ids( $user_id ) { + $ids = []; + for ( $n = 1; $n <= 3; $n++ ) { + $role_id = get_user_meta( $user_id, 'role_' . $n, true ); + if ( $role_id ) $ids[] = intval( $role_id ); + } + return $ids; +} + +/** + * Sort key: first word of last_name user meta (handles compound last names like + * "Duclaux de l'Estoile" → "Duclaux"). Falls back to last word of display_name. + */ +function thalim_get_sort_key( $user_id, $display_name ) { + $last = get_user_meta( $user_id, 'last_name', true ); + if ( $last ) { + $parts = explode( ' ', trim( $last ) ); + return $parts[0]; + } + $parts = explode( ' ', trim( $display_name ) ); + return end( $parts ); +} + +/** + * Return all member groups for the /membres page. + * Each group: ['title' => string, 'members' => array of member data arrays]. + * Empty groups are omitted. + */ +function thalim_get_membres_groups() { + // Fetch all users that have role_1 set + $users = get_users( [ + 'meta_key' => 'role_1', + 'number' => -1, + ] ); + + // Direction: read directeur and directeur_adjoint from "Le laboratoire" page + $labo_page = get_page_by_path( 'le-laboratoire' ); + $directeur_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur', true ) ) : 0; + $adjoint_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur_adjoint', true ) ) : 0; + + $direction_users = []; + foreach ( [ $directeur_id, $adjoint_id ] as $uid ) { + if ( $uid ) { + $u = get_userdata( $uid ); + if ( $u ) $direction_users[] = $u; + } + } + + // Pre-build member data for all relevant users (cache by ID) + $member_cache = []; + $all_users = array_merge( $users, $direction_users ); + foreach ( $all_users as $user ) { + if ( ! isset( $member_cache[ $user->ID ] ) ) { + $member_cache[ $user->ID ] = thalim_build_membre_data( $user ); + } + } + + // Prepend direction title to status for director / deputy director + if ( $directeur_id && isset( $member_cache[ $directeur_id ] ) ) { + $existing = $member_cache[ $directeur_id ]['status']; + $member_cache[ $directeur_id ]['status'] = 'Directeur' . ( $existing ? ', ' . $existing : '' ); + } + if ( $adjoint_id && isset( $member_cache[ $adjoint_id ] ) ) { + $existing = $member_cache[ $adjoint_id ]['status']; + $member_cache[ $adjoint_id ]['status'] = 'Directeur adjoint' . ( $existing ? ', ' . $existing : '' ); + } + + // Build a slug→ID map for the 'role' taxonomy so group definitions survive + // database migrations where auto-incremented term IDs change. + $slug_to_id = []; + foreach ( get_terms( [ 'taxonomy' => 'role', 'hide_empty' => false ] ) as $term ) { + $slug_to_id[ $term->slug ] = $term->term_id; + } + $by_slug = fn( ...$slugs ) => array_values( + array_filter( array_map( fn( $s ) => $slug_to_id[ $s ] ?? null, $slugs ) ) + ); + + // Group definitions (title => role slugs that qualify a user for membership) + $group_definitions = [ + 'Chercheuses et chercheurs CNRS' => $by_slug( 'directeur-de-recherche', 'charge-de-recherche' ), + 'Enseignantes-chercheuses et enseignants-chercheurs' => $by_slug( 'professeur', 'maitre-de-conferences' ), + 'Doctorantes et doctorants' => $by_slug( 'doctorant' ), + 'Docteures et docteurs' => $by_slug( 'docteur' ), + 'Postdoctorantes et postdoctorants' => $by_slug( 'postdoctorant' ), + 'Personnel contractuel' => $by_slug( 'personnel-contractuel' ), + "Personnel d'accompagnement à la recherche" => $by_slug( 'personnel-technique' ), + 'Membres associées et membres associés' => $by_slug( 'membre-associe' ), + 'Anciennes et anciens membres' => $by_slug( 'anciens-membres' ), + ]; + + $groups = []; + + // Direction group first: directeur before directeur adjoint + $direction_members = []; + if ( $directeur_id && isset( $member_cache[ $directeur_id ] ) ) { + $direction_members[] = $member_cache[ $directeur_id ]; + } + if ( $adjoint_id && isset( $member_cache[ $adjoint_id ] ) ) { + $direction_members[] = $member_cache[ $adjoint_id ]; + } + if ( ! empty( $direction_members ) ) { + $groups[] = [ + 'title' => 'Direction', + 'members' => $direction_members, + 'fixed_order' => true, + ]; + } + + // Role-based groups (a user appears in every group that matches any of their roles) + foreach ( $group_definitions as $title => $term_ids ) { + $group_members = []; + foreach ( $users as $user ) { + $user_role_ids = thalim_get_user_role_ids( $user->ID ); + if ( array_intersect( $term_ids, $user_role_ids ) ) { + $group_members[] = $member_cache[ $user->ID ]; + } + } + + if ( empty( $group_members ) ) continue; + + // Sort alphabetically by last name, accent- and case-insensitive (fr locale) + static $collator = null; + if ( $collator === null ) { + $collator = class_exists( 'Collator' ) ? new Collator( 'fr_FR' ) : false; + if ( $collator ) $collator->setStrength( Collator::PRIMARY ); + } + usort( $group_members, function( $a, $b ) use ( $collator ) { + $la = $a['sort_key']; + $lb = $b['sort_key']; + if ( $collator ) return $collator->compare( $la, $lb ); + return strcmp( mb_strtolower( $la, 'UTF-8' ), mb_strtolower( $lb, 'UTF-8' ) ); + } ); + + // In "Personnel d'accompagnement", place "Gestion et pilotage" first + $fixed = false; + if ( $title === "Personnel d'accompagnement à la recherche" ) { + $priority = []; + $rest = []; + foreach ( $group_members as $m ) { + if ( stripos( $m['status'], 'Gestion et pilotage' ) !== false ) { + $priority[] = $m; + } else { + $rest[] = $m; + } + } + $group_members = array_merge( $priority, $rest ); + $fixed = true; + } + + $groups[] = [ + 'title' => $title, + 'members' => $group_members, + 'fixed_order' => $fixed, + ]; + } + + return $groups; +} diff --git a/inc/pods-conditional-required.php b/inc/pods-conditional-required.php new file mode 100644 index 0000000..ac3d121 --- /dev/null +++ b/inc/pods-conditional-required.php @@ -0,0 +1,94 @@ + $field ) { + $field_values[ $name ] = isset( $field['value'] ) ? $field['value'] : ''; + } + // Pour un post existant, si un champ n'a pas été soumis explicitement via + // pods_meta_* (ex. Pods DFV React en éditeur classique), remplir sa valeur + // depuis la BDD. Cela corrige à la fois : + // - la validation du champ lui-même (pieces['fields']['value']) + // - l'évaluation de la logique conditionnelle des autres champs ($field_values) + if ( ! $is_new_item && $id ) { + foreach ( $pieces['fields'] as $name => $field ) { + // Skip pick/file/avatar fields: their value format in $pieces is complex + // and get_post_meta returns a raw value that corrupts Pods' pick processing. + // These fields are always submitted via POST by Pods DFV React. + $field_type = pods_v( 'type', $field, '' ); + if ( in_array( $field_type, [ 'pick', 'file', 'avatar' ], true ) ) { + continue; + } + + $current_val = isset( $field['value'] ) ? $field['value'] : ''; + if ( ( '' === $current_val || null === $current_val ) && ! isset( $_POST[ 'pods_meta_' . $name ] ) ) { + $db_val = get_post_meta( (int) $id, $name, true ); + if ( '' !== $db_val && null !== $db_val ) { + $pieces['fields'][ $name ]['value'] = $db_val; + $field_values[ $name ] = $db_val; + } + } + } + } + + foreach ( $pieces['fields'] as $field_name => $field_data ) { + // Ne traiter que les champs required + $required = is_object( $field_data ) && method_exists( $field_data, 'get_field_object' ) + ? (int) $field_data->get_field_object()->get_arg( 'required', 0 ) + : (int) pods_v( 'required', $field_data, 0 ); + + if ( 1 !== $required ) { + continue; + } + + // Récupérer la logique conditionnelle + $conditional_logic = null; + + if ( is_object( $field_data ) && method_exists( $field_data, 'get_field_object' ) ) { + $conditional_logic = $field_data->get_field_object()->get_conditional_logic(); + } + + // Fallback : charger le champ via l'API Pods + if ( ! $conditional_logic ) { + $field_obj = pods_api()->load_field( [ + 'name' => $field_name, + 'pod' => 'post', + ] ); + + if ( $field_obj && method_exists( $field_obj, 'get_conditional_logic' ) ) { + $conditional_logic = $field_obj->get_conditional_logic(); + } + } + + if ( ! $conditional_logic ) { + continue; + } + + // Évaluer si le champ est visible avec les valeurs actuelles + if ( ! $conditional_logic->is_visible( $field_values ) ) { + // Le champ est masqué → désactiver le required + if ( is_object( $field_data ) && method_exists( $field_data, 'get_field_object' ) ) { + $field_data->get_field_object()->set_arg( 'required', 0 ); + } + $pieces['fields'][ $field_name ]['required'] = 0; + if ( isset( $pieces['fields'][ $field_name ]['options'] ) ) { + $pieces['fields'][ $field_name ]['options']['required'] = 0; + } + } + } + + return $pieces; +} diff --git a/inc/pods-save-error-handler.php b/inc/pods-save-error-handler.php new file mode 100644 index 0000000..f806770 --- /dev/null +++ b/inc/pods-save-error-handler.php @@ -0,0 +1,195 @@ + $val ) { + if ( str_starts_with( $key, 'pods_meta_' ) ) { + $restore[ $key ] = is_array( $val ) ? array_map( 'wp_unslash', $val ) : wp_unslash( $val ); + } + } + $error_text = is_wp_error( $error ) ? $error->get_error_message() : (string) $error; + $restore['_msg'] = wp_strip_all_tags( $error_text ); + $restore['_title'] = sanitize_text_field( wp_unslash( $_POST['post_title'] ?? '' ) ); + + set_transient( 'thalim_pods_restore_' . $post_id . '_' . $user_id, $restore, 10 * MINUTE_IN_SECONDS ); + + $GLOBALS['thalim_pods_error_post_id'] = $post_id; + + return false; // empêche wp_die() +}, 10, 2 ); + +// Après le save : rediriger vers la page d'édition + annuler le statut si besoin +add_filter( 'redirect_post_location', function ( $location ) { + $post_id = $GLOBALS['thalim_pods_error_post_id'] ?? 0; + if ( ! $post_id ) { + return $location; + } + + // Annuler le changement de statut vers publish si le post n'était pas encore publié + $original = isset( $_POST['original_post_status'] ) ? sanitize_key( $_POST['original_post_status'] ) : ''; + $post = get_post( $post_id ); + + if ( + $post && + in_array( $post->post_status, [ 'publish', 'future', 'pending' ], true ) && + ! in_array( $original, [ 'publish', 'future', 'pending' ], true ) + ) { + global $wpdb; + $wpdb->update( + $wpdb->posts, + [ 'post_status' => $original ?: 'draft' ], + [ 'ID' => $post_id ], + [ '%s' ], + [ '%d' ] + ); + clean_post_cache( $post_id ); + } + + return admin_url( 'post.php?post=' . $post_id . '&action=edit' ); +}, 10 ); + +// Sur la page d'édition (GET) : lire le transient une seule fois → global → supprimer +add_action( 'current_screen', function ( $screen ) { + if ( $screen->base !== 'post' ) { + return; + } + $post_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0; + if ( ! $post_id ) { + return; + } + + $user_id = get_current_user_id(); + $key = 'thalim_pods_restore_' . $post_id . '_' . $user_id; + $data = get_transient( $key ); + if ( ! $data ) { + return; + } + + $GLOBALS['thalim_pods_restore'] = [ + 'post_id' => $post_id, + 'data' => $data, + ]; + delete_transient( $key ); +} ); + +// Injecter les valeurs dans get_post_meta → Pods DFV les embarque dans son JSON React +add_filter( 'get_post_metadata', function ( $value, $object_id, $meta_key, $single ) { + $restore = $GLOBALS['thalim_pods_restore'] ?? null; + if ( ! $restore || $restore['post_id'] !== (int) $object_id ) { + return $value; + } + + $pods_key = 'pods_meta_' . $meta_key; + if ( ! isset( $restore['data'][ $pods_key ] ) ) { + return $value; + } + + $val = $restore['data'][ $pods_key ]; + return $single ? $val : [ $val ]; +}, 10, 4 ); + +// Restauration JS : titre + champs Pods select/pick via PodsDFV (même pattern que les modales) +add_action( 'admin_footer', function () { + $restore = $GLOBALS['thalim_pods_restore'] ?? null; + if ( ! $restore ) { + return; + } + + $screen = get_current_screen(); + if ( ! $screen || $screen->base !== 'post' ) { + return; + } + + $post_id = intval( $restore['post_id'] ); + $title = $restore['data']['_title'] ?? ''; + + // Construire le map fieldName => value pour les champs Pods + $fields = []; + foreach ( $restore['data'] as $key => $val ) { + if ( str_starts_with( $key, 'pods_meta_' ) ) { + $fields[ substr( $key, 10 ) ] = $val; + } + } + + ?> + + base !== 'post' ) { + return; + } + + $msg = esc_html( $restore['data']['_msg'] ?? '' ); + if ( $msg ) { + echo '

' . $msg . '

'; + } + echo '

Votre contenu a été restauré. Vérifiez les champs obligatoires avant de republier.

'; +} ); diff --git a/inc/post-card-helpers.php b/inc/post-card-helpers.php new file mode 100644 index 0000000..b85e61e --- /dev/null +++ b/inc/post-card-helpers.php @@ -0,0 +1,172 @@ + null, + 'card_membres' => [], + 'card_axes' => [], + 'card_etiquettes' => [], + 'parent_slug' => '', + 'card_category_name' => '', + 'card_category_url' => '', + 'card_type' => '', + 'card_event_date' => '', + 'card_event_date_iso' => '', + 'card_link' => '', + ]; + + // Event date — date_de_debut (events), fallback to datetime (communications) + // Used for display instead of post_date when set + foreach (['date_de_debut', 'datetime'] as $date_key) { + $event_raw = get_post_meta($post_id, $date_key, true) ?: ''; + if ($event_raw && !str_starts_with($event_raw, '0000-00-00')) { + $ts = strtotime($event_raw); + if ($ts) { + $data['card_event_date'] = date_i18n('d/m/Y', $ts); + $data['card_event_date_iso'] = date('Y-m-d', $ts); + break; + } + } + } + + // Resolve top-level parent category slug for color theming and direct category name for display + $categories = wp_get_post_categories($post_id, ['fields' => 'all']); + $excluded_ids = [12, 31]; + $is_seance = false; + foreach ($categories as $cat) { + if ($cat->term_id === 12) { $is_seance = true; } + } + foreach ($categories as $cat) { + if (in_array($cat->term_id, $excluded_ids)) continue; + $ancestor_ids = get_ancestors($cat->term_id, 'category'); + if (!empty($ancestor_ids)) { + $root = get_category(end($ancestor_ids)); + } else { + $root = $cat; + } + $data['parent_slug'] = $root->slug; + $data['card_category_name'] = thalim_cat_name($cat); + $data['card_category_url'] = get_category_link($cat->term_id); + break; + } + + // Séances de séminaire: link to parent séminaire with hash, derive color from parent's categories + if ($is_seance) { + // Always show the category label for séances even though cat 12 is excluded from color resolution + if (!$data['card_category_name']) { + $seance_cat = get_category(12); + if ($seance_cat) { + $data['card_category_name'] = thalim_cat_name($seance_cat); + $data['card_category_url'] = get_category_link(12); + } + } + global $wpdb; + $parent_id = $wpdb->get_var($wpdb->prepare( + "SELECT pm.post_id FROM {$wpdb->postmeta} pm + JOIN {$wpdb->posts} p ON p.ID = pm.post_id + WHERE pm.meta_key = 'seances' AND pm.meta_value = %s + AND p.post_status = 'publish' + LIMIT 1", + (string) $post_id + )); + if ($parent_id) { + $data['card_link'] = get_permalink((int) $parent_id) . '#seance-' . $post_id; + // Derive color from parent séminaire's categories if not already set + if (!$data['parent_slug']) { + foreach (wp_get_post_categories((int) $parent_id, ['fields' => 'all']) as $cat) { + if (in_array($cat->term_id, $excluded_ids)) continue; + $ancestor_ids = get_ancestors($cat->term_id, 'category'); + $root = !empty($ancestor_ids) ? get_category(end($ancestor_ids)) : $cat; + $data['parent_slug'] = $root->slug; + break; + } + } + } + } + + // Type label (first non-empty type_* field) + $type_fields = [ + 'type_colloque_journee_d_etude', + 'type_soutenance', + 'type_evenement_culturel', + 'type_media', + 'type_captation', + 'type_revue_collection', + 'type_autre', + ]; + foreach ($type_fields as $field) { + $val = get_post_meta($post_id, $field, true); + if ($val) { + $data['card_type'] = $val; + break; + } + } + + // First image from documents_joints + $doc_ids = get_post_meta($post_id, 'documents_joints', false); + foreach ($doc_ids as $doc_id) { + $mime = get_post_mime_type($doc_id); + if ($mime && str_starts_with($mime, 'image/')) { + $src = wp_get_attachment_image_url($doc_id, 'medium'); + if ($src) { + $data['card_image'] = $src; + break; + } + } + } + + // Members (user IDs → display names + profile URLs) + // Falls back to autre_membres if membres is empty + $membre_ids = get_post_meta($post_id, 'membres', false); + if (empty($membre_ids)) { + $membre_ids = get_post_meta($post_id, 'autre_membres', false); + } + foreach ($membre_ids as $uid) { + $user = get_userdata($uid); + if ($user) { + $data['card_membres'][] = [ + 'name' => $user->display_name, + 'url' => get_author_posts_url($uid), + ]; + } + } + + // Axes thématiques (post IDs → titles) + $axe_ids = get_post_meta($post_id, 'axes_thematiques', false); + foreach ($axe_ids as $axe_id) { + $axe = get_post($axe_id); + if ($axe) { + $data['card_axes'][] = $axe->post_title; + } + } + + // Etiquettes (post IDs → titles) + $tag_ids = get_post_meta($post_id, 'etiquettes', false); + foreach ($tag_ids as $tag_id) { + $tag_post = get_post($tag_id); + if ($tag_post) { + $data['card_etiquettes'][] = $tag_post->post_title; + } + } + + return $data; +} + +/** + * Build card data map for a collection of posts. + * Returns an array keyed by post ID. + */ +function thalim_get_cards_data($posts) { + $cards = []; + foreach ($posts as $post) { + $cards[$post->ID] = thalim_get_card_data($post->ID); + } + return $cards; +} diff --git a/inc/post-title-required.php b/inc/post-title-required.php new file mode 100644 index 0000000..07d01c5 --- /dev/null +++ b/inc/post-title-required.php @@ -0,0 +1,57 @@ +post_type, 'title' ) ) { + return; + } + + // Title was provided — nothing to do. + if ( trim( wp_unslash( $_POST['post_title'] ?? '' ) ) !== '' ) { + return; + } + + // Title is empty: store restore transient and signal the redirect handler + // (same keys as pods-save-error-handler.php so everything is shared). + $user_id = get_current_user_id(); + $restore = []; + + foreach ( $_POST as $key => $val ) { + if ( str_starts_with( $key, 'pods_meta_' ) ) { + $restore[ $key ] = is_array( $val ) + ? array_map( 'wp_unslash', $val ) + : wp_unslash( $val ); + } + } + + $restore['_msg'] = __( 'Le champ Titre est obligatoire.', 'thalim' ); + $restore['_title'] = ''; // intentionally empty — user must fill it + + set_transient( + 'thalim_pods_restore_' . $post_id . '_' . $user_id, + $restore, + 10 * MINUTE_IN_SECONDS + ); + + // Signal redirect_post_location (defined in pods-save-error-handler.php): + // it will revert the post status if needed and redirect to the edit screen. + $GLOBALS['thalim_pods_error_post_id'] = $post_id; +} diff --git a/inc/single-helpers.php b/inc/single-helpers.php new file mode 100644 index 0000000..bc2cf50 --- /dev/null +++ b/inc/single-helpers.php @@ -0,0 +1,447 @@ + thalim_bilingual( get_post_meta($post_id, 'sous-titre', true) ?: '', $lang ), + 'reference_bibliographique' => get_post_meta($post_id, 'reference_bibliographique', true) ?: '', + 'editeur' => get_post_meta($post_id, 'editeur', true) ?: '', + 'journal' => get_post_meta($post_id, 'journal', true) ?: '', + 'lieu' => thalim_bilingual( get_post_meta($post_id, 'lieu', true) ?: '', $lang ), + 'adresse' => get_post_meta($post_id, 'adresse', true) ?: '', + 'autrepersonnes' => get_post_meta($post_id, 'autrepersonnes', true) ?: '', + 'autre_autrepersonnes' => get_post_meta($post_id, 'autre_autrepersonnes', true) ?: '', + 'body_en' => apply_filters( 'the_content', get_post_meta($post_id, 'body_en', true) ?: '' ), + + // Dates (formatted for display) + 'datetime' => thalim_format_date(get_post_meta($post_id, 'datetime', true), $lang), + 'date_de_debut' => '', + 'date_de_fin' => '', + 'date_debut_ymd' => '', + 'date_fin_ymd' => '', + 'heure_de_debut' => substr( get_post_meta($post_id, 'heure_de_debut', true) ?: '', 0, 5 ), + 'heure_de_fin' => substr( get_post_meta($post_id, 'heure_de_fin', true) ?: '', 0, 5 ), + + // URLs + 'hal_url' => get_post_meta($post_id, 'hal_url', true) ?: '', + 'hal_file' => get_post_meta($post_id, 'hal_file', true) ?: '', + 'canal_u' => array_values( array_filter( array_map( function( $url ) { + if ( preg_match( '/(\d+)\/?$/', trim( $url ), $m ) ) { + return 'https://www.canal-u.tv/embed/' . $m[1] . '?t=0'; + } + return ''; + }, get_post_meta( $post_id, 'lien_canal_u', false ) ) ) ), + 'youtube' => array_values( array_filter( array_map( function( $url ) { + $url = trim( $url ); + // youtu.be/ID or youtube.com/embed/ID or youtube.com/watch?v=ID + if ( preg_match( '/(?:youtu\.be\/|youtube\.com\/(?:embed\/|watch\?.*v=|shorts\/))([A-Za-z0-9_-]{11})/', $url, $m ) ) { + return 'https://www.youtube-nocookie.com/embed/' . $m[1]; + } + return ''; + }, get_post_meta( $post_id, 'lien_youtube', false ) ) ) ), + + // Resolved below + 'liens_externes' => [], + 'membres' => [], + 'autre_membres' => [], + 'autre_fonction_label' => '', + 'axes' => [], + 'etiquettes' => [], + 'programmes' => [], + 'annonces_liees' => [], + 'seances_a_venir' => [], + 'seances_passees' => [], + 'show_image_titles' => (bool) get_post_meta($post_id, 'afficher_le_titre_des_images_en_legende', true), + 'images' => [], + 'documents' => [], + 'type_label' => '', + 'fonction_label' => '', + 'parent_slug' => '', + 'parent_name' => '', + 'parent_link' => '', + 'category_name' => '', + 'category_link' => '', + ]; + + // --- Dates --- + $raw_debut = get_post_meta($post_id, 'date_de_debut', true); + $raw_fin = get_post_meta($post_id, 'date_de_fin', true); + $ts_debut = ($raw_debut && !str_starts_with($raw_debut, '0000-00-00')) ? strtotime($raw_debut) : 0; + $ts_fin = ($raw_fin && !str_starts_with($raw_fin, '0000-00-00')) ? strtotime($raw_fin) : 0; + + $data['date_de_debut'] = thalim_format_date($raw_debut, $lang); + $data['date_de_fin'] = thalim_format_date($raw_fin, $lang); + if ($ts_debut) $data['date_debut_ymd'] = date('Y-m-d', $ts_debut); + if ($ts_fin) $data['date_fin_ymd'] = date('Y-m-d', $ts_fin); + + // --- External links (up to 3) --- + for ($i = 1; $i <= 3; $i++) { + $url = get_post_meta($post_id, 'lien_externe_' . $i, true); + if ($url) { + $titre = thalim_bilingual( get_post_meta($post_id, 'titre_du_lien_externe_' . $i, true) ?: '', $lang ); + if (!$titre) { + $host = parse_url($url, PHP_URL_HOST) ?: $url; + $parts = explode('.', $host); + $titre = count($parts) >= 2 ? implode('.', array_slice($parts, -2)) : $host; + } + $data['liens_externes'][] = [ + 'url' => $url, + 'titre' => $titre, + ]; + } + } + + // --- Category hierarchy for breadcrumb and color --- + $categories = wp_get_post_categories($post_id, ['fields' => 'all']); + $excluded_ids = [12, 31]; + foreach ($categories as $cat) { + if (in_array($cat->term_id, $excluded_ids)) continue; + $ancestor_ids = get_ancestors($cat->term_id, 'category'); + if (!empty($ancestor_ids)) { + $root = get_category(end($ancestor_ids)); + $data['parent_slug'] = $root->slug; + $data['parent_name'] = $root->name; + $data['parent_link'] = get_category_link($root->term_id); + $data['category_name'] = $cat->name; + } else { + $data['parent_slug'] = $cat->slug; + $data['parent_name'] = $cat->name; + $data['parent_link'] = get_category_link($cat->term_id); + $data['category_name'] = $lang === 'en' ? 'Other' : 'Autre'; + } + // category_link: for direct posts (no ancestors), point to the /autres index + $data['category_link'] = empty($ancestor_ids) + ? trailingslashit(get_category_link($cat->term_id)) . 'autres/' + : get_category_link($cat->term_id); + break; + } + + // --- Documents joints: split images vs files --- + $doc_ids = get_post_meta($post_id, 'documents_joints', false); + foreach ($doc_ids as $doc_id) { + $mime = get_post_mime_type($doc_id); + if (!$mime) continue; + if (str_starts_with($mime, 'image/')) { + $src = wp_get_attachment_image_url($doc_id, 'large'); + if ($src) { + $meta = wp_get_attachment_metadata($doc_id); + $w = isset($meta['width']) ? $meta['width'] : 0; + $h = isset($meta['height']) ? $meta['height'] : 0; + $data['images'][] = [ + 'url' => $src, + 'alt' => get_post_meta($doc_id, '_wp_attachment_image_alt', true) ?: '', + 'caption' => thalim_bilingual(wp_get_attachment_caption($doc_id) ?: '', $lang), + 'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang), + 'portrait' => ($h > $w), + ]; + } + } else { + $data['documents'][] = [ + 'url' => wp_get_attachment_url($doc_id), + 'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang) ?: basename(get_attached_file($doc_id)), + ]; + } + } + + // --- Members (user IDs → name + profile URL) --- + foreach (get_post_meta($post_id, 'membres', false) as $uid) { + $user = get_userdata($uid); + if ($user) { + $data['membres'][] = [ + 'name' => $user->display_name, + 'url' => get_author_posts_url($uid), + ]; + } + } + + // --- Autre membres (user IDs → name + profile URL) --- + foreach (get_post_meta($post_id, 'autre_membres', false) as $uid) { + $user = get_userdata($uid); + if ($user) { + $data['autre_membres'][] = [ + 'name' => $user->display_name, + 'url' => get_author_posts_url($uid), + ]; + } + } + + // --- Axes thématiques (taxonomy term IDs) --- + $axe_ids = get_post_meta($post_id, 'axes_thematiques', false); + foreach ($axe_ids as $axe_id) { + $term = get_term(intval($axe_id), 'axe_thematique'); + if ($term && !is_wp_error($term)) { + $data['axes'][] = [ + 'id' => $term->term_id, + 'name' => thalim_bilingual($term->name, $lang), + 'url' => get_term_link($term), + ]; + } + } + + // --- Étiquettes (taxonomy term IDs) --- + $tag_ids = get_post_meta($post_id, 'etiquettes', false); + foreach ($tag_ids as $tag_id) { + $term = get_term(intval($tag_id), 'post_tag'); + if ($term && !is_wp_error($term)) { + $data['etiquettes'][] = [ + 'id' => $term->term_id, + 'name' => thalim_bilingual($term->name, $lang), + 'url' => get_term_link($term), + ]; + } + } + + // --- Programmes de recherche (taxonomy term IDs) --- + $prog_ids = get_post_meta($post_id, 'programmes_de_recherche', false); + foreach ($prog_ids as $prog_id) { + $term = get_term(intval($prog_id), 'programme_de_recherche'); + if ($term && !is_wp_error($term)) { + $data['programmes'][] = [ + 'id' => $term->term_id, + 'name' => thalim_bilingual($term->name, $lang), + 'url' => get_term_link($term), + ]; + } + } + + // --- Annonces liées (related posts) --- + $related_ids = get_post_meta($post_id, 'annonces_liees', false); + if (!empty($related_ids)) { + $data['annonces_liees'] = Timber::get_posts([ + 'post_type' => 'post', + 'post__in' => array_map('intval', $related_ids), + 'posts_per_page' => -1, + 'lang' => '', + ]); + } + + // --- Séances (session posts) — split into upcoming / past --- + $seance_ids = get_post_meta($post_id, 'seances', false); + $data['seances_a_venir'] = []; + $data['seances_passees'] = []; + if (!empty($seance_ids)) { + $seance_posts = Timber::get_posts([ + 'post_type' => 'post', + 'post__in' => array_map('intval', $seance_ids), + 'posts_per_page' => -1, + 'orderby' => 'meta_value', + 'meta_key' => 'date_de_debut', + 'order' => 'ASC', + 'lang' => '', + 'post_status' => ['publish', 'future'], + ]); + $now = time(); + $current_year = date('Y'); + $months_fr = ['jan.', 'fév.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']; + $months_en = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.']; + + foreach ($seance_posts as $seance) { + $raw_date = get_post_meta($seance->ID, 'date_de_debut', true); + $ts = $raw_date ? strtotime($raw_date) : strtotime($seance->post_date); + + // Only expose date_fin when it's a different day than date_de_debut + $raw_fin = get_post_meta($seance->ID, 'date_de_fin', true); + $ts_fin = $raw_fin && !str_starts_with($raw_fin, '0000-00-00') ? strtotime($raw_fin) : false; + $date_fin_display = ($ts_fin && date('Y-m-d', $ts_fin) !== date('Y-m-d', $ts)) + ? thalim_format_date($raw_fin, $lang) + : ''; + + $month_idx = intval(date('n', $ts)) - 1; + $seance_data = [ + 'post' => $seance, + 'day' => date('d', $ts), + 'month' => ($lang === 'en') ? $months_en[$month_idx] : $months_fr[$month_idx], + 'year' => (date('Y', $ts) !== $current_year) ? date('Y', $ts) : '', + 'date_full' => thalim_format_date($raw_date, $lang), + 'date_fin' => $date_fin_display, + 'heure_de_debut' => substr( get_post_meta($seance->ID, 'heure_de_debut', true) ?: '', 0, 5 ), + 'heure_de_fin' => substr( get_post_meta($seance->ID, 'heure_de_fin', true) ?: '', 0, 5 ), + 'lieu' => thalim_bilingual( get_post_meta($seance->ID, 'lieu', true) ?: '', $lang ), + 'adresse' => get_post_meta($seance->ID, 'adresse', true) ?: '', + 'body_en' => apply_filters( 'the_content', get_post_meta($seance->ID, 'body_en', true) ?: '' ), + 'intervenants' => [], + 'images' => [], + 'documents' => [], + 'liens_externes' => [], + 'annonces_liees' => [], + ]; + + // Resolve intervenants (membres or autrepersonnes) + $m_ids = get_post_meta($seance->ID, 'membres', false); + if (empty($m_ids)) { + $m_ids = get_post_meta($seance->ID, 'autre_membres', false); + } + foreach ($m_ids as $uid) { + $user = get_userdata($uid); + if ($user) { + $seance_data['intervenants'][] = [ + 'name' => $user->display_name, + 'url' => get_author_posts_url($uid), + ]; + } + } + $seance_data['autrepersonnes'] = get_post_meta($seance->ID, 'autrepersonnes', true) ?: ''; + $seance_data['show_image_titles'] = (bool) get_post_meta($seance->ID, 'afficher_le_titre_des_images_en_legende', true); + + // Documents joints: images and files + $s_doc_ids = get_post_meta($seance->ID, 'documents_joints', false); + foreach ($s_doc_ids as $doc_id) { + $mime = get_post_mime_type($doc_id); + if (!$mime) continue; + if (str_starts_with($mime, 'image/')) { + $src = wp_get_attachment_image_url($doc_id, 'large'); + if ($src) { + $seance_data['images'][] = [ + 'url' => $src, + 'alt' => get_post_meta($doc_id, '_wp_attachment_image_alt', true) ?: '', + 'caption' => thalim_bilingual(wp_get_attachment_caption($doc_id) ?: '', $lang), + 'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang), + ]; + } + } else { + $seance_data['documents'][] = [ + 'url' => wp_get_attachment_url($doc_id), + 'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang) ?: basename(get_attached_file($doc_id)), + ]; + } + } + + // External links (up to 3) + for ($i = 1; $i <= 3; $i++) { + $url = get_post_meta($seance->ID, 'lien_externe_' . $i, true); + if ($url) { + $titre = thalim_bilingual( get_post_meta($seance->ID, 'titre_du_lien_externe_' . $i, true) ?: '', $lang ); + if (!$titre) { + $host = parse_url($url, PHP_URL_HOST) ?: $url; + $parts = explode('.', $host); + $titre = count($parts) >= 2 ? implode('.', array_slice($parts, -2)) : $host; + } + $seance_data['liens_externes'][] = ['url' => $url, 'titre' => $titre]; + } + } + + // Annonces liées + $s_related_ids = get_post_meta($seance->ID, 'annonces_liees', false); + if (!empty($s_related_ids)) { + $seance_data['annonces_liees'] = Timber::get_posts([ + 'post_type' => 'post', + 'post__in' => array_map('intval', $s_related_ids), + 'posts_per_page' => -1, + 'lang' => '', + ]); + } + + if ($ts >= $now) { + $data['seances_a_venir'][] = $seance_data; + } else { + $data['seances_passees'][] = $seance_data; + } + } + // Past séances: most recent first + $data['seances_passees'] = array_reverse($data['seances_passees']); + } + + // --- Type label (category-conditional type fields) --- + $type_fields = [ + 'type_colloque_journee_d_etude', + 'type_soutenance', + 'type_evenement_culturel', + 'type_media', + 'type_captation', + 'type_revue_collection', + 'type_autre', + ]; + foreach ($type_fields as $field) { + $val = get_post_meta($post_id, $field, true); + if ($val) { + $data['type_label'] = thalim_bilingual( $val, $lang ); + break; + } + } + + // --- Fonction label (first non-empty fonction_* field) --- + $fonction_fields = [ + 'fonction_auteur', + 'fonction_organisation', + 'fonction_intervention', + 'fonction_redaction', + 'fonction_realisation', + 'fonction_dirige', + 'fonction_responsable', + 'fonction_candidat', + ]; + foreach ($fonction_fields as $field) { + $val = get_post_meta($post_id, $field, true); + if ($val) { + $data['fonction_label'] = thalim_bilingual( $val, $lang ); + break; + } + } + + // --- Autre fonction label (first non-empty autre_fonction_* field) --- + $autre_fonction_fields = [ + 'autre_fonction_autre', + 'autre_fonction_concerne', + 'autre_fonction_directeur', + 'autre_fonction_direction_d_ouvrage', + 'autre_fonction_intervenant', + 'autre_fonction_participants', + ]; + foreach ($autre_fonction_fields as $field) { + $val = get_post_meta($post_id, $field, true); + if ($val) { + $data['autre_fonction_label'] = thalim_bilingual( $val, $lang ); + break; + } + } + + // --- Fallback: derive labels from Pods categorie ID for older posts --- + if (!$data['fonction_label'] || !$data['autre_fonction_label']) { + $pods_cat = get_post_meta($post_id, '_pods_categorie', true); + $cat_id = (is_array($pods_cat) && !empty($pods_cat)) ? intval($pods_cat[0]) : 0; + + // Pods categorie ID → fonction label (main membres) + $cat_to_fonction = [ + 3 => 'Organisation', 4 => 'Auteur', 6 => 'Responsable', + 8 => 'Organisation', 9 => 'Responsable', 10 => 'Organisation', + 11 => 'Organisation', 12 => 'Intervention', 13 => 'Intervention', + 14 => 'Candidat', 15 => 'Auteur', 16 => 'Auteur', + 17 => 'Responsable', 18 => 'Organisation', 19 => 'Intervention', + 21 => 'Rédaction', 22 => 'Réalisation', 23 => 'Intervention', + 24 => 'Responsable', 25 => 'Responsable', 65 => 'Dirigé par', + ]; + + // Pods categorie ID → autre_fonction label (autre membres) + $cat_to_autre_fonction = [ + 3 => 'Participants', 4 => "Direction d'ouvrage", + 10 => 'Participants', 14 => 'Directeur de thèse', + 15 => "Direction d'ouvrage", 16 => "Direction d'ouvrage", + 19 => 'Membre concerné', 22 => 'Intervenant', + ]; + + if (!$data['fonction_label'] && isset($cat_to_fonction[$cat_id])) { + $data['fonction_label'] = $cat_to_fonction[$cat_id]; + } + if (!$data['autre_fonction_label'] && isset($cat_to_autre_fonction[$cat_id])) { + $data['autre_fonction_label'] = $cat_to_autre_fonction[$cat_id]; + } + } + + return $data; +} diff --git a/index.php b/index.php new file mode 100755 index 0000000..6c2c204 --- /dev/null +++ b/index.php @@ -0,0 +1,263 @@ +ID, $pin_field, true); + $fin = get_post_meta($post->ID, 'date_de_fin_depinglage', true); + $active = $epingle == '1' && (empty($fin) || $fin === '0000-00-00' || $fin >= $today); + if ($active) { $pinned[] = $post; } else { $normal[] = $post; } + } + return array_merge($pinned, array_slice($normal, 0, $max_normal)); +}; + +// --- Nombre d'items des diaporamas (depuis la page Le Laboratoire) --- +$labo_page = get_page_by_path('le-laboratoire'); +$max_swiper = $labo_page ? intval(get_post_meta($labo_page->ID, 'nombres_ditems_des_diaporamas', true)) : 0; +if ($max_swiper < 1) $max_swiper = 10; + +// --- Annonces diaporama --- +$annonces_raw = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => -1, + 'meta_query' => [[ + 'key' => 'afficher_dans_le_diaporama_dannonces_page_daccueil', + 'value' => '1', + ]], + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'thalim_event_date_order' => true, +]); +$context['annonces'] = $sort_with_pinned($annonces_raw, 'epingler_dans_le_diaporama_dannonces', $max_swiper); +$context['annonces_cards'] = thalim_get_cards_data($annonces_raw); + +// --- Publications et productions diaporama --- +$publications_raw = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => 30, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'tax_query' => [ + 'relation' => 'AND', + [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [4], + 'operator' => 'IN', + 'include_children' => true, + ], + [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [16], + 'operator' => 'NOT IN', + ], + ], + 'meta_query' => [ + 'relation' => 'OR', + [ + 'key' => 'type_autre', + 'value' => "Chapitre d'ouvrage", + 'compare' => '!=', + ], + [ + 'key' => 'type_autre', + 'compare' => 'NOT EXISTS', + ], + ], +]); +$context['publications'] = $sort_with_pinned($publications_raw, 'epingler_dans_le_diaporama_des_publications_et_productions', $max_swiper); +$context['publications_cards'] = thalim_get_cards_data($publications_raw); +$context['publications_link'] = thalim_en_url( get_category_link(4) ); +$context['annonces_link'] = thalim_en_url( get_permalink(29100) ); + +// --- Message du laboratoire --- +$messages_labo = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => 5, + 'cat' => 268, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', +]); +$context['messages_labo'] = $messages_labo ?: []; +$context['message_labo_link'] = thalim_en_url( get_category_link(268) ); + +// --- Agenda (médiation scientifique + séances de séminaire à venir) --- +$agenda_lang = thalim_current_language(); +$mediation_cat_ids = [5, 18, 19, 20, 21, 22, 23]; +$months_fr = ['jan.', 'fév.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']; +$months_en = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.']; +$agenda_type_fields = [ + 'type_colloque_journee_d_etude', 'type_soutenance', 'type_evenement_culturel', + 'type_media', 'type_captation', 'type_revue_collection', 'type_autre', +]; +$now_str = date('Y-m-d H:i:s'); + +$make_agenda_item = function ($post, $raw_date, $type_label, $lieu, $link) use ($agenda_lang, $months_fr, $months_en) { + $ts = strtotime($raw_date); + if (!$ts) return null; + $month_idx = intval(date('n', $ts)) - 1; + return [ + 'post' => $post, + 'ts' => $ts, + 'day' => intval(date('j', $ts)), + 'month' => $agenda_lang === 'en' ? $months_en[$month_idx] : $months_fr[$month_idx], + 'type_label' => $type_label, + 'lieu' => $lieu, + 'link' => $link, + ]; +}; + +$agenda_items = []; + +// 1. Upcoming médiation scientifique posts +$mediation_upcoming = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => 8, + 'category__in' => $mediation_cat_ids, + 'orderby' => ['date_clause' => 'ASC'], + 'lang' => '', + 'meta_query' => [ + 'date_clause' => [ + 'key' => 'date_de_debut', + 'value' => $now_str, + 'compare' => '>=', + 'type' => 'DATETIME', + ], + ], +]); +foreach ($mediation_upcoming as $mpost) { + $raw_date = get_post_meta($mpost->ID, 'date_de_debut', true); + if (!$raw_date) continue; + $type_label = ''; + foreach ($agenda_type_fields as $field) { + $val = get_post_meta($mpost->ID, $field, true); + if ($val) { $type_label = $val; break; } + } + if (!$type_label) { + foreach (get_the_category($mpost->ID) as $cat) { + if (in_array($cat->term_id, $mediation_cat_ids)) { $type_label = thalim_cat_name($cat); break; } + } + } + $item = $make_agenda_item($mpost, $raw_date, $type_label, get_post_meta($mpost->ID, 'lieu', true) ?: '', get_permalink($mpost->ID)); + if ($item) $agenda_items[] = $item; +} + +// 2. Upcoming séances de séminaire (cat 12) +$seances_upcoming = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => 8, + 'category__in' => [12], + 'orderby' => ['date_clause' => 'ASC'], + 'lang' => '', + 'meta_query' => [ + 'date_clause' => [ + 'key' => 'date_de_debut', + 'value' => $now_str, + 'compare' => '>=', + 'type' => 'DATETIME', + ], + ], +]); +foreach ($seances_upcoming as $seance) { + $raw_date = get_post_meta($seance->ID, 'date_de_debut', true); + if (!$raw_date) continue; + // Direct DB lookup — bypasses Polylang and other hook filters + global $wpdb; + $parent_id = $wpdb->get_var($wpdb->prepare( + "SELECT pm.post_id FROM {$wpdb->postmeta} pm + JOIN {$wpdb->posts} p ON p.ID = pm.post_id + WHERE pm.meta_key = 'seances' AND pm.meta_value = %s + AND p.post_status = 'publish' + LIMIT 1", + (string) $seance->ID + )); + $link = $parent_id + ? get_permalink((int) $parent_id) . '#seance-' . $seance->ID + : get_permalink($seance->ID); + $label = $agenda_lang === 'en' ? 'Seminar session' : 'Séance de séminaire'; + $item = $make_agenda_item($seance, $raw_date, $label, get_post_meta($seance->ID, 'lieu', true) ?: '', $link); + if ($item) $agenda_items[] = $item; +} + +// Sort merged list by date, keep 5 soonest +usort($agenda_items, fn($a, $b) => $a['ts'] <=> $b['ts']); +$agenda_items = array_slice($agenda_items, 0, 5); + +// Fallback: if no upcoming events, show 5 most recent mediation events +if (empty($agenda_items)) { + $fallback = Timber::get_posts([ + 'post_type' => 'post', + 'posts_per_page' => 5, + 'category__in' => $mediation_cat_ids, + 'orderby' => ['date_clause' => 'DESC'], + 'lang' => '', + 'meta_query' => [ + 'date_clause' => ['key' => 'date_de_debut', 'type' => 'DATETIME'], + ], + ]); + foreach ($fallback as $fpost) { + $raw_date = get_post_meta($fpost->ID, 'date_de_debut', true); + if (!$raw_date) continue; + $type_label = ''; + foreach ($agenda_type_fields as $field) { + $val = get_post_meta($fpost->ID, $field, true); + if ($val) { $type_label = $val; break; } + } + if (!$type_label) { + foreach (get_the_category($fpost->ID) as $cat) { + if (in_array($cat->term_id, $mediation_cat_ids)) { $type_label = thalim_cat_name($cat); break; } + } + } + $item = $make_agenda_item($fpost, $raw_date, $type_label, get_post_meta($fpost->ID, 'lieu', true) ?: '', get_permalink($fpost->ID)); + if ($item) $agenda_items[] = $item; + } +} + +$context['agenda_items'] = $agenda_items; +$context['manifestations_link'] = thalim_en_url( add_query_arg( 'view', 'agenda', get_category_link(3) ) ); + +// --- Quick links --- +$newsletter_cat = get_category_by_slug('newsletter'); +$newsletter_url = ''; +if ($newsletter_cat) { + $nl_posts = get_posts([ + 'post_type' => 'post', + 'posts_per_page' => 1, + 'category__in' => [ $newsletter_cat->term_id ], + 'include_children' => false, + 'orderby' => 'date', + 'order' => 'DESC', + 'suppress_filters' => true, + ]); + if ( ! empty( $nl_posts ) ) { + $newsletter_url = thalim_en_url( get_permalink( $nl_posts[0]->ID ) ); + } +} +if ( ! $newsletter_url ) { + $newsletter_url = thalim_en_url( + $newsletter_cat ? get_category_link( $newsletter_cat->term_id ) : home_url( '/category/le-laboratoire/newsletter/' ) + ); +} +$context['quick_links'] = [ + 'agenda' => thalim_en_url(add_query_arg('view', 'agenda', get_category_link(3))), + 'contacts' => thalim_en_url(home_url('/contacts/')), + 'newsletter' => $newsletter_url, +]; + +// --- Tags (étiquettes) pour le nuage de mots-clés --- +$context['has_tags'] = !empty(get_terms([ + 'taxonomy' => 'post_tag', + 'hide_empty' => true, + 'number' => 1, + 'lang' => '', +])); + +Timber::render('index.twig', $context); \ No newline at end of file diff --git a/js/adminDashboardMods.js b/js/adminDashboardMods.js new file mode 100644 index 0000000..8470066 --- /dev/null +++ b/js/adminDashboardMods.js @@ -0,0 +1,883 @@ +(function($) { + 'use strict'; + + function isPostEditPage() { + return window.pagenow === 'post' + || window.pagenow === 'post-new' + // On CPTs, pagenow is the post_type slug — also catch them via the + // body classes WP sets for any post.php / post-new.php screen. + || document.body.classList.contains('post-php') + || document.body.classList.contains('post-new-php'); + } + + function isProfileEditPage() { + return window.pagenow === 'profile' || window.pagenow === 'user-edit' || window.pagenow === 'user-new'; + } + + function getProfileForm() { + return document.querySelector('#your-profile, #createuser'); + } + + function isPodsModal() { + return new URLSearchParams(window.location.search).has('pods_modal'); + } + + function renameArticlesToAnnonces() { + const replacements = [ + [/Tous les articles/g, 'Toutes les annonces'], + [/Ajouter un article/g, 'Ajouter une annonce'], + [/Modifier l.article/g, "Modifier l'annonce"], + [/Pr\u00e9visualiser l.article/g, "Pr\u00e9visualiser l'annonce"], + [/Afficher l.article/g, "Afficher l'annonce"], + [/Voir l.article/g, "Voir l'annonce"], + [/Article publi\u00e9/g, 'Annonce publi\u00e9e'], + [/Article mis \u00e0 jour/g, 'Annonce mise \u00e0 jour'], + [/Article planifi\u00e9/g, 'Annonce planifi\u00e9e'], + [/Articles par page/g, 'Annonces par page'], + [/Articles/g, 'Annonces'], + [/Article/g, 'Annonce'], + [/Rechercher des articles/g, 'Rechercher des annonces'], + ]; + + function applyReplacements(text) { + return replacements.reduce((t, [s, r]) => t.replace(s, r), text); + } + + function replaceInTextNodes(el) { + if (!el) return; + const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT); + const nodes = []; + while (walker.nextNode()) nodes.push(walker.currentNode); + nodes.forEach(function(node) { + const replaced = applyReplacements(node.textContent); + if (replaced !== node.textContent) node.textContent = replaced; + }); + } + + // Menu latéral + replaceInTextNodes(document.querySelector('#menu-posts')); + + // Titre de page (h1) et bouton d'ajout + document.querySelectorAll('.wp-heading-inline, .page-title-action').forEach(replaceInTextNodes); + + // Notifications après sauvegarde (Article publié, mis à jour…) + document.querySelectorAll('#message, .notice').forEach(replaceInTextNodes); + + // Boîte de publication — lien "Voir l'article" + replaceInTextNodes(document.querySelector('.submitbox')); + + // Options d'écran — "Articles par page" + replaceInTextNodes(document.querySelector('#screen-options-wrap')); + + // Bouton de recherche (attribut value + aria-label) + var searchSubmit = document.querySelector('#search-submit'); + if (searchSubmit) { + if (searchSubmit.value) { + searchSubmit.value = applyReplacements(searchSubmit.value); + } + var ariaLabel = searchSubmit.getAttribute('aria-label'); + if (ariaLabel) { + searchSubmit.setAttribute('aria-label', applyReplacements(ariaLabel)); + } + } + + // Titre de l'onglet du navigateur + document.title = applyReplacements(document.title); + } + + function updatePostboxVisibility() { + document.querySelectorAll('.postbox').forEach((postBox) => { + if (postBox.id.startsWith('pods')) { + // body-en is controlled by language tabs — never auto-hide it + if (postBox.id === 'pods-meta-body-en') return; + const fields = postBox.querySelectorAll('tr'); + const hasVisibleFields = Array.from(fields).some(field => field.style.display !== 'none'); + postBox.style.display = hasVisibleFields ? 'block' : 'none'; + } + }); + } + + // Force Visual (TinyMCE) mode on page load. + // WP stores the last-used editor mode in localStorage and restores it at document.ready. + // When Code mode is restored, TinyMCE is never initialised — tinymce.get() returns null. + // Instead, check the wrapper's CSS class: + // tmce-active = Visual mode (fine) + // html-active = Code mode (switch to Visual) + function ensureVisualMode(editorId, attempt) { + attempt = attempt || 0; + if (attempt > 15) return; + var wrap = document.getElementById('wp-' + editorId + '-wrap'); + if (!wrap) { + setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200); + return; + } + if (wrap.classList.contains('html-active')) { + var ed = window.tinymce && tinymce.get(editorId); + if (!ed || !ed.initialized) { + // TinyMCE not ready yet — retry rather than calling switchEditors.go() prematurely + setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200); + return; + } + if (typeof switchEditors !== 'undefined') { + switchEditors.go(editorId, 'tmce'); + } + return; + } + if (!wrap.classList.contains('tmce-active')) { + // Mode not yet determined — retry + setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200); + } + } + + // Phase 1: insert the tab bar and relocate #pods-meta-body-en. + // The DOM move breaks TinyMCE's iframe (browsers reset iframe content on detach), + // so we leave the container visible here and let Pods/TinyMCE initialise normally. + // The broken iframe is repaired by reinitEditor() on first EN tab open. + function setupBodyTabsDom() { + var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv'); + var bodyEnBox = document.getElementById('pods-meta-body-en'); + if (!nativeEditor || !bodyEnBox) return; + + var tabBar = document.createElement('div'); + tabBar.className = 'body-lang-tabs'; + tabBar.innerHTML = + '' + + ''; + nativeEditor.parentNode.insertBefore(tabBar, nativeEditor); + + // Move EN metabox to sit right after the native editor for correct visual layout. + // Do NOT hide it yet — Pods must init TinyMCE with the container visible so the + // iframe can measure its dimensions. Page is still opacity:0 so no flash. + nativeEditor.parentNode.insertBefore(bodyEnBox, nativeEditor.nextSibling); + } + + // Rebuild a TinyMCE editor whose iframe is broken (empty/non-interactive). + // This happens when TinyMCE is initialised on a hidden (display:none) element: + // the iframe can't measure dimensions and its document body stays empty. + // + // We reinit from tinyMCEPreInit.mceInit — first trying the editor's own config + // (registered by Pods server-side), falling back to 'content' (the native WP editor). + // + // Inline toolbar positioning fix: + // TinyMCE's 'wordpress' plugin captures document.getElementById(id+'_ifr') during + // 'preinit' — before the iframe is created — so mceIframe is always null. + // Fix: intercept getElementById during preinit so the 'wordpress' plugin captures + // a proxyIframe instead of null. After init, proxy delegates to the real iframe. + function reinitEditor(editorId) { + var ed = window.tinymce && tinymce.get(editorId); + // Preserve existing content before destroying the instance + var savedContent = ''; + if (ed) { + try { savedContent = ed.getContent(); } catch (e) {} + ed.remove(); + } + if (!savedContent) { + var ta = document.getElementById(editorId); + if (ta) savedContent = ta.value || ''; + } + + if (!window.tinyMCEPreInit || !window.tinymce) return; + + // Use the editor's own server-side config if available, else clone from 'content' + var baseInit = (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit[editorId]) + || (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit['content']); + if (!baseInit) return; + + // Proxy iframe: getBoundingClientRect() falls back to the editor wrap + var wrapId = 'wp-' + editorId + '-wrap'; + var proxyIframe = { + getBoundingClientRect: function() { + var el = document.getElementById(wrapId); + return el ? el.getBoundingClientRect() + : { top: 0, left: 0, right: window.innerWidth, + bottom: window.innerHeight, width: window.innerWidth, + height: window.innerHeight }; + } + }; + + var savedGetById = document.getElementById; + var origSetup = baseInit.setup; + var content = savedContent; + + tinymce.init($.extend({}, baseInit, { + selector: '#' + editorId, + setup: function(editor) { + if (typeof origSetup === 'function') origSetup(editor); + + editor.on('focus', function() { + window.wpActiveEditor = editorId; + }); + + editor.on('preinit', function() { + document.getElementById = function(id) { + if (id === editorId + '_ifr') return proxyIframe; + return savedGetById.call(document, id); + }; + setTimeout(function() { + document.getElementById = savedGetById; + }, 0); + }); + + editor.on('init', function() { + // Point proxy to real iframe + var realIframe = savedGetById.call(document, editorId + '_ifr'); + if (realIframe) { + proxyIframe.getBoundingClientRect = function() { + return realIframe.getBoundingClientRect(); + }; + } + // Restore content that was in the textarea + if (content) { + editor.setContent(content); + } + }); + } + })); + } + + // Phase 2: wire tab click handlers — runs at t=100ms after metabox reordering. + function initBodyLanguageTabs() { + var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv'); + var bodyEnBox = document.getElementById('pods-meta-body-en'); + var tabBar = document.querySelector('.body-lang-tabs'); + if (!nativeEditor || !bodyEnBox || !tabBar) { + // body_en not available (e.g. contributor role) — still force visual mode on main editor + if (nativeEditor) ensureVisualMode('content'); + return; + } + + var enEditorId = 'pods-form-ui-pods-meta-body-en'; + var enTmceReady = false; + + // Hide EN panel — page is still opacity:0, user won't see the switch + bodyEnBox.style.display = 'none'; + + tabBar.querySelectorAll('.body-lang-tab').forEach(function(btn) { + btn.addEventListener('click', function() { + tabBar.querySelectorAll('.body-lang-tab').forEach(function(b) { + b.classList.remove('is-active'); + }); + btn.classList.add('is-active'); + + var revealedPanel; + + if (btn.dataset.panel === 'fr') { + bodyEnBox.style.display = 'none'; + nativeEditor.style.opacity = '0'; + nativeEditor.style.display = ''; + revealedPanel = nativeEditor; + } else { + nativeEditor.style.display = 'none'; + bodyEnBox.style.opacity = '0'; + bodyEnBox.style.display = 'block'; + revealedPanel = bodyEnBox; + + if (!enTmceReady) { + enTmceReady = true; + // Reinit while container is visible so TinyMCE can measure dimensions + reinitEditor(enEditorId); + } + } + + // Notify TinyMCE to reflow, then fade in once layout is correct + setTimeout(function() { + window.dispatchEvent(new Event('resize')); + requestAnimationFrame(function() { + requestAnimationFrame(function() { + revealedPanel.style.opacity = ''; + }); + }); + }, 50); + }); + }); + + // Ensure both editors start in Visual (not Code) mode + ensureVisualMode('content'); + ensureVisualMode(enEditorId); + } + + function groupAxesCheckboxes() { + if (!window.thalimAxesGroups || !thalimAxesGroups.length) return; + + var row = document.querySelector('.pods-form-ui-row-name-axes-thematiques'); + if (!row) return; + + var list = row.querySelector('ul'); + if (!list) return; + + // Already grouped — nothing to do + if (list.querySelector('.axes-group-label')) return; + + // Map existing
  • by checkbox value; preserve "add new" button + var liMap = {}; + var addNewItem = null; + list.querySelectorAll('li').forEach(function(li) { + if (li.classList.contains('pods-pick-add-new')) { addNewItem = li; return; } + var cb = li.querySelector('input[type="checkbox"]'); + if (cb) liMap[cb.value] = li; + }); + + // Rebuild list in group order + list.innerHTML = ''; + thalimAxesGroups.forEach(function(group) { + var labelLi = document.createElement('li'); + labelLi.className = 'axes-group-label'; + labelLi.textContent = group.label; + list.appendChild(labelLi); + group.terms.forEach(function(term) { + var li = liMap[String(term.id)]; + if (li) list.appendChild(li); + }); + }); + + if (addNewItem) list.appendChild(addNewItem); + } + + var REF_BIB_EDITOR_ID = 'pods-form-ui-pods-meta-reference-bibliographique'; + var refBibReinited = false; + + // Reinit the référence bibliographique TinyMCE editor. + // Called at page load (if the field is already visible) and by the + // MutationObserver (when the field becomes visible after a category change). + function initRefBibEditor() { + if (refBibReinited) return; + var row = document.querySelector('.pods-form-ui-row-name-reference-bibliographique'); + if (!row || row.style.display === 'none') return; + refBibReinited = true; + reinitEditor(REF_BIB_EDITOR_ID); + ensureVisualMode(REF_BIB_EDITOR_ID); + } + + function initAxesGroupObserver() { + // Pods shows/hides conditional rows by removing inline style="display:none" + // Watch the entire Pods meta form for style changes on the axes row + var podsForm = document.querySelector('.pods-pick-values, #pods-meta-champs-contextuels, form#post'); + if (!podsForm) podsForm = document.body; + + var observer = new MutationObserver(function(mutations) { + for (var i = 0; i < mutations.length; i++) { + var target = mutations[i].target; + if (target.classList && target.classList.contains('pods-form-ui-row-name-axes-thematiques')) { + if (target.style.display !== 'none') { + setTimeout(groupAxesCheckboxes, 50); + } + } + // Reinit TinyMCE on the référence bibliographique field when its + // row becomes visible — Pods hides it with display:none which breaks + // the TinyMCE iframe. Only reinit once per page load. + if (!refBibReinited && target.classList && + target.classList.contains('pods-form-ui-row-name-reference-bibliographique')) { + if (target.style.display !== 'none') { + setTimeout(initRefBibEditor, 100); + } + } + } + }); + + observer.observe(podsForm, { attributes: true, attributeFilter: ['style'], subtree: true }); + } + + function initPostEditPage() { + // Disable category options (CSS handles the color) + const categorieSelect = document.querySelector('#pods-form-ui-pods-meta-categorie'); + if (categorieSelect) { + const categoriesToDisable = ['1', '12', '5', '20']; + categorieSelect.querySelectorAll('option').forEach(option => { + if (categoriesToDisable.includes(option.value)) { + option.disabled = true; + } + }); + } + + // Reorder meta boxes + const sideSortables = document.querySelector('#side-sortables'); + if (sideSortables) { + const typeDannonce = document.querySelector('#pods-meta-type-dannonce'); + const affichageAccueil = document.querySelector('#pods-meta-affichage-sur-laccueil'); + const thematique = document.querySelector('#pods-meta-thematique'); + if (typeDannonce) sideSortables.prepend(typeDannonce); + if (affichageAccueil) sideSortables.appendChild(affichageAccueil); + if (thematique) sideSortables.appendChild(thematique); + } + + const submitDiv = document.querySelector('#submitdiv'); + if (submitDiv && submitDiv.parentNode) { + submitDiv.parentNode.appendChild(submitDiv); + } + + const champsContextuels = document.querySelector('#pods-meta-champs-contextuels'); + if (champsContextuels && champsContextuels.parentNode) { + champsContextuels.parentNode.prepend(champsContextuels); + } + + initBodyLanguageTabs(); + initRefBibEditor(); + groupAxesCheckboxes(); + initAxesGroupObserver(); + updatePostboxVisibility(); + initDatePickerPopoverFix(); + initInfoPopovers(); + + // Place #pods-meta-documents-joints in #normal-sortables, right after + // #pods-meta-champs-contextuels. This keeps it out of #post-body-content + // (the body editor section) regardless of whether champsContextuels is + // currently visible. When champsContextuels is hidden it takes no space, + // so documentsJoints simply appears first in #normal-sortables. + const documentsJoints = document.querySelector('#pods-meta-documents-joints'); + if (documentsJoints) { + if (champsContextuels && champsContextuels.parentNode) { + champsContextuels.parentNode.insertBefore(documentsJoints, champsContextuels.nextSibling); + } else { + const normalSortables = document.querySelector('#normal-sortables'); + if (normalSortables) normalSortables.prepend(documentsJoints); + } + } + + // Inject separator row for the Membres grid layout + var membresTbody = document.querySelector('#pods-meta-membres .form-table tbody'); + if (membresTbody && !membresTbody.querySelector('.membres-grid-separator')) { + var sep = document.createElement('tr'); + sep.className = 'membres-grid-separator'; + membresTbody.appendChild(sep); + } + updateMembresGridSeparator(); + } + + var INFO_ICON = ''; + var TRANSLATE_ICON = ''; + + var TRANSLATE_LINES = [ + 'Traduction en anglais apr\u00e8s //', + 'ex\u00a0: Texte en fran\u00e7ais // English text' + ]; + + // Tips without a `page` key default to 'post'. + // type: 'translate' uses the globe icon + green button style. + var INFO_TIPS = [ + // --- post edit page: info --- + { + selector: '.wp-heading-inline', + lines: [ + 'Saisir le titre anglais apr\u00e8s //', + 'ex\u00a0: Titre de l\u2019annonce // Title of the announcement' + ] + }, + { + selector: '#pods-meta-documents-joints .postbox-header h2', + lines: [ + 'Ajouter les images dans les documents.', + 'Ajouter les l\u00e9gendes comme titre du document.' + ] + }, + { + selector: '#pods-meta-membres .postbox-header h2', + lines: [ + 'Le champ fonction change le libell\u00e9 de la liste de personnes cit\u00e9es.', + 'Le champ membre permet de lister les membres de Thalim li\u00e9s \u00e0 l\u2019annonce.', + 'Le champ autre personnes permet de lister des personnes ext\u00e9rieures \u00e0 Thalim.' + ] + }, + { + selector: '#pods-meta-dates .postbox-header h2', + lines: [ + 'Pour entrer une date sans l\u2019heure, r\u00e9gler l\u2019heure sur 00\u202f:00.' + ] + }, + { + selector: '#pods-meta-affichage-sur-laccueil .postbox-header h2', + lines: [ + '\u00c9pingler l\u2019annonce dans le diaporama la fait s\u2019afficher avant les autres.' + ] + }, + { + selector: '#pods-meta-medias .postbox-header h2', + lines: [ + 'Pour ajouter un m\u00e9dia Canal\u00a0U, copier le lien depuis \u00ab\u00a0Citer cette ressource\u00a0\u00bb.', + 'ex\u00a0: https://www.canal-u.tv/166564' + ] + }, + // --- post edit page: translate --- + { type: 'translate', selector: '#pods-meta-documents-joints .postbox-header h2', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-sous-titre th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-lieu th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-1 th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-2 th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-3 th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-organisation th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-intervention th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-candidat th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-realisation th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-dirige th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-redaction th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-auteur th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-fonction-responsable th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-autre th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-concerne th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-directeur th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-direction-d-ouvrage th',lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-intervenant th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-participants th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-type-autre th', lines: TRANSLATE_LINES }, + // --- contenu_general edit page: translate --- + { type: 'translate', selector: '.pods-form-ui-row-name-umr th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-thalim th', lines: TRANSLATE_LINES }, + { type: 'translate', selector: '.pods-form-ui-row-name-siecles th', lines: TRANSLATE_LINES }, + // --- user/profile edit page: translate --- + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-1 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-2 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-3 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-4 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-1 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-2 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-3 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-1 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-2 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-3 th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affiliation-autre th', lines: TRANSLATE_LINES }, + { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-de-these th', lines: TRANSLATE_LINES }, + // --- taxonomy edit pages: translate --- + { type: 'translate', page: 'taxonomy', selector: 'label[for="name"]', lines: TRANSLATE_LINES }, + // --- user/profile edit page: info --- + { + page: 'user', + selector: '.pods-form-ui-label-pods-meta-identifiant-hal', + lines: [ + 'Renseigner votre idHAL (en lettres), pas votre PersonId (en chiffres).' + ] + }, + { + page: 'user', + selector: '.pods-form-ui-label-pods-meta-affichage-du-statut-1', + lines: [ + 'Texte de statut affiché sur le profil publique.' + ] + } + ]; + + var _popoverCloseHandlerRegistered = false; + + function initInfoPopovers(currentPage) { + currentPage = currentPage || 'post'; + + INFO_TIPS.forEach(function(tip) { + if ((tip.page || 'post') !== currentPage) return; + + var el = document.querySelector(tip.selector); + if (!el) return; + + var isTranslate = tip.type === 'translate'; + + var btn = document.createElement('button'); + btn.type = 'button'; + btn.className = isTranslate ? 'thalim-translate-btn' : 'thalim-info-btn'; + btn.setAttribute('aria-label', isTranslate ? 'Traduction bilingue' : 'Informations'); + btn.innerHTML = isTranslate ? TRANSLATE_ICON : INFO_ICON; + + var popover = document.createElement('div'); + popover.className = 'thalim-info-popover' + (isTranslate ? ' thalim-translate-popover' : ''); + popover.innerHTML = tip.lines.map(function(line) { + return '

    ' + line + '

    '; + }).join(''); + + var wrapper = document.createElement('span'); + wrapper.className = 'thalim-info-wrapper'; + wrapper.appendChild(btn); + wrapper.appendChild(popover); + + el.appendChild(wrapper); + + btn.addEventListener('click', function(e) { + e.stopPropagation(); + var isOpen = popover.classList.contains('is-open'); + document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) { + p.classList.remove('is-open'); + }); + if (!isOpen) { + var rect = btn.getBoundingClientRect(); + popover.style.top = (rect.bottom + 6) + 'px'; + popover.style.left = (rect.left + rect.width / 2) + 'px'; + popover.classList.add('is-open'); + } + }); + + popover.addEventListener('click', function(e) { + e.stopPropagation(); + }); + }); + + if (!_popoverCloseHandlerRegistered) { + _popoverCloseHandlerRegistered = true; + document.addEventListener('click', function() { + document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) { + p.classList.remove('is-open'); + }); + }); + } + } + + // Only native WP field sections — never touch Pods tables (they may contain TinyMCE editors) + var PROFILE_SECTION_KEYS = [ + 'user-language-wrap', + 'user-first-name-wrap', + 'user-email-wrap', + 'user-pass1-wrap', + 'upload-avatar-row', + ]; + + // Desired order. Groups of 2 are wrapped in a flex row and displayed side by side. + var PROFILE_ORDER = [ + ['user-first-name-wrap', 'upload-avatar-row'], + ['user-email-wrap'], + ['user-language-wrap', 'user-pass1-wrap'], + ]; + + function reorderProfileSections() { + var form = getProfileForm(); + if (!form) return; + + var pairMap = {}; + + form.querySelectorAll('table.form-table').forEach(function(table) { + PROFILE_SECTION_KEYS.forEach(function(key) { + if (pairMap[key] || !table.querySelector('.' + key)) return; + + // Find the associated heading: first try preceding sibling in same parent, + // then look for an h2/h3 inside the same wrapper element. + var h2 = null; + var el = table.previousElementSibling; + while (el) { + if (el.tagName === 'H2' || el.tagName === 'H3') { h2 = el; break; } + if (el.tagName === 'TABLE') break; + el = el.previousElementSibling; + } + if (!h2 && table.parentElement !== form) { + h2 = table.parentElement.querySelector('h2, h3'); + } + + // The unit to move: if h2 and table share a non-form wrapper, move the wrapper. + var wrapper = null; + if (h2 && h2.parentElement !== form && h2.parentElement === table.parentElement) { + wrapper = h2.parentElement; + } + + pairMap[key] = { h2: h2, table: table, wrapper: wrapper }; + }); + }); + + // Remove all matched units from DOM (dedup by actual element) + var removed = new Set(); + function removeEl(el) { + if (el && !removed.has(el)) { removed.add(el); el.remove(); } + } + Object.values(pairMap).forEach(function(unit) { + if (unit.wrapper) { removeEl(unit.wrapper); } + else { removeEl(unit.h2); removeEl(unit.table); } + }); + + // Re-insert in declared order before the submit button + var submitAnchor = form.querySelector('p.submit'); + function append(el) { + if (submitAnchor && submitAnchor.parentNode) form.insertBefore(el, submitAnchor); + else form.appendChild(el); + } + function appendUnit(unit) { + if (unit.wrapper) { append(unit.wrapper); } + else { if (unit.h2) append(unit.h2); append(unit.table); } + } + + PROFILE_ORDER.forEach(function(group) { + var available = group.filter(function(key) { return !!pairMap[key]; }); + if (!available.length) return; + + // Dedup: two keys may resolve to the same table/wrapper + var seen = new Set(); + var units = []; + available.forEach(function(key) { + var unit = pairMap[key]; + var id = unit.wrapper || unit.table; + if (!seen.has(id)) { seen.add(id); units.push(unit); } + }); + + if (units.length === 1) { + appendUnit(units[0]); + } else { + var row = document.createElement('div'); + row.className = 'profile-section-row'; + units.forEach(function(unit) { + var col = document.createElement('div'); + col.className = 'profile-section-col'; + if (unit.wrapper) { col.appendChild(unit.wrapper); } + else { if (unit.h2) col.appendChild(unit.h2); col.appendChild(unit.table); } + row.appendChild(col); + }); + append(row); + } + }); + } + + function initProfileEditors() { + reorderProfileSections(); + initInfoPopovers('user'); + + // Hide the "À propos du compte" section heading + document.querySelectorAll('#your-profile h2, #adduser h2, #createuser h2').forEach(function(h2) { + if (h2.textContent.trim() === '\u00c0 propos du compte') { + h2.style.display = 'none'; + } + }); + + // Rename "Rôle" label to "Rôle sur le site" + var roleLabel = document.querySelector('label[for="role"]'); + if (roleLabel && roleLabel.textContent.trim() === 'R\u00f4le') { + roleLabel.textContent = 'R\u00f4le sur le site'; + } + } + + // Gutenberg's Popover component closes on outside click via focusout detection. + // But if focus never enters the popover, focusout never fires and clicking outside + // does nothing. Fix: focus the popover container as soon as it appears in the DOM. + function initDatePickerPopoverFix() { + var observer = new MutationObserver(function(mutations) { + for (var i = 0; i < mutations.length; i++) { + var added = mutations[i].addedNodes; + for (var j = 0; j < added.length; j++) { + var node = added[j]; + if (node.nodeType !== 1) continue; + var content = node.classList.contains('components-popover__content') + ? node + : node.querySelector && node.querySelector('.components-popover__content'); + if (content) { + var c = content; + requestAnimationFrame(function() { + if (!c.hasAttribute('tabindex')) c.setAttribute('tabindex', '-1'); + c.focus(); + }); + } + } + } + }); + observer.observe(document.body, { childList: true, subtree: true }); + } + + function updateMembresGridSeparator() { + var sep = document.querySelector('#pods-meta-membres .membres-grid-separator'); + if (!sep) return; + var autreRows = document.querySelectorAll('#pods-meta-membres [class*="pods-form-ui-row-name-autre-"]'); + var anyVisible = Array.from(autreRows).some(function(row) { + return row.style.display !== 'none'; + }); + sep.style.display = anyVisible ? '' : 'none'; + } + + // Inject a "Type de programme" filter select into the taxonomy search form. + // The form already has hidden taxonomy/post_type fields so the select value + // is submitted with them and picked up by pre_get_terms server-side. + function initProgrammeFilter() { + var form = document.querySelector('form.search-form'); + if (!form) return; + + var types = [ + 'Programme subventionné', + 'Autre programme', + 'Ancien programme' + ]; + + // Read current filter value from the URL. + var params = new URLSearchParams(window.location.search); + var current = params.get('type_de_programme') || ''; + + var select = document.createElement('select'); + select.name = 'type_de_programme'; + select.id = 'filter-type-de-programme'; + select.style.cssText = 'margin-right:6px;'; + + var blank = document.createElement('option'); + blank.value = ''; + blank.textContent = 'Tous les types'; + select.appendChild(blank); + + types.forEach(function(type) { + var opt = document.createElement('option'); + opt.value = type; + opt.textContent = type; + if (type === current) opt.selected = true; + select.appendChild(opt); + }); + + // Insert before the first

    (search-box) inside the form. + var searchBox = form.querySelector('p.search-box'); + form.insertBefore(select, searchBox || null); + } + + $(document).ready(function() { + renameArticlesToAnnonces(); + + if (isPostEditPage()) { + setupBodyTabsDom(); + } + + setTimeout(() => { + if (isPostEditPage()) { + initPostEditPage(); + } + if (isProfileEditPage()) { + initProfileEditors(); + } + var TRANSLATE_TAXONOMIES = ['axe_thematique', 'programme_de_recherche', 'post_tag']; + var isTaxonomyListPage = TRANSLATE_TAXONOMIES.some(function(tax) { + return window.location.search.indexOf('taxonomy=' + tax) !== -1; + }); + if (isTaxonomyListPage) { + initInfoPopovers('taxonomy'); + } + if (window.location.search.indexOf('taxonomy=programme_de_recherche') !== -1) { + initProgrammeFilter(); + } + document.body.classList.add('admin-mods-ready'); + }, 100); + + // Fallback: force reveal after 2s in case the 100ms path failed (e.g. JS error mid-init) + setTimeout(() => { + document.body.classList.add('admin-mods-ready'); + }, 2000); + + $('#pods-form-ui-pods-meta-categorie').change(function() { + setTimeout(function() { + updatePostboxVisibility(); + updateMembresGridSeparator(); + }, 10); + }); + + if (isProfileEditPage() || window.pagenow === 'edit-tags' || window.pagenow === 'term') { + $(window).on('load', function() { + var scope = getProfileForm() || document; + scope.querySelectorAll('.pods-dfv-container-wysiwyg textarea').forEach(function(ta) { + if (!ta.id) return; + ensureVisualMode(ta.id); + }); + }); + } + + if (isPodsModal()) { + $(window).on('load', function() { + var itemId = $('#post_ID').val(); + if (window.PodsDFV && itemId) { + window.PodsDFV.setFieldValue('post', itemId, 'categorie', '12', 0); + } + + // Lock category select to 12 in iframe — delay to run after Pods React re-render + setTimeout(function() { + var $select = $('#pods-form-ui-pods-meta-categorie'); + if ($select.length) { + $select.find('option').each(function() { + this.disabled = this.value !== '12'; + }); + $select.val('12'); + } + updatePostboxVisibility(); + }, 200); + }); + } + }); + +})(jQuery); diff --git a/js/adminFormRestore.js b/js/adminFormRestore.js new file mode 100644 index 0000000..ece0ae9 --- /dev/null +++ b/js/adminFormRestore.js @@ -0,0 +1,119 @@ +(function($) { + 'use strict'; + + var STORAGE_PREFIX = 'thalim_form_restore_'; + var MAX_AGE_MS = 10 * 60 * 1000; // 10 min + + function getPostId() { + return $('#post_ID').val() || 'new'; + } + + function getStorageKey() { + return STORAGE_PREFIX + getPostId(); + } + + function saveFormData() { + var data = { timestamp: Date.now() }; + + var $title = $('#title'); + if ($title.length) data.title = $title.val(); + + var $content = $('#content'); + if ($content.length) { + if (typeof tinyMCE !== 'undefined' && tinyMCE.get('content')) { + data.content = tinyMCE.get('content').getContent(); + } else { + data.content = $content.val(); + } + } + + $('[name^="pods_meta_"]').each(function() { + data[this.name] = $(this).val(); + }); + + try { + sessionStorage.setItem(getStorageKey(), JSON.stringify(data)); + } catch (e) {} + } + + function restoreFormData() { + var navEntries = performance.getEntriesByType('navigation'); + if (!navEntries.length || navEntries[0].type !== 'back_forward') return; + + var key = getStorageKey(); + var stored, data; + try { + stored = sessionStorage.getItem(key); + if (!stored) return; + data = JSON.parse(stored); + } catch (e) { return; } + + if (!data.timestamp || Date.now() - data.timestamp > MAX_AGE_MS) { + try { sessionStorage.removeItem(key); } catch (e) {} + return; + } + + // Restaurer titre + if (data.title !== undefined) $('#title').val(data.title); + + // Restaurer contenu (TinyMCE ou textarea brut) + if (data.content !== undefined) { + $('#content').val(data.content); + if (typeof tinyMCE !== 'undefined') { + var ed = tinyMCE.get('content'); + if (ed) { + ed.setContent(data.content); + } else { + tinyMCE.on('AddEditor', function(e) { + if (e.editor.id === 'content') { + e.editor.on('init', function() { + e.editor.setContent(data.content); + }); + } + }); + } + } + } + + // Restaurer champs Pods après rendu React + var restorePods = function() { + setTimeout(function() { + var postId = getPostId(); + Object.keys(data).forEach(function(name) { + if (!name.startsWith('pods_meta_')) return; + var fieldName = name.replace(/^pods_meta_/, ''); + var value = data[name]; + var $el = $('[name="' + name + '"]'); + if ($el.length) $el.val(value).trigger('change'); + if (window.PodsDFV && postId) { + try { window.PodsDFV.setFieldValue('post', postId, fieldName, value, 0); } catch (e) {} + } + }); + }, 300); + }; + + if (document.readyState === 'complete') { + restorePods(); + } else { + $(window).on('load', restorePods); + } + + // Notice informative + var $notice = $('

    Votre contenu a \u00e9t\u00e9 restaur\u00e9 suite \u00e0 une erreur de validation. V\u00e9rifiez les champs obligatoires avant de publier.

    '); + $('#wpbody-content').find('.wrap').first().find('h1').after($notice); + } + + $(document).ready(function() { + if (window.pagenow !== 'post' && window.pagenow !== 'post-new') return; + + // Nettoyage si sauvegarde réussie + if ($('.notice-success, #message.updated').length) { + try { sessionStorage.removeItem(getStorageKey()); } catch (e) {} + return; + } + + $('#post').on('submit', saveFormData); + restoreFormData(); + }); + +})(jQuery); diff --git a/js/agendaView.js b/js/agendaView.js new file mode 100644 index 0000000..dba2fe0 --- /dev/null +++ b/js/agendaView.js @@ -0,0 +1,146 @@ +(function () { + 'use strict'; + + document.addEventListener('DOMContentLoaded', function () { + var toggleBtn = document.querySelector('.agenda-toggle-btn'); + var gridSections = document.getElementById('grid-sections'); + var agendaEl = document.getElementById('agenda-view'); + if (!toggleBtn || !agendaEl) return; + + var swiper = null; + var agendaPage = 0; + var agendaDone = false; + var agendaLoading = false; + var swiperWrapper = document.getElementById('agenda-swiper-wrapper'); + var agendaSpinner = document.getElementById('agenda-spinner'); + + var categoryId = agendaEl.dataset.category || ''; + var includeChildren = agendaEl.dataset.includeChildren || '0'; + var axe = agendaEl.dataset.axe || ''; + var dateFrom = agendaEl.dataset.dateFrom || ''; + var dateTo = agendaEl.dataset.dateTo || ''; + + function showAgenda() { + if (gridSections) gridSections.style.display = 'none'; + agendaEl.classList.add('is-active'); + toggleBtn.innerHTML = + '' + + (agendaViewData.lang === 'en' ? 'Switch to grid view' : 'Passer à la vue grille'); + if (!swiper) { + loadMoreAgenda(function (data) { + var todayOffset = Math.max(0, ((data && data.today_offset) || 0) - 1); + var anchorPage = Math.ceil((todayOffset + 1) / 12); + if (anchorPage > 1) { + chainLoadPages(2, anchorPage, function () { + initSwiper(todayOffset); + }); + } else { + initSwiper(todayOffset); + } + }); + } + } + + function showGrid() { + agendaEl.classList.remove('is-active'); + if (gridSections) gridSections.style.display = ''; + toggleBtn.innerHTML = + '' + + (agendaViewData.lang === 'en' ? 'Switch to agenda view' : 'Passer à la vue agenda'); + } + + function initSwiper(initialSlide) { + swiper = new Swiper(agendaEl.querySelector('.agenda-swiper'), { + slidesPerView: 1.2, + spaceBetween: 20, + initialSlide: initialSlide || 0, + navigation: { + nextEl: agendaEl.querySelector('.agenda-swiper-prev'), + prevEl: agendaEl.querySelector('.agenda-swiper-next'), + }, + breakpoints: { + 640: { slidesPerView: 2, spaceBetween: 24 }, + 1024: { slidesPerView: 3, spaceBetween: 32 }, + }, + on: { + reachEnd: function () { + if (!agendaDone) loadMoreAgenda(); + }, + }, + }); + swiper.changeLanguageDirection('rtl'); + } + + function chainLoadPages(fromPage, toPage, callback) { + if (fromPage > toPage) { callback(); return; } + loadMoreAgenda(function () { + chainLoadPages(fromPage + 1, toPage, callback); + }); + } + + function loadMoreAgenda(callback) { + if (agendaLoading || agendaDone) return; + agendaLoading = true; + agendaPage++; + agendaSpinner.style.display = 'flex'; + + var data = new FormData(); + data.append('action', 'load_more_agenda'); + data.append('page', agendaPage); + data.append('nonce', agendaViewData.nonce); + data.append('lang', agendaViewData.lang); + if (categoryId) data.append('category', categoryId); + if (includeChildren==='1') data.append('include_children', '1'); + if (axe) data.append('axe', axe); + if (dateFrom) data.append('date_from', dateFrom); + if (dateTo) data.append('date_to', dateTo); + + fetch(agendaViewData.ajaxUrl, { method: 'POST', body: data }) + .then(function (r) { return r.json(); }) + .then(function (result) { + agendaSpinner.style.display = 'none'; + agendaLoading = false; + if (result.success && result.data.html) { + var tmp = document.createElement('div'); + tmp.innerHTML = result.data.html; + Array.from(tmp.children).forEach(function (slide) { + if (swiper) { + swiper.appendSlide(slide.outerHTML); + } else { + swiperWrapper.appendChild(slide); + } + }); + if (swiper) swiper.update(); + if (callback) callback(result.data); + } else { + agendaDone = true; + if (callback) callback(result.data); + } + }) + .catch(function () { + agendaSpinner.style.display = 'none'; + agendaLoading = false; + }); + } + + toggleBtn.addEventListener('click', function (e) { + e.preventDefault(); + if (agendaEl.classList.contains('is-active')) { + var url = new URL(window.location.href); + url.searchParams.delete('view'); + history.pushState({}, '', url.toString()); + showGrid(); + } else { + var url = new URL(window.location.href); + url.searchParams.set('view', 'agenda'); + history.pushState({}, '', url.toString()); + showAgenda(); + } + }); + + // Initial state driven by server-side class + if (agendaEl.classList.contains('is-active')) { + showAgenda(); + } + }); +}()); diff --git a/js/animatedLogo.js b/js/animatedLogo.js new file mode 100644 index 0000000..78b78ba --- /dev/null +++ b/js/animatedLogo.js @@ -0,0 +1,577 @@ +const CURSOR_INFLUENCE_INNER = 150; +const CURSOR_INFLUENCE_OUTER = 300; +const EASING_FACTOR = 0.03; + +const COLORS = [ +'#e0775d', +'#7cc0c6', +'#e05680', +'#46ae51', +'#bb8dd9', +'#f7ff29', +]; + +class FloatingShape { +constructor(element, originalX, originalY, width, height) { + this.element = element; + this.width = width; + this.height = height; + this.originalX = originalX; + this.originalY = originalY; + this.posX = originalX; + this.posY = originalY; + this.targetX = originalX; + this.targetY = originalY; +} + +update(mouseX, mouseY) { + const shapeCenterX = this.originalX + this.width / 2; + const shapeCenterY = this.originalY + this.height / 2; + + const dx = mouseX - shapeCenterX; + const dy = mouseY - shapeCenterY; + const distance = Math.sqrt(dx * dx + dy * dy); + + const innerRadius = CURSOR_INFLUENCE_INNER / 2; + const outerRadius = CURSOR_INFLUENCE_OUTER / 2; + + if (distance < outerRadius && distance > 0) { + const dirX = dx / distance; + const dirY = dy / distance; + + let strength, maxDisplacement; + + if (distance < innerRadius) { + strength = (innerRadius - distance) / innerRadius; + maxDisplacement = innerRadius; + } else { + const outerZoneProgress = (outerRadius - distance) / (outerRadius - innerRadius); + strength = outerZoneProgress * 0.5; + maxDisplacement = innerRadius * 0.6; + } + + this.targetX = this.originalX - dirX * strength * maxDisplacement; + this.targetY = this.originalY - dirY * strength * maxDisplacement; + } else { + this.targetX = this.originalX; + this.targetY = this.originalY; + } + + this.posX += (this.targetX - this.posX) * EASING_FACTOR; + this.posY += (this.targetY - this.posY) * EASING_FACTOR; +} + +render() { + this.element.style.transform = `translate3d(${this.posX}px, ${this.posY}px, 0)`; +} +} + +class FloatingShapesManager { +constructor(containerId) { + this.container = document.getElementById(containerId); + if (!this.container) { + console.error(`Container #${containerId} not found`); + return; + } + + this.shapes = []; + this.strokePaths = []; // Store all stroke paths for fill control + this.fillShape = null; // Reference to the filled shape SVG for fade control + this.fillShapeReady = false; // Track if fillshape animation has completed + this.thalimText = null; // Reference to the THALIM text element + this.textReady = false; // Track if text animation has completed + this.mouseX = 0; + this.mouseY = 0; + this.animationId = null; + this.isTouching = false; // Track if currently in a touch interaction + + this.init(); +} + +init() { + const containerRect = this.container.getBoundingClientRect(); + const containerWidth = containerRect.width; + const containerHeight = containerRect.height; + + const shapeConfigs = [ + { + id: 'shape-1', + svgPath: `${themeDirURI}/assets/logo-shapes/shape1.svg`, + baseWidth: 53.564522, + baseHeight: 112.37409, + scale: 1.5, + posX: this.isMobile() ? 20: 35, + posY: -20, + strokeWidth: 2, + animationDuration: 1.9, + gradientStart: COLORS[0], // orange + gradientEnd: '#e0b7ad' + }, + { + id: 'shape-2', + svgPath: `${themeDirURI}/assets/logo-shapes/shape4.svg`, + baseWidth: 74.08564, + baseHeight: 121.90051, + scale: 1.5, + posX: 0, + posY: this.isMobile() ? -8 : -5, + strokeWidth: 2, + animationDuration: 2.5, + gradientStart: '#aec4c6', + gradientEnd: COLORS[1] // blue + }, + { + id: 'shape-3', + svgPath: `${themeDirURI}/assets/logo-shapes/shape3.svg`, + baseWidth: 159.16571, + baseHeight: 87.756729, + scale: 1.5, + posX: 0, + posY: -10, + strokeWidth: 2, + animationDuration: 3.1, + gradientStart: COLORS[2], // pink + gradientEnd: '#e0b0be' + }, + { + id: 'shape-4', + svgPath: `${themeDirURI}/assets/logo-shapes/shape2.svg`, + baseWidth: 143.26076, + baseHeight: 20.26207, + scale: 1.5, + posX: 0, + posY: this.isMobile() ? 20 : 45, + strokeWidth: 2, + animationDuration: 3.7, + gradientStart: '#8bc491', + gradientEnd: COLORS[3] // green + }, + { + id: 'shape-5', + svgPath: `${themeDirURI}/assets/logo-shapes/shape5.svg`, + baseWidth: 155.66518, + baseHeight: 87.785599, + scale: 1.5, + posX: this.isMobile() ? 13 : 19.5, + posY: this.isMobile() ? -18 : -23.5, + strokeWidth: 2, + animationDuration: 4.3, + gradientStart: COLORS[4], // purple + gradientEnd: '#c9b0d9' + }, + { + id: 'fillshape', + svgPath: `${themeDirURI}/assets/logo-shapes/fillshape.svg`, + baseWidth: 142.78297, + baseHeight: 72.903015, + scale: this.isMobile() ? 0.78 : 1.47, + posX: this.isMobile() ? 7.5 : 11, + posY: this.isMobile() ? -13 : -14.5, + isFilled: true, + targetOpacity: 1, + animationDuration: 0.9, + animationDelay: 3.7 // Start after longest stroke animation + } + ]; + + // Create shapes with center-based positioning + const centerX = containerWidth / 2; + const centerY = containerHeight / 2; + + // Apply mobile scale on smaller viewports + const mobileScale = 0.8; + shapeConfigs.forEach((config) => { + const scale = config.id === 'fillshape' ? config.scale : this.isMobile() ? mobileScale : config.scale; + const scaledWidth = config.baseWidth * scale; + const scaledHeight = config.baseHeight * scale; + + // Calculate position from center with offset, minus half dimensions to center the shape + const x = centerX + (config.posX || 0) - scaledWidth / 2; + const y = centerY + (config.posY || 0) - scaledHeight / 2; + + // Create animated stroke element + this.createAnimatedStrokeElement(config, scaledWidth, scaledHeight, x, y); + }); + + // Create THALIM text overlay + this.createThalimText(centerX, centerY); + + this.container.addEventListener('mousemove', this.handleMouseMove.bind(this)); + this.container.addEventListener('mouseleave', this.handleMouseLeave.bind(this)); + this.container.addEventListener('touchstart', this.handleTouchStart.bind(this)); + this.container.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false }); + this.container.addEventListener('touchend', this.handleTouchEnd.bind(this)); + window.addEventListener('resize', this.handleResize.bind(this)); + + // Set sketch margin on mobile based on hero-logos height + this.setSketchMarginOnMobile(); + + this.animate(); +} + +isMobile() { + return window.innerWidth < 768; // tablet breakpoint from scss/_variables.scss +} + +setSketchMarginOnMobile() { + if (this.isMobile()) { + const heroLogos = document.querySelector('.hero-logos'); + if (heroLogos) { + const logoHeight = heroLogos.offsetHeight; + this.container.style.marginTop = `${logoHeight}px`; + } + } else { + // Reset margin-top on desktop (handled by CSS) + this.container.style.marginTop = ''; + } +} + +async createAnimatedStrokeElement(config, width, height, x, y) { + try { + const response = await fetch(config.svgPath); + const svgText = await response.text(); + + const parser = new DOMParser(); + const svgDoc = parser.parseFromString(svgText, 'image/svg+xml'); + const svgElement = svgDoc.querySelector('svg'); + + if (!svgElement) { + console.error(`Failed to parse SVG for ${config.id}`); + return; + } + + // Create shape div for physics + const shapeDiv = document.createElement('div'); + shapeDiv.className = 'floating-shape'; + shapeDiv.id = config.id; + shapeDiv.style.zIndex = config.isFilled ? '10' : '1'; // Fillshape above strokes + + // Set SVG dimensions + svgElement.setAttribute('width', width); + svgElement.setAttribute('height', height); + + if (config.isFilled) { + // Handle filled shapes (fade-in animation) + svgElement.style.opacity = '0'; + svgElement.style.transition = 'opacity 0.3s ease-out'; // Smooth fade on mouse interaction + const duration = config.animationDuration || 1.5; + const delay = config.animationDelay || 0; + const targetOpacity = config.targetOpacity || 0.5; + + svgElement.style.setProperty('--target-opacity', targetOpacity); + svgElement.style.animation = `fadeIn ${duration}s ease-in-out ${delay}s forwards`; + + // Remove animation after it completes so we can control opacity manually + setTimeout(() => { + svgElement.style.animation = 'none'; + svgElement.style.opacity = targetOpacity; + this.fillShapeReady = true; // Mark fillshape as ready for interaction + }, (delay + duration) * 1000); + + // Store reference for mouse interaction + this.fillShape = svgElement; + this.fillShapeOpacity = targetOpacity; + + // Position the fillshape div (since it's not in the physics system) + shapeDiv.style.transform = `translate3d(${x}px, ${y}px, 0)`; + } else { + // Handle stroked shapes (gradient + stroke drawing animation) + // Create gradient for stroke + const gradientId = `gradient-${config.id}`; + + // Create defs element if it doesn't exist + let defs = svgElement.querySelector('defs'); + if (!defs) { + defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); + svgElement.insertBefore(defs, svgElement.firstChild); + } + + // Create linear gradient + const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient'); + gradient.setAttribute('id', gradientId); + gradient.setAttribute('x1', '0%'); + gradient.setAttribute('y1', '0%'); + gradient.setAttribute('x2', '100%'); + gradient.setAttribute('y2', '100%'); + + // Create gradient stops + const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); + stop1.setAttribute('offset', '0%'); + stop1.setAttribute('stop-color', config.gradientStart); + + const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); + stop2.setAttribute('offset', '100%'); + stop2.setAttribute('stop-color', config.gradientEnd); + + gradient.appendChild(stop1); + gradient.appendChild(stop2); + defs.appendChild(gradient); + + // Apply stroke styling and animation to all paths in the SVG + const paths = svgElement.querySelectorAll('path, polyline, polygon, line, circle, ellipse, rect'); + paths.forEach(path => { + // Set stroke properties + path.style.fill = 'white'; + path.style.fillOpacity = '0'; // Start invisible + path.style.transition = 'fill-opacity 0.5s ease-in-out'; // Smooth fill changes + path.style.stroke = `url(#${gradientId})`; + // Don't override stroke-width - preserve SVG's compensated values + path.style.strokeLinecap = 'round'; + path.style.strokeLinejoin = 'round'; + + // Calculate path length for animation + const pathLength = path.getTotalLength ? path.getTotalLength() : 1000; + + // Set CSS variable for path length + path.style.setProperty('--path-length', pathLength); + + // Set up stroke dash animation + path.style.strokeDasharray = pathLength; + path.style.strokeDashoffset = pathLength; + + // Create CSS animation (plays once and stays completed) + const duration = config.animationDuration || 3; + path.style.animation = `drawStroke ${duration}s ease-in-out forwards`; + + // Store path reference for fill control + this.strokePaths.push(path); + }); + + // Fade in white fill when text starts appearing (at 4.3s) + setTimeout(() => { + paths.forEach(path => { + path.style.fillOpacity = '0.7'; + }); + }, 4300); + } + + // Append SVG to shape div + shapeDiv.appendChild(svgElement); + this.container.appendChild(shapeDiv); + + // Create FloatingShape instance (only for shapes with physics, not filled shapes) + if (!config.isFilled) { + const floatingShape = new FloatingShape(shapeDiv, x, y, width, height); + this.shapes.push(floatingShape); + } + + } catch (error) { + console.error(`Error loading SVG for ${config.id}:`, error); + } +} + +createThalimText(centerX, centerY) { + + // Create container for the text + const textContainer = document.createElement('div'); + textContainer.className = 'thalim-text'; + textContainer.style.top = '-20px'; + textContainer.style.left = '10px'; + textContainer.style.transform = `translate3d(${centerX}px, ${centerY}px, 0) translate(-50%, -50%)`; // Center the text + textContainer.style.color = '#000000cc'; + + // Create individual letter spans + const letters = 'thalim'.split(''); + letters.forEach((letter, index) => { + const span = document.createElement('span'); + span.textContent = letter; + span.style.opacity = '0'; + span.style.animation = `letterAppear 0.8s ease-in-out ${4.2 + index * 0.1}s forwards`; + textContainer.appendChild(span); + }); + + // Mark text as ready after last letter finishes animating + // Last letter: 4.2s + 0.5s (6th letter) + 0.8s (duration) = 5.5s + setTimeout(() => { + this.textReady = true; + }, 5500); + + this.container.appendChild(textContainer); + this.thalimText = textContainer; +} + +handleMouseMove(e) { + // Ignore mouse events during touch interactions (prevents interference from synthetic mouse events) + if (this.isTouching) { + return; + } + + const rect = this.container.getBoundingClientRect(); + this.mouseX = e.clientX - rect.left; + this.mouseY = e.clientY - rect.top; + + // Fade out fillshape and text on mouse move (only if they've finished appearing) + if (this.fillShape && this.fillShapeReady) { + this.fillShape.style.opacity = '0'; + } + if (this.thalimText && this.textReady) { + this.thalimText.style.opacity = '0'; + } + // Fade out white fills on stroke shapes + this.strokePaths.forEach(path => { + path.style.fillOpacity = '0'; + }); +} + +handleMouseLeave() { + // Move mouse far away to trigger return animation + this.mouseX = -10000; + this.mouseY = -10000; + + // Fade fillshape and text back in after delay (let strokes settle first) + // Only if they have completed their initial animations + if (this.fillShape && this.fillShapeReady) { + setTimeout(() => { + this.fillShape.style.opacity = this.fillShapeOpacity; + }, 800); // Delay to allow strokes to return to position + } + if (this.thalimText && this.textReady) { + setTimeout(() => { + this.thalimText.style.opacity = '1'; + }, 800); + } + // Fade white fills back in on stroke shapes + setTimeout(() => { + this.strokePaths.forEach(path => { + path.style.fillOpacity = '0.7'; + }); + }, 800); +} + +handleTouchStart() { + // Mark that we're in a touch interaction to prevent mouse event interference + this.isTouching = true; +} + +handleTouchMove(e) { + e.preventDefault(); // Prevent scrolling while interacting with sketch + const rect = this.container.getBoundingClientRect(); + const touch = e.touches[0]; + this.mouseX = touch.clientX - rect.left; + this.mouseY = touch.clientY - rect.top; + + // Fade out fillshape and text on touch move (only if they've finished appearing) + if (this.fillShape && this.fillShapeReady) { + this.fillShape.style.opacity = '0'; + } + if (this.thalimText && this.textReady) { + this.thalimText.style.opacity = '0'; + } + // Fade out white fills on stroke shapes + this.strokePaths.forEach(path => { + path.style.fillOpacity = '0'; + }); +} + +handleTouchEnd() { + // Reset shapes to original position + this.handleMouseLeave(); + + // Clear touch flag after a delay to ignore synthetic mouse events + setTimeout(() => { + this.isTouching = false; + }, 500); +} + +handleResize() { + // Don't refresh on mobile (prevents refresh during scroll when browser bar appears/disappears) + if (this.isMobile()) { + // Still update margin-top on mobile if needed + this.setSketchMarginOnMobile(); + return; + } + + // Debounced resize: only recreate after user stops resizing for 250ms + // This prevents page crashes from rapid resize events + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + } + + this.resizeTimeout = setTimeout(() => { + // Stop animation loop before destroying + if (this.animationId) { + cancelAnimationFrame(this.animationId); + this.animationId = null; + } + + // Clear shapes and container + this.shapes = []; + this.container.innerHTML = ''; + + // Reinitialize + this.init(); + }, 250); +} + +animate() { + // Update all shapes + this.shapes.forEach(shape => { + shape.update(this.mouseX, this.mouseY); + shape.render(); + }); + + // Continue loop + this.animationId = requestAnimationFrame(this.animate.bind(this)); +} + +destroy() { + if (this.animationId) { + cancelAnimationFrame(this.animationId); + } + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + } + this.container.removeEventListener('mousemove', this.handleMouseMove); + this.container.removeEventListener('mouseleave', this.handleMouseLeave); + this.container.removeEventListener('touchstart', this.handleTouchStart); + this.container.removeEventListener('touchmove', this.handleTouchMove); + this.container.removeEventListener('touchend', this.handleTouchEnd); + window.removeEventListener('resize', this.handleResize); +} +} + +// Inject CSS for stroke and fade animations +const style = document.createElement('style'); +style.textContent = ` +@keyframes drawStroke { + 0% { + stroke-dashoffset: var(--path-length); + opacity: 0; + } + 10% { + opacity: 1; + } + 100% { + stroke-dashoffset: 0; + opacity: 1; + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: var(--target-opacity); + } +} + +@keyframes letterAppear { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +`; +document.head.appendChild(style); + +// Initialize when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + window.floatingShapes = new FloatingShapesManager('sketch'); + }); +} else { + window.floatingShapes = new FloatingShapesManager('sketch'); +} diff --git a/js/annoncesSwiper.js b/js/annoncesSwiper.js new file mode 100644 index 0000000..1312b6f --- /dev/null +++ b/js/annoncesSwiper.js @@ -0,0 +1,28 @@ +document.addEventListener('DOMContentLoaded', function () { + document.querySelectorAll('[data-swiper]').forEach(function (section) { + new Swiper(section.querySelector('.swiper'), { + navigation: { + addIcons: false, + nextEl: section.querySelector('.swiper-button-next'), + prevEl: section.querySelector('.swiper-button-prev'), + }, + autoplay: { + delay: 5000, + disableOnInteraction: false, + pauseOnMouseEnter: true, + }, + slidesPerView: 1, + spaceBetween: 30, + breakpoints: { + 768: { + slidesPerView: 2, + spaceBetween: 40, + }, + 1024: { + slidesPerView: 3, + spaceBetween: 50, + }, + }, + }); + }); +}); diff --git a/js/categoryFilters.js b/js/categoryFilters.js new file mode 100644 index 0000000..7b7c966 --- /dev/null +++ b/js/categoryFilters.js @@ -0,0 +1,138 @@ +document.addEventListener('DOMContentLoaded', function () { + // ── Filters toggle ──────────────────────────────────────── + var toggleBtn = document.getElementById('category-filters-toggle'); + var filtersEl = document.getElementById('category-filters'); + + if (toggleBtn && filtersEl) { + toggleBtn.addEventListener('click', function () { + var isOpen = filtersEl.classList.toggle('is-open'); + toggleBtn.classList.toggle('is-open', isOpen); + toggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + }); + } + + var dateBtn = document.getElementById('filter-date-btn'); + var datePopover = document.getElementById('filter-date-popover'); + var dateFrom = document.getElementById('filter-date-from'); + var dateTo = document.getElementById('filter-date-to'); + var dateApply = document.getElementById('filter-date-apply'); + var axeBtn = document.getElementById('filter-axe-btn'); + var axePopover = document.getElementById('filter-axe-popover'); + + if (!dateBtn) return; + + // Build URL with updated query params + function buildUrl(params) { + var url = new URL(window.location.href); + Object.keys(params).forEach(function (key) { + if (params[key]) { + url.searchParams.set(key, params[key]); + } else { + url.searchParams.delete(key); + } + }); + return url.toString(); + } + + function formatDate(d) { + var y = d.getFullYear(); + var m = String(d.getMonth() + 1).padStart(2, '0'); + var day = String(d.getDate()).padStart(2, '0'); + return y + '-' + m + '-' + day; + } + + // Dropdown toggle + function openDropdown(popover) { + popover.style.display = ''; + popover.closest('.filter-dd').classList.add('is-open'); + } + + function closeDropdown(popover) { + popover.style.display = 'none'; + popover.closest('.filter-dd').classList.remove('is-open'); + } + + function toggleDropdown(popover) { + if (popover.style.display === 'none') { + openDropdown(popover); + } else { + closeDropdown(popover); + } + } + + // Close dropdowns on outside click + document.addEventListener('click', function (e) { + if (!e.target.closest('#filter-date-dd')) { + closeDropdown(datePopover); + } + if (axePopover && !e.target.closest('#filter-axe-dd')) { + closeDropdown(axePopover); + } + }); + + // --- Date dropdown --- + dateBtn.addEventListener('click', function (e) { + e.stopPropagation(); + if (axePopover) closeDropdown(axePopover); + toggleDropdown(datePopover); + }); + + // Date presets + datePopover.addEventListener('click', function (e) { + var preset = e.target.dataset.preset; + if (!preset) return; + + var now = new Date(); + var from, to; + + if (preset === 'week') { + var day = now.getDay(); + var diff = day === 0 ? 6 : day - 1; + from = new Date(now); + from.setDate(now.getDate() - diff); + to = new Date(from); + to.setDate(from.getDate() + 6); + } else if (preset === 'month') { + from = new Date(now.getFullYear(), now.getMonth(), 1); + to = new Date(now.getFullYear(), now.getMonth() + 1, 0); + } else if (preset === 'lastmonth') { + from = new Date(now.getFullYear(), now.getMonth() - 1, 1); + to = new Date(now.getFullYear(), now.getMonth(), 0); + } else if (preset === 'upcoming') { + from = now; + to = null; + } + + window.location.href = buildUrl({ + date_from: formatDate(from), + date_to: to ? formatDate(to) : '' + }); + }); + + // Date apply → navigate + dateApply.addEventListener('click', function () { + window.location.href = buildUrl({ + date_from: dateFrom.value, + date_to: dateTo.value + }); + }); + + // --- Axe dropdown --- + if (axeBtn && axePopover) { + axeBtn.addEventListener('click', function (e) { + e.stopPropagation(); + closeDropdown(datePopover); + toggleDropdown(axePopover); + }); + + axePopover.addEventListener('click', function (e) { + var li = e.target.closest('[data-axe-id]'); + if (!li) return; + if (li.dataset.axeHref) { + window.location.href = li.dataset.axeHref; + } else { + window.location.href = buildUrl({ axe: li.dataset.axeId }); + } + }); + } +}); diff --git a/js/coloredWordsHero.js b/js/coloredWordsHero.js new file mode 100644 index 0000000..a57dbb9 --- /dev/null +++ b/js/coloredWordsHero.js @@ -0,0 +1,22 @@ +document.addEventListener('DOMContentLoaded', function() { + if (document.querySelector('body').classList.contains('home')) { + const colors = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9']; + const timeouts = new Map(); + + // Color changing on hover + document.querySelectorAll('.color-changer').forEach(element => { + element.addEventListener('mouseenter', (e) => { + if (timeouts.has(e.target)) { + clearTimeout(timeouts.get(e.target)); + } + + e.target.style.color = colors[Math.floor(Math.random() * colors.length)]; + const timeoutId = setTimeout(() => { + e.target.style.color = 'black'; + timeouts.delete(e.target); + }, 2000); + timeouts.set(e.target, timeoutId); + }); + }); + } +}); \ No newline at end of file diff --git a/js/fitPostCardTitle.js b/js/fitPostCardTitle.js new file mode 100644 index 0000000..81fa3b6 --- /dev/null +++ b/js/fitPostCardTitle.js @@ -0,0 +1,43 @@ +document.addEventListener('DOMContentLoaded', function () { + function fitTitles() { + var cards = document.querySelectorAll('.post-card'); + cards.forEach(function (card) { + var h2 = card.querySelector('.gradient-container h2'); + if (!h2) return; + + var container = h2.closest('.gradient-container'); + var maxHeight = container.clientHeight; + var fontSize = parseFloat(window.getComputedStyle(h2).fontSize); + var minSize = 12; + + h2.style.fontSize = ''; + fontSize = parseFloat(window.getComputedStyle(h2).fontSize); + + while (h2.scrollHeight > maxHeight && fontSize > minSize) { + fontSize -= 1; + h2.style.fontSize = fontSize + 'px'; + } + }); + } + + window.fitPostCardTitles = fitTitles; + + fitTitles(); + + var resizeTimer; + window.addEventListener('resize', function () { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(fitTitles, 200); + }); + + // Re-fit after infinite scroll loads new cards + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (m) { + if (m.addedNodes.length) fitTitles(); + }); + }); + var grid = document.getElementById('post-grid'); + if (grid) { + observer.observe(grid, { childList: true }); + } +}); diff --git a/js/frenchTypography.js b/js/frenchTypography.js new file mode 100644 index 0000000..64f16d4 --- /dev/null +++ b/js/frenchTypography.js @@ -0,0 +1,37 @@ +/** + * French typography: replaces normal spaces with non-breaking spaces + * where French typographic rules require them. + */ +function applyFrenchTypography(elements) { + elements.forEach(function (el) { + var html = el.innerHTML; + // Collapse multiple spaces into one (outside HTML tags) + html = html.replace(/([^<>]) {2,}(?=[^<>])/g, '$1 '); + // Space before ? ! : ; » + html = html.replace(/ ([?!:;»])/g, '\u00A0$1'); + // Space after « + html = html.replace(/(«) /g, '$1\u00A0'); + // "p. 42" → non-breaking space after "p." + html = html.replace(/\bp\. /g, 'p.\u00A0'); + // "n° 3" → non-breaking space after "n°" + html = html.replace(/\bn° /g, 'n°\u00A0'); + el.innerHTML = html; + }); +} + +document.addEventListener('DOMContentLoaded', function () { + applyFrenchTypography(document.querySelectorAll('.post-card h2, .post-card .post-card__title')); + + // Re-apply on any post-card dynamically added anywhere on the page + new MutationObserver(function (mutations) { + mutations.forEach(function (m) { + m.addedNodes.forEach(function (node) { + if (node.nodeType !== 1) return; + var targets = node.classList && node.classList.contains('post-card') + ? node.querySelectorAll('h2, .post-card__title') + : node.querySelectorAll('.post-card h2, .post-card .post-card__title'); + applyFrenchTypography(targets); + }); + }); + }).observe(document.body, { childList: true, subtree: true }); +}); diff --git a/js/imageSwiper.js b/js/imageSwiper.js new file mode 100644 index 0000000..20373a4 --- /dev/null +++ b/js/imageSwiper.js @@ -0,0 +1,16 @@ +document.addEventListener('DOMContentLoaded', function () { + document.querySelectorAll('[data-image-swiper]').forEach(function (container) { + new Swiper(container.querySelector('.swiper'), { + slidesPerView: 1, + loop: true, + navigation: { + nextEl: container.querySelector('.swiper-button-next'), + prevEl: container.querySelector('.swiper-button-prev'), + }, + pagination: { + el: container.querySelector('.swiper-pagination'), + clickable: true, + }, + }); + }); +}); diff --git a/js/infiniteScroll.js b/js/infiniteScroll.js new file mode 100644 index 0000000..8d4e739 --- /dev/null +++ b/js/infiniteScroll.js @@ -0,0 +1,71 @@ +document.addEventListener('DOMContentLoaded', function () { + var sentinel = document.getElementById('scroll-sentinel'); + var grid = document.getElementById('post-grid'); + var spinner = document.getElementById('scroll-spinner'); + + if (!sentinel || !grid) return; + + var page = 1; + var loading = false; + var done = false; + + // Read filter params from sentinel data attributes (set server-side) + var categoryId = sentinel.dataset.category || ''; + var axe = sentinel.dataset.axe || ''; + var dateFrom = sentinel.dataset.dateFrom || ''; + var dateTo = sentinel.dataset.dateTo || ''; + var taxonomy = sentinel.dataset.taxonomy || ''; + var termId = sentinel.dataset.term || ''; + var catFilter = sentinel.dataset.filterCat || ''; + var filterAutres = sentinel.dataset.filterAutres || ''; + var excludeCats = sentinel.dataset.excludeCats || ''; + var searchQuery = sentinel.dataset.search || ''; + + function fetchPosts() { + if (loading || done) return; + loading = true; + page++; + spinner.style.display = 'flex'; + + var data = new FormData(); + data.append('action', 'load_more_posts'); + data.append('page', page); + data.append('nonce', infiniteScrollData.nonce); + data.append('lang', infiniteScrollData.lang || 'fr'); + if (categoryId) data.append('category', categoryId); + if (axe) data.append('axe', axe); + if (dateFrom) data.append('date_from', dateFrom); + if (dateTo) data.append('date_to', dateTo); + if (taxonomy) data.append('taxonomy', taxonomy); + if (termId) data.append('term', termId); + if (catFilter) data.append('filter_cat', catFilter); + if (filterAutres) data.append('filter_autres', filterAutres); + if (excludeCats) data.append('exclude_cats', excludeCats); + if (searchQuery) data.append('search', searchQuery); + + fetch(infiniteScrollData.ajaxUrl, { method: 'POST', body: data }) + .then(function (response) { return response.json(); }) + .then(function (result) { + spinner.style.display = 'none'; + if (result.success && result.data.html) { + grid.insertAdjacentHTML('beforeend', result.data.html); + loading = false; + } else { + done = true; + observer.disconnect(); + } + }) + .catch(function () { + spinner.style.display = 'none'; + loading = false; + }); + } + + var observer = new IntersectionObserver(function (entries) { + if (entries[0].isIntersecting) { + fetchPosts(); + } + }, { rootMargin: '200px' }); + + observer.observe(sentinel); +}); diff --git a/js/keywordCloud.js b/js/keywordCloud.js new file mode 100644 index 0000000..69aa409 --- /dev/null +++ b/js/keywordCloud.js @@ -0,0 +1,249 @@ +(function () { + 'use strict'; + + document.addEventListener('DOMContentLoaded', function () { + var container = document.getElementById('keyword-container'); + var tags = window.thalimTags; + + if (!container || !tags || !tags.length) return; + + // — init — + + var GAP = 14; + var COLORS = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9']; + var colorTimeouts = new Map(); + + // Mélange aléatoire pour varier la disposition à chaque chargement + var shuffled = tags.slice().sort(function () { return Math.random() - 0.5; }); + + // Crée les éléments une seule fois (réutilisés à chaque layout) + var items = shuffled.map(function (tag) { + var el = document.createElement('a'); + el.className = 'keyword'; + el.href = tag.url; + el.textContent = tag.name; + + el.addEventListener('mouseenter', function () { + if (colorTimeouts.has(el)) clearTimeout(colorTimeouts.get(el)); + el.style.color = COLORS[Math.floor(Math.random() * COLORS.length)]; + colorTimeouts.set(el, setTimeout(function () { + el.style.color = ''; + colorTimeouts.delete(el); + }, 2000)); + }); + + container.appendChild(el); + return { el: el, w: 0, h: 0 }; + }); + + var gradOverlay = null; + var lastLayoutWidth = 0; + + function layoutCloud() { + var cw = container.offsetWidth; + if (cw === lastLayoutWidth) return; + lastLayoutWidth = cw; + + var cx = cw / 2; + var isMobile = cw < 768; + var a = cx - GAP; // demi-grand axe horizontal + var b = Math.round(cw * (isMobile ? 0.45 : 0.20)); // demi-petit axe vertical (plus haut sur mobile) + var R_V = Math.round(b * (isMobile ? 0.45 : 0.70)); // demi-axe vertical de la zone d'exclusion (plus petit sur mobile) + var R_H = Math.round(b * (isMobile ? 0.70 : 1.15)); // demi-axe horizontal (plus petit sur mobile) + + // Re-mesurer les éléments (la taille peut changer avec le viewport) + // +1 compense les arrondis sub-pixel sur écrans haute densité + items.forEach(function (item) { + var rect = item.el.getBoundingClientRect(); + item.w = Math.ceil(rect.width) + 1; + item.h = Math.ceil(rect.height) + 1; + }); + + var placed = []; + + function hasOverlap(x, y, w, h) { + for (var i = 0; i < placed.length; i++) { + var p = placed[i]; + if (x < p.x + p.w + GAP && + x + w + GAP > p.x && + y < p.y + p.h + GAP && + y + h + GAP > p.y) return true; + } + return false; + } + + // Placement contraint à l'anneau elliptique : + // { intérieur ellipse (a, b) } ∩ { extérieur ellipse d'exclusion (R_H, R_V) } + // Les candidats sont triés par distance à l'ellipse d'exclusion (croissante) : + // les mots s'accumulent au plus près du centre avant de s'étendre. + function findPos(w, h) { + var candidates = []; + + for (var x = 0; x <= cw - w; x += 8) { + var dx = (x + w / 2) - cx; + var ratio = dx / a; + if (Math.abs(ratio) >= 1) continue; + + // Limite verticale du centre du mot imposée par l'ellipse externe (avec marge h/2) + var maxPcy = b * Math.sqrt(1 - ratio * ratio); + if (maxPcy < h / 2) continue; + var maxAbsY = maxPcy - h / 2; + + // Limite verticale minimale imposée par l'ellipse d'exclusion (R_H, R_V) + var minAbsY = (Math.abs(dx) >= R_H) ? 0 + : R_V * Math.sqrt(Math.max(0, 1 - (dx * dx) / (R_H * R_H))); + if (minAbsY > maxAbsY) continue; + + for (var absY = minAbsY; absY <= maxAbsY; absY += 4) { + // Distance normalisée à l'ellipse d'exclusion (0 = sur le bord) + var nx = dx / R_H, ny = absY / R_V; + var dist = Math.sqrt(nx * nx + ny * ny) - 1; + var yB = Math.round(absY - h / 2); + if (absY > 0) { + candidates.push({ x: x, y: Math.round(-absY - h / 2), dist: dist }); + } + candidates.push({ x: x, y: yB, dist: dist }); + } + } + + // Trier par distance à l'ellipse d'exclusion croissante → attraction vers le centre + candidates.sort(function (ca, cb) { return ca.dist - cb.dist; }); + + for (var i = 0; i < candidates.length; i++) { + var c = candidates[i]; + if (!hasOverlap(c.x, c.y, w, h)) return { x: c.x, y: c.y }; + } + + return null; // aucune position dans l'anneau + } + + // Place les mots dans l'anneau elliptique, collecte les débordements + var overflow = []; + items.forEach(function (item) { + var pos = findPos(item.w, item.h); + if (pos) { + item.pos = { x: pos.x, y: pos.y, w: item.w, h: item.h }; + placed.push(item.pos); + item.el.style.left = pos.x + 'px'; + item.el.style.top = pos.y + 'px'; + } else { + overflow.push(item); + } + }); + + // Placement des débordements en lignes centrées (style flex-wrap center) + if (overflow.length) { + var startY = placed.reduce(function (m, p) { return Math.max(m, p.y + p.h); }, 0) + GAP; + var rows = []; + var currentRow = []; + var currentRowW = 0; + + overflow.forEach(function (item) { + var needed = currentRowW > 0 ? item.w + GAP : item.w; + if (currentRowW + needed > cw && currentRow.length > 0) { + rows.push(currentRow); + currentRow = []; + currentRowW = 0; + } + currentRow.push(item); + currentRowW += (currentRowW > 0 ? GAP : 0) + item.w; + }); + if (currentRow.length) rows.push(currentRow); + + var curY = startY; + rows.forEach(function (row) { + var rowW = row.reduce(function (s, item) { return s + item.w; }, 0) + (row.length - 1) * GAP; + var rowH = row.reduce(function (m, item) { return Math.max(m, item.h); }, 0); + var offsetX = Math.round((cw - rowW) / 2); + + row.forEach(function (item) { + item.pos = { x: offsetX, y: curY, w: item.w, h: item.h }; + placed.push(item.pos); + item.el.style.left = offsetX + 'px'; + item.el.style.top = curY + 'px'; + offsetX += item.w + GAP; + }); + curY += rowH + GAP; + }); + } + + // Normalisation Y : cy=0 → shift px depuis le haut du conteneur + var minY = items.reduce(function (m, item) { return Math.min(m, item.pos.y); }, Infinity); + var shift = Math.max(0, GAP - minY); + + items.forEach(function (item) { + item.pos.y += shift; + item.el.style.top = item.pos.y + 'px'; + }); + + // Hauteur basée sur le contenu réel (évite le débordement sur mobile) + var maxPlacedY = items.reduce(function (m, item) { return Math.max(m, item.pos.y + item.pos.h); }, 0); + container.style.height = (maxPlacedY + GAP) + 'px'; + + // Dégradé sur un overlay séparé pour permettre l'animation scale au survol + if (gradOverlay) { + container.removeChild(gradOverlay); + } + gradOverlay = document.createElement('div'); + gradOverlay.style.cssText = + 'position:absolute;inset:0;pointer-events:none;' + + 'background:radial-gradient(circle ' + Math.round(R_V * 2) + 'px at ' + cx + 'px ' + shift + 'px,' + + 'rgba(247,255,41,1) 0%,rgba(247,255,41,0.6) 16%,rgba(247,255,41,0.15) 55%,transparent 70%);' + + 'transform-origin:' + cx + 'px ' + shift + 'px;' + + 'transition:transform 0.4s ease;'; + container.insertBefore(gradOverlay, container.firstChild); + } + + // Premier layout + layoutCloud(); + + // Événements du dégradé (référencent gradOverlay via closure) + container.addEventListener('mouseenter', function () { + if (!gradOverlay) return; + gradOverlay.style.transition = 'transform 0.4s ease'; + gradOverlay.style.transform = 'scale(0.82)'; + }); + container.addEventListener('mousemove', function (e) { + if (!gradOverlay) return; + var cw = container.offsetWidth; + var cx = cw / 2; + var rect = container.getBoundingClientRect(); + var dx = (e.clientX - rect.left) - cx; + var dy = (e.clientY - rect.top) - parseFloat(gradOverlay.style.transformOrigin.split(' ')[1]); + var tx = (-dx * 0.09).toFixed(2); + var ty = (-dy * 0.09).toFixed(2); + gradOverlay.style.transition = 'transform 0.15s ease-out'; + gradOverlay.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(0.82)'; + }); + container.addEventListener('mouseleave', function () { + if (!gradOverlay) return; + gradOverlay.style.transition = 'transform 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; + gradOverlay.style.transform = 'scale(1)'; + }); + + // Resize avec debounce + var resizeTimer; + window.addEventListener('resize', function () { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(function () { + lastLayoutWidth = 0; // forcer le recalcul + layoutCloud(); + }, 250); + }); + + // Animation d'apparition au scroll + var observer = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) { + items.forEach(function (item, i) { + item.el.style.animationDelay = (i * 0.03) + 's'; + item.el.classList.add('keyword--visible'); + }); + observer.unobserve(container); + } + }); + }, { threshold: 0.05 }); + + observer.observe(container); + }); +})(); diff --git a/js/membresFilters.js b/js/membresFilters.js new file mode 100644 index 0000000..93b0d3c --- /dev/null +++ b/js/membresFilters.js @@ -0,0 +1,229 @@ +document.addEventListener('DOMContentLoaded', function () { + // ── Filters toggle ──────────────────────────────────────── + var membresToggleBtn = document.getElementById('membres-filters-toggle'); + var membresFiltersEl = document.getElementById('membres-filters'); + + if (membresToggleBtn && membresFiltersEl) { + membresToggleBtn.addEventListener('click', function () { + var isOpen = membresFiltersEl.classList.toggle('is-open'); + membresToggleBtn.classList.toggle('is-open', isOpen); + membresToggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + if (isOpen) { + var searchInput = document.getElementById('membres-search'); + if (searchInput) searchInput.focus(); + } + }); + } + + var activeRole = ''; + var searchQuery = ''; + + // ── Dropdown (role filter) ──────────────────────────────── + var roleBtn = document.getElementById('filter-role-btn'); + var rolePopover = document.getElementById('filter-role-popover'); + var roleLabel = document.getElementById('filter-role-label'); + var roleReset = document.getElementById('role-reset'); + var roleDd = document.getElementById('filter-role-dd'); + + function openRole() { rolePopover.style.display = ''; roleDd.classList.add('is-open'); } + function closeRole() { rolePopover.style.display = 'none'; roleDd.classList.remove('is-open'); } + + roleBtn.addEventListener('click', function () { + rolePopover.style.display === 'none' ? openRole() : closeRole(); + }); + + rolePopover.querySelectorAll('[data-role]').forEach(function (item) { + item.addEventListener('click', function () { + activeRole = item.dataset.role; + roleLabel.textContent = item.textContent.trim(); + roleReset.style.display = activeRole ? '' : 'none'; + roleDd.classList.toggle('is-active', !!activeRole); + closeRole(); + updateChips(); + applyFilters(); + }); + }); + + roleReset.addEventListener('click', function (e) { + e.preventDefault(); + activeRole = ''; + roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim(); + roleReset.style.display = 'none'; + roleDd.classList.remove('is-active'); + updateChips(); + applyFilters(); + }); + + document.addEventListener('click', function (e) { + if (!roleDd.contains(e.target)) closeRole(); + }); + + // ── Search input ────────────────────────────────────────── + var searchInput = document.getElementById('membres-search'); + var searchReset = document.getElementById('search-reset'); + + searchInput.addEventListener('input', function () { + searchQuery = searchInput.value.trim().toLowerCase(); + searchReset.style.display = searchQuery ? '' : 'none'; + searchInput.classList.toggle('is-active', !!searchQuery); + updateChips(); + applyFilters(); + }); + + searchReset.addEventListener('click', function (e) { + e.preventDefault(); + searchQuery = ''; + searchInput.value = ''; + searchReset.style.display = 'none'; + searchInput.classList.remove('is-active'); + updateChips(); + applyFilters(); + }); + + // ── Active chips ────────────────────────────────────────── + var chipsContainer = document.getElementById('membres-active-chips'); + + function makeChip(label, onRemove) { + var btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'filter-chip'; + btn.innerHTML = label + ''; + btn.addEventListener('click', onRemove); + return btn; + } + + function updateChips() { + if (!chipsContainer) return; + chipsContainer.innerHTML = ''; + if (activeRole) { + chipsContainer.appendChild(makeChip(activeRole, function () { + activeRole = ''; + roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim(); + roleReset.style.display = 'none'; + roleDd.classList.remove('is-active'); + updateChips(); + applyFilters(); + })); + } + if (searchQuery) { + chipsContainer.appendChild(makeChip(searchInput.value, function () { + searchQuery = ''; + searchInput.value = ''; + searchReset.style.display = 'none'; + searchInput.classList.remove('is-active'); + updateChips(); + applyFilters(); + })); + } + } + + // ── Column sort ─────────────────────────────────────────── + var sortKey = 'nom'; + var sortDir = 'asc'; + + function getSortValue(row, key) { + if (key === 'nom') { + return (row.dataset.sortName || '').trim().toLowerCase(); + } + if (key === 'statut') return (row.dataset.status || '').toLowerCase(); + if (key === 'affiliation') return (row.dataset.affiliation || '').toLowerCase(); + return ''; + } + + function applySortToTable(tbody) { + var rows = Array.from(tbody.querySelectorAll('tr')); + rows.sort(function (a, b) { + var va = getSortValue(a, sortKey); + var vb = getSortValue(b, sortKey); + var cmp = va.localeCompare(vb, 'fr', { sensitivity: 'base' }); + return sortDir === 'asc' ? cmp : -cmp; + }); + rows.forEach(function (row) { tbody.appendChild(row); }); + } + + function applySort() { + document.querySelectorAll('.membres-table tbody').forEach(function (tbody) { + if (!tbody.closest('table').hasAttribute('data-fixed-order')) { + applySortToTable(tbody); + } + restripe(tbody); + }); + document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) { + th.classList.remove('sort-asc', 'sort-desc'); + if (th.dataset.sort === sortKey) { + th.classList.add('sort-' + sortDir); + } + }); + } + + document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) { + th.addEventListener('click', function () { + var key = th.dataset.sort; + if (sortKey === key) { + sortDir = sortDir === 'asc' ? 'desc' : 'asc'; + } else { + sortKey = key; + sortDir = 'asc'; + } + applySort(); + }); + }); + + applySort(); + + // ── Row restriping (fixes alternating colors after filter) ── + function restripe(tbody) { + var n = 0; + tbody.querySelectorAll('tr').forEach(function (row) { + if (row.style.display === 'none') return; + row.classList.toggle('is-even-row', n % 2 === 1); + n++; + }); + } + + // ── Apply both filters ──────────────────────────────────── + function applyFilters() { + var isFiltering = activeRole || searchQuery; + + document.querySelectorAll('.membres-item').forEach(function (item) { + // Filter rows by role and/or name + var rows = item.querySelectorAll('tbody tr'); + var visible = 0; + rows.forEach(function (row) { + var name = (row.dataset.name || '').toLowerCase(); + var status = (row.dataset.status || '').toLowerCase(); + var affiliation = (row.dataset.affiliation || '').toLowerCase(); + var roles = (row.dataset.roles || '').split('|').map(function (r) { return r.trim().toLowerCase(); }); + + var matchesRole = !activeRole || roles.includes(activeRole.toLowerCase()); + var matchesName = !searchQuery || name.includes(searchQuery) + || status.includes(searchQuery) + || affiliation.includes(searchQuery); + + var show = matchesRole && matchesName; + row.style.display = show ? '' : 'none'; + if (show) visible++; + }); + item.querySelectorAll('tbody').forEach(restripe); + + // Hide group if no rows are visible + if (isFiltering && visible === 0) { + item.style.display = 'none'; + return; + } + + item.style.display = ''; + + var content = item.querySelector('.membres-content'); + if (isFiltering) { + // Auto-expand when a filter is active + content.style.display = ''; + item.classList.add('is-open'); + } else { + // Collapse back when all filters are cleared + content.style.display = 'none'; + item.classList.remove('is-open'); + } + }); + } +}); diff --git a/js/membresPopover.js b/js/membresPopover.js new file mode 100644 index 0000000..95c4c9e --- /dev/null +++ b/js/membresPopover.js @@ -0,0 +1,81 @@ +document.addEventListener('DOMContentLoaded', function () { + var section = document.querySelector('.membres-section'); + if (!section) return; + + // Build the popover element once and append to body + var popover = document.createElement('div'); + popover.id = 'membre-popover'; + popover.innerHTML = + '
    ' + + '' + + '
    ' + + '

    ' + + '

    ' + + '

    ' + + '

    ' + + '
    ' + + '
    '; + document.body.appendChild(popover); + + var pic = popover.querySelector('.membre-popover-pic'); + var elName = popover.querySelector('.membre-popover-name'); + var elStat = popover.querySelector('.membre-popover-status'); + var elDom = popover.querySelector('.membre-popover-domaines'); + var elAut = popover.querySelector('.membre-popover-autres'); + + var visible = false; + var currentRow = null; + + // ── Show/hide via event delegation on the section ──────── + section.addEventListener('mouseover', function (e) { + var row = e.target.closest('tbody tr'); + if (!row || row === currentRow) return; + currentRow = row; + + var avatar = row.dataset.avatar || ''; + if (avatar) { + pic.src = avatar; + pic.style.display = ''; + } else { + pic.src = ''; + pic.style.display = 'none'; + } + + elName.textContent = row.dataset.name || ''; + elStat.textContent = row.dataset.status || ''; + + var domVal = row.dataset.domaines || ''; + elDom.textContent = domVal; + elDom.style.display = domVal ? '' : 'none'; + + var autVal = row.dataset.autresDomaines || ''; + elAut.innerHTML = autVal.replace(/\n/g, '
    '); + elAut.style.display = autVal ? '' : 'none'; + + popover.classList.add('is-visible'); + visible = true; + }); + + section.addEventListener('mouseout', function (e) { + var row = e.target.closest('tbody tr'); + if (!row) return; + // only hide when leaving the row entirely (not moving to a child) + if (e.relatedTarget && row.contains(e.relatedTarget)) return; + currentRow = null; + popover.classList.remove('is-visible'); + visible = false; + }); + + // ── Follow the cursor ───────────────────────────────────── + document.addEventListener('mousemove', function (e) { + if (!visible) return; + var x = e.clientX + 18; + var y = e.clientY + 18; + var pw = popover.offsetWidth; + var ph = popover.offsetHeight; + if (x + pw > window.innerWidth) x = e.clientX - pw - 8; + if (y + ph > window.innerHeight) y = e.clientY - ph - 8; + popover.style.left = x + 'px'; + popover.style.top = y + 'px'; + }); +}); diff --git a/js/messageLabo.js b/js/messageLabo.js new file mode 100644 index 0000000..7802ef1 --- /dev/null +++ b/js/messageLabo.js @@ -0,0 +1,57 @@ +document.addEventListener('DOMContentLoaded', function () { + var messageList = document.querySelector('.messages-list'); + var agendaContent = document.querySelector('.agenda-content'); + var sectionTitle = document.querySelector('.message-du-labo .section-title'); + var buttonMessages = document.querySelector('.button-messages'); + if (!messageList || !agendaContent) return; + + var items = Array.from(messageList.querySelectorAll('.message-item')); + + function sync() { + items.forEach(function (item) { + item.style.display = ''; + var content = item.querySelector('.message-content'); + if (content) { content.style.maxHeight = ''; content.classList.remove('is-overflowing'); } + }); + + if (window.innerWidth < 768) { + // Mobile : afficher uniquement le premier message + items.forEach(function (item, i) { item.style.display = i === 0 ? '' : 'none'; }); + return; + } + + var budget = agendaContent.offsetHeight + - (sectionTitle ? sectionTitle.offsetHeight : 0) + - (buttonMessages ? buttonMessages.offsetHeight : 0); + var used = 0; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var itemHeight = item.offsetHeight; + if (used + itemHeight <= budget) { + used += itemHeight; + } else { + var remaining = budget - used; + var content = item.querySelector('.message-content'); + if (content && remaining > 100) { + var contentBudget = remaining - (itemHeight - content.offsetHeight); + if (contentBudget > 60) { + content.style.maxHeight = contentBudget + 'px'; + content.classList.add('is-overflowing'); + } else { + item.style.display = 'none'; + } + } else { + item.style.display = 'none'; + } + for (var j = i + 1; j < items.length; j++) { + items[j].style.display = 'none'; + } + break; + } + } + } + + sync(); + window.addEventListener('resize', sync); +}); diff --git a/js/navAxesToggle.js b/js/navAxesToggle.js new file mode 100644 index 0000000..56ad034 --- /dev/null +++ b/js/navAxesToggle.js @@ -0,0 +1,32 @@ +document.addEventListener('DOMContentLoaded', function () { + var item = document.querySelector('.nav-axes-item'); + if (!item) return; + + var trigger = item.querySelector('.nav-axes-trigger'); + var mainMenu = document.querySelector('.main-menu'); + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + var isOpen = item.classList.toggle('is-open'); + trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + }); + + // Close when clicking outside + document.addEventListener('click', function (e) { + if (!item.contains(e.target)) { + item.classList.remove('is-open'); + trigger.setAttribute('aria-expanded', 'false'); + } + }); + + // Reset when main menu closes + if (mainMenu) { + var observer = new MutationObserver(function () { + if (!mainMenu.classList.contains('active')) { + item.classList.remove('is-open'); + trigger.setAttribute('aria-expanded', 'false'); + } + }); + observer.observe(mainMenu, { attributes: true, attributeFilter: ['class'] }); + } +}); diff --git a/js/overlay.js b/js/overlay.js new file mode 100644 index 0000000..e892b2d --- /dev/null +++ b/js/overlay.js @@ -0,0 +1,133 @@ +document.addEventListener('DOMContentLoaded', function() { + const header = document.querySelector('header'); + const body = document.querySelector('body'); + const menuToggle = document.querySelector('.menu-toggle'); + const mainMenu = document.querySelector('.main-menu'); + const menuOverlay = document.querySelector('.overlay'); + const menuIcon = document.querySelector('.menu-toggle i'); + const wpAdminBar = document.querySelector('#wpadminbar'); + const stickyHeaderMobile = document.querySelector('.header-right'); + const searchButton = document.querySelector('.search-button'); + const searchPanel = document.querySelector('.search-panel'); + const searchIcon = document.querySelector('.search-button i'); + const searchInput = document.querySelector('.search-panel__input'); + + const breakpointTablet = 768; + + mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`; + searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`; + + // Compute the pixel offset at which panels should appear (just below the header) + function getHeaderBottom() { + const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0; + if (window.innerWidth < breakpointTablet) { + if (window.scrollY > header.getBoundingClientRect().bottom) { + return stickyHeaderMobile.getBoundingClientRect().bottom + window.scrollY; + } + return header.getBoundingClientRect().bottom + window.scrollY; + } + return header.offsetHeight + adminBarHeight; + } + + function updateOverlay() { + const anyOpen = mainMenu.classList.contains('active') || searchPanel.classList.contains('active'); + menuOverlay.classList.toggle('active', anyOpen); + if (anyOpen) { + body.style.overflow = 'hidden'; + } else { + body.style.removeProperty('overflow'); + } + } + + // --- Menu --- + function openMenu() { + mainMenu.scrollTo(0, 0); + if (window.innerWidth < breakpointTablet) { + const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0; + if (window.scrollY > header.getBoundingClientRect().bottom) { + mainMenu.style.height = `${window.innerHeight - adminBarHeight - stickyHeaderMobile.offsetHeight}px`; + } else { + mainMenu.style.height = `${window.innerHeight - header.getBoundingClientRect().bottom}px`; + } + } else { + mainMenu.style.removeProperty('height'); + } + mainMenu.style.top = `${getHeaderBottom()}px`; + mainMenu.classList.add('active'); + menuIcon.classList.remove('iconoir-menu'); + menuIcon.classList.add('iconoir-xmark'); + } + + function closeMenu() { + mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`; + mainMenu.classList.remove('active'); + menuIcon.classList.remove('iconoir-xmark'); + menuIcon.classList.add('iconoir-menu'); + } + + function toggleMenu() { + if (searchPanel.classList.contains('active')) closeSearch(); + if (mainMenu.classList.contains('active')) { + closeMenu(); + } else { + openMenu(); + } + updateOverlay(); + } + + // --- Search --- + function openSearch() { + searchPanel.style.top = `${getHeaderBottom()}px`; + searchPanel.classList.add('active'); + searchIcon.classList.remove('iconoir-search'); + searchIcon.classList.add('iconoir-xmark'); + setTimeout(function() { if (searchInput) searchInput.focus(); }, 400); + } + + function closeSearch() { + searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`; + searchPanel.classList.remove('active'); + searchIcon.classList.remove('iconoir-xmark'); + searchIcon.classList.add('iconoir-search'); + } + + function toggleSearch() { + if (mainMenu.classList.contains('active')) closeMenu(); + if (searchPanel.classList.contains('active')) { + closeSearch(); + } else { + openSearch(); + } + updateOverlay(); + } + + // --- Event listeners --- + menuToggle.addEventListener('click', toggleMenu); + searchButton.addEventListener('click', toggleSearch); + + menuOverlay.addEventListener('click', function() { + if (mainMenu.classList.contains('active')) closeMenu(); + if (searchPanel.classList.contains('active')) closeSearch(); + updateOverlay(); + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + if (mainMenu.classList.contains('active')) closeMenu(); + if (searchPanel.classList.contains('active')) closeSearch(); + updateOverlay(); + } + }); + + let resizeTimeout; + window.addEventListener('resize', function() { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(function() { + if (mainMenu.classList.contains('active')) closeMenu(); + if (searchPanel.classList.contains('active')) closeSearch(); + updateOverlay(); + mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`; + searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`; + }, 150); + }); +}); diff --git a/js/quickLinks.js b/js/quickLinks.js new file mode 100644 index 0000000..aef72a4 --- /dev/null +++ b/js/quickLinks.js @@ -0,0 +1,24 @@ +document.addEventListener('DOMContentLoaded', function () { + var quickLinks = document.querySelector('.quick-links'); + if (!quickLinks) return; + + // Last section: keyword cloud if present, otherwise last swiper section + var lastSection = document.querySelector('.keyword-cloud'); + if (!lastSection) { + var swiperSections = document.querySelectorAll('.swiper-section'); + lastSection = swiperSections[swiperSections.length - 1] || null; + } + if (!lastSection) return; + + var initialTop = quickLinks.getBoundingClientRect().top + window.scrollY; + var quickLinksHeight = quickLinks.offsetHeight; + + window.addEventListener('scroll', function () { + var sectionBottom = lastSection.getBoundingClientRect().bottom; + if (initialTop - sectionBottom > 0) { + quickLinks.style.top = (initialTop - (initialTop - sectionBottom) - quickLinksHeight) + 'px'; + } else { + quickLinks.style.top = initialTop + 'px'; + } + }); +}); diff --git a/js/seanceToggle.js b/js/seanceToggle.js new file mode 100644 index 0000000..d862658 --- /dev/null +++ b/js/seanceToggle.js @@ -0,0 +1,39 @@ +document.addEventListener('DOMContentLoaded', function () { + document.querySelectorAll('[data-seance-toggle]').forEach(function (header) { + header.addEventListener('click', function (e) { + // Don't toggle when clicking a link + if (e.target.closest('a')) return; + + var item = header.closest('.seance-item, .membres-item, .author-posts-item, .labo-dropdown-item'); + var content = item.querySelector('.seance-content, .membres-content, .author-posts-content, .labo-dropdown-content'); + var isOpen = item.classList.contains('is-open'); + + if (isOpen) { + content.style.display = 'none'; + item.classList.remove('is-open'); + } else { + content.style.display = ''; + item.classList.add('is-open'); + if (window.fitPostCardTitles) window.fitPostCardTitles(); + } + }); + }); + + // Auto-expand and scroll to a séance targeted by URL hash (#seance-{ID}) + var hash = window.location.hash; + if (hash && hash.startsWith('#seance-')) { + var target = document.querySelector(hash + '.seance-item'); + if (target) { + var content = target.querySelector('.seance-content'); + if (content) { + content.style.display = ''; + target.classList.add('is-open'); + if (window.fitPostCardTitles) window.fitPostCardTitles(); + setTimeout(function () { + var top = target.getBoundingClientRect().top + window.scrollY - 150; + window.scrollTo({ top: top, behavior: 'smooth' }); + }, 150); + } + } + } +}); diff --git a/js/stickyHeader.js b/js/stickyHeader.js new file mode 100644 index 0000000..735e2c7 --- /dev/null +++ b/js/stickyHeader.js @@ -0,0 +1,91 @@ +document.addEventListener('DOMContentLoaded', function() { + const header = document.querySelector('header'); + const wpAdminBar = document.querySelector('#wpadminbar'); + + const stickyHeaderMobile = document.querySelector('.header-right'); + const relativeHeaderMobile = document.querySelector('.header-left'); + + const mainLogo = document.querySelector('.main-logo'); + const description = document.querySelector('.description'); + const burgerContainer = document.querySelector('.menu-toggle > div'); + const menuIconContainer = document.querySelector('.menu-toggle > div > div'); + const menuText = document.querySelector('.menu-toggle > div > p'); + + const breakpointTablet = 768; + + function checkMobile() { + if (window.innerWidth < breakpointTablet) { + stickyHeaderMobile.style.top = wpAdminBar ? `${wpAdminBar.offsetHeight}px` : '0px'; + } else { + stickyHeaderMobile.style.top = 'unset'; + } + } + + function resetStyles() { + header.style.removeProperty("height"); + mainLogo.style.removeProperty("padding"); + description.style.removeProperty("opacity"); + burgerContainer.style.removeProperty("padding"); + burgerContainer.style.removeProperty("justify-content"); + menuIconContainer.style.removeProperty("font-size"); + menuText.style.removeProperty("display"); + } + + window.addEventListener('scroll', () => { + const isScrolledTop = window.scrollY === 0; + if (window.innerWidth < breakpointTablet) { + // mobile + if (window.scrollY > header.getBoundingClientRect().bottom) { + // déployer petit logo à gauche + if(!stickyHeaderMobile.classList.contains('scrolled')) { + stickyHeaderMobile.classList.add('scrolled') + } + } else { + // rétracter petit logo à gauche + if(stickyHeaderMobile.classList.contains('scrolled')) { + stickyHeaderMobile.classList.remove('scrolled'); + } + } + } else { + // desktop + header.style.height = isScrolledTop ? '12vh' : '6vh'; + header.style.minHeight = isScrolledTop ? '100px' : 'unset'; + mainLogo.style.padding = isScrolledTop ? '1.5rem 2rem' : '0.2rem 0.4rem'; + description.style.opacity = isScrolledTop ? '1' : '0'; + burgerContainer.style.padding = isScrolledTop ? '2rem' : '0.6rem 1rem'; + burgerContainer.style.justifyContent = isScrolledTop ? 'space-between' : 'center'; + menuIconContainer.style.fontSize = isScrolledTop ? '2rem' : '1.5rem'; + menuText.style.display = isScrolledTop ? '' : 'none'; + + + + if (window.scrollY === 0) { + // agrandir le header + } else { + // diminuer le header + } + } + + + }); + +let resizeTimeout; +let previousWidth = window.innerWidth; + +window.addEventListener('resize', () => { + let currentWidth = window.innerWidth; + + if (currentWidth !== previousWidth) { + window.scrollTo(0, 0); + previousWidth = currentWidth; + } + + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(function() { + resetStyles(); + checkMobile(); + }, 150); +}); + + checkMobile(); +}); \ No newline at end of file diff --git a/page-annonces.php b/page-annonces.php new file mode 100644 index 0000000..419af66 --- /dev/null +++ b/page-annonces.php @@ -0,0 +1,165 @@ + $active_axe ?: null, + 'date_from' => $active_date_from ?: null, + 'date_to' => $active_date_to ?: null, + ]); + $redir_url = $redir_params + ? add_query_arg( $redir_params, get_category_link( $cat_obj->term_id ) ) + : get_category_link( $cat_obj->term_id ); + wp_redirect( $redir_url, 301 ); + exit; + } +} + +// Determine active rubrique +$active_rubrique_id = 0; +if ($active_cat_id) { + $active_cat_obj = get_category($active_cat_id); + $active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent) + ? $active_cat_obj->parent + : $active_cat_id; +} +$context['active_rubrique'] = $active_rubrique_id; + +// Base filter params preserved across all filter links +$base_filter_params = array_filter([ + 'axe' => $active_axe ?: null, + 'date_from' => $active_date_from ?: null, + 'date_to' => $active_date_to ?: null, +]); + +// Build tax_query: exclude séances + optional category filter +$tax_query = [ + 'relation' => 'AND', + [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => $excluded_cat_ids, + 'operator' => 'NOT IN', + ], +]; +if ($active_cat_id) { + $tax_query[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$active_cat_id], + 'include_children' => !$filter_autres, + ]; +} + +$query_args = [ + 'post_type' => 'post', + 'posts_per_page' => 12, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'tax_query' => $tax_query, + 'thalim_event_date_order' => true, +]; +if ($active_axe) { + $query_args['meta_query'] = [[ + 'key' => 'axes_thematiques', + 'value' => $active_axe, + 'type' => 'NUMERIC', + ]]; +} +if ($active_date_from || $active_date_to) { + $query_args['thalim_event_date_filter'] = ['from' => $active_date_from, 'to' => $active_date_to]; +} + +// Axes thématiques for filter dropdown +$axes_groups = thalim_get_axes_filter_groups(); +$current_axes = $axes_groups[0]['terms'] ?? []; +$context['filter_axes'] = $current_axes; + +// Rubrique/catégorie filter links (stay on this page with filter_cat param) +$page_url = get_permalink(); +$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]); + +$filter_parents = []; +foreach ($all_cats as $cat) { + if ($cat->parent == 0) { + $filter_parents[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => $base_filter_params + ? add_query_arg($base_filter_params, get_category_link($cat->term_id)) + : get_category_link($cat->term_id), + ]; + } +} +$context['filter_parents'] = $filter_parents; + +$filter_categories = []; +if ($active_rubrique_id) { + foreach ($all_cats as $cat) { + if ($cat->parent == $active_rubrique_id) { + $filter_categories[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => $base_filter_params + ? add_query_arg($base_filter_params, get_category_link($cat->term_id)) + : get_category_link($cat->term_id), + ]; + } + } +} +// Add "Autres" entry if active rubrique has posts directly assigned to it +if ($active_rubrique_id && !empty($filter_categories)) { + $lang = thalim_current_language(); + $direct_check = new WP_Query([ + 'post_type' => 'post', + 'posts_per_page' => 1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'lang' => '', + 'tax_query' => [[ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$active_rubrique_id], + 'include_children' => false, + ]], + ]); + if ($direct_check->have_posts()) { + $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1])); + $filter_categories[] = [ + 'id' => 'autres', + 'name' => $lang === 'en' ? 'Other' : 'Autres', + 'slug' => 'autres', + 'link' => add_query_arg($params, $page_url), + ]; + } +} +$context['filter_categories'] = $filter_categories; + +$posts = Timber::get_posts($query_args); +$context['cards'] = thalim_get_cards_data($posts); +$context['posts'] = $posts; + +Timber::render('page-annonces.twig', $context); diff --git a/page-announcements.php b/page-announcements.php new file mode 100644 index 0000000..c1f77ce --- /dev/null +++ b/page-announcements.php @@ -0,0 +1,2 @@ +ID; + +// ── Liens (internal page links) ────────────────────────────── +$lien_ids = get_post_meta( $page_id, 'liens', false ); +$liens = []; +foreach ( $lien_ids as $lid ) { + $lid = intval( $lid ); + if ( ! $lid ) continue; + $liens[] = [ + 'title' => get_the_title( $lid ), + 'url' => get_permalink( $lid ), + ]; +} +$context['liens'] = $liens; + +$labo_lang = thalim_current_language(); + +// ── Images (two side-by-side slots) ────────────────────────── +$images = []; +foreach ( [ 'image_labo_1', 'image_labo_2' ] as $field ) { + $img_id = intval( get_post_meta( $page_id, $field, true ) ); + if ( ! $img_id ) continue; + $src = wp_get_attachment_image_url( $img_id, 'large' ); + if ( ! $src ) continue; + $images[] = [ + 'url' => $src, + 'alt' => get_post_meta( $img_id, '_wp_attachment_image_alt', true ) ?: '', + 'title' => thalim_bilingual( get_the_title( $img_id ), $labo_lang ), + ]; +} +$context['images'] = $images; + +// ── Axes thématiques grouped by period ─────────────────────── +$terms = get_terms( [ 'taxonomy' => 'axe_thematique', 'hide_empty' => false ] ); +$axes_map = []; + +$label_prefix = $labo_lang === 'en' ? 'Research areas ' : 'Axes thématiques '; +$label_passes = $labo_lang === 'en' ? 'Past research areas' : 'Axes thématiques antérieurs'; + +foreach ( $terms as $term ) { + $debut = trim( get_term_meta( $term->term_id, 'annee_debut', true ) ); + $fin = trim( get_term_meta( $term->term_id, 'annee_fin', true ) ); + + if ( $debut && $fin ) { + $key = $debut . '-' . $fin; + $label = $label_prefix . $debut . ' – ' . $fin; + } else { + $key = 'passes'; + $label = $label_passes; + } + + if ( ! isset( $axes_map[ $key ] ) ) { + $axes_map[ $key ] = [ + 'label' => $label, + 'debut' => intval( $debut ), + 'terms' => [], + ]; + } + $ordre = trim( get_term_meta( $term->term_id, 'ordre_daffichage', true ) ); + $axes_map[ $key ]['terms'][] = [ + 'name' => $term->name, + 'url' => get_term_link( $term ), + 'ordre' => $ordre !== '' ? intval( $ordre ) : null, + ]; +} + +// Sort: newest first by annee_debut, 'passes' always last (debut === 0) +uasort( $axes_map, function ( $a, $b ) { + if ( $a['debut'] === 0 ) return 1; + if ( $b['debut'] === 0 ) return -1; + return $b['debut'] - $a['debut']; +} ); + +// Within each group: numbered items first (ascending), then unnumbered alphabetically +foreach ( $axes_map as &$group ) { + usort( $group['terms'], function ( $a, $b ) { + $a_has = $a['ordre'] !== null; + $b_has = $b['ordre'] !== null; + if ( $a_has && $b_has ) return $a['ordre'] - $b['ordre']; + if ( $a_has ) return -1; + if ( $b_has ) return 1; + return strcmp( $a['name'], $b['name'] ); + } ); +} +unset( $group ); + +$context['axes_groups'] = array_values( $axes_map ); + +// ── Body (English override) ────────────────────────────────── +$context['body_en'] = apply_filters( 'the_content', get_post_meta( $page_id, 'body_en', true ) ?: '' ); + +// ── WYSIWYG fields ──────────────────────────────────────────── +$context['partenaires_internationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_internationaux_en', true ) ) + ? get_post_meta( $page_id, 'partenaires_internationaux_en', true ) + : ( get_post_meta( $page_id, 'partenaires_internationaux', true ) ?: '' ) ); +$context['partenaires_nationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_nationaux_en', true ) ) + ? get_post_meta( $page_id, 'partenaires_nationaux_en', true ) + : ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) ); +$context['bibliotheques'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'bibliotheques_en', true ) ) + ? get_post_meta( $page_id, 'bibliotheques_en', true ) + : ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) ); + +// ── Edit link ───────────────────────────────────────────────── +$context['page_edit_link'] = current_user_can( 'edit_page', $page_id ) ? get_edit_post_link( $page_id ) : ''; + +Timber::render( 'page-le-laboratoire.twig', $context ); diff --git a/page-membres.php b/page-membres.php new file mode 100644 index 0000000..5f4e5f2 --- /dev/null +++ b/page-membres.php @@ -0,0 +1,8 @@ + 'programme_de_recherche', 'hide_empty' => false ] ); + +$sections = [ + 'subventionne' => [ 'label' => 'Programmes subventionnés', 'items' => [] ], + 'autre' => [ 'label' => 'Autres programmes', 'items' => [] ], + 'ancien' => [ 'label' => 'Anciens programmes', 'items' => [] ], +]; + +foreach ( $terms as $term ) { + $type = get_term_meta( $term->term_id, 'type_de_programme', true ); + + $item = [ + 'name' => $term->name, + 'description' => wpautop( $term->description ), + 'url' => get_term_link( $term ), + 'annee_fin' => (int) get_term_meta( $term->term_id, 'annee_fin', true ), + ]; + + if ( $type === 'Programme subventionné' ) { + $sections['subventionne']['items'][] = $item; + } elseif ( $type === 'Ancien programme' ) { + $sections['ancien']['items'][] = $item; + } else { + // "Autre programme" or no type set + $sections['autre']['items'][] = $item; + } +} + +// Sort by annee_fin descending (most recent end year first); items without a year go last +foreach ( $sections as &$section ) { + usort( $section['items'], function( $a, $b ) { + if ( $a['annee_fin'] === $b['annee_fin'] ) return strcmp( $a['name'], $b['name'] ); + if ( ! $a['annee_fin'] ) return 1; + if ( ! $b['annee_fin'] ) return -1; + return $b['annee_fin'] - $a['annee_fin']; + } ); +} +unset( $section ); + +$context['sections'] = array_values( $sections ); +$context['page_edit_link'] = current_user_can( 'edit_page', $post->ID ) ? get_edit_post_link( $post->ID ) : ''; + +Timber::render( 'page-programmes-de-recherche.twig', $context ); diff --git a/page.php b/page.php new file mode 100644 index 0000000..851a4fe --- /dev/null +++ b/page.php @@ -0,0 +1,8 @@ +ID ) ? get_edit_post_link( $post->ID ) : ''; + +Timber::render( 'page.twig', $context ); diff --git a/scss/_author.scss b/scss/_author.scss new file mode 100644 index 0000000..8debf33 --- /dev/null +++ b/scss/_author.scss @@ -0,0 +1,164 @@ +// ==================================== +// AUTHOR PROFILE PAGE +// ==================================== + +.author-header { + display: flex; + gap: 2rem; + align-items: flex-start; + margin: 2rem 0; +} + +.author-avatar { + flex-shrink: 0; + + img { + width: 140px; + height: 140px; + object-fit: cover; + + @media ($tablet) { + width: 180px; + height: 180px; + } + } +} + +.author-identity { + h2 { + margin-top: 0; + } + & + .author-bio { + margin-top: 2rem; + } +} + +.author-role { + font-family: $font-primary; + margin-top: 0.4rem; + opacity: 0.85; + line-height: 1.4; +} + +.author-bio { + margin-bottom: 1.5rem; + line-height: 1.6; + + > p { + margin: 0.8rem 0; + } + + hr { + display: none; + } + + h3 { + font-family: $font-primary; + text-transform: uppercase; + } +} + +.author-resume-these { + margin-top: 1rem; + line-height: 1.6; + + > p { + margin: 0.8rem 0; + } +} + +.author-cat-footer { + display: flex; + justify-content: center; + margin-top: 1.5rem; + padding-top: 1rem; + border-top: 1px solid $light-gray; +} + +.author-titre-these { + font-family: $font-heading; + font-size: 1.4rem; + line-height: 1.3 !important; + margin-bottom: 0.8rem; +} + +.these-inline-title { + text-transform: uppercase; +} + +// WYSIWYG article-fields (domaines de recherches, recherches en cours): +// the wpautop'd

    follows the inline title — give it a small breathing space. +.domaines-autres, +.recherches-en-cours { + > p:first-of-type { + margin-top: 0.4rem; + } +} + +// ── Author posts dropdowns ──────────────────────────────────── +.author-posts-section { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 5rem; +} + +.author-posts-header { + display: flex; + align-items: center; + gap: 1.2rem; + padding: 0.8rem; + background-color: $light-gray; + cursor: pointer; + transition: background-color 0.15s; + font-family: $font-primary; + text-transform: uppercase; + + &:hover { + background-color: $less-light-gray; + } +} + +.author-posts-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + margin-left: auto; + + .author-posts-item.is-open & { + transform: rotate(180deg); + } +} + +.author-posts-content { + padding: 1rem; + background-color: $light-light-gray; + position: relative; + padding-bottom: 3rem; + &::after { + @include yellow-gradient-after; + } + + article a { + text-decoration: none !important; + h2 { + margin-top: 0 !important; + } + + } +} + +.author-post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + width: 100%; + + h2::after { + display: none; + } + + @media ($tablet) { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/scss/_base.scss b/scss/_base.scss new file mode 100644 index 0000000..a9b9812 --- /dev/null +++ b/scss/_base.scss @@ -0,0 +1,3 @@ +#wpadminbar { + position: fixed !important; +} \ No newline at end of file diff --git a/scss/_category.scss b/scss/_category.scss new file mode 100644 index 0000000..361262a --- /dev/null +++ b/scss/_category.scss @@ -0,0 +1,557 @@ +// Category archive pages + +.category-archive { + margin-top: 0; + .category-header-top { + display: flex; + justify-content: space-between; + flex-direction: column; + align-items: start; + .breadcrumb { + font-size: 0.85rem; + margin-bottom: 1.5rem; + text-transform: uppercase; + line-height: 1.3; + + &__separator { + margin: 0 0.4rem; + } + } + @media ($tablet) { + flex-direction: row; + } + } + + h1 { + font-family: Gelasio; + font-weight: normal; + font-size: 1.8rem; + position: relative; + margin-top: 2rem; + display: inline-block; + &::after { + content: ''; + display: block; + position: absolute; + height: 6px; + width: 100%; + bottom: -1.2rem; + left: 0; + z-index: 2; + } + @media ($tablet) { + font-size: 2.6rem; + &::after { + bottom: -0.4rem; + } + } + } + + .category--le-laboratoire h1::after { + background: linear-gradient(to bottom, transparent 0%, $laboratoire 50%); + } + + .category--manifestations-scientifiques h1::after { + background: linear-gradient(to bottom, transparent 0%, $manifestations 50%); + } + + .category--publications-et-productions h1::after { + background: linear-gradient(to bottom, transparent 0%, $publications 50%); + } + + .category--mediation-scientifique h1::after { + background: linear-gradient(to bottom, transparent 0%, $mediations 50%); + } + + .category--ressources h1::after { + background: linear-gradient(to bottom, transparent 0%, $ressources 50%); + } + +} + +// Breadcrumb: non-category links (Accueil, etc.) underlined in muted gray +.breadcrumb a:not(.breadcrumb__cat) { text-decoration: underline; text-decoration-color: $less-light-gray; text-underline-offset: 3px; } + +// Breadcrumb category color underlines — links and current page indicator +.category--le-laboratoire .breadcrumb__cat, +.category--le-laboratoire .breadcrumb__current { text-decoration: underline; text-decoration-color: $laboratoire; text-underline-offset: 3px; } +.category--manifestations-scientifiques .breadcrumb__cat, +.category--manifestations-scientifiques .breadcrumb__current { text-decoration: underline; text-decoration-color: $manifestations; text-underline-offset: 3px; } +.category--publications-et-productions .breadcrumb__cat, +.category--publications-et-productions .breadcrumb__current { text-decoration: underline; text-decoration-color: $publications; text-underline-offset: 3px; } +.category--mediation-scientifique .breadcrumb__cat, +.category--mediation-scientifique .breadcrumb__current { text-decoration: underline; text-decoration-color: $mediations; text-underline-offset: 3px; } +.category--ressources .breadcrumb__cat, +.category--ressources .breadcrumb__current { text-decoration: underline; text-decoration-color: $ressources; text-underline-offset: 3px; } + +.category-archive { + + .taxonomy-description { + font-family: $font-primary; + margin-top: 2rem; + line-height: 1.6; + max-width: 70ch; + + + a { + text-decoration: underline; + } + + p { + margin-bottom: 1rem; + line-height: 1.6; + strong { + font-weight: bold; + } + em { + font-style: italic; + } + &:first-child { + margin-top: 0 !important; + } + } + + ul, ol { + line-height: 1.6; + padding-left: 0.8rem; + } + + ul { + list-style: inside "· "; + } + + ol { + list-style: inside decimal; + } + + blockquote{ + padding-left: 1rem; + margin-left: 1.5rem; + border-left: solid 1px $light-gray; + } + } + + .subcategory-section { + margin-top: 5rem; + &:first-of-type { + margin-top: 2rem; + } + .subcategory-section__title { + font-family: NewsCycle; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + font-size: 1.2rem; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } + } + + .post-grid { + display: grid; + align-items: start; + grid-template-columns: 1fr; + gap: 2rem; + width: 100%; + + .post-card { + min-width: 0; + } + + @media ($tablet) { + grid-template-columns: repeat(2, 1fr); + } + + @media ($desktop) { + grid-template-columns: repeat(3, 1fr); + } + } + + .category-section-footer { + margin-top: 3rem; + display: flex; + justify-content: center; + } + } + +} + +/* +.category-header { + width: 100%; + margin-bottom: 3vh; + + &__back { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-size: 0.9rem; + text-decoration: none; + margin-bottom: 1rem; + + i { + font-size: 1.1rem; + } + } + + &__title { + font-size: 2rem; + margin-bottom: 0.5rem; + + @media ($tablet) { + font-size: 2.5rem; + } + } + + &__description { + color: $less-dark-gray; + } +} + +// Sub-category sections on parent category page +.subcategory-section { + width: 100%; + margin-bottom: 4vh; + + &__header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 2vh; + border-bottom: 2px solid $publications; + padding-bottom: 0.5rem; + } + + &__title { + font-size: 1.5rem; + + a { + text-decoration: none; + } + } + + &__see-all { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-size: 0.9rem; + text-decoration: none; + white-space: nowrap; + } + + &__empty { + color: $less-less-light-gray; + font-style: italic; + } +} + +// Post grid +.post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + width: 100%; + + @media ($tablet) { + grid-template-columns: repeat(2, 1fr); + } + + @media ($desktop) { + grid-template-columns: repeat(3, 1fr); + } +} + +// Post card +.post-card { + background-color: white; + display: flex; + flex-direction: column; + overflow: hidden; + + &__image { + width: 100%; + aspect-ratio: 4 / 3; + overflow: hidden; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + &__content { + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.4rem; + } + + &__title { + font-size: 1.1rem; + line-height: 1.3; + + a { + text-decoration: none; + } + } + + &__subtitle { + font-size: 0.9rem; + color: $less-dark-gray; + font-style: italic; + } + + &__date { + font-size: 0.8rem; + color: $less-less-light-gray; + } + + &__authors { + font-size: 0.9rem; + display: flex; + flex-wrap: wrap; + gap: 0.2rem; + + .post-card__author { + &:not(:last-child)::after { + content: ','; + } + } + + &--external { + color: $less-dark-gray; + } + } + + &__role, + &__publisher, + &__journal { + font-size: 0.85rem; + color: $less-dark-gray; + } + + &__axes, + &__tags { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + } + + &__axe, + &__tag { + font-size: 0.75rem; + background-color: $light-gray; + padding: 0.15rem 0.5rem; + } + + &__axe { + border-left: 2px solid $publications; + } + + &__links { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.3rem; + } + + &__link { + font-size: 0.8rem; + + &--hal { + color: $publications; + } + } +} + +// Infinite scroll spinner +.scroll-spinner { + display: flex; + justify-content: center; + align-items: center; + gap: 0.4rem; + padding: 2rem 0; + + &__dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: $publications; + animation: scroll-spinner-bounce 1.2s infinite ease-in-out; + + &:nth-child(2) { + animation-delay: 0.2s; + } + + &:nth-child(3) { + animation-delay: 0.4s; + } + } +} + +@keyframes scroll-spinner-bounce { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.4; + } + 40% { + transform: scale(1); + opacity: 1; + } +} +*/ + +// ── Agenda slider view ──────────────────────────────────────────────────────── + +.agenda-view-container { + display: none; + margin-top: 3rem; + &.is-active { display: block; } +} + +.agenda-view-title { + font-family: NewsCycle; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + font-size: 1.2rem; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } +} + +.agenda-swiper-wrap { + display: flex; + align-items: center; + gap: 0.8rem; + margin-top: 2rem; +} + +.agenda-swiper { + flex: 1; + overflow: hidden; +} + +.agenda-swiper-prev, +.agenda-swiper-next { + flex-shrink: 0; + background: none; + border: none; + cursor: pointer; + font-size: 1.2rem; + padding: 0.4rem; + color: $dark-gray; + line-height: 1; + + &:hover { color: black; } + + &.swiper-button-disabled { + opacity: 0.25; + pointer-events: none; + } +} + +.agenda-card { + display: flex; + flex-direction: column; + text-decoration: none; + color: inherit; + padding-bottom: 1.5rem; + border-bottom: 1px solid $light-gray; + height: 100%; + position: relative; + transition: transform 0.2s ease-out; + margin-right: 0 !important; + + // Timeline line running through the center of the date boxes + &::before { + content: ''; + position: absolute; + top: 1.7rem; + left: 0; + right: 0; + height: 1px; + background-color: $less-light-gray; + z-index: 0; + width: calc(100% + 20px); + @media ($tablet) { + width: calc(100% + 24px); + } + @media ($desktop) { + width: calc(100% + 32px); + } + } + + &:hover { transform: scale(0.98); } + + &__dates { + display: flex; + align-items: flex-start; + gap: 0.5rem; + margin-bottom: 1rem; + } + + &__body { flex: 1; } + + &__meta { + display: flex; + flex-wrap: wrap; + gap: 0.3rem 0.8rem; + font-family: $font-primary; + font-size: 0.75rem; + text-transform: uppercase; + opacity: 0.65; + margin-bottom: 0.5rem; + } + + &__title { + font-family: $font-heading; + font-size: 1rem; + line-height: 1.35; + } +} + +// Category-specific accent color on agenda card border + date box gradient +.category--manifestations-scientifiques .agenda-card { border-bottom-color: $manifestations; } +.category--le-laboratoire .agenda-card { border-bottom-color: $laboratoire; } +.category--publications-et-productions .agenda-card { border-bottom-color: $publications; } +.category--mediation-scientifique .agenda-card { border-bottom-color: $mediations; } +.category--ressources .agenda-card { border-bottom-color: $ressources; } + +.category--manifestations-scientifiques .agenda-date-box { @include category-gradient($manifestations); } +.category--le-laboratoire .agenda-date-box { @include category-gradient($laboratoire); } +.category--publications-et-productions .agenda-date-box { @include category-gradient($publications); } +.category--mediation-scientifique .agenda-date-box { @include category-gradient($mediations); } +.category--ressources .agenda-date-box { @include category-gradient($ressources); } + +.agenda-date-box { + position: relative; + z-index: 1; + background-color: $light-gray; + font-family: $font-primary; + text-transform: uppercase; + text-align: center; + padding: 0.4rem 0.55rem; + flex-shrink: 0; + min-width: 2.6rem; + + .agenda-date-day { display: block; font-size: 1.3rem; line-height: 1; } + .agenda-date-month { display: block; font-size: 0.65rem; margin-top: 2px; } + .agenda-date-year { display: block; font-size: 0.6rem; opacity: 0.65; } + + sup { + font-size: 0.7rem; + } +} + +.agenda-date-arrow { + font-size: 0.85rem; + opacity: 0.5; +} \ No newline at end of file diff --git a/scss/_filters.scss b/scss/_filters.scss new file mode 100644 index 0000000..e4b7b00 --- /dev/null +++ b/scss/_filters.scss @@ -0,0 +1,344 @@ +.filters-bar { + margin-top: 2.5rem; + display: flex; + align-items: center; + gap: 0.8rem; + flex-wrap: wrap; + position: relative; + padding-bottom: 0.6rem; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: -5vw; + width: calc(100% + 10vw); + height: 1px; + background-color: $light-gray; + } + + @media ($tablet) { + &::after { + left: -3vw; + width: calc(100% + 6vw); + } + } +} + +.filters-toggle-btn { + display: flex; + align-items: center; + gap: 0.5rem; + font-family: $font-primary; + font-size: 0.9rem; + text-transform: uppercase; + background-color: $light-gray; + border: unset; + cursor: pointer; + padding: 0.4rem 0.8rem; + color: $dark-gray; + flex-shrink: 0; + + .filters-chevron { + transition: transform 0.2s; + } + + &.is-open .filters-chevron { + transform: rotate(180deg); + } + + &:hover { + background-color: $less-light-gray; + } +} + +.filters-active-chips { + display: flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; +} + +.filter-chip { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-family: $font-primary; + font-size: 0.8rem; + text-transform: uppercase; + background-color: $light-gray; + padding: 0.3rem 0.5rem; + text-decoration: none; + color: $dark-gray; + border: none; + cursor: pointer; + + i { + font-size: 0.7rem; + opacity: 0.5; + } + + &:hover { + background-color: $less-light-gray; + + i { opacity: 1; } + } +} + +.category-filters { + font-size: 0.9rem; + font-family: $font-primary; + margin-top: 0; + display: none; + padding: 2rem 5vw; + padding-bottom: 0.5rem !important; + margin-left: -5vw; + width: calc(100% + 10vw); + justify-content: space-between; + background-color: $light-light-gray; + border-top: solid 1px $light-gray; + border-bottom: solid 1px $light-gray; + flex-direction: column; + + &.is-open { + display: flex; + } + > div { + margin-top: 1.2rem; + margin-bottom: 1.2rem; + flex: auto; + + &.filtre-rubrique { + flex: 2; + } + + &.filtre-categorie { + flex: 1; + } + + &.filtre-date { + flex: 1; + } + + &.filtre-axe { + flex: 1; + } + + &:first-of-type { + margin-top: 0; + padding-left: 0; + } + + &:last-of-type { + margin-bottom: 0; + } + + &:not(:last-of-type) { + padding-bottom: 1.8rem; + border-bottom: solid 1px $less-light-gray; + } + + .filter-section-header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 2rem; + } + + .section-title { + text-transform: uppercase; + position: relative; + display: inline-block; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } + } + + ul { + display: flex; + justify-content: start; + flex-wrap: wrap; + align-items: start; + gap: 0.5rem 0.2rem; + list-style: none; + margin: 0; + padding: 0; + + li { + font-size: 1rem; + cursor: pointer; + padding: 0.4rem 0.6rem; + + a { + text-decoration: none; + } + + &:hover, + &.is-active { + background-color: $less-light-gray; + } + } + } + } + + .filter-dd { + position: relative; + margin-top: 0; + display: inline-block; + + .dd-title { + padding: 0.3rem 0.5rem; + display: flex; + align-items: center; + gap: 0.4rem; + cursor: pointer; + + > p, + > i { + font-size: 0.9rem; + } + + > i { + transition: transform 0.2s; + } + } + + &.is-open .dd-title > i { + transform: rotate(180deg); + } + + &.is-active .dd-title { + background-color: $less-light-gray; + } + + .dd-content { + position: absolute; + padding: 0; + z-index: 5; + background: white; + border: 1px solid $less-light-gray; + min-width: 100%; + + ul { + gap: 0.3rem; + padding: 0.5rem; + + li { + padding: 0.4rem; + white-space: nowrap; + background-color: $light-gray; + } + } + &#filter-axe-popover { + right: auto; + left: 0; + min-width: 280px; + max-width: min(420px, 90vw); + + ul { + flex-direction: column; + padding: 0; + gap: 0; + li { + width: 100%; + margin: 0; + white-space: normal; + &:nth-of-type(odd) { + background-color: white; + } + &:first-of-type { + background-color: $less-light-gray; + } + &.dd-axe-group-label { + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: $less-dark-gray; + background-color: $light-gray !important; + padding: 0.5rem 0.4rem; + cursor: default; + pointer-events: none; + border-top: solid 1px $less-light-gray; + &:first-child { + margin-top: 0; + } + } + } + } + } + } + + .dd-date-fields { + padding: 0.6rem; + padding-top: 1rem; + display: flex; + flex-direction: column; + gap: 0.4rem; + border-top: 1px solid $less-light-gray; + + label { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.85rem; + text-transform: uppercase; + } + + input[type="date"] { + padding: 0.2rem 0.4rem; + border: 1px solid $less-light-gray; + font-size: 0.85rem; + font-family: $font-primary; + } + } + + .dd-date-apply { + margin-top: 0.6rem; + width: 100%; + padding: 0.3rem; + border: none; + font-size: 0.85rem; + font-family: $font-primary; + text-transform: uppercase; + cursor: pointer; + } + } + + .membres-search-input.is-active { + background-color: $less-light-gray; + } + + .date-reset-link { + display: block; + font-size: 0.75rem; + text-transform: uppercase; + text-decoration: none; + color: inherit; + opacity: 0.6; + padding: 0 0.5rem 0.4rem; + + &:hover { + opacity: 1; + } + } + + @media ($tablet) { + padding: 2rem 3vw; + margin-left: -3vw; + width: calc(100% + 6vw); + flex-direction: row; + > div { + margin-top: 0; + margin-bottom: 0; + padding-left: 1.5rem; + &:not(:last-of-type) { + padding-right: 0.8rem; + border-bottom: unset; + border-right: solid 1px $less-light-gray; + } + } + } +} diff --git a/scss/_footer.scss b/scss/_footer.scss new file mode 100644 index 0000000..0408881 --- /dev/null +++ b/scss/_footer.scss @@ -0,0 +1,55 @@ +footer { + background-color: white; + width: 100%; + padding: 1rem 2rem; + margin-top: auto; +} + +.footer-content { + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + gap: 2rem; + @media ($tablet) { + gap: unset; + flex-direction: row; + } +} + +.footer-nav { + ul#menu-footer, + ul#menu-footer-en { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.5rem 2rem; + @media ($tablet) { + justify-content: start; + } + + li { + a { + text-transform: uppercase; + font-size: 0.75rem; + } + } + } +} + +.footer-logos { + display: inline-flex; + height: 3rem; + gap: 1rem; + padding: 0.5rem; + align-items: start; + + > a { + height: 100%; + > img { + height: 100%; + } + } +} \ No newline at end of file diff --git a/scss/_header.scss b/scss/_header.scss new file mode 100644 index 0000000..6547672 --- /dev/null +++ b/scss/_header.scss @@ -0,0 +1,330 @@ +body > +header { + display: flex; + flex-direction: column-reverse; + justify-content: space-between; + transition: height 0.3s ease-out; + background-color: white; + width: 100%; + position: relative; + z-index: 5; + @media ($tablet) { + position: fixed; + height: 12vh; + min-height: 100px; + max-height: 130px; + flex-direction: row; + } +} + +.header-left { + display: flex; + height: 6rem; + margin-top: 3rem; + @media ($tablet) { + margin-top: unset; + height: 100%; + } +} + +.main-logo-container { + display: inline-block; + background-color: $light_gray; + height: 100%; + @media ($tablet) { + flex-direction: row; + } +} + +.main-logo { + display: inline-block; + padding: 0.3rem 0.6rem; + height: 100%; + @include hover-gradient-background; + @media ($tablet) { + padding: 1.5rem 2rem; + } + + > img, + > a > img { + height: 100%; + transform: scale(1); + transition: transform 0.2s ease-out; + @media ($tablet) { + } + } + + &:hover > img, + &:hover > a > img { + transform: scale(1.05); + } +} + +.description { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + gap: 0.3rem; + margin-left: 1.2rem; + opacity: 1; + transition: opacity 0.2s ease-out; + + > div:first-of-type { + text-transform: uppercase; + font-size: 0.8rem; + background-color: $light_gray; + padding: 0.2rem; + } + + > div:last-of-type { + font-size: 0.8rem; + > sup { + font-size: 0.6rem; + vertical-align: super; + } + } +} + +.header-right { + display: flex; + align-items: center; + gap: 2rem; + justify-content: space-between; + position: fixed; + width: 100%; + background-color: white; + height: 3rem; + @media ($tablet) { + padding-left: unset; + height: unset; + position: relative; + width: unset; + } + &.scrolled { + .secondary-logo-container { + max-width: 40vw; + .main-logo:hover { + background-position: unset; + img { + transform: unset; + } + } + } + } +} + +.secondary-logo-container { + height: 100%; + max-width: 0; + overflow: hidden; + transition: max-width 0.6s ease-out; + @media ($tablet) { + display: none; + } +} + +.lang-switch { + text-transform: uppercase; + font-size: 0.8rem; + transition: font-size 0.2s ease-out; + > ul { + display: flex; + gap: 1rem; + > li:not(.active) { + > a { + text-decoration: none; + } + } + } +} + +.search-button { + margin-left: auto; + > div { + background-color: $light_gray; + padding: 0.5rem; + border-radius: 5rem; + min-width: 2rem; + aspect-ratio: 1 / 1; + display: flex; + justify-content: center; + align-items: center; + transition: background-color 0.3s ease-out; + cursor: pointer; + + &:hover { + background-color: $less_light-gray; + } + } +} + +.menu-toggle { + display: inline-block; + background-color: $light_gray; + width: fit-content; + height: 100%; + + > div { + height: 100%; + display: flex; + padding: 0.5rem; + flex-direction: column; + align-items: center; + justify-content: space-between; + gap: 0.3rem; + @include hover-gradient-background; + @media ($tablet) { + gap: unset; + padding: 2rem; + } + + > div { + font-size: 1rem; + transform: scale(1); + transition: transform 0.2s ease-out, font-size 0.2s ease-out; + @media ($tablet) { + font-size: 2rem; + } + } + + > p { + font-family: NewsCycle; + text-transform: uppercase; + transform: scale(1); + font-size: 0.8rem; + transition: transform 0.2s ease-out, font-size 0.2s ease-out; + @media ($tablet) { + font-size: 1rem; + } + } + } + + &:hover > div > div, + &:hover > div > p { + transform: scale(0.9); + } +} + +.search-panel { + position: absolute; + right: 0; + width: 100%; + background-color: white; + z-index: 4; + border-top: 2px solid $light-gray; + transition: top 0.4s ease-out, opacity 0.2s ease-out; + opacity: 0; + pointer-events: none; + + @media ($tablet) { + position: fixed; + width: 33.333%; + } + + &.active { + opacity: 1; + pointer-events: all; + } + + &__inner { + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; + } + + &__title { + font-family: $font-primary; + font-size: 1.1rem; + text-transform: uppercase; + display: inline-block; + align-self: start; + position: relative; + line-height: 1.6; + &::after { + @include yellow-gradient-after; + bottom: -10px; + } + } + + &__desc { + font-family: $font-primary; + color: $less-dark-gray; + } + + &__input-wrap { + position: relative; + } + + &__icon-btn { + position: absolute; + right: 0.8rem; + top: 50%; + transform: translateY(-50%); + color: $less-dark-gray; + background: none; + border: none; + padding: 0; + cursor: pointer; + line-height: 1; + + &:hover { + color: $dark-gray; + } + } + + &__input { + width: 100%; + border: none; + padding: 0.6rem 2.5rem 0.6rem 0.8rem; + font-family: $font-primary; + font-size: 0.85rem; + outline: none; + background-color: $light-gray; + + &::placeholder { + color: $less-dark-gray; + text-transform: uppercase; + } + + &:focus { + border-color: $less-light-gray; + } + } + + &__submit { + display: block; + margin-left: auto; + background-color: $light-gray; + padding: 0.5rem 1rem; + font-family: $font-primary; + font-size: 0.85rem; + text-transform: uppercase; + cursor: pointer; + margin-top: 1rem; + border: none; + + &:hover { + background-color: $less-light-gray; + } + } +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: $less_light-gray; + opacity: 0; + z-index: 3; + pointer-events: none; + transition: opacity 0.3s ease-out; + + &.active { + opacity: 0.6; + pointer-events: all; + } +} \ No newline at end of file diff --git a/scss/_index.scss b/scss/_index.scss new file mode 100644 index 0000000..ccab7eb --- /dev/null +++ b/scss/_index.scss @@ -0,0 +1,562 @@ +.hero-header { + display: flex; + flex-direction: column-reverse; + position: relative; + @media ($tablet) { + flex-direction: row; + } +} + +.hero-logos { + position: absolute; + top: 2.5vh; + display: inline-flex; + height: 3rem; + gap: 1rem; + padding: 0.5rem; + align-items: start; + background-color: $light_gray; + @media ($tablet) { + position: unset; + top: unset; + } + + > a { + height: 100%; + > img { + height: 100%; + + } + &:nth-of-type(2) { + mix-blend-mode: darken; + } + } +} + +.color-changer { + transition: color 0.3s ease-out; +} + +.hero-presentation { + font-family: $font-heading; + font-size: 1.6rem; + line-height: 1.1; + @media ($tablet) { + margin-top: 2rem; + font-size: 2.6rem; + } +} + +.hero-presentation-detail { + margin-top: 1.5rem; + width: 90%; +} + +.hero-content > .link-button { + margin-top: 2.5rem; +} + +.hero-content { + @media ($tablet) { + width: 75%; + } +} + +// ==================================== +// SKETCH +// ==================================== + +#sketch { + z-index: 0; + position: relative; + display: block; + height: 200px; + // background-color: white; + @media ($tablet) { + height: unset; + width: 25%; + margin-top: 0 !important; + } +} + +// Floating shapes (DOM-based implementation) +.floating-shape { + position: absolute; + top: 0; + left: 0; + transform-origin: top left; + will-change: transform; + pointer-events: none; + + svg { + overflow: visible; + } + + path, polyline, polygon, line, circle, ellipse, rect { + transition: fill-opacity 0.5s ease-in-out; + } +} + +.thalim-text { + position: absolute; + pointer-events: none; + transition: opacity 0.3s ease-out; + display: flex; + gap: 0; + z-index: 20; + font-family: 'NewsCycle', sans-serif; + font-size: 26px; + @media ($tablet) { + font-size: 48px; + } +} +// ==================================== +// END SKETCH +// ==================================== + +// ==================================== +// SWIPER SECTIONS (annonces, publications…) +// ==================================== + +.swiper-section { + margin-top: 3rem; + position: relative; + + .section-title { + font-family: NewsCycle; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } + } + + .swiper_content_controls { + display: flex; + gap: 0.8rem; + align-items: center; + margin-top: 2rem; + + .swiper { + overflow: hidden; + flex: 1; + } + + .swiper-button-prev, + .swiper-button-next { + position: static; + width: 2.4rem; + height: 2.4rem; + aspect-ratio: 1; + margin: 0; + background-color: $light-gray; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: $dark-gray; + transition: background-color 0.2s ease-out; + cursor: pointer; + flex-shrink: 0; + + &::after { + display: none; // hide Swiper default arrow + } + + &:hover { + background-color: $less-light-gray; + } + + i { + font-size: 1.1rem; + line-height: 1; + } + } + } + + .button-annonces { + display: flex; + justify-content: center; + margin-top: 1.2rem; + } +} + +// ==================================== +// END SWIPER SECTIONS +// ==================================== + +// ==================================== +// MESSAGE DU LABORATOIRE + AGENDA +// ==================================== + +.message-agenda-section { + display: flex; + flex-direction: column; + width: 100%; + gap: 2rem; + margin-top: 3rem; + + @media ($tablet) { + flex-direction: row; + } +} + +.message-du-labo, +.agenda { + position: relative; + padding: 2.5vh 5vw; + padding-bottom: 6vh; + background-color: white; + display: flex; + flex-direction: column; + align-items: flex-start; + + &::after { + @include yellow-gradient-after; + } + + .section-title { + font-family: $font-primary; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 3rem; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } + } + + @media ($tablet) { + padding: 3vh 3vw; + padding-bottom: 8vh; + } +} + +.message-du-labo { + @media ($tablet) { + flex: 4; + } +} + +.messages-list { + width: 100%; + + @media ($tablet) { + flex: 1; + overflow: hidden; + } +} + +.agenda { + @media ($tablet) { + flex: 3; + // border-left: 1px solid $light-gray; + } +} + +.message-date { + font-size: 0.75rem; + color: $less-dark-gray; + display: block; + margin-bottom: 0.3rem; +} + +.message-item { + & + .message-item { + margin-top: 1.5rem; + padding-top: 1.5rem; + border-top: 1px solid $light-gray; + } +} + +.message-content { + margin-bottom: 1.5rem; + padding-right: 2rem; + position: relative; + + > p { + margin: 0.7rem 0; + } + + + p { + margin-bottom: 1rem; + line-height: 1.6; + strong { + font-weight: bold; + } + em { + font-style: italic; + } + } + + ul, ol { + line-height: 1.6; + padding-left: 0.8rem; + } + + ul { + list-style: inside "· "; + } + + ol { + list-style: inside decimal; + } + + blockquote{ + padding-left: 1rem; + margin-left: 1.5rem; + border-left: solid 1px $light-gray; + } + + + @media ($tablet) { + overflow: hidden; + + &.is-overflowing::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 8rem; + background: linear-gradient(to bottom, transparent 0%, white 70%); + pointer-events: none; + } + } +} + +.button-messages, +.button-agenda { + align-self: center; + margin-top: auto; +} + +.message-read-more { + display: none; + position: absolute; + bottom: 1rem; + left: 0; + z-index: 1; + font-size: 0.85rem; + text-decoration: none; + font-family: $font-primary; + text-transform: uppercase; + + .is-overflowing & { + display: inline-block; + } +} + +.agenda-content { + width: 100%; + + .agenda-item { + display: flex; + align-items: center; + gap: 1.5rem; + transform: scale(1); + transition: transform 0.2s ease-out; + margin-bottom: 2rem; + text-decoration: none; + color: inherit; + + &:hover { + transform: scale(0.97); + } + } + + .date-container { + background-color: $light-gray; + text-transform: uppercase; + font-family: $font-primary; + text-align: center; + padding: 0.5rem; + position: relative; + flex-shrink: 0; + min-width: 3rem; + + > p { + position: relative; + z-index: 1; + + &:first-of-type { + font-size: 1.3rem; + } + + &:last-of-type { + margin-bottom: 3px; + } + } + + &::after { + content: ''; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, $light-gray 0%, $manifestations 100%); + z-index: 0; + } + } + + .event-content { + padding-bottom: 1rem; + border-bottom: 1px solid $manifestations; + flex: 1; + + .meta { + font-family: $font-primary; + display: flex; + gap: 1rem; + padding-bottom: 0.5rem; + text-transform: uppercase; + font-size: 0.8rem; + flex-wrap: wrap; + opacity: 0.7; + } + + .event-title { + font-family: $font-heading; + font-size: 1.1rem; + } + } +} + +// ==================================== +// END MESSAGE DU LABORATOIRE + AGENDA +// ==================================== + +// ==================================== +// NUAGE DE MOTS-CLÉS +// ==================================== + +.keyword-cloud { + margin-top: 4rem; + margin-bottom: 3rem; + width: 100%; + + @media ($tablet) { + margin-bottom: 0; + } + .section-title { + font-family: $font-primary; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } + } +} + +#keyword-container { + position: relative; + width: 100%; + min-height: 120px; +} + +.keyword { + position: absolute; + font-family: $font-primary; + font-size: 0.7rem; + text-transform: uppercase; + white-space: nowrap; + text-decoration: none; + color: $dark-gray; + opacity: 0; + cursor: pointer; + + @media ($tablet) { + font-size: 0.95rem; + } + + &.keyword--visible { + animation: keywordFadeIn 0.7s ease-out forwards; + } +} + +@keyframes keywordFadeIn { + from { + opacity: 0; + transform: scale(0.92); + } + to { + opacity: 1; + transform: scale(1); + } +} + +// ==================================== +// END NUAGE DE MOTS-CLÉS +// ==================================== +// Quick links widget +.quick-links { + position: fixed; + display: block; + background-color: $light-light-gray; + right: 0; + top: 35vh; + z-index: 3; + font-family: $font-primary; + max-width: 2.2rem; + overflow: hidden; + transition: max-width 0.9s ease-out, top 0.2s ease; + text-decoration: none; + z-index: 10; + + @media ($tablet) { + right: 2vw; + } + + @media ($desktop) { + right: 4vw; + } + + > ul { + display: flex; + flex-direction: column; + align-items: end; + gap: 0.8rem; + padding: 0.8rem 0.6rem; + + > li > a { + display: flex; + align-items: center; + gap: 0.8rem; + text-decoration: none; + white-space: nowrap; + + &:hover { + font-weight: bold; + } + } + } + + &:hover { + max-width: 40vw; + } + + &::after { + content: ""; + display: block; + position: absolute; + height: 10px; + width: 100%; + bottom: 0; + left: 0; + background: linear-gradient(to bottom, transparent 0%, $yellow 100%); + z-index: 2; + } +} diff --git a/scss/_layout.scss b/scss/_layout.scss new file mode 100644 index 0000000..15abab5 --- /dev/null +++ b/scss/_layout.scss @@ -0,0 +1,44 @@ +body { + display: flex; + flex-direction: column; + height: 100vh; + background-color: $light_gray; +} + +main { + width: 100vw; + display: flex; + flex-direction: column; + align-items: center; + @media ($tablet) { + margin-top: 12vh; + } +} + +.container { + display: flex; + align-items: center; + justify-content: start; + flex-direction: column; + position: relative; + z-index: 1; + padding: 4vh 5vw; + max-width: 1640px; + width: 100vw; +} + +.full-block { + width: 100%; + background-color: white; + padding: 2.5vh 5vw; + position: relative; + padding-bottom: 6vh; + @media ($tablet) { + padding: 3vh 3vw; + padding-bottom: 8vh; + } + + &::after { + @include yellow-gradient-after; + } +} \ No newline at end of file diff --git a/scss/_membres.scss b/scss/_membres.scss new file mode 100644 index 0000000..cf3c500 --- /dev/null +++ b/scss/_membres.scss @@ -0,0 +1,211 @@ +.membres-section { + margin-top: 4rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.membres-item.is-open { + position: relative; + padding-bottom: 1rem; + &::after { + @include yellow-gradient-after; + } +} + +.membres-header { + display: flex; + align-items: center; + gap: 1.2rem; + padding: 0.8rem; + background-color: $light-gray; + cursor: pointer; + transition: background-color 0.15s; + font-family: $font-primary; + text-transform: uppercase; + + &:hover { + background-color: $less-light-gray; + } +} + +.membres-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + margin-left: auto; + + .membres-item.is-open & { + transform: rotate(180deg); + } +} + +.membres-sort-chevron { + font-size: 0.8rem; + opacity: 0.3; + transition: transform 0.2s, opacity 0.15s; + vertical-align: middle; + margin-left: 0.3rem; +} + +.membres-content { + padding: 1rem; + background-color: $light-light-gray; + font-family: $font-primary; + font-size: 0.9rem; + line-height: 1.6; +} + +.membres-table { + width: 100%; + table-layout: fixed; + border-collapse: collapse; + font-size: 0.9rem; + + th { + width: 33.333%; + text-align: left; + text-transform: uppercase; + font-family: $font-primary; + font-weight: normal; + font-size: 0.8rem; + padding: 0.5rem 1.5rem 0.5rem 0.5rem; + background-color: $light-gray; + cursor: pointer; + user-select: none; + white-space: nowrap; + + &:hover .membres-sort-chevron { + opacity: 0.7; + } + + &.sort-asc .membres-sort-chevron { + opacity: 1; + transform: rotate(180deg); + } + + &.sort-desc .membres-sort-chevron { + opacity: 1; + transform: rotate(0deg); + } + } + + tbody tr { + cursor: pointer; + transition: background-color 0.15s; + background-color: white; + + &.is-even-row { + background-color: $light-gray; + } + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: $less-light-gray; + } + } + + td { + padding: 0.6rem 1.5rem 0.6rem 0.5rem; + vertical-align: top; + line-height: 1.4; + + // Name column + &:first-child { + white-space: nowrap; + } + + a { + text-decoration: none; + } + } +} + +// Member hover popover +#membre-popover { + position: fixed; + z-index: 9999; + background: white; + pointer-events: none; + max-width: 380px; + min-width: 180px; + padding: 1rem; + font-family: $font-primary; + font-size: 0.85rem; + opacity: 0; + transition: opacity 0.1s; + border: solid 1px $light-gray; + + &.is-visible { + opacity: 1; + } + + .membre-popover-inner { + display: flex; + gap: 1rem; + align-items: flex-start; + } + + .membre-popover-pic { + width: 80px; + height: 80px; + object-fit: cover; + object-position: top; + flex-shrink: 0; + } + + .membre-popover-name { + font-size: 1.1rem; + font-weight: normal; + margin: 0 0 0.3rem; + } + + .membre-popover-status { + text-transform: uppercase; + color: $less-dark-gray; + margin: 0 0 0.6rem; + } + + .membre-popover-domaines { + font-size: 0.8rem; + margin: 0 0 0.3rem; + line-height: 1.5; + } + + .membre-popover-autres { + font-size: 0.8rem; + margin: 0; + line-height: 1.5; + color: $less-dark-gray; + } +} + +// Filter bar sizing +.filtre-role { flex: 2; } +.filtre-recherche { flex: 1; } + +// Member search input +.membres-search-input { + width: 100%; + border: none; + padding: 0.6rem 0.8rem; + font-family: $font-primary; + font-size: 0.85rem; + background-color: $light-gray; + outline: none; + + &::placeholder { + color: $less-dark-gray; + } + + &:focus { + background-color: $less-light-gray; + } + + @media ($desktop) { + width: 50%; + } +} diff --git a/scss/_mixins.scss b/scss/_mixins.scss new file mode 100644 index 0000000..aa93556 --- /dev/null +++ b/scss/_mixins.scss @@ -0,0 +1,32 @@ +@mixin hover-gradient-background { + background: linear-gradient(to bottom, $light_gray 60%, $yellow 100%); + background-position: bottom 0px left 0px; + background-repeat: no-repeat; + cursor: pointer; + transition: background 0.3s ease-out, padding 0.2s ease-out; + + &:hover { + background-position: bottom -10px left 0px; + } +} + +@mixin yellow-gradient { + background: linear-gradient(to bottom, transparent 50%, $yellow 100%); +} + +@mixin yellow-gradient-after($height: 30px) { + content: ''; + display: block; + position: absolute; + height: $height; + width: 100%; + bottom: 0; + left: 0; + @include yellow-gradient; + z-index: 2; + pointer-events: none; +} + +@mixin category-gradient($color) { + background: linear-gradient(to bottom, $light_gray 60%, $color); +} \ No newline at end of file diff --git a/scss/_navigation.scss b/scss/_navigation.scss new file mode 100644 index 0000000..90e4d0e --- /dev/null +++ b/scss/_navigation.scss @@ -0,0 +1,205 @@ +.main-menu { + overflow-y: scroll; + left: 0; + width: 100%; + background-color: white; + z-index: 4; + padding-bottom: 4vh; + transition: top 0.4s ease-out, opacity 0.2s ease-out; + border-top: 2px solid $light_gray; + position: absolute; + opacity: 0; + @media ($tablet) { + overflow-y: unset; + position: fixed; + } + + &::after { + @include yellow-gradient-after(); + opacity: 0; + transition: opacity 0.2s ease-out 0.3s; + @media ($tablet) { + opacity: 1; + } + } + &.active { + opacity: 1; + &::after { + position: fixed; + opacity: 1; + @media ($tablet) { + position: absolute; + } + } + } +} + +.menu-navigation-container, +.menu-navigation-en-container { + > ul { + display: grid; + gap: 3rem; + padding: 2rem 1.5rem; + max-width: 1400px; + margin: 0 auto; + @media ($tablet) { + padding: 3rem 4rem; + grid-template-columns: repeat(3, auto); + } + @media ($desktop) { + grid-template-columns: repeat(5, auto); + } + > li { + display: flex; + flex-direction: column; + gap: 0.8rem; + align-items: start; + > a { + text-transform: uppercase; + text-decoration: none; + position: relative; + padding-bottom: 0.8rem; + margin-bottom: 0.5rem; + &::after { + content: ''; + display: block; + position: absolute; + height: 8px; + width: 100%; + bottom: 0; + left: 0; + z-index: 2; + } + } + &:nth-of-type(1) { + a:hover { + color: $laboratoire; + } + > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $laboratoire 100%); + } + > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid $laboratoire; + padding-bottom: 0.8rem; + } + + } + + &:nth-of-type(2) { + a:hover { + color: $manifestations; + } + > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $manifestations 100%); + } + > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid $manifestations; + padding-bottom: 0.8rem; + } + } + + &:nth-of-type(3) { + a:hover { + color: $publications; + } + > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $publications 100%); + } + > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid $publications; + padding-bottom: 0.8rem; + } + + } + + &:nth-of-type(4) { + a:hover { + color: $mediations; + } + > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $mediations 100%); + } + > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid $mediations; + padding-bottom: 0.8rem; + } + + } + + &:nth-of-type(5) { + a:hover { + color: $ressources; + } + > a::after { + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $ressources 100%); + } + > ul.sub-menu > li:first-of-type { + border-bottom: 1px solid $ressources; + padding-bottom: 0.8rem; + } + + } + + > ul.sub-menu { + display: flex; + flex-direction: column; + gap: 0.8rem; + > li { + > a { + text-decoration: none; + } + } + } + } + } +} + +// Axes thématiques dropdown inside nav first column +.nav-axes-item { + .nav-axes-trigger { + background: none; + border: none; + padding: 0; + cursor: pointer; + font-family: $font-primary; + font-size: inherit; + color: inherit; + display: flex; + align-items: center; + gap: 0.3rem; + + i { + transition: transform 0.2s ease; + } + } + + &.is-open .nav-axes-trigger i { + transform: rotate(180deg); + } + + .nav-axes-list { + display: none; + flex-direction: column; + gap: 0.8rem; + padding-top: 0.8rem; + + li a { + font-size: 0.8rem; + padding-left: 0.5rem; + text-decoration: none; + } + } + + &.is-open .nav-axes-list { + display: flex; + } +} + +.menu-navigation-container, +.menu-navigation-en-container { + > ul > li:nth-of-type(1) { + .nav-axes-trigger:hover { + color: $laboratoire; + } + } +} \ No newline at end of file diff --git a/scss/_page-laboratoire.scss b/scss/_page-laboratoire.scss new file mode 100644 index 0000000..b522329 --- /dev/null +++ b/scss/_page-laboratoire.scss @@ -0,0 +1,183 @@ +// ==================================== +// PAGE LE LABORATOIRE +// ==================================== + +// ── Images ──────────────────────────────────────────────────── +.labo-images { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + margin-bottom: 3rem; +} + +.labo-image { + flex: 0 0 auto; + width: 100%; + margin-top: 2rem; + + img { + width: 100%; + height: auto; + display: block; + } + + figcaption { + font-family: $font-primary; + font-size: 0.85rem; + color: $less-dark-gray; + margin-top: 0.5rem; + } + + @media ($tablet) { + width: calc(50% - 0.75rem); + } +} + +// ── Section titles ──────────────────────────────────────────── +.labo-section { + margin-top: 5rem; + + > h3 { + font-family: $font-primary; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } + } +} + +// ── Dropdown wrapper ────────────────────────────────────────── +.labo-dropdowns { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 5rem; + + .labo-section & { + margin-top: 0; + } +} + +// ── Dropdown item ───────────────────────────────────────────── +.labo-dropdown-item.is-open { + position: relative; + padding-bottom: 1rem; + + &::after { + @include yellow-gradient-after; + } +} + +.labo-dropdown-header { + display: flex; + align-items: center; + gap: 1.2rem; + padding: 0.8rem; + background-color: $light-gray; + cursor: pointer; + transition: background-color 0.15s; + font-family: $font-primary; + font-size: inherit; + font-weight: normal; + text-transform: uppercase; + margin: 0; + + &:hover { + background-color: $less-light-gray; + } +} + +.labo-dropdown-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + margin-left: auto; + + .labo-dropdown-item.is-open & { + transform: rotate(180deg); + } +} + +.labo-dropdown-content { + padding: 1rem 1rem 1rem 1rem; + background-color: $light-light-gray; + font-family: $font-primary; + font-size: 0.9rem; + line-height: 1.6; + + p { + margin-bottom: 0.6rem; + } + + p + ul { + margin-top: -1rem; + } + + ul, ol { + padding-left: 1.2rem; + margin-bottom: 0.6rem; + } +} + +// ── Axes list ───────────────────────────────────────────────── +.labo-axes-list { + list-style: none; + padding: 0; + margin: 0; + + li { + padding: 0.5rem 0; + border-bottom: 1px solid $light-gray; + + &:last-child { + border-bottom: none; + } + + a { + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } +} + +// ── Bibliothèques ───────────────────────────────────────────── +.labo-bibliotheques { + font-family: $font-primary; + font-size: 0.9rem; + line-height: 1.6; + + p { + margin-bottom: 0.8rem; + } + + p + ul { + margin-top: -1rem; + margin-bottom: 0.6rem; + } + + a { + text-decoration: underline; + } +} + +// ── Programme de recherche ──────────────────────────────────── +.programme-description { + margin-bottom: 1.5rem; + + p { margin-bottom: 0.6rem; } + ul, ol { padding-left: 1.2rem; } + a { text-decoration: underline; } +} + +.programme-link { + padding-top: 0.5rem; + border-top: 1px solid $light-gray; +} diff --git a/scss/_postcard.scss b/scss/_postcard.scss new file mode 100644 index 0000000..dbc539b --- /dev/null +++ b/scss/_postcard.scss @@ -0,0 +1,114 @@ +.post-card { + padding-bottom: 0.8rem; + border-bottom: solid 1px; + + // Category-specific gradients + &.gradient--le-laboratoire { + .gradient-container { + @include category-gradient($laboratoire); + } + border-color: $laboratoire + } + &.gradient--manifestations-scientifiques { + .gradient-container { + @include category-gradient($manifestations); + } + border-color: $manifestations; + } + &.gradient--publications-et-productions { + .gradient-container { + @include category-gradient($publications); + } + border-color: $publications; + } + &.gradient--mediation-scientifique { + .gradient-container { + @include category-gradient($mediations); + } + border-color: $mediations + } + &.gradient--ressources { + .gradient-container { + @include category-gradient($ressources); + } + border-color: $ressources + } + + &:hover { + .gradient-container { + img, h2 { + transform: scale(0.98); + } + } + } + .gradient-container { + height: 25vh; + padding: 0.7rem; + display: flex; + align-items: center; + justify-content: center; + text-decoration: unset; + + img { + max-height: 100%; + transition: transform 0.2s ease-out; + transform: scale(1); + max-width: 100%; + } + + h2 { + font-family: Gelasio; + font-size: 1.7rem; + line-height: 1.1; + padding: 1.5rem; + transition: transform 0.2s ease-out; + transform: scale(1); + text-decoration: unset; + } + + + &.text-only { + font-family: Gelasio; + font-size: 1.7rem; + padding: 1.5rem; + + p { + transition: transform 0.2s ease-out; + transform: scale(1); + } + } + } + .contextual-infos { + text-transform: uppercase; + font-size: 0.8rem; + display: flex; + justify-content: space-between; + margin-top: 0.6rem; + line-height: 1.3; + .authors { + a { + text-decoration: none; + } + > span:not(:last-of-type)::after { + content: ", "; + } + } + .date-category { + display: flex; + flex-direction: column; + align-items: flex-end; + text-align: right; + a { + text-decoration: none; + } + } + } + .title-bottom { + font-size: 1.2rem; + margin-top: 0.8rem; + line-height: 1.2; + a { + text-decoration: none; + } + } +} diff --git a/scss/_reset.scss b/scss/_reset.scss new file mode 100644 index 0000000..1854ca1 --- /dev/null +++ b/scss/_reset.scss @@ -0,0 +1,46 @@ +*, *:before, *:after{ + box-sizing: border-box; +} + +html, body, div, span, object, iframe, figure, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, code, em, img, small, strike, strong, sub, sup, tt, b, u, i, ol, ul, li, fieldset, form, label, table, caption, tbody, tfoot, thead, tr, th, td, main, canvas, embed, footer, header, nav, section, video{ + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + text-size-adjust: none; +} + +footer, header, nav, section, main{ + display: block; +} + +body{ + line-height: 1; +} + +ol, ul{ + list-style: none; +} + +blockquote, q{ + quotes: none; +} + +blockquote:before, blockquote:after, q:before, q:after{ + content: ''; + content: none; +} + +table{ + border-collapse: collapse; + border-spacing: 0; +} + +input{ + -webkit-appearance: none; + border-radius: 0; +} \ No newline at end of file diff --git a/scss/_search.scss b/scss/_search.scss new file mode 100644 index 0000000..1bbcbcf --- /dev/null +++ b/scss/_search.scss @@ -0,0 +1,153 @@ +.search-page-form { + margin-top: 2rem; + margin-bottom: 3rem; + @media ($desktop) { + width: 50%; + } + .search-panel__desc { + margin-bottom: 1.3rem; + } +} + +.search-page-form + #category-filters { + margin-top: 0; +} + +// Author search results +.author-results { + margin-bottom: 3rem; + + &__title { + font-family: $font-primary; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 1.2rem; + color: $less-dark-gray; + } +} + +// Taxonomy search results (axes & programmes) +.taxonomy-results { + margin-bottom: 3rem; + + &__title { + font-family: $font-primary; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 1.2rem; + color: $less-dark-gray; + } + + &__list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + } + + &__link { + display: block; + padding: 0.5rem 1rem; + border: 1px solid $light-gray; + text-decoration: none; + transition: border-color 0.15s; + + &:hover { + border-color: $dark-gray; + } + } + + &__name { + font-family: $font-heading; + font-size: 0.95rem; + line-height: 1.3; + } + + &__meta { + display: block; + font-family: $font-primary; + font-size: 0.7rem; + text-transform: uppercase; + color: $less-dark-gray; + margin-top: 0.15rem; + } +} + +.author-cards-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + + @media ($tablet) { + grid-template-columns: repeat(3, 1fr); + } + + @media ($desktop) { + grid-template-columns: repeat(6, 1fr); + } +} + +.author-card { + border-bottom: solid 1px $laboratoire; + padding-bottom: 0.4rem; + + &__visual { + display: flex; + align-items: center; + justify-content: center; + height: 14vh; + padding: 0.7rem; + background-color: lighten($laboratoire, 28%); + overflow: hidden; + text-decoration: none; + + img { + max-height: 100%; + max-width: 100%; + width: auto; + height: auto; + transition: transform 0.2s ease-out; + } + + &:hover img { + transform: scale(0.98); + } + } + + &__initials { + font-family: $font-heading; + font-size: 2rem; + color: $laboratoire; + user-select: none; + } + + &__info { + padding-top: 0.5rem; + } + + &__name { + font-family: $font-heading; + font-size: 1rem; + font-weight: normal; + line-height: 1.2; + margin-bottom: 0.3rem; + + a { + text-decoration: none; + } + } + + &__role, + &__affiliation { + font-family: $font-primary; + font-size: 0.75rem; + text-transform: uppercase; + line-height: 1.3; + color: $less-dark-gray; + margin: 0; + } +} \ No newline at end of file diff --git a/scss/_single.scss b/scss/_single.scss new file mode 100644 index 0000000..a45a0e9 --- /dev/null +++ b/scss/_single.scss @@ -0,0 +1,584 @@ +.article { + margin-top: 0; + width: 100%; + + .category-header-top { + display: flex; + flex-direction: column; + align-items: start; + justify-content: space-between; + .breadcrumb { + font-size: 0.85rem; + margin-bottom: 1.5rem; + text-transform: uppercase; + line-height: 1.3; + + &__separator { + margin: 0 0.4rem; + } + } + @media ($tablet) { + flex-direction: row; + } + } + + + h2 { + font-family: Gelasio; + font-weight: normal; + font-size: 1.8rem; + position: relative; + display: inline-block; + margin-top: 2rem; + margin-bottom: 2rem; + + &::after { + content: ''; + display: block; + position: absolute; + height: 5px; + width: 100%; + // bottom: -1.1rem; + left: 0; + z-index: 2; + } + + p { + line-height: 1.3; + &:last-of-type { + margin-top: 0.3rem; + margin-bottom: 0.3rem; + } + } + + p + p { + font-size: 1.6rem; + } + + @media ($tablet) { + p { + font-size: 2.2rem !important; + } + + p + p { + font-size: 1.6rem !important; + } + &::after { + bottom: -0.4rem; + } + } + } + + // Category color gradients on h2::after + &.category--le-laboratoire h2::after { + background: linear-gradient(to bottom, transparent 0%, $laboratoire 30%); + } + &.category--manifestations-scientifiques h2::after { + background: linear-gradient(to bottom, transparent 0%, $manifestations 30%); + } + &.category--publications-et-productions h2::after { + background: linear-gradient(to bottom, transparent 0%, $publications 30%); + } + &.category--mediation-scientifique h2::after { + background: linear-gradient(to bottom, transparent 0%, $mediations 30%); + } + &.category--ressources h2::after { + background: linear-gradient(to bottom, transparent 0%, $ressources 30%); + } + + .article-type { + display: inline-block; + margin-top: 2rem; + font-family: $font-primary; + font-size: 0.85rem; + text-transform: uppercase; + background-color: $light-gray; + padding: 0.2rem 0.6rem; + } + + .maj { + font-family: $font-primary; + font-size: 0.85rem !important; + color: $less-dark-gray; + margin-top: 1rem; + margin-bottom: 2.5rem; + text-transform: uppercase; + } + + .imgs { + width: 100%; + margin-bottom: 3rem; + + figure { + width: 100%; + + img { + width: 100%; + height: auto; + } + + figcaption { + font-family: $font-primary; + font-size: 0.85rem; + color: $less-dark-gray; + margin-top: 0.5rem; + font-style: italic; + } + + @media ($tablet) { + width: 50%; + } + } + + &--swiper { + display: flex; + align-items: center; + gap: 0.5rem; + + @media ($tablet) { + width: 50%; + } + + .swiper { + flex: 1; + min-width: 0; + } + + figure { + width: 100%; + } + + .swiper-pagination { + position: static; + margin-top: 0.5rem; + text-align: center; + } + + .swiper-pagination-bullet-active { + background-color: $less-dark-gray !important; + } + } + } + + .article-content { + display: flex; + flex-direction: column-reverse; + gap: 3rem; + + @media ($tablet) { + flex-direction: row; + } + } + + .sidebar { + background-color: $light-light-gray; + @media ($tablet) { + width: 25%; + padding: 0.8rem; + flex-shrink: 0; + } + + .sidebar-container { + position: sticky; + top: 6rem; + display: flex; + flex-direction: column; + gap: 1.5rem; + } + + .sidebar-section { + display: flex; + flex-direction: column; + gap: 0.8rem; + line-height: 1.4; + + p { + margin: 0; + } + + &.reference-bibliographique { + display: inline-block; + em, i { + font-style: italic; + } + strong { + font-weight: bold; + } + } + } + + p { + font-family: $font-primary; + } + + .link-button { + width: fit-content; + } + + .imgs--portrait { + @media ($tablet) { + width: calc(100% + 1.6rem); + margin-left: -0.8rem; + margin-right: -0.8rem; + } + + .sidebar-portrait { + width: 100%; + } + } + + .sidebar-portrait { + width: 100%; + + img { + width: 100%; + height: auto; + } + + figcaption { + font-family: $font-primary; + font-size: 0.85rem; + color: $less-dark-gray; + margin-top: 0.5rem; + font-style: italic; + } + } + } + + .main-content-text { + margin-top: 1rem; + flex: 1; + min-height: unset; + + /* Affichage posts newsletter */ + &:has(table[role=presentation]) { + p:not(table[role=presentation] p):not(.maj) { + display: none; + } + table { + &[role=presentation] p { + margin: unset; + } + td { + vertical-align: top; + } + br { + display: none; + } + } + } + + > *:not(.article-field) { + font-size: 1.25rem; + } + + a { + text-decoration: underline; + } + + p { + margin-bottom: 1rem; + line-height: 1.4; + strong { + font-weight: bold; + } + em { + font-style: italic; + } + &:first-child { + margin-top: 0 !important; + } + } + + ul, ol { + line-height: 1.4; + padding-left: 0.8rem; + } + + ul { + list-style: inside "· "; + } + + ol { + list-style: inside decimal; + } + + blockquote{ + padding-left: 1rem; + margin-left: 1.5rem; + border-left: solid 1px $light-gray; + } + + p:first-of-type + .mots-cles { + margin-top: 1rem; + } + + .article-field { + font-size: 0.9rem !important; + line-height: 1.4 !important; + margin-bottom: 1.5rem !important; + i { + font-style: italic; + } + } + + .mots-cles { + margin-top: 4rem; // style par défaut = style du premier + } + .mots-cles ~ .mots-cles { + margin-top: 1rem; // les 2e et 3e ont moins d'espace + } + + .canal-u-embeds, + .youtube-embeds { + margin-top: 2rem; + display: flex; + flex-direction: column; + gap: 1.5rem; + } + + .canal-u-embed, + .video-embed { + position: relative; + width: 100%; + padding-bottom: 56.25%; // 16:9 + height: 0; + + iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } + } + + } + + + .inline-title { + text-transform: uppercase; + position: relative; + + &::after { + content: ''; + width: 100%; + height: 10px; + bottom: -1px; + left: 0; + position: absolute; + @include yellow-gradient; + } + } + + .related-posts, + .seances-section { + margin-top: 5rem; + + h3 { + font-family: $font-primary; + text-transform: uppercase; + position: relative; + display: inline-block; + margin-bottom: 2rem; + + &::after { + @include yellow-gradient-after(10px); + bottom: -10px; + } + } + } + + .related-posts { + .post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + + .post-card { + min-width: 0; + a { + text-decoration: none; + h2::after { + display: none; + } + } + } + + @media ($tablet) { + grid-template-columns: repeat(2, 1fr); + } + } + } + + .seances-list { + display: flex; + flex-direction: column; + gap: 1.5rem; + } + + .seance-header { + display: flex; + align-items: stretch; + gap: 1.2rem; + padding: 0.1rem; + background-color: $light-gray; + cursor: pointer; + transition: background-color 0.15s; + + &:hover { + background-color: $less-light-gray; + } + @media ($tablet) { + padding: 0.8rem; + } + } + + .seance-date { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 3rem; + font-family: $font-primary; + text-transform: uppercase; + line-height: 1.2; + padding: 0.4rem 0; + background: linear-gradient(to bottom, $light_light_gray 60%, $manifestations); + + &__day { + font-size: 1.4rem; + } + + &__month { + font-size: 0.85rem; + } + + &__year { + font-size: 0.75rem; + } + } + + .seance-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.2rem; + + .seance-title { + font-family: $font-heading; + font-size: 1.15rem; + line-height: 1.2; + text-decoration: none; + padding: 0.6rem 0; + @media ($tablet) { + padding: unset; + } + } + + .seance-intervenants { + font-family: $font-primary; + font-size: 0.85rem; + margin-top: 0.1rem; + color: black; + + a { + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + } + + .seance-chevron { + font-size: 1.2rem; + transition: transform 0.2s; + flex-shrink: 0; + align-self: center; + margin-right: 0.5rem; + } + + .seance-item.is-open .seance-chevron { + transform: rotate(180deg); + } + + .seance-content { + padding: 1rem 1rem 1rem 1rem; + background-color: $light-light-gray; + font-family: $font-primary; + font-size: 0.9rem; + line-height: 1.4; + + .seance-content-infos { + display: flex; + width: 100%; + justify-content: space-between; + margin-bottom: 1rem; + > .seance-content-lieu { + text-align: right; + > p { + margin: 0; + } + } + } + + p { + margin-bottom: 0.6rem; + } + .seance-images { + margin-top: 1.5rem; + margin-bottom: 0; + + figure { + @media ($tablet) { + width: 50%; + } + } + } + + .seance-extras { + display: flex; + flex-direction: column; + align-items: start; + gap: 0.8rem; + margin-top: 1.5rem; + } + + .seance-related { + margin-top: 1.5rem; + + h4 { + font-family: $font-primary; + text-transform: uppercase; + margin-bottom: 1rem; + } + + .post-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + + .post-card { + min-width: 0; + a { + text-decoration: none; + h2::after { + display: none; + } + } + } + + @media ($tablet) { + grid-template-columns: repeat(2, 1fr); + } + } + } + + @media ($tablet) { + padding: 1rem 1rem 1rem 5.5rem; + } + } +} diff --git a/scss/_typography.scss b/scss/_typography.scss new file mode 100644 index 0000000..0bfd60e --- /dev/null +++ b/scss/_typography.scss @@ -0,0 +1,69 @@ +@font-face { + font-family: 'Gelasio'; + src: url('../assets/fonts/Gelasio-Regular.woff2') format('woff2'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'NewsCycle'; + src: url('../assets/fonts/NewsCycle-Regular.woff2') format('woff2'); + font-weight: normal; + font-style: normal; +} + +body { + font-family: $font-primary; +} + +h1, h2, h3, h4, h5, h6 { + font-family: $font-heading; +} + +a, +a:active { + color: $dark-gray; + transition: color 0.2s ease-out; +} + +a:hover { + color: $less-dark-gray; +} + +p { + line-height: 1.2; +} + +.link-button { + display: inline-flex; + background-color: $light_gray; + color: $dark_gray; + padding: 0.6rem 0.7rem; + font-size: 0.9rem; + transition: background-color 0.3s ease-out; + text-decoration: none; + justify-content: center; + align-items: center; + word-break: break-all; + max-width: 100%; + + > i { + margin-right: 0.6rem; + } + + &:hover { + background-color: $less_light-gray; + } + + @media ($tablet) { + font-size: unset; + padding: 0.6rem 1rem; + } + + // Multi-word titles: break at word boundaries; only split a word mid-letter + // as a last resort when it overflows the container. + &--wrap-word { + word-break: normal; + overflow-wrap: break-word; + } +} \ No newline at end of file diff --git a/scss/_variables.scss b/scss/_variables.scss new file mode 100644 index 0000000..3db6af4 --- /dev/null +++ b/scss/_variables.scss @@ -0,0 +1,31 @@ +// Neutral colors +$light-light-gray: #fcfcfc; +$light-gray: #eeeeee; +$less-light-gray: #cccccc; +$less-less-light-gray: #bbbbbb; +$yellow: #f7ff29; +$dark-gray: #1a1a1a; +$less-dark-gray: #3e3e3e; + +// Theme colors +$laboratoire: #e0775d; +$manifestations: #7cc0c6; +$mediations: #e05680; +$publications: #46ae51; +$ressources: #bb8dd9; + +// Fonts +$font-primary: 'NewsCycle', sans-serif; +$font-heading: 'Gelasio', serif; + +// Breakpoints +$breakpoint-tablet: 768px; +$breakpoint-desktop: 1024px; +$breakpoint-large: 1440px; + +// Media queries (mobile first) +$tablet: 'min-width: #{$breakpoint-tablet}'; +$desktop: 'min-width: #{$breakpoint-desktop}'; +$large: 'min-width: #{$breakpoint-large}'; + +// Fonts sizes diff --git a/scss/style.scss b/scss/style.scss new file mode 100644 index 0000000..88bd0fd --- /dev/null +++ b/scss/style.scss @@ -0,0 +1,25 @@ +@import 'reset'; +@import 'variables'; +@import 'mixins'; +@import 'base'; +@import 'typography'; +@import 'layout'; +@import 'header'; +@import 'navigation'; +@import 'footer'; +@import 'index'; +@import 'postcard'; +@import 'category'; +@import 'filters'; +@import 'single'; +@import 'author'; +@import 'membres'; +@import 'page-laboratoire'; +@import 'search'; + +/* +Theme Name: Thalim +Author: Valentin Le Moign +Version: 1.0 +*/ + diff --git a/search.php b/search.php new file mode 100644 index 0000000..a772475 --- /dev/null +++ b/search.php @@ -0,0 +1,248 @@ +parent) + ? $active_cat_obj->parent + : $active_cat_id; +} +$context['active_rubrique'] = $active_rubrique_id; + +// Base URL for search filter links (language-aware) +$search_base = thalim_en_url( home_url('/') ); + +// Override annonces_url: rubrique reset stays on search page (no filter_cat) +$context['annonces_url'] = add_query_arg(['s' => $search_query], $search_base); + +// Base params preserved across filter links (preserves search term) +$base_filter_params = array_filter([ + 's' => $search_query, + 'axe' => $active_axe ?: null, + 'date_from' => $active_date_from ?: null, + 'date_to' => $active_date_to ?: null, +]); + +// Build tax_query +$tax_query = [ + 'relation' => 'AND', + [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => $excluded_cat_ids, + 'operator' => 'NOT IN', + ], +]; +if ($active_cat_id) { + $tax_query[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$active_cat_id], + 'include_children' => !$filter_autres, + ]; +} + +$query_args = [ + 'post_type' => 'post', + 's' => $search_query, + 'relevanssi' => true, + 'posts_per_page' => 12, + 'orderby' => 'relevance', + 'order' => 'DESC', + 'lang' => '', + 'tax_query' => $tax_query, +]; +if ($active_axe) { + $query_args['meta_query'] = [[ + 'key' => 'axes_thematiques', + 'value' => $active_axe, + 'type' => 'NUMERIC', + ]]; +} +if ($active_date_from || $active_date_to) { + $date_query = ['inclusive' => true]; + if ($active_date_from) $date_query['after'] = $active_date_from; + if ($active_date_to) $date_query['before'] = $active_date_to; + $query_args['date_query'] = [$date_query]; +} + +// Axes thématiques for filter dropdown +$axes_groups = thalim_get_axes_filter_groups(); +$current_axes = $axes_groups[0]['terms'] ?? []; +$context['filter_axes'] = $current_axes; +$context['axe_stay_on_page'] = true; + +// Rubrique/catégorie filter links (all preserve search term) +$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]); + +$filter_parents = []; +foreach ($all_cats as $cat) { + if ($cat->parent == 0) { + $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id])); + $filter_parents[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => add_query_arg($params, $search_base), + ]; + } +} +$context['filter_parents'] = $filter_parents; + +$filter_categories = []; +if ($active_rubrique_id) { + foreach ($all_cats as $cat) { + if ($cat->parent == $active_rubrique_id) { + $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id])); + $filter_categories[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => add_query_arg($params, $search_base), + ]; + } + } +} +// Add "Autres" entry if active rubrique has posts directly assigned to it +if ($active_rubrique_id && !empty($filter_categories)) { + $lang = thalim_current_language(); + $direct_check = new WP_Query([ + 'post_type' => 'post', + 'posts_per_page' => 1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'lang' => '', + 'tax_query' => [[ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$active_rubrique_id], + 'include_children' => false, + ]], + ]); + if ($direct_check->have_posts()) { + $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1])); + $filter_categories[] = [ + 'id' => 'autres', + 'name' => $lang === 'en' ? 'Other' : 'Autres', + 'slug' => 'autres', + 'link' => add_query_arg($params, $search_base), + ]; + } +} +$context['filter_categories'] = $filter_categories; + +$posts = Timber::get_posts($query_args); +$context['cards'] = thalim_get_cards_data($posts); +$context['posts'] = $posts; + +// Search users (members) by display_name +$author_cards = []; +if ( $search_query ) { + $excluded_role_ids = [ 600, 598 ]; // "À ranger", "Archive" + $user_query = new WP_User_Query([ + 'search' => '*' . $search_query . '*', + 'search_columns' => ['display_name'], + 'number' => 6, + 'orderby' => 'display_name', + 'order' => 'ASC', + 'meta_query' => [ + [ + 'key' => 'role_1', + 'value' => $excluded_role_ids, + 'compare' => 'NOT IN', + ], + ], + ]); + $lang = thalim_current_language(); + + // Direction IDs (same source as membres page and author page) + $labo_page = get_page_by_path( 'le-laboratoire' ); + $labo_directeur_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur', true ) ) : 0; + $labo_adjoint_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur_adjoint', true ) ) : 0; + + foreach ( $user_query->get_results() as $user ) { + $avatar_url = thalim_get_user_avatar_url( $user->ID ); + + $role_id = get_user_meta( $user->ID, 'role_1', true ); + $role_label = ''; + if ( $role_id ) { + $role_term = get_term( intval( $role_id ), 'role' ); + if ( $role_term && ! is_wp_error( $role_term ) ) { + $override = thalim_bilingual( get_user_meta( $user->ID, 'affichage_du_statut_1', true ) ?: '', $lang ); + $role_label = $override ?: $role_term->name; + } + } + + if ( $user->ID === $labo_directeur_id ) { + $role_label = 'Directeur' . ( $role_label ? ', ' . $role_label : '' ); + } elseif ( $user->ID === $labo_adjoint_id ) { + $role_label = 'Directeur adjoint' . ( $role_label ? ', ' . $role_label : '' ); + } + + $affiliation = get_user_meta( $user->ID, 'affiliation', true ) ?: ''; + if ( strtolower( $affiliation ) === 'autre' ) { + $affiliation = thalim_bilingual( get_user_meta( $user->ID, 'affiliation_autre', true ) ?: '', $lang ); + } + + $words = preg_split( '/\s+/', trim( $user->display_name ) ); + $initials = implode( '', array_map( fn( $w ) => mb_substr( $w, 0, 1 ), $words ) ); + + $author_cards[] = [ + 'id' => $user->ID, + 'name' => $user->display_name, + 'url' => get_author_posts_url( $user->ID ), + 'avatar_url' => $avatar_url, + 'initials' => mb_strtoupper( $initials ), + 'role_label' => $role_label, + 'affiliation' => $affiliation, + ]; + } +} +$context['author_cards'] = $author_cards; + +// Search taxonomy terms (axes thématiques + programmes de recherche) +$taxonomy_cards = []; +if ( $search_query ) { + $matching_terms = get_terms([ + 'taxonomy' => [ 'axe_thematique', 'programme_de_recherche' ], + 'hide_empty' => false, + 'name__like' => $search_query, + ]); + if ( ! is_wp_error( $matching_terms ) ) { + foreach ( $matching_terms as $term ) { + $tax_obj = get_taxonomy( $term->taxonomy ); + $taxonomy_cards[] = [ + 'name' => $term->name, + 'url' => get_term_link( $term ), + 'taxonomy_label' => $tax_obj ? $tax_obj->labels->singular_name : $term->taxonomy, + 'count' => $term->count, + ]; + } + } +} +$context['taxonomy_cards'] = $taxonomy_cards; + +Timber::render('search.twig', $context); diff --git a/single.php b/single.php new file mode 100644 index 0000000..5fd0a92 --- /dev/null +++ b/single.php @@ -0,0 +1,21 @@ +ID); + +// Card data for related posts (main + séances) +$related_cards = []; +if (!empty($context['article']['annonces_liees'])) { + $related_cards += thalim_get_cards_data($context['article']['annonces_liees']); +} +foreach (['seances_a_venir', 'seances_passees'] as $seance_group) { + foreach ($context['article'][$seance_group] as $s) { + if (!empty($s['annonces_liees'])) { + $related_cards += thalim_get_cards_data($s['annonces_liees']); + } + } +} +$context['related_cards'] = $related_cards; + +Timber::render('single.twig', $context); diff --git a/style.css b/style.css new file mode 100644 index 0000000..1f983da --- /dev/null +++ b/style.css @@ -0,0 +1,9 @@ +/* +Theme Name: Thalim +Author: THALIM — Théorie et Histoire des Arts et des Littératures de la Modernité +Description: Thème personnalisé pour le laboratoire THALIM (UMR 7172). Basé sur Timber/Twig. +Version: 1.0.0 +Requires at least: 6.0 +Requires PHP: 7.4 +Text Domain: thalim +*/ diff --git a/tag.php b/tag.php new file mode 100644 index 0000000..83f8a5f --- /dev/null +++ b/tag.php @@ -0,0 +1,2 @@ +taxonomy; + +$context['term'] = Timber::get_term($term); +$context['taxonomy_slug'] = $taxonomy; +$context['term_id'] = $term->term_id; +$context['parent_slug'] = ''; +$tax_object = get_taxonomy($taxonomy); +$context['taxonomy_label'] = $tax_object ? $tax_object->labels->singular_name : $taxonomy; + +$excluded_ids = [12, 31]; // Séance de séminaire, Non classé +if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo + +// Read filter query params +$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0; +$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : ''; +$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : ''; +$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0; +$filter_autres = isset($_GET['filter_autres']) ? 1 : 0; + +$context['active_axe'] = $active_axe; +$context['active_date_from'] = $active_date_from; +$context['active_date_to'] = $active_date_to; +$context['active_category_id'] = $filter_autres ? 'autres' : $active_cat_id; +$context['active_cat_id'] = $active_cat_id; +$context['filter_autres'] = $filter_autres; + +// Determine active rubrique from active category (parent if subcategory, itself if top-level) +$active_rubrique_id = 0; +if ($active_cat_id) { + $active_cat_obj = get_category($active_cat_id); + $active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent) + ? $active_cat_obj->parent + : $active_cat_id; +} +$context['active_rubrique'] = $active_rubrique_id; + +// Base params shared across all filter links (preserves active filters when navigating) +$base_filter_params = array_filter([ + 'axe' => $active_axe ?: null, + 'date_from' => $active_date_from ?: null, + 'date_to' => $active_date_to ?: null, +]); + +// Build tax_query — combine all clauses with AND +$tax_query = [ + 'relation' => 'AND', + // Terme de la taxonomie courante + [ + 'taxonomy' => $taxonomy, + 'field' => 'term_id', + 'terms' => [$term->term_id], + ], + // Exclure les séances de séminaire (catégorie 12) + [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [12], + 'operator' => 'NOT IN', + ], +]; +if ($active_cat_id) { + $tax_query[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$active_cat_id], + 'include_children' => !$filter_autres, + ]; +} + +// On axe_thematique pages, the current term IS the active axe (for display only — taxonomy query handles filtering) +$axe_taxonomy_mode = ($taxonomy === 'axe_thematique'); +if ($axe_taxonomy_mode) { + $active_axe = $term->term_id; + $context['active_axe'] = $active_axe; +} + +// Build remaining query args (meta/date) +$extra_query_args = []; +if ($active_axe && !$axe_taxonomy_mode) { + $extra_query_args['meta_query'] = [[ + 'key' => 'axes_thematiques', + 'value' => $active_axe, + 'type' => 'NUMERIC', + ]]; +} +if ($active_date_from || $active_date_to) { + $extra_query_args['thalim_event_date_filter'] = ['from' => $active_date_from, 'to' => $active_date_to]; +} + +// Axes thématiques filter +$axes_groups = thalim_get_axes_filter_groups(); +$current_axes = $axes_groups[0]['terms'] ?? []; +$context['filter_axes'] = $current_axes; +$context['axe_taxonomy_mode'] = $axe_taxonomy_mode; +$context['axe_stay_on_page'] = !$axe_taxonomy_mode; + +// Build rubrique/catégorie filter links pointing back to the current taxonomy URL +$current_term_url = get_term_link($term); +$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_ids]); + +$filter_parents = []; +foreach ($all_cats as $cat) { + if ($cat->parent == 0) { + $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id])); + $filter_parents[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => add_query_arg($params, $current_term_url), + ]; + } +} +$context['filter_parents'] = $filter_parents; + +$filter_categories = []; +if ($active_rubrique_id) { + foreach ($all_cats as $cat) { + if ($cat->parent == $active_rubrique_id) { + $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id])); + $filter_categories[] = [ + 'id' => $cat->term_id, + 'name' => thalim_cat_name($cat), + 'slug' => $cat->slug, + 'link' => add_query_arg($params, $current_term_url), + ]; + } + } +} +// Add "Autres" entry if active rubrique has posts directly assigned to it +if ($active_rubrique_id && !empty($filter_categories)) { + $lang = thalim_current_language(); + $direct_check = new WP_Query([ + 'post_type' => 'post', + 'posts_per_page' => 1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'lang' => '', + 'tax_query' => [ + 'relation' => 'AND', + [ + 'taxonomy' => $taxonomy, + 'field' => 'term_id', + 'terms' => [$term->term_id], + ], + [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => [$active_rubrique_id], + 'include_children' => false, + ], + ], + ]); + if ($direct_check->have_posts()) { + $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1])); + $filter_categories[] = [ + 'id' => 'autres', + 'name' => $lang === 'en' ? 'Other' : 'Autres', + 'slug' => 'autres', + 'link' => add_query_arg($params, $current_term_url), + ]; + } +} +$context['filter_categories'] = $filter_categories; + +$posts = Timber::get_posts(array_merge([ + 'post_type' => 'post', + 'tax_query' => $tax_query, + 'posts_per_page' => 12, + 'orderby' => 'date', + 'order' => 'DESC', + 'lang' => '', + 'thalim_event_date_order' => true, +], $extra_query_args)); +$context['cards'] = thalim_get_cards_data($posts); +$context['posts'] = $posts; + +// Custom Pods presentation fields (not the WP built-in description) +$tax_lang = thalim_current_language(); +$pres_fr = get_term_meta($term->term_id, 'presentation', true) ?: ''; +$pres_en = get_term_meta($term->term_id, 'presentation_en', true) ?: ''; +$context['term_presentation'] = wpautop( ( $tax_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr ); + +Timber::render('taxonomy.twig', $context); diff --git a/templates/404.twig b/templates/404.twig new file mode 100644 index 0000000..3390f93 --- /dev/null +++ b/templates/404.twig @@ -0,0 +1,21 @@ +{% extends "base.twig" %} + +{% block content %} +

    +
    +

    404

    +

    + {{ current_language == 'en' ? 'Page not found' : 'Page introuvable' }} +

    +

    + {{ current_language == 'en' + ? 'The page you are looking for does not exist or has been moved.' + : "La page que vous cherchez n'existe pas ou a été déplacée." }} +

    + + {{ current_language == 'en' ? '← Back to home' : "← Retour à l'accueil" }} + +
    +
    +
    +{% endblock %} diff --git a/templates/author.twig b/templates/author.twig new file mode 100644 index 0000000..a5ab085 --- /dev/null +++ b/templates/author.twig @@ -0,0 +1,181 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    + + + + {% if author.avatar_url %} +
    +
    + {{ author.display_name }} +
    +
    +

    {{ author.display_name }}

    + {% if author.role_label or author.role_complement or author.affiliation %} +

    + {{ author.role_label }}{% if author.role_complement %} {{ author.role_complement }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation }}{% endif %} +

    + {% endif %} +

    {{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}

    +
    +
    + {% endif %} + +
    + + {% if author.email or author.liens_externes or author.documents or author.hal_publications_url %} + + {% endif %} + +
    + + {% if not author.avatar_url %} +
    +

    {{ author.display_name }}

    + {% if author.role_label or author.role_complement or author.affiliation %} +

    + {{ author.role_label }}{% if author.role_complement %} {{ author.role_complement }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation }}{% endif %} +

    + {% endif %} +

    {{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}

    +
    + {% endif %} + + {% if current_language == 'en' and author.bio_en %} +
    {{ author.bio_en|raw }}
    + {% elseif author.bio %} +
    {{ author.bio|raw }}
    + {% endif %} + + {% if author.domaines_tags %} +

    + {{ current_language == 'en' ? 'Research areas' : 'Domaines de recherches' }} : + {% for tag in author.domaines_tags %} + {{ tag.name }}{% if not loop.last %}, {% endif %} + {% endfor %} +

    + {% endif %} + {% if current_language == 'en' and author.domaines_en %} +
    + {% if not author.domaines_tags %}Research areas :{% endif %} + {{ author.domaines_en|raw }} +
    + {% elseif author.domaines %} +
    + {% if not author.domaines_tags %}{{ current_language == 'en' ? 'Research areas' : 'Domaines de recherches' }} :{% endif %} + {{ author.domaines|raw }} +
    + {% endif %} + + {% if current_language == 'en' and author.recherches_en %} +
    + Current research : + {{ author.recherches_en|raw }} +
    + {% elseif author.recherches %} +
    + {{ current_language == 'en' ? 'Current research' : 'Recherches en cours' }} : + {{ author.recherches|raw }} +
    + {% endif %} + + {% if author.axes %} +

    + {{ current_language == 'en' ? 'Thematic axes' : 'Axes thématiques' }} : + {% for axe in author.axes %} + {{ axe.name }}{% if not loop.last %}, {% endif %} + {% endfor %} +

    + {% endif %} + + {% if author.titre_these or author.resume_these or author.resume_these_en %} +
    +

    {{ current_language == 'en' ? 'Thesis' : 'Thèse' }}

    + {% if author.titre_these %} +

    {{ author.titre_these }}

    + {% endif %} + {% if author.date_soutenance %} +

    + {{ current_language == 'en' ? 'Defended in' : 'Soutenue en' }} + {{ author.date_soutenance }} +

    + {% endif %} + {% if author.directeur_thalim or author.autre_directeur %} +

    + {{ current_language == 'en' ? 'Supervisor' : 'Direction' }} : + {% if author.directeur_thalim %} + {{ author.directeur_thalim.name }}{% if author.autre_directeur %}, {% endif %} + {% endif %} + {% if author.autre_directeur %}{{ author.autre_directeur }}{% endif %} +

    + {% endif %} + {% if current_language == 'en' and author.resume_these_en %} +
    {{ author.resume_these_en|raw }}
    + {% elseif author.resume_these %} +
    {{ author.resume_these|raw }}
    + {% endif %} +
    + {% endif %} + + {% if author_posts %} +
    + {% for group in author_posts %} +
    +

    + {{ group.cat_name }} + +

    + +
    + {% endfor %} +
    + {% endif %} + +
    +
    + +
    +
    +{% endblock %} diff --git a/templates/base.twig b/templates/base.twig new file mode 100644 index 0000000..40a7623 --- /dev/null +++ b/templates/base.twig @@ -0,0 +1,21 @@ + + + + + + {{ function('wp_head') }} + + + {% include 'partials/header.twig' %} + +
    +
    + {% block content %}{% endblock %} +
    +
    + + {% include 'partials/footer.twig' %} + + {{ function('wp_footer') }} + + \ No newline at end of file diff --git a/templates/category.twig b/templates/category.twig new file mode 100644 index 0000000..f12510b --- /dev/null +++ b/templates/category.twig @@ -0,0 +1,132 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    +
    +
    + +

    + {%- if is_direct -%} + {{ current_language == 'en' ? 'Other' : 'Autres' }} {{ (category|cat_name)|lower }} + {%- else -%} + {{ category|cat_name }} + {%- endif -%} +

    + {% if term_presentation %} +
    {{ term_presentation|raw }}
    + {% endif %} +
    + {% include 'partials/category-filters.twig' %} + + + {# Agenda view — shared by parent and leaf categories #} +
    +

    Agenda

    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +{% endblock %} diff --git a/templates/index.twig b/templates/index.twig new file mode 100644 index 0000000..bf703d3 --- /dev/null +++ b/templates/index.twig @@ -0,0 +1,135 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    + +

    + {{ gc.presentation }} +

    +

    + {{ gc.presentation_detail }} +

    + + {{ current_language == 'fr' ? 'En savoir plus' : 'Learn more' }} + +
    +
    +
    + + {% include 'partials/swiper-section.twig' with { + section_posts: annonces, + section_cards: annonces_cards, + section_title: current_language == 'en' ? 'Announcements' : 'Annonces', + all_link: annonces_link, + all_label: current_language == 'en' ? 'All announcements' : 'Toutes les annonces' + } %} + + {% if messages_labo or agenda_items %} +
    + {% if messages_labo %} +
    +
    +

    {{ current_language == 'en' ? 'Laboratory messages' : 'Messages du laboratoire' }}

    +
    +
    + {% for message in messages_labo %} +
    + + +
    + {% endfor %} +
    + +
    + {% endif %} + {% if agenda_items %} + + {% endif %} +
    + {% endif %} + + {% include 'partials/swiper-section.twig' with { + section_posts: publications, + section_cards: publications_cards, + section_title: current_language == 'en' ? 'Books & journals' : 'Ouvrages et Revues', + all_link: publications_link, + all_label: current_language == 'en' ? 'All publications' : 'Toutes les publications' + } %} + + {% if has_tags %} +
    +
    +
    + {% endif %} + + +{% endblock %} \ No newline at end of file diff --git a/templates/page-annonces.twig b/templates/page-annonces.twig new file mode 100644 index 0000000..cf3096a --- /dev/null +++ b/templates/page-annonces.twig @@ -0,0 +1,43 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    +
    +
    +
    + +
    +

    {{ current_language == 'en' ? 'Announcements' : 'Annonces' }}

    +
    + + {% include 'partials/category-filters.twig' %} + +
    +
    + {% for post in posts %} + {% include 'partials/post-card.twig' with { post: post, card: cards[post.ID], show_category: true } %} + {% endfor %} +
    + +
    + +
    +
    +
    +
    +{% endblock %} diff --git a/templates/page-le-laboratoire.twig b/templates/page-le-laboratoire.twig new file mode 100644 index 0000000..fc9ab40 --- /dev/null +++ b/templates/page-le-laboratoire.twig @@ -0,0 +1,123 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    + + + +

    {{ post.title | bilingual(current_language) }}

    + + {% if images %} +
    + {% for img in images %} +
    + {{ img.alt }} + {% if img.title %} +
    {{ img.title }}
    + {% endif %} +
    + {% endfor %} +
    + {% endif %} + +
    + + {% if liens %} + + {% endif %} + +
    + + {% if current_language == 'en' and body_en %} + {{ body_en|raw }} + {% else %} + {{ post.content }} + {% endif %} + + {% if axes_groups %} +
    + {% for group in axes_groups %} +
    +

    + {{ group.label }} + +

    + +
    + {% endfor %} +
    + {% endif %} + + {% if partenaires_internationaux or partenaires_nationaux %} +
    +

    {{ current_language == 'en' ? 'Partner institutions' : 'Institutions partenaires' }}

    +
    + {% if partenaires_internationaux %} +
    +

    + {{ current_language == 'en' ? 'International partners' : 'Partenaires internationaux' }} + +

    + +
    + {% endif %} + {% if partenaires_nationaux %} +
    +

    + {{ current_language == 'en' ? 'National partners' : 'Partenaires nationaux' }} + +

    + +
    + {% endif %} +
    +
    + {% endif %} + + {% if bibliotheques %} +
    +

    {{ current_language == 'en' ? 'Libraries' : 'Bibliothèques' }}

    +
    + {{ bibliotheques|raw }} +
    +
    + {% endif %} + +
    +
    + +
    +
    +{% endblock %} diff --git a/templates/page-membres.twig b/templates/page-membres.twig new file mode 100644 index 0000000..1a95561 --- /dev/null +++ b/templates/page-membres.twig @@ -0,0 +1,107 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    +
    +
    +
    + +
    +

    {{ current_language == 'en' ? 'Lab members' : 'Membres du laboratoire' }}

    +
    + +
    + +
    +
    +
    + +
    +
    +

    {{ current_language == 'en' ? 'Filter by status' : 'Filtrer par statut' }}

    + +
    +
    +
    +

    {{ current_language == 'en' ? 'All statuses' : 'Tous les statuts' }}

    + +
    + +
    +
    + +
    +
    +

    {{ current_language == 'en' ? 'Search a member' : 'Rechercher un membre' }}

    + +
    + +
    + +
    + +
    + {% for group in groups %} +
    +

    + {{ group.title }} + +

    + +
    + {% endfor %} +
    +
    +
    +
    +{% endblock %} diff --git a/templates/page-programmes-de-recherche.twig b/templates/page-programmes-de-recherche.twig new file mode 100644 index 0000000..390c32d --- /dev/null +++ b/templates/page-programmes-de-recherche.twig @@ -0,0 +1,60 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    + +
    + + {% if page_edit_link %} + + {{ current_language == 'en' ? 'Edit page' : 'Éditer la page' }} + + {% endif %} +
    + +

    {{ post.title | bilingual(current_language) }}

    + +
    +
    + + {% for section in sections %} + {% if section.items %} +
    +

    {{ section.label }}

    +
    + {% for programme in section.items %} +
    +

    + {{ programme.name }} + +

    + +
    + {% endfor %} +
    +
    + {% endif %} + {% endfor %} + +
    +
    + +
    +
    +{% endblock %} diff --git a/templates/page.twig b/templates/page.twig new file mode 100644 index 0000000..7121cd7 --- /dev/null +++ b/templates/page.twig @@ -0,0 +1,30 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    + + + +

    {{ post.title | bilingual(current_language) }}

    + +
    +
    + {{ post.content }} +
    +
    + +
    +
    +{% endblock %} diff --git a/templates/partials/agenda-card.twig b/templates/partials/agenda-card.twig new file mode 100644 index 0000000..9bd8bb3 --- /dev/null +++ b/templates/partials/agenda-card.twig @@ -0,0 +1,25 @@ + +
    +
    + {{ day }}{% if day == 1 %}er{% endif %} + {{ month }} + {{ year }} +
    + {% if end_day %} + +
    + {{ end_day }} + {{ end_month }} + {{ end_year }} +
    + {% endif %} +
    +
    +
    + {% if date_label %}{{ date_label }}{% endif %} + {% if type_label %}{{ type_label }}{% endif %} + {% if lieu %}{{ lieu }}{% endif %} +
    +

    {{ post.title }}

    +
    +
    diff --git a/templates/partials/author-card.twig b/templates/partials/author-card.twig new file mode 100644 index 0000000..072c6ce --- /dev/null +++ b/templates/partials/author-card.twig @@ -0,0 +1,18 @@ + diff --git a/templates/partials/category-filters.twig b/templates/partials/category-filters.twig new file mode 100644 index 0000000..701248b --- /dev/null +++ b/templates/partials/category-filters.twig @@ -0,0 +1,171 @@ +
    + +
    + {% if filter_parents is defined and active_rubrique %} + {% for parent in filter_parents %} + {% if parent.id == active_rubrique %} + {{ parent.name }} + {% endif %} + {% endfor %} + {% endif %} + {% if filter_categories is defined and active_category_id and active_category_id != active_rubrique %} + {% for cat in filter_categories %} + {% if cat.id == active_category_id %} + {% set cat_reset_url = annonces_url %} + {% if filter_parents is defined %} + {% for parent in filter_parents %} + {% if parent.id == active_rubrique %}{% set cat_reset_url = parent.link %}{% endif %} + {% endfor %} + {% endif %} + {{ cat.name }} + {% endif %} + {% endfor %} + {% endif %} + {% if active_date_from or active_date_to %} + + {%- if active_date_from %}{{ active_date_from|date('d/m/Y') }}{% endif -%} + {{- active_date_from and active_date_to ? ' → ' : '' -}} + {%- if active_date_to %}{{ active_date_to|date('d/m/Y') }}{% endif -%} + + + {% endif %} + {% if active_axe is defined and active_axe and filter_axes is defined %} + {% for axe in filter_axes %} + {% if axe.id == active_axe %} + {{ axe.name }} + {% endif %} + {% endfor %} + {% endif %} +
    +
    +
    + {% if filter_parents is defined and filter_parents %} +
    +
    +

    {{ current_language == 'en' ? 'Filter by section' : 'Filtrer par rubrique' }}

    + {% if active_rubrique %} + + {{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }} + + {% endif %} +
    + +
    + {% endif %} + + {% if filter_categories is defined and filter_categories %} +
    +
    +

    {{ current_language == 'en' ? 'Filter by category' : 'Filtrer par catégorie' }}

    + {% if active_category_id and active_category_id != active_rubrique %} + {% for parent in filter_parents %} + {% if parent.id == active_rubrique %} + + {{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }} + + {% endif %} + {% endfor %} + {% endif %} +
    +
      + {% for cat in filter_categories %} + + {{ cat.name }} + + {% endfor %} +
    +
    + {% endif %} + +
    +
    +

    {{ current_language == 'en' ? 'Filter by date' : 'Filtrer par date' }}

    + {% if active_date_from or active_date_to %} + + {{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }} + + {% endif %} +
    +
    +
    +

    + {%- if active_date_from or active_date_to -%} + {%- if active_date_from -%}{{ active_date_from|date('d/m/Y') }}{%- endif -%} + {{- active_date_from and active_date_to ? ' → ' : '' -}} + {%- if active_date_to -%}{{ active_date_to|date('d/m/Y') }}{%- endif -%} + {%- else -%} + {{ current_language == 'en' ? 'Show all' : 'Tout afficher' }} + {%- endif -%} +

    + +
    + +
    +
    + + {% if filter_axes is defined and filter_axes %} +
    +
    +

    {{ current_language == 'en' ? 'Filter by thematic axis' : 'Filtrer par axe thématique' }}

    + {% if active_axe and not axe_taxonomy_mode %} + + {{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }} + + {% endif %} +
    +
    +
    +

    + {%- set axe_label = current_language == 'en' ? 'All axes' : 'Tous les axes' -%} + {%- for axe in filter_axes -%} + {%- if axe.id == active_axe -%} + {%- set axe_label = axe.name -%} + {%- endif -%} + {%- endfor -%} + {{ axe_label }} +

    + +
    + +
    +
    + {% endif %} +
    diff --git a/templates/partials/footer.twig b/templates/partials/footer.twig new file mode 100644 index 0000000..0aafd36 --- /dev/null +++ b/templates/partials/footer.twig @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/templates/partials/header.twig b/templates/partials/header.twig new file mode 100644 index 0000000..e157dc4 --- /dev/null +++ b/templates/partials/header.twig @@ -0,0 +1,57 @@ +
    +
    +
    + +
    +
    +
    {{ gc.umr }}
    +
    {{ gc.thalim }}
    +
    {{ gc.siecles }}
    +
    +
    +
    +
    + +
    +
    + {% if languages %} + + {% endif %} +
    +
    +
    + +
    +
    + +
    +
    + +
    + +{% include 'partials/search-panel.twig' %} + +{% include 'partials/navigation.twig' %} \ No newline at end of file diff --git a/templates/partials/navigation.twig b/templates/partials/navigation.twig new file mode 100644 index 0000000..3470fec --- /dev/null +++ b/templates/partials/navigation.twig @@ -0,0 +1,34 @@ + diff --git a/templates/partials/post-card.twig b/templates/partials/post-card.twig new file mode 100644 index 0000000..f163a83 --- /dev/null +++ b/templates/partials/post-card.twig @@ -0,0 +1,116 @@ +
    + + {% if card.card_image %} + {{ post.title|bilingual(current_language) }} + {% else %} +

    {{ post.title|bilingual(current_language) }}

    + {% endif %} +
    +
    +
    + {% set autres = post.meta('autrepersonnes') %} + {% set autres_list = autres ? autres|split(', ') : [] %} + {% set membres_count = card.card_membres|length %} + {% set total = membres_count + autres_list|length %} + {% set slots_left = 3 - membres_count %} + {% for membre in card.card_membres|slice(0, 3) %} + {{ membre.name }} + {% endfor %} + {% if slots_left > 0 and autres_list|length > 0 %} + {{ autres_list|slice(0, slots_left)|join(', ') }} + {% endif %} + {% if total > 3 %}…{% endif %} +
    + {% if show_category %} +
    + + {% if card.card_type %} + {{ card.card_type }} + {% elseif card.card_category_name and not type_only %} + {{ card.card_category_name }} + {% endif %} +
    + {% else %} + + {% endif %} +
    + {% if card.card_image %} +

    + {{ post.title|bilingual(current_language) }} +

    + {% endif %} +
    +{# +
    + {% if card.card_image %} +
    + {{ post.title }} +
    + {% endif %} + +
    +

    + {{ post.title }} +

    + + {% if post.meta('sous-titre') %} +

    {{ post.meta('sous-titre') }}

    + {% endif %} + + + + {% if card.card_membres is not empty or post.meta('autrepersonnes') %} +
    + {% for name in card.card_membres %} + {{ name }} + {% endfor %} + {% if post.meta('autrepersonnes') %} + {{ post.meta('autrepersonnes') }} + {% endif %} +
    + {% endif %} + + {% if post.meta('fonction_auteur') %} + {{ post.meta('fonction_auteur') }} + {% endif %} + + {% if post.meta('editeur') %} + {{ post.meta('editeur') }} + {% endif %} + + {% if post.meta('journal') %} + {{ post.meta('journal') }} + {% endif %} + + {% if card.card_axes is not empty %} +
    + {% for axe in card.card_axes %} + {{ axe }} + {% endfor %} +
    + {% endif %} + + {% if card.card_etiquettes is not empty %} +
    + {% for tag in card.card_etiquettes %} + {{ tag }} + {% endfor %} +
    + {% endif %} + +
    + {% if post.meta('lien_externe_1') %} + + {{ post.meta('titre_du_lien_externe_1') ?: post.meta('lien_externe_1') }} + + {% endif %} + + {% if post.meta('hal_url') %} + + HAL + + {% endif %} +
    +
    +
    + #} \ No newline at end of file diff --git a/templates/partials/search-panel.twig b/templates/partials/search-panel.twig new file mode 100644 index 0000000..c126254 --- /dev/null +++ b/templates/partials/search-panel.twig @@ -0,0 +1,17 @@ +
    +
    +

    {{ current_language == 'en' ? 'Search the site' : 'Rechercher sur le site' }}

    +

    {{ current_language == 'en' ? 'Search for an event, a lab member, a publication…' : 'Rechercher un événement, un membre du laboratoire, un ouvrage…' }}

    + +
    +
    diff --git a/templates/partials/swiper-section.twig b/templates/partials/swiper-section.twig new file mode 100644 index 0000000..096eddd --- /dev/null +++ b/templates/partials/swiper-section.twig @@ -0,0 +1,27 @@ +{% if section_posts %} +
    +
    +

    {{ section_title }}

    +
    +
    +
    + +
    +
    +
    + {% for post in section_posts %} +
    + {% include 'partials/post-card.twig' with { post: post, card: section_cards[post.ID], show_category: true } %} +
    + {% endfor %} +
    +
    +
    + +
    +
    + +
    +{% endif %} diff --git a/templates/search.twig b/templates/search.twig new file mode 100644 index 0000000..f075089 --- /dev/null +++ b/templates/search.twig @@ -0,0 +1,92 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    +
    +
    +
    + +
    +

    {{ current_language == 'en' ? 'Search the site' : 'Recherche sur le site' }}

    +
    + +
    +

    {{ current_language == 'en' ? 'Search for an event, a lab member, a publication…' : 'Rechercher un événement, un membre du laboratoire, un ouvrage…' }}

    + +
    + + {% if author_cards is not empty %} +
    +

    {{ current_language == 'en' ? 'Lab members' : 'Membres du laboratoire' }}

    +
    + {% for author in author_cards %} + {% include 'partials/author-card.twig' %} + {% endfor %} +
    +
    + {% endif %} + + {% if taxonomy_cards is not empty %} +
    +

    {{ current_language == 'en' ? 'Axes & research programs' : 'Axes & programmes de recherche' }}

    + +
    + {% endif %} + + {% include 'partials/category-filters.twig' %} + +
    + {% if posts is not empty %} +
    + {% for post in posts %} + {% include 'partials/post-card.twig' with { post: post, card: cards[post.ID], show_category: true } %} + {% endfor %} +
    +
    + + {% else %} +

    + {{ current_language == 'en' ? 'No results for' : 'Aucun résultat pour' }} « {{ search_query }} ». +

    + {% endif %} +
    +
    +
    +
    +{% endblock %} diff --git a/templates/single.twig b/templates/single.twig new file mode 100644 index 0000000..c37fb8c --- /dev/null +++ b/templates/single.twig @@ -0,0 +1,487 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    +
    + + {% if post.edit_link %} + + {{ current_language == 'en' ? 'Edit post' : 'Éditer l\'annonce' }} + + {% endif %} +
    + +

    +

    {{ post.title | bilingual(current_language) }}

    + {% if article.sous_titre %} +

    {{ article.sous_titre }}

    + {% endif %} +

    + + {% if article.type_label %} + {{ article.type_label }} + {% endif %} + + {% set landscape_images = [] %} + {% set portrait_images = [] %} + {% for img in article.images %} + {% if img.portrait %} + {% set portrait_images = portrait_images|merge([img]) %} + {% else %} + {% set landscape_images = landscape_images|merge([img]) %} + {% endif %} + {% endfor %} + {% if landscape_images %} + {% if landscape_images|length > 1 %} +
    +
    +
    + {% for img in landscape_images %} +
    +
    + {{ img.alt }} + {% set legend = article.show_image_titles and img.title ? img.title : img.caption %} + {% if legend %} +
    {{ legend }}
    + {% endif %} +
    +
    + {% endfor %} +
    +
    +
    +
    + {% else %} +
    + {% for img in landscape_images %} +
    + {{ img.alt }} + {% set legend = article.show_image_titles and img.title ? img.title : img.caption %} + {% if legend %} +
    {{ legend }}
    + {% endif %} +
    + {% endfor %} +
    + {% endif %} + {% endif %} + +
    + {% if portrait_images or article.date_de_debut or article.date_de_fin or article.datetime + or article.lieu|trim or article.adresse|trim + or article.documents or article.liens_externes or article.hal_url or article.hal_file + or article.reference_bibliographique %} + + {% endif %} + +
    + {% if article.membres is not empty or article.autrepersonnes %} +

    + {% if article.fonction_label %} + {{ article.fonction_label }} : + {% endif %} + {% for m in article.membres %} + {{ m.name }}{% if not loop.last %}, {% endif %} + {% endfor %} + {% if article.autrepersonnes %}{% if article.membres is not empty %}, {% endif %}{{ article.autrepersonnes }}{% endif %} +

    + {% endif %} + + {% if article.autre_membres is not empty or article.autre_autrepersonnes %} +

    + {% if article.autre_fonction_label %} + {{ article.autre_fonction_label }} : + {% endif %} + {% for m in article.autre_membres %} + {{ m.name }}{% if not loop.last %}, {% endif %} + {% endfor %} + {% if article.autre_autrepersonnes %}{% if article.autre_membres is not empty %}, {% endif %}{{ article.autre_autrepersonnes }}{% endif %} +

    + {% endif %} + + {% if article.editeur %} +

    + {{ current_language == 'en' ? 'Publisher' : 'Éditeur' }} : + {{ article.editeur }} +

    + {% endif %} + + {% if article.journal %} +

    + Journal : + {{ article.journal }} +

    + {% endif %} + + {% if current_language == 'en' and article.body_en %} + {{ article.body_en|raw }} + {% else %} + {{ post.content }} + {% endif %} + + {% if article.canal_u %} +
    + {% for embed_url in article.canal_u %} +
    + +
    + {% endfor %} +
    + {% endif %} + + {% if article.youtube %} +
    + {% for embed_url in article.youtube %} +
    + +
    + {% endfor %} +
    + {% endif %} + + {% if article.axes %} +

    + {{ current_language == 'en' ? 'Thematic axes' : 'Axes thématiques' }} : + {% for kw in article.axes %} + {{ kw.name }}{% if not loop.last %}, {% endif %} + {% endfor %} +

    + {% endif %} + + {% if article.programmes %} +

    + {{ current_language == 'en' ? 'Research programmes' : 'Programmes de recherche' }} : + {% for kw in article.programmes %} + {{ kw.name }}{% if not loop.last %}, {% endif %} + {% endfor %} +

    + {% endif %} + + {% if article.etiquettes %} +

    + {{ current_language == 'en' ? 'Keywords' : 'Mots-clés' }} : + {% for kw in article.etiquettes %} + {{ kw.name }}{% if not loop.last %}, {% endif %} + {% endfor %} +

    + {% endif %} + + {% if article.annonces_liees is not empty %} + + {% endif %} + + {% if article.seances_a_venir is not empty %} +
    +

    {{ current_language == 'en' ? 'Upcoming sessions' : 'Séances à venir' }}

    +
    + {% for s in article.seances_a_venir %} +
    +
    +
    + {{ s.day }} + {{ s.month }} + {% if s.year %}{{ s.year }}{% endif %} +
    +
    +

    {{ s.post.title | bilingual(current_language) }}

    + {% if s.intervenants is not empty or s.autrepersonnes %} + + {% for i in s.intervenants %} + {{ i.name }}{% if not loop.last %}, {% endif %} + {% endfor %} + {% if s.autrepersonnes %}{% if s.intervenants is not empty %}, {% endif %}{{ s.autrepersonnes }}{% endif %} + + {% endif %} +
    + +
    + +
    + {% endfor %} +
    +
    + {% endif %} + + {% if article.seances_passees is not empty %} +
    +

    {{ current_language == 'en' ? 'Past sessions' : 'Séances passées' }}

    +
    + {% for s in article.seances_passees %} +
    +
    +
    + {{ s.day }} + {{ s.month }} + {% if s.year %}{{ s.year }}{% endif %} +
    +
    +

    {{ s.post.title | bilingual(current_language) }}

    + {% if s.intervenants is not empty or s.autrepersonnes %} + + {% for i in s.intervenants %} + {{ i.name }}{% if not loop.last %}, {% endif %}{% endfor %}{% if s.autrepersonnes %}{% if s.intervenants is not empty %}, {% endif %}{{ s.autrepersonnes }}{% endif %} + + {% endif %} +
    + +
    + +
    + {% endfor %} +
    +
    + {% endif %} + +

    {{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ post.modified_date('d/m/Y') ?: post.date('d/m/Y') }}

    +
    +
    + +
    +
    +{% endblock %} diff --git a/templates/taxonomy.twig b/templates/taxonomy.twig new file mode 100644 index 0000000..5ccc053 --- /dev/null +++ b/templates/taxonomy.twig @@ -0,0 +1,49 @@ +{% extends "base.twig" %} + +{% block content %} +
    +
    +
    +
    +
    + +
    +

    {{ term.name | bilingual(current_language) }}

    + {% if term_presentation %} +
    {{ term_presentation|raw }}
    + {% endif %} +
    + + {% include 'partials/category-filters.twig' %} + +
    +
    + {% for post in posts %} + {% include 'partials/post-card.twig' with { post: post, card: cards[post.ID], show_category: true } %} + {% endfor %} +
    + +
    + +
    +
    +
    +
    +{% endblock %}