diff --git a/web/themes/custom/leshed/assets/js/main.js b/web/themes/custom/leshed/assets/js/main.js
index 27952fb..c68b692 100644
--- a/web/themes/custom/leshed/assets/js/main.js
+++ b/web/themes/custom/leshed/assets/js/main.js
@@ -59,46 +59,119 @@ import Splitting from "splitting";
return Math.floor(Math.random() * (max - min)) + min;
}
- function initTitles(){
- let titles = document.querySelectorAll('#block-leshed-identitedusite>a, article.node-type-projet>h2');
+ // Rendu typographique des titres. Police Epilogue = axe variable `wght`
+ // (100–900) uniquement ; l'italique est un fichier séparé (font-style).
+ const TITLE_STYLE = {
+ // 3 groupes de graisse : thin = majorité (défaut), medium = quelques,
+ // bold = les pics. Les fines ne sont pas comptées (c'est le reste).
+ wghtThin: { min: 100, max: 200 }, // majorité (défaut)
+ wghtMedium: { min: 200, max: 400 }, // quelques
+ wghtBold: { min: 800, max: 900 }, // pics
+ boldRatio: 0.28, // proportion de pics gras par mot
+ mediumRatio: 0.12, // proportion de medium par mot
+ italicOnBold: 0.6, // proba d'italique sur un pic
+ italicOnMedium: 0.4, // proba d'italique sur un medium
+ italicOnThin: 0.30, // proba d'italique sur une fine
+ };
+ function initTitles() {
+ const titles = document.querySelectorAll('#block-leshed-identitedusite>a, article.node-type-projet>header>h2');
for (const txt of titles) {
- console.log(txt.innerText, '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -');
- let splitted = Splitting({ target: txt, by: 'chars'});
- console.log("splitted", splitted);
-
+ const splitted = Splitting({ target: txt, by: 'chars' });
for (const word of splitted[0].words) {
- let chars = word.getElementsByClassName('char');
- console.log("chars", chars);
+ styleWordChars(Array.from(word.getElementsByClassName('char')));
+ }
+ }
+ }
- for (const char of chars) {
- console.log(char.innerText);
+ /**
+ * Tire k positions réparties régulièrement dans [0, n[ : le mot est découpé
+ * en k cases contiguës, une position au hasard par case (aléa contrôlé, pas
+ * de paquets). Les positions déjà prises par un autre groupe sont évitées.
+ */
+ function spreadPositions(n, k, exclude = new Set()) {
+ const set = new Set();
+ for (let i = 0; i < k; i++) {
+ const start = Math.floor((i * n) / k);
+ const end = Math.floor(((i + 1) * n) / k); // borne exclue
+ const span = Math.max(1, end - start);
+ let pos = randomInt(start, start + span);
+ let guard = 0;
+ while ((exclude.has(pos) || set.has(pos)) && guard < span) {
+ pos = start + ((pos - start + 1) % span);
+ guard++;
+ }
+ set.add(pos);
+ }
+ return set;
+ }
- // Weight
- let wght = randomInt(150, 600);//200 + Math.random() * 800;
+ /**
+ * Applique graisse + italique caractère par caractère.
+ *
+ * Majorité de fines ; quelques medium et quelques pics gras, chacun réparti
+ * régulièrement dans le mot. L'italique est corrélé à la graisse.
+ */
+ function styleWordChars(chars) {
+ const n = chars.length;
+ if (!n) return;
- // Ital
- let ital = Math.random() > 0.7 ? 1 : 0;
- // char.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${ital}`;
- char.style.fontVariationSettings = `'wght' ${wght}`;
- char.style.fontStyle = ital ? 'italic' : 'normal';
+ // Accents répartis régulièrement : d'abord les pics gras, puis les medium
+ // (en évitant les positions déjà grasses). Le reste sera fin.
+ const bold = spreadPositions(n, Math.round(n * TITLE_STYLE.boldRatio));
+ const medium = spreadPositions(n, Math.round(n * TITLE_STYLE.mediumRatio), bold);
- // // Letter spacing
- // let wdth_wght_ratio = wdth*wght / 10000;
- // console.log('wdth_wght_ratio',wdth_wght_ratio);
+ // 1) Décide graisse + italique pour chaque caractère.
+ const decisions = chars.map((char, i) => {
+ let range, pItal;
+ if (bold.has(i)) {
+ range = TITLE_STYLE.wghtBold;
+ pItal = TITLE_STYLE.italicOnBold;
+ }
+ else if (medium.has(i)) {
+ range = TITLE_STYLE.wghtMedium;
+ pItal = TITLE_STYLE.italicOnMedium;
+ }
+ else {
+ range = TITLE_STYLE.wghtThin;
+ pItal = TITLE_STYLE.italicOnThin;
+ }
+ return {
+ char,
+ wght: randomInt(range.min, range.max + 1),
+ ital: Math.random() < pItal,
+ };
+ });
- // char.style.letterSpacing = wdth_wght_ratio < 1 ? "0.5em" : ital ? "0.05em" : 0;
- // if (ital) {
- // let prev_char = char.previousSibling;
- // if (prev_char) {
- // char.style.letterSpacing = prev_char.style.letterSpacing = "0.03em";
- // }
- // }
+ // 2) Au moins une italique par mot : si aucune, on en force une, de
+ // préférence sur un pic gras ou un medium, sinon sur une lettre au hasard.
+ if (!decisions.some((d) => d.ital)) {
+ const accents = [...bold, ...medium];
+ const idx = accents.length ? accents[randomInt(0, accents.length)] : randomInt(0, n);
+ decisions[idx].ital = true;
+ }
+
+ // 3) Pas plus de 2 italiques consécutives : on casse les séries de 3+.
+ let run = 0;
+ for (const d of decisions) {
+ if (d.ital) {
+ run += 1;
+ if (run > 2) {
+ d.ital = false;
+ run = 0;
}
}
+ else {
+ run = 0;
+ }
+ }
-
-
+ // 4) Applique.
+ for (const { char, wght, ital } of decisions) {
+ char.style.fontVariationSettings = `'wght' ${wght}`;
+ char.style.fontStyle = ital ? 'italic' : 'normal';
+ char.classList.toggle('italic', ital);
+ char.classList.toggle('normal', !ital);
}
}
diff --git a/web/themes/custom/leshed/assets/scss/main.scss b/web/themes/custom/leshed/assets/scss/main.scss
index a65d642..9e7aa52 100644
--- a/web/themes/custom/leshed/assets/scss/main.scss
+++ b/web/themes/custom/leshed/assets/scss/main.scss
@@ -72,7 +72,7 @@ header[role="banner"]{
display: flex;
justify-content:space-between;
align-items: center;
- padding: 0.2em 2em;
+ padding: 0.2em 0;
}
#block-leshed-identitedusite{
@@ -170,3 +170,76 @@ header[role="banner"]{
}
} // end of header[role="banner"]{
+
+// _____ _____ __ ____ ___ _ ____
+// |_ _/ _ \ \ / / / ___/ _ \| | / ___|
+// | || | | \ \ /\ / / | | | | | | | \___ \
+// | || |_| |\ V V / | |__| |_| | |___ ___) |
+// |_| \___/ \_/\_/ \____\___/|_____|____/
+
+div.layout.layout--twocol-section--50-50{
+ flex-wrap: nowrap;
+ column-gap: 1em;
+ div.layout__region{
+ .views-row{
+ // border: 1px solid red;
+ // box-sizing: border-box;
+ margin-bottom: 1.5em;
+ article.node-type-projet{
+ >header{
+ >h2{
+ margin: 0;
+ font-size: 5em;
+ text-transform:lowercase;
+ filter: drop-shadow(0 0 5px #fff) drop-shadow(0 0 15px #fff);
+ span.word{
+ hyphens: auto;
+
+ // $bgcolor:rgba(255,255,255,0.8);
+ // // background-color: $bgcolor;
+ // border-radius: 0.8em;
+ // box-shadow: 0 0 20px #fff;
+
+ span.char{
+ letter-spacing:-0.2em;
+ }
+ }
+ // span.whitespace{
+ // letter-spacing: 0.5em;
+ // }
+ }
+ }
+ >div{
+ // margin-top: 3em;
+ }
+ // only with images, title overlap the image
+ &.has-image{
+ >header{
+ >h2{
+ position: relative;
+ max-height: 1em;
+ overflow: visible;
+ z-index: 10;
+ // text-shadow: 0 0 10px #fff;
+ // box-shadow: 0 0 15px #f00;
+ // filter: drop-shadow(0 0 5px #fff);
+ }
+ div.field_images{
+ position:relative;
+ // width: 100%;
+ z-index: 5;
+ a{
+ display:block;
+ img{
+ width:100%;
+ height: auto;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+ }
+}
diff --git a/web/themes/custom/leshed/leshed.theme b/web/themes/custom/leshed/leshed.theme
index 76ed4c4..7cf0453 100644
--- a/web/themes/custom/leshed/leshed.theme
+++ b/web/themes/custom/leshed/leshed.theme
@@ -74,15 +74,25 @@ function parse_menu_item(&$items, $key){
* Implements hook_preprocess_HOOK() for node.html.twig.
*/
function leshed_preprocess_node(&$variables) {
- $node_type = $variables['node']->getType();
+ /** @var \Drupal\node\Entity\Node $node */
+ $node = $variables['node'];
+ $node_type = $node->getType();
if (!isset($variables["attributes"]['class'])) {
$variables["attributes"]['class'] = [];
}
$variables["attributes"]['class'][] = "node-type-{$node_type}";
+
+ if ($variables['view_mode'] === "teaser") {
+ /** @var \Drupal\file\Plugin\\Field\FieldType\FileFieldItemList $field_images */
+ $field_images = $node->get('field_images');
+ if (!$field_images->isEmpty()) {
+ $variables["attributes"]['class'][] = "has-image";
+ }
+ }
}
-function leshed_preprocess_contanier(&$variables) {
+function leshed_preprocess_container(&$variables) {
}
diff --git a/web/themes/custom/leshed/templates/node--personne--teaser.html.twig b/web/themes/custom/leshed/templates/node--personne--teaser.html.twig
index e69de29..1e0ba9e 100644
--- a/web/themes/custom/leshed/templates/node--personne--teaser.html.twig
+++ b/web/themes/custom/leshed/templates/node--personne--teaser.html.twig
@@ -0,0 +1,89 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a node.
+ *
+ * Available variables:
+ * - node: The node entity with limited access to object properties and methods.
+ * Only method names starting with "get", "has", or "is" and a few common
+ * methods such as "id", "label", and "bundle" are available. For example:
+ * - node.getCreatedTime() will return the node creation timestamp.
+ * - node.hasField('field_example') returns TRUE if the node bundle includes
+ * field_example. (This does not indicate the presence of a value in this
+ * field.)
+ * - node.isPublished() will return whether the node is published or not.
+ * Calling other methods, such as node.delete(), will result in an exception.
+ * See \Drupal\node\Entity\Node for a full list of public properties and
+ * methods for the node object.
+ * - label: (optional) The title of the node.
+ * - content: All node items. Use {{ content }} to print them all,
+ * or print a subset such as {{ content.field_example }}. Use
+ * {{ content|without('field_example') }} to temporarily suppress the printing
+ * of a given child element.
+ * - author_picture: The node author user entity, rendered using the "compact"
+ * view mode.
+ * - metadata: Metadata for this node.
+ * - date: (optional) Themed creation date field.
+ * - author_name: (optional) Themed author name field.
+ * - url: Direct URL of the current node.
+ * - display_submitted: Whether submission information should be displayed.
+ * - attributes: HTML attributes for the containing element.
+ * The attributes.class element may contain one or more of the following
+ * classes:
+ * - node: The current template type (also known as a "theming hook").
+ * - node--type-[type]: The current node type. For example, if the node is an
+ * "Article" it would result in "node--type-article". Note that the machine
+ * name will often be in a short form of the human readable label.
+ * - node--view-mode-[view_mode]: The View Mode of the node; for example, a
+ * teaser would result in: "node--view-mode-teaser", and
+ * full: "node--view-mode-full".
+ * The following are controlled through the node publishing options.
+ * - node--promoted: Appears on nodes promoted to the front page.
+ * - node--sticky: Appears on nodes ordered above other non-sticky nodes in
+ * teaser listings.
+ * - node--unpublished: Appears on unpublished nodes visible only to site
+ * admins.
+ * - title_attributes: Same as attributes, except applied to the main title
+ * tag that appears in the template.
+ * - content_attributes: Same as attributes, except applied to the main
+ * content tag that appears in the template.
+ * - author_attributes: Same as attributes, except applied to the author of
+ * the node tag that appears in the template.
+ * - title_prefix: Additional output populated by modules, intended to be
+ * displayed in front of the main title tag that appears in the template.
+ * - title_suffix: Additional output populated by modules, intended to be
+ * displayed after the main title tag that appears in the template.
+ * - view_mode: View mode; for example, "teaser" or "full".
+ * - teaser: Flag for the teaser state. Will be true if view_mode is 'teaser'.
+ * - page: Flag for the full page state. Will be true if view_mode is 'full'.
+ *
+ * @see template_preprocess_node()
+ *
+ * @ingroup themeable
+ */
+#}
+
+
+ {{ title_prefix }}
+ {% if label and not page %}
+
+
+
diff --git a/web/themes/custom/leshed/templates/node--projet--teaser.html.twig b/web/themes/custom/leshed/templates/node--projet--teaser.html.twig
new file mode 100644
index 0000000..5d37cfa
--- /dev/null
+++ b/web/themes/custom/leshed/templates/node--projet--teaser.html.twig
@@ -0,0 +1,89 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a node.
+ *
+ * Available variables:
+ * - node: The node entity with limited access to object properties and methods.
+ * Only method names starting with "get", "has", or "is" and a few common
+ * methods such as "id", "label", and "bundle" are available. For example:
+ * - node.getCreatedTime() will return the node creation timestamp.
+ * - node.hasField('field_example') returns TRUE if the node bundle includes
+ * field_example. (This does not indicate the presence of a value in this
+ * field.)
+ * - node.isPublished() will return whether the node is published or not.
+ * Calling other methods, such as node.delete(), will result in an exception.
+ * See \Drupal\node\Entity\Node for a full list of public properties and
+ * methods for the node object.
+ * - label: (optional) The title of the node.
+ * - content: All node items. Use {{ content }} to print them all,
+ * or print a subset such as {{ content.field_example }}. Use
+ * {{ content|without('field_example') }} to temporarily suppress the printing
+ * of a given child element.
+ * - author_picture: The node author user entity, rendered using the "compact"
+ * view mode.
+ * - metadata: Metadata for this node.
+ * - date: (optional) Themed creation date field.
+ * - author_name: (optional) Themed author name field.
+ * - url: Direct URL of the current node.
+ * - display_submitted: Whether submission information should be displayed.
+ * - attributes: HTML attributes for the containing element.
+ * The attributes.class element may contain one or more of the following
+ * classes:
+ * - node: The current template type (also known as a "theming hook").
+ * - node--type-[type]: The current node type. For example, if the node is an
+ * "Article" it would result in "node--type-article". Note that the machine
+ * name will often be in a short form of the human readable label.
+ * - node--view-mode-[view_mode]: The View Mode of the node; for example, a
+ * teaser would result in: "node--view-mode-teaser", and
+ * full: "node--view-mode-full".
+ * The following are controlled through the node publishing options.
+ * - node--promoted: Appears on nodes promoted to the front page.
+ * - node--sticky: Appears on nodes ordered above other non-sticky nodes in
+ * teaser listings.
+ * - node--unpublished: Appears on unpublished nodes visible only to site
+ * admins.
+ * - title_attributes: Same as attributes, except applied to the main title
+ * tag that appears in the template.
+ * - content_attributes: Same as attributes, except applied to the main
+ * content tag that appears in the template.
+ * - author_attributes: Same as attributes, except applied to the author of
+ * the node tag that appears in the template.
+ * - title_prefix: Additional output populated by modules, intended to be
+ * displayed in front of the main title tag that appears in the template.
+ * - title_suffix: Additional output populated by modules, intended to be
+ * displayed after the main title tag that appears in the template.
+ * - view_mode: View mode; for example, "teaser" or "full".
+ * - teaser: Flag for the teaser state. Will be true if view_mode is 'teaser'.
+ * - page: Flag for the full page state. Will be true if view_mode is 'full'.
+ *
+ * @see template_preprocess_node()
+ *
+ * @ingroup themeable
+ */
+#}
+
+{#
+{% if content.field_images.getvalue|length %}
+ {% set attributes = attributes.addClass('has-image') %}
+{% endif %} #}
+
+
+
+ {# {{ title_prefix }} #}
+ {# {% if label and not page %} #}
+
+