Ver código fonte

Merge pull request #5681 from unacms/frontend-a18y

Site accessibility: every audited page reaches a Lighthouse score of 100
Aleksandr Trofimov 1 mês atrás
pai
commit
4b9667ddce
100 arquivos alterados com 554 adições e 220 exclusões
  1. 7 1
      inc/js/classes/BxDolMenuMoreAuto.js
  2. 16 5
      inc/js/editor.quill.js
  3. 34 3
      inc/js/functions.js
  4. 1 1
      inc/js/jquery.dolPopup.js
  5. 16 3
      inc/js/jquery.webForms.js
  6. 2 0
      modules/base/general/classes/BxBaseModGeneralTemplate.php
  7. 2 2
      modules/base/general/template/breadcrumb.html
  8. 1 1
      modules/base/general/template/uploader_button_html5_attach.html
  9. 2 2
      modules/base/groups/template/cover_block.html
  10. 2 2
      modules/base/groups/template/unit.html
  11. 1 1
      modules/base/groups/template/unit_live_search.html
  12. 2 0
      modules/base/profile/classes/BxBaseModProfileTemplate.php
  13. 2 2
      modules/base/profile/template/cover.html
  14. 2 2
      modules/base/profile/template/cover_block.html
  15. 1 1
      modules/base/profile/template/image_popup.html
  16. 2 2
      modules/base/profile/template/unit.html
  17. 1 1
      modules/base/profile/template/unit_live_search.html
  18. 2 2
      modules/base/profile/template/unit_with_cover.html
  19. 1 1
      modules/base/profile/template/unit_wo_info.html
  20. 1 1
      modules/base/profile/template/unit_wo_info_links.html
  21. 1 1
      modules/base/profile/template/unit_wo_links.html
  22. 27 2
      modules/boonex/artificer/data/template/bx_timeline/css/view.css
  23. 2 2
      modules/boonex/artificer/data/template/bx_timeline/item_outline.html
  24. 2 2
      modules/boonex/artificer/data/template/bx_timeline/item_timeline.html
  25. 2 2
      modules/boonex/artificer/data/template/mod_groups/cover.html
  26. 2 2
      modules/boonex/artificer/data/template/mod_groups/cover_block.html
  27. 2 2
      modules/boonex/artificer/data/template/mod_profile/cover.html
  28. 2 2
      modules/boonex/artificer/data/template/mod_profile/cover_block.html
  29. 2 2
      modules/boonex/artificer/data/template/system/_block_explore.html
  30. 2 2
      modules/boonex/artificer/data/template/system/_block_splash.html
  31. 27 6
      modules/boonex/artificer/data/template/system/_header_js_custom.html
  32. 1 1
      modules/boonex/artificer/data/template/system/block_join.html
  33. 1 1
      modules/boonex/artificer/data/template/system/block_login.html
  34. 4 2
      modules/boonex/artificer/data/template/system/cover.html
  35. 2 2
      modules/boonex/artificer/data/template/system/cover_home.html
  36. 10 3
      modules/boonex/artificer/data/template/system/css/forms.css
  37. 1 1
      modules/boonex/artificer/data/template/system/menu_main_submenu.html
  38. 1 1
      modules/boonex/artificer/data/template/system/menu_main_submenu_more_auto.html
  39. 1 1
      modules/boonex/artificer/data/template/system/menu_meta_item.html
  40. 1 1
      modules/boonex/artificer/data/template/system/profile_avatar.html
  41. 1 1
      modules/boonex/artificer/data/template/system/profile_stats.html
  42. 3 3
      modules/boonex/artificer/data/template/system/pt_application.html
  43. 1 0
      modules/boonex/artificer/data/template/system/scripts/BxTemplMenuSidebarSite.php
  44. 15 0
      modules/boonex/artificer/data/template/system/scripts/BxTemplMenuToolbar.php
  45. 1 1
      modules/boonex/artificer/data/template/system/sidebar_account.html
  46. 2 1
      modules/boonex/artificer/data/template/system/sidebar_site.html
  47. 0 0
      modules/boonex/artificer/install/sql/enable.sql
  48. 2 2
      modules/boonex/courses/template/unit_personal.html
  49. 2 2
      modules/boonex/decorous/data/template/bx_organizations/cover.html
  50. 2 2
      modules/boonex/decorous/data/template/bx_persons/cover.html
  51. 6 0
      modules/boonex/english/data/langs/system/en.xml
  52. 4 0
      modules/boonex/forum/classes/BxForumGrid.php
  53. 2 0
      modules/boonex/massmailer/classes/BxMassMailerTemplate.php
  54. 1 0
      modules/boonex/tasks/classes/BxTasksTemplate.php
  55. 1 0
      modules/boonex/timeline/classes/BxTimelineTemplate.php
  56. 8 2
      modules/boonex/timeline/template/css/view.css
  57. 1 1
      modules/boonex/timeline/template/item_outline.html
  58. 1 1
      modules/boonex/timeline/template/item_timeline.html
  59. 1 1
      modules/boonex/timeline/template/load_more.html
  60. 1 1
      modules/boonex/timeline/template/load_more_auto.html
  61. 0 3
      studio/template/scripts/BxBaseStudioMenu.php
  62. 1 1
      template/_page_content_beg.html
  63. 1 1
      template/_page_submenu.html
  64. 4 3
      template/_page_toolbar_search.html
  65. 1 1
      template/comments_uploader_bs.html
  66. 1 1
      template/connection_counter.html
  67. 4 2
      template/cover.html
  68. 1 1
      template/cover_empty.html
  69. 2 1
      template/css/forms.css
  70. 35 0
      template/css/fullcalendar.css
  71. 45 6
      template/css/tailwind.css
  72. 1 1
      template/designbox_1.html
  73. 1 1
      template/designbox_11.html
  74. 1 1
      template/designbox_14.html
  75. 1 1
      template/designbox_4.html
  76. 1 1
      template/form_field_password.html
  77. 4 4
      template/layout_2_columns.html
  78. 8 8
      template/layout_3_columns.html
  79. 8 8
      template/layout_bar_content_bar.html
  80. 4 4
      template/layout_bar_left.html
  81. 4 4
      template/layout_bar_right.html
  82. 4 4
      template/layout_bottom_area_2_columns.html
  83. 4 4
      template/layout_top_area_2_columns.html
  84. 8 8
      template/layout_top_area_3_columns.html
  85. 8 8
      template/layout_top_area_bar_content_bar.html
  86. 4 4
      template/layout_top_area_bar_left.html
  87. 4 4
      template/layout_top_area_bar_right.html
  88. 4 4
      template/layout_topbottom_area_2_columns.html
  89. 4 4
      template/layout_topbottom_area_bar_left.html
  90. 4 4
      template/layout_topbottom_area_bar_right.html
  91. 8 8
      template/layout_topbottom_area_col1_col3_col2.html
  92. 4 4
      template/layout_topbottom_area_col1_col5.html
  93. 8 8
      template/layout_topbottom_area_col2_col5_col3.html
  94. 1 1
      template/pt_application.html
  95. 15 1
      template/scripts/BxBaseCover.php
  96. 12 0
      template/scripts/BxBaseFunctions.php
  97. 19 0
      template/scripts/BxBaseGrid.php
  98. 53 11
      template/scripts/BxBaseMenu.php
  99. 1 1
      template/scripts/BxBaseMenuManage.php
  100. 15 0
      template/scripts/BxBaseMenuMoreAuto.php

+ 7 - 1
inc/js/classes/BxDolMenuMoreAuto.js

@@ -102,7 +102,13 @@ BxDolMenuMoreAuto.prototype.more = function(oElement)
             el: oElement
         }, 
         moveToDocRoot: false,
-        cssClass: 'bx-popup-menu'
+        cssClass: 'bx-popup-menu',
+        onShow: function() {
+            oElement.attr('aria-expanded', 'true');
+        },
+        onHide: function() {
+            oElement.attr('aria-expanded', 'false');
+        }
     });
 }
 

+ 16 - 5
inc/js/editor.quill.js

@@ -423,12 +423,23 @@ function bx_editor_init(oParams) {
     $(oParams.selector).next().find('button.ql-link').attr('title', _t('_sys_txt_quill_tooltip_link'));
     $(oParams.selector).next().find('button.ql-image').attr('title', _t('_sys_txt_quill_tooltip_image'));
     $(oParams.selector).next().find('button.ql-code-block').attr('title', _t('_sys_txt_quill_tooltip_code_block'));
-    $(oParams.selector).next().find('span.ql-color').attr('title', _t('_sys_txt_quill_tooltip_color'));
-    $(oParams.selector).next().find('span.ql-background').attr('title', _t('_sys_txt_quill_tooltip_background'));
-    $(oParams.selector).next().find('span.ql-align').attr('title', _t('_sys_txt_quill_tooltip_align'));
-    $(oParams.selector).next().find('span.ql-header').attr('title', _t('_sys_txt_quill_tooltip_header'));
+    $(oParams.selector).next().find('button.ql-embed').attr('title', _t('_sys_txt_quill_tooltip_embed'));
+    $(oParams.selector).next().find('button.ql-emoji').attr('title', _t('_sys_txt_quill_tooltip_emoji'));
+    $(oParams.selector).next().find('button.ql-show-html').attr('title', _t('_sys_txt_quill_tooltip_show_html'));
+    $(oParams.selector).next().find('span.ql-color .ql-picker-label').attr({'title': _t('_sys_txt_quill_tooltip_color'), 'aria-label': _t('_sys_txt_quill_tooltip_color')});
+    $(oParams.selector).next().find('span.ql-background .ql-picker-label').attr({'title': _t('_sys_txt_quill_tooltip_background'), 'aria-label': _t('_sys_txt_quill_tooltip_background')});
+    $(oParams.selector).next().find('span.ql-align .ql-picker-label').attr({'title': _t('_sys_txt_quill_tooltip_align'), 'aria-label': _t('_sys_txt_quill_tooltip_align')});
+    //--- a mini toolbar renders alignment as four separate buttons rather than one picker
+    $(oParams.selector).next().find('button.ql-align[value=""]').attr('title', _t('_sys_txt_quill_tooltip_align_left'));
+    $(oParams.selector).next().find('button.ql-align[value="center"]').attr('title', _t('_sys_txt_quill_tooltip_align_center'));
+    $(oParams.selector).next().find('button.ql-align[value="right"]').attr('title', _t('_sys_txt_quill_tooltip_align_right'));
+    $(oParams.selector).next().find('button.ql-align[value="justify"]').attr('title', _t('_sys_txt_quill_tooltip_align_justify'));
+    $(oParams.selector).next().find('span.ql-header .ql-picker-label').attr({'title': _t('_sys_txt_quill_tooltip_header'), 'aria-label': _t('_sys_txt_quill_tooltip_header')});
 
-    $('#' + oParams.name).find('.ql-editor').addClass('bx-def-vanilla-html');
+    //--- quill-mention points aria-owns at its list, but only inserts that list into the document when
+    //--- the list opens, so the reference dangles and every page with an editor reports an invalid
+    //--- ARIA value. The editor is not a combobox, so the attribute has nothing to say here.
+    $('#' + oParams.name).find('.ql-editor').addClass('bx-def-vanilla-html').removeAttr('aria-owns');
 
     if (bEmptyToolbar)
         $(oParams.selector).next().hide();

+ 34 - 3
inc/js/functions.js

@@ -501,11 +501,42 @@ function bx_menu_popup (o, e, options, vars) {
     if ('undefined' == typeof(e))
         e = window;
 
+    //--- Only a trigger that names no target of its own is this popup's disclosure. A trigger carrying
+    //--- aria-controls belongs to whatever it names - a sidebar, the search box - and that owner keeps
+    //--- its state; writing to it from here would leave it describing the wrong thing.
+    var bExpanded = e !== window && $(e).length != 0 && $(e).is('[aria-expanded]') && !$(e).is('[aria-controls]');
+    var fOnShow = options.onShow, fOnHide = options.onHide;
+
+    // a string onShow is handed back to dolPopup, which evaluates it; a string onHide has never been
+    // supported on this path, since dolPopupAjax replaces onHide before dolPopup sees it
+    var aExpanded = {};
+    if(typeof fOnShow != 'string')
+        aExpanded.onShow = function(oPopup) {
+            if(bExpanded)
+                $(e).attr('aria-expanded', 'true');
+
+            // remembered on the popup: the hide handler dolPopup keeps is the one from the first show, whichever trigger opened it since
+            oPopup.data('bx-popup-trigger', bExpanded ? $(e) : null);
+
+            if(typeof fOnShow == 'function')
+                fOnShow(oPopup);
+        };
+
+    if(typeof fOnHide != 'string')
+        aExpanded.onHide = function(oPopup) {
+            var oTrigger = oPopup.data('bx-popup-trigger');
+            if(oTrigger)
+                oTrigger.attr('aria-expanded', 'false');
+
+            if(typeof fOnHide == 'function')
+                fOnHide(oPopup);
+        };
+
     var o = $.extend({}, $.fn.dolPopupDefaultOptions, {
-        id: o, 
-        url: bx_append_url_params('menu.php', $.extend({o:o}, vars)), 
+        id: o,
+        url: bx_append_url_params('menu.php', $.extend({o:o}, vars)),
         cssClass: 'bx-popup-menu'
-    }, options);
+    }, options, aExpanded);
 
     $(e).dolPopupAjax(o);
 }

+ 1 - 1
inc/js/jquery.dolPopup.js

@@ -514,7 +514,7 @@
                 if (!$.isWindow(e[0]))
                     bx_menu_on(e, true);
 
-                $('<div id="' + sPopupId + '" style="display:none;">' + $('#bx-popup-loading').html() + '</div>').appendTo(options.parent).find(options.container).hide();
+                $('<div id="' + sPopupId + '" role="dialog" style="display:none;">' + $('#bx-popup-loading').html() + '</div>').appendTo(options.parent).find(options.container).hide();
 
                 $('#' + sPopupId).addClass($('#bx-popup-loading').attr('class'));
                 

+ 16 - 3
inc/js/jquery.webForms.js

@@ -167,15 +167,15 @@
                 $(this).parents(".bx-form-input-wrapper-password:first").removeClass(sClassFocus);
             });
 
-            $(this).find("a").on("click", function () {
+            $(this).find(".password-show, .password-hide").on("click", function () {
                 var oLink = $(this);
                 var oFld = oLink.parents(".bx-form-input-wrapper-password:first").find("input");
                 if (oLink.hasClass('password-show')) {
-                    oLink.attr('title', _t('_sys_form_input_password_hide'));
+                    oLink.attr('title', _t('_sys_form_input_password_hide')).attr('aria-label', _t('_sys_form_input_password_hide'));
                     oFld.attr("type", "text");
                 }
                 else {
-                    oLink.attr('title', _t('_sys_form_input_password_show'));
+                    oLink.attr('title', _t('_sys_form_input_password_show')).attr('aria-label', _t('_sys_form_input_password_show'));
                     oFld.attr("type", "password");
                 }
                 oLink.toggleClass('password-show password-hide');
@@ -394,6 +394,19 @@
                 }
                 
                 if(bDatePicker) {
+                    // flatpickr's altInput is the field the user actually sees and focuses, but it is a bare clone:
+                    // it copies class/placeholder/disabled/required/tabindex and no accessible name, so carry the
+                    // labelling of the original (now hidden) input over to it.
+                    var oAltInput = this._flatpickr ? this._flatpickr.altInput : null;
+                    if (oAltInput) {
+                        var aLabelAttrs = ['aria-label', 'aria-labelledby', 'aria-describedby', 'title'];
+                        for (var iLabelAttr = 0; iLabelAttr < aLabelAttrs.length; iLabelAttr++) {
+                            var sLabelValue = this.getAttribute(aLabelAttrs[iLabelAttr]);
+                            if (sLabelValue)
+                                oAltInput.setAttribute(aLabelAttrs[iLabelAttr], sLabelValue);
+                        }
+                    }
+
                     if (typeof window.glOnColorSchemeChange === 'undefined')
                         window.glOnColorSchemeChange = [];    
 

+ 2 - 0
modules/base/general/classes/BxBaseModGeneralTemplate.php

@@ -359,9 +359,11 @@ class BxBaseModGeneralTemplate extends BxDolModuleTemplate
     public function entryBreadcrumb($aContentInfo, $aTmplVarsItems = array())
     {
     	$CNF = &BxDolModule::getInstance($this->MODULE)->_oConfig->CNF;
+    	$sTitleHome = BxDolModule::getInstance($this->MODULE)->_aModule['title'];
     	return bx_is_api() ? ['id' => 2, 'type' => 'breadcrumb', 'data' => ['name' => BxDolModule::getInstance($this->MODULE)->_aModule['title'], 'url' => BxDolPermalinks::getInstance()->permalink($CNF['URL_HOME'])]] : $this->parseHtmlByName('breadcrumb.html', [
     		'url_home' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_HOME'])),
     		'icon_home' => $CNF['ICON'],
+    		'title_home' => bx_html_attribute(strip_tags(_t($sTitleHome ? $sTitleHome : '_sys_menu_item_title_system_home')), BX_ESCAPE_STR_QUOTE),
     		'bx_repeat:items' => $aTmplVarsItems
     	]);
     }

+ 2 - 2
modules/base/general/template/breadcrumb.html

@@ -1,6 +1,6 @@
 <div class="bx-base-general-breadcrumb flex items-center">
-    <a class ="bx-base-general-breadcrumb-home" href="__url_home__"><i class="sys-icon __icon_home__"></i></a>
+    <a class ="bx-base-general-breadcrumb-home" href="__url_home__" title="__title_home__" aria-label="__title_home__"><i class="sys-icon __icon_home__" aria-hidden="true"></i></a>
     <bx_repeat:items>
-        <i class="sys-icon chevron-right bx-def-margin-sec-leftright"></i><a href="__url__">__title__</a>
+        <i class="sys-icon chevron-right bx-def-margin-sec-leftright" aria-hidden="true"></i><a href="__url__">__title__</a>
     </bx_repeat:items>
 </div>

+ 1 - 1
modules/base/general/template/uploader_button_html5_attach.html

@@ -1,6 +1,6 @@
 <div>
     <div id="__div_id__" class="filepond bx-uploader-html5-drop-area border-2 border-gray-300 border-dashed rounded-lg hidden"></div>
-    <input type="text" id="__focus_div_id__" class="bx-uploader-html5-paste-catcher" tabindex="1" style="display: none" />
+    <input type="text" id="__focus_div_id__" class="bx-uploader-html5-paste-catcher" tabindex="-1" aria-label="<bx_text_attribute:_sys_uploader_simple_submit_button />" style="display: none" />
     <div id="__errors_container_id__" class="bx-uploader-html5-errors-area bx-def-font-small"></div>
     <div id="__progress_div_id__" class="hidden">
         <div class="w-full bg-gray-200 rounded-full h-1 dark:bg-gray-700">

+ 2 - 2
modules/base/groups/template/cover_block.html

@@ -9,7 +9,7 @@
                     </div>
                     <div class="bx-base-pofile-cover-image relative h-64 lg:h-40 max-w-7xl mx-auto opacity-100 __add_class__">
                         <p class="bx-base-pofile-cover-image box-border absolute h-full w-full" >
-                            <img class="bx-base-pofile-cover-image h-64 mx-auto w-full object-cover bg-cover lg:h-40 rounded-t z-10 __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />" />
+                            <img alt="" class="bx-base-pofile-cover-image h-64 mx-auto w-full object-cover bg-cover lg:h-40 rounded-t z-10 __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />" />
                         </p>
                     </div>
                 </div>
@@ -19,7 +19,7 @@
                     <bx_if:show_avatar>
                         <div class="bx-base-pofile-cover-thumb __add_class__">
                             <a class="bx-base-pofile-cover-thumb" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                                <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                                <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                                 <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size flex items-center justify-center box-border m-0 rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 text-white font-bold text-5xl __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>" >__letter__</p>
                             </a>
                             <bx_if:show_online>

+ 2 - 2
modules/base/groups/template/unit.html

@@ -1,11 +1,11 @@
 <div class="__class__ p-2 box-border">
     <div class="bx-base-pofile-unit-cnt bx-def-box bx-def-box-round-corners bx-def-color-bg-box relative h-full overflow-hidden">
         <div class="absolute z-0 w-full h-40 bg-gradient-to-b from-green-400 to-green-500"></div>
-        <a class="bx-base-pofile-unit-cover relative flex items-center justify-start w-full h-40 bg-no-repeat bg-center bg-cover hover:no-underline" style="background-image:url(__cover_url__); __cover_settings__" href="__content_url__" onclick="__content_click__">
+        <a class="bx-base-pofile-unit-cover relative flex items-center justify-start w-full h-40 bg-no-repeat bg-center bg-cover hover:no-underline" style="background-image:url(__cover_url__); __cover_settings__" href="__content_url__" onclick="__content_click__" aria-hidden="true" tabindex="-1">
             <bx_if:show_thumbnail>
                 <div class="bx-def-unit-img bx-def-__size__-size __class_size__ relative ml-5">
                     <bx_if:show_thumb_image>
-                        <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ ring-4 ring-white dark:ring-gray-900 rounded-md box-border" src="__thumb_url__" />
+                        <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ ring-4 ring-white dark:ring-gray-900 rounded-md box-border" src="__thumb_url__" />
                     </bx_if:show_thumb_image>
                     <bx_if:show_thumb_letter>
                         <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center m-0 text-white font-bold ring-4 ring-white dark:ring-gray-900 rounded-md box-border" style="background-color:rgba(__color__)">__letter__</p>

+ 1 - 1
modules/base/groups/template/unit_live_search.html

@@ -2,7 +2,7 @@
     <a class="bx-def-box bx-def-box-hor bx-def-box-round-corners bx-def-color-bg-box bx-def-color-bg-box-hover relative items-center w-full p-2 box-border" href="__content_url__">
         <div class="bx-def-unit-img bx-def-__size__-size">
             <bx_if:show_thumb_image>
-                <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size" src="__thumb_url__" />
+                <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size" src="__thumb_url__" />
             </bx_if:show_thumb_image>
             <bx_if:show_thumb_letter>
                 <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size flex items-center justify-center text-white font-bold rounded box-border" style="background-color:rgba(__color__)">__letter__</p>

+ 2 - 0
modules/base/profile/classes/BxBaseModProfileTemplate.php

@@ -591,6 +591,7 @@ class BxBaseModProfileTemplate extends BxBaseModGeneralTemplate
 
             $aTmplVarsShowAvatar = array(
                 'add_class' => $sAddClassPicture,
+                'title_attr' => bx_html_attribute($sTitle),
                 'letter' => mb_strtoupper(mb_substr($sTitle, 0, 1)),
                 'img_class' => $sAddClassPicture != '' ? 'bx-media-editable-src' : '',
                 'ava_url' => $sUrlAvatar,
@@ -625,6 +626,7 @@ class BxBaseModProfileTemplate extends BxBaseModGeneralTemplate
             if($bProfileViewAllowed && $CNF['FIELD_PICTURE'] && $aData[$CNF['FIELD_PICTURE']]) {
                 $sPicturePopup = BxTemplFunctions::getInstance()->transBox($sPicturePopupId, $this->parseHtmlByName('image_popup.html', [
                     'image_url' => $sUrlPicture,
+                    'title_attr' => bx_html_attribute($sTitle),
                 ]), true, true);
             }
         }

+ 2 - 2
modules/base/profile/template/cover.html

@@ -3,7 +3,7 @@
         <div class="bx-base-pofile-cover bx-def-cover-color-bg bx-def-box-sizing">
             <div class="bx-base-pofile-cover-image __add_class__">
                 <p class="bx-base-pofile-cover-image bx-def-box-sizing flex justify-center items-center" >
-                    <img class="bx-base-pofile-cover-image __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />"/>
+                    <img alt="" class="bx-base-pofile-cover-image __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />"/>
                 </p>
                 <div class="bx-cover-gradient"></div>  
             </div>
@@ -12,7 +12,7 @@
                     <bx_if:show_avatar>
                         <div class="bx-base-pofile-cover-thumb __add_class__">
                             <a class="bx-base-pofile-cover-thumb" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                                <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                                <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                                 <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size flex items-center justify-center box-border m-0 rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 text-white font-bold text-5xl __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>">__letter__</p>
                             </a>
                             <bx_if:show_online>

+ 2 - 2
modules/base/profile/template/cover_block.html

@@ -5,7 +5,7 @@
             <bx_if:show_cover>
                 <div class="bx-base-pofile-cover-image __add_class__">
                     <p class="bx-base-pofile-cover-image bx-def-box-sizing flex justify-center items-center">
-                        <img class="bx-base-pofile-cover-image h-full object-cover bg-cover w-full __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />"/>
+                        <img alt="" class="bx-base-pofile-cover-image h-full object-cover bg-cover w-full __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />"/>
                     </p>
                     
                 </div>
@@ -15,7 +15,7 @@
                     <bx_if:show_avatar>
                         <div class="bx-base-pofile-cover-thumb __add_class__">
                             <a class="bx-base-pofile-cover-thumb" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                                <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                                <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                                 <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size flex items-center justify-center box-border m-0 rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 text-white font-bold text-5xl __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>" >__letter__</p>
                             </a>
                             <bx_if:show_online>

+ 1 - 1
modules/base/profile/template/image_popup.html

@@ -1,3 +1,3 @@
 <div class="bx-base-pofile-popup-wrapper">
-    <img class="bx-base-pofile-popup-image bx-def-border-radius-block" src="__image_url__" />
+    <img class="bx-base-pofile-popup-image bx-def-border-radius-block" src="__image_url__" alt="__title_attr__" />
 </div>

+ 2 - 2
modules/base/profile/template/unit.html

@@ -1,8 +1,8 @@
 <div class="__class__ bx-def-unit flex items-center w-46 mb-4 mr-2">
     <div class="bx-def-unit-img bx-def-__size__ bx-def-__size__-size __class_size__ relative flex-none">
-        <a class="text-center hover:no-underline" href="__content_url__" target="__content_target__"  onclick="__content_click__">
+        <a class="text-center hover:no-underline" href="__content_url__" target="__content_target__"  onclick="__content_click__" aria-hidden="true" tabindex="-1">
             <bx_if:show_thumb_image>
-                <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
+                <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
             </bx_if:show_thumb_image>
             <bx_if:show_thumb_letter>
                 <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center box-border m-0 rounded-full text-white font-bold" style="background-color:rgba(__color__)">__letter__</p>

+ 1 - 1
modules/base/profile/template/unit_live_search.html

@@ -2,7 +2,7 @@
     <a class="bx-def-box bx-def-box-hor bx-def-box-round-corners bx-def-color-bg-box bx-def-color-bg-box-hover relative items-center w-full p-2 box-border" href="__content_url__">
         <div class="bx-def-unit-img bx-def-__size__-size">
             <bx_if:show_thumb_image>
-                <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size" src="__thumb_url__" />
+                <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size" src="__thumb_url__" />
             </bx_if:show_thumb_image>
             <bx_if:show_thumb_letter>
                 <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size flex items-center justify-center text-white font-bold rounded-full box-border" style="background-color:rgba(__color__)">__letter__</p>

+ 2 - 2
modules/base/profile/template/unit_with_cover.html

@@ -1,11 +1,11 @@
 <div class="__class__ p-2 box-border">
     <div class="bx-base-pofile-unit-cnt bx-def-box bx-def-box-round-corners bx-def-color-bg-box relative h-full overflow-hidden">
         <div class="absolute z-0 w-full h-40 bg-gradient-to-b from-blue-400 to-blue-500"></div>
-        <a class="bx-base-pofile-unit-cover relative flex items-center justify-center w-full h-40 bg-no-repeat bg-center bg-cover hover:no-underline" style="background-image:url(__cover_url__);__cover_settings__" href="__content_url__" onclick="__content_click__">
+        <a class="bx-base-pofile-unit-cover relative flex items-center justify-center w-full h-40 bg-no-repeat bg-center bg-cover hover:no-underline" style="background-image:url(__cover_url__);__cover_settings__" href="__content_url__" onclick="__content_click__" aria-hidden="true" tabindex="-1">
             <bx_if:show_thumbnail>
                 <div class="bx-def-unit-img bx-def-__size__-size __class_size__ relative">
                     <bx_if:show_thumb_image>
-                        <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
+                        <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
                     </bx_if:show_thumb_image>
                     <bx_if:show_thumb_letter>
                         <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center box-border m-0 rounded-full text-white font-bold" style="background-color:rgba(__color__)">__letter__</p>

+ 1 - 1
modules/base/profile/template/unit_wo_info.html

@@ -2,7 +2,7 @@
     <div class="bx-def-unit-img bx-def-__size__-size __class_size__ relative">
         <a class="bx-def-font-align-center" href="__content_url__" target="__content_target__" onclick="__content_click__" title="__title_attr__">
             <bx_if:show_thumb_image>
-                <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
+                <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
             </bx_if:show_thumb_image>
             <bx_if:show_thumb_letter>
                 <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center box-border m-0 rounded-full text-white font-bold" style="background-color:rgba(__color__)">__letter__</p>

+ 1 - 1
modules/base/profile/template/unit_wo_info_links.html

@@ -1,7 +1,7 @@
 <div class="__class__ bx-def-__size__-size __class_size__ text-center">
     <div class="bx-def-unit-img bx-def-__size__-size __class_size__ relative">
         <bx_if:show_thumb_image>
-            <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
+            <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
         </bx_if:show_thumb_image>
         <bx_if:show_thumb_letter>
             <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center box-border m-0 rounded-full text-white font-bold" style="background-color:rgba(__color__)">__letter__</p>

+ 1 - 1
modules/base/profile/template/unit_wo_links.html

@@ -1,7 +1,7 @@
 <div class="__class__ bx-def-unit flex items-center w-46 mb-4 mr-2">
     <div class="bx-def-unit-img bx-def-__size__-size __class_size__ relative">
         <bx_if:show_thumb_image>
-            <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
+            <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ rounded-full box-border" src="__thumb_url__" />
         </bx_if:show_thumb_image>
         <bx_if:show_thumb_letter>
             <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center box-border m-0 rounded-full text-white font-bold" style="background-color:rgba(__color__)">__letter__</p>

+ 27 - 2
modules/boonex/artificer/data/template/bx_timeline/css/view.css

@@ -82,8 +82,14 @@ div.bx-tl-item-content form {
     padding: 1rem 1rem 0 1rem;
 }
 div.bx-tl-item-content div.bx-tl-title {
-    text-overflow: ellipsis;
-    overflow: hidden;
+    /* This box used to be `overflow: hidden`, which clipped the focus ring of the title link sitting
+       flush inside it. The clipping was really there to stop a long unbroken title from widening the
+       page, and wrapping does that without hiding anything; `text-overflow` never applied here anyway,
+       the box is a flex container. */
+    overflow-wrap: anywhere;
+}
+div.bx-tl-item-content div.bx-tl-title div.bx-tl-title-cnt {
+    min-width: 0;
 }
 div.bx-tl-item-text div.bx-tl-content {
     position: relative;
@@ -136,6 +142,25 @@ div.bx-tl-item div.bx-tl-item-activity.bx-tl-ia-counters .bx-menu-custom.bx-menu
     height: 2.5rem;
 }
 
+/*--- Item Owner: Name ---*/
+/* The owner-info wrapper is `overflow-hidden` (Tailwind), which would clip the
+   extra hit area added below; the name itself still truncates via `.truncate`. */
+div.bx-tl-item div.bx-tl-owner-info {
+    overflow: visible;
+}
+
+/* WCAG 2.5.8 (AA): the author link must be at least 24x24 CSS px. The extra
+   height is added as padding-top cancelled by an equal negative margin-top, so
+   the link's box grows UPWARD into the gap that already sits above the header
+   row. The text position, the row height, the meta row below and the card
+   height are all unchanged, and the box never reaches the date link beneath. */
+div.bx-tl-item div.bx-tl-owner-name > a.bx-tl-owner-name {
+    min-height: 1.5rem;
+
+    padding-top: 0.375rem;
+    margin-top: -0.375rem;
+}
+
 /*--- Menu: Item Manage ---*/
 .bx-menu-object-bx_timeline_menu_item_manage .sys-action-do-text {
     margin-left: 0rem;

+ 2 - 2
modules/boonex/artificer/data/template/bx_timeline/item_outline.html

@@ -37,7 +37,7 @@
                                 <bx_if:show_timeline_owner>
                                     <span class="mr-1"><bx_text:_bx_timeline_txt_to /></span> 
                                     <span class="__style_prefix__-timeline-owner-name mr-1">
-                                        <bx_repeat:owners><span class="__style_prefix__-ton-div">, </span><a href="__owner_url__" class="__style_prefix__-ton-item text-blue-600 dark:text-blue-500 font-semibold hover:underline">__owner_username__</a></bx_repeat:owners>
+                                        <bx_repeat:owners><span class="__style_prefix__-ton-div">, </span><a href="__owner_url__" class="__style_prefix__-ton-item text-blue-600 dark:text-blue-400 font-semibold hover:underline">__owner_username__</a></bx_repeat:owners>
                                     </span> 
                                 </bx_if:show_timeline_owner>
                             </div>
@@ -77,7 +77,7 @@
                 </bx_if:show_menu_item_actions>
                <bx_if:show_manage>
                 <div class="__style_prefix__-item-manage">
-                    <a class="group bx-stl-mil-sm" href="javascript:void(0)" onclick="__onclick__">
+                    <a class="group bx-stl-mil-sm" href="javascript:void(0)" onclick="__onclick__" title="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-label="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-haspopup="dialog" aria-expanded="false">
                         <span class="bx-menu-item-icon bx-def-align-center leading-5"><i class="sys-icon ellipsis-h"></i></span>
                     </a>
                 </div>

+ 2 - 2
modules/boonex/artificer/data/template/bx_timeline/item_timeline.html

@@ -36,7 +36,7 @@
                             <bx_if:show_timeline_owner>
                                 <span class="mr-1"><bx_text:_bx_timeline_txt_to /></span> 
                                 <span class="__style_prefix__-timeline-owner-name mr-1">
-                                    <bx_repeat:owners><span class="__style_prefix__-ton-div">, </span><a href="__owner_url__" class="__style_prefix__-ton-item text-blue-600 dark:text-blue-500 font-semibold hover:underline">__owner_username__</a></bx_repeat:owners>
+                                    <bx_repeat:owners><span class="__style_prefix__-ton-div">, </span><a href="__owner_url__" class="__style_prefix__-ton-item text-blue-600 dark:text-blue-400 font-semibold hover:underline">__owner_username__</a></bx_repeat:owners>
                                 </span> 
                             </bx_if:show_timeline_owner>
                         </div>
@@ -80,7 +80,7 @@
             </bx_if:show_menu_item_actions>
             <bx_if:show_manage>
                 <div class="__style_prefix__-item-manage">
-                    <a class="group bx-lnk-cnt bx-stl-mil-sm" href="javascript:void(0)" onclick="__onclick__">
+                    <a class="group bx-lnk-cnt bx-stl-mil-sm" href="javascript:void(0)" onclick="__onclick__" title="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-label="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-haspopup="dialog" aria-expanded="false">
                         <span class="bx-menu-item-icon bx-def-align-center leading-5"><i class="sys-icon ellipsis-h"></i></span>
                     </a>
                 </div>

+ 2 - 2
modules/boonex/artificer/data/template/mod_groups/cover.html

@@ -4,7 +4,7 @@
             <div class="bx-base-pofile-cover-image-wrp relative xl:px-4 overflow-hidden">
                 <div class="bx-base-pofile-cover-image bx-def-page-width relative mx-auto opacity-100 xl:rounded-b-lg <bx_if:show_cover_placeholder>bx-bpci-holder h-32 lg:h-32 bg-gradient-to-b from-green-400 to-green-500</bx_if:show_cover_placeholder><bx_if:show_cover>h-64 lg:h-80</bx_if:show_cover> __add_class__ ">
                     <p class="bx-base-pofile-cover-image box-border absolute h-full w-full flex justify-center items-center">
-                        <img class="bx-base-pofile-cover-image w-full object-cover bg-cover xl:rounded-b-lg h-full relative __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />" />
+                        <img alt="" class="bx-base-pofile-cover-image w-full object-cover bg-cover xl:rounded-b-lg h-full relative __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />" />
                     </p>
                     __cover_tweak__
                 </div>
@@ -15,7 +15,7 @@
                     <bx_if:show_avatar>
                         <div class="bx-base-pofile-cover-thumb relative lg:absolute flex justify-center lg:flex-none z-10 __add_class__">
                             <a class="bx-base-pofile-cover-thumb hover:no-underline" href="__picture_href__" >
-                                <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                                <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                                 <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size flex items-center justify-center box-border m-0 rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 text-white font-bold text-5xl __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>" >__letter__</p>
                             </a>
                             <bx_if:show_online>

+ 2 - 2
modules/boonex/artificer/data/template/mod_groups/cover_block.html

@@ -6,7 +6,7 @@
                 <div class="bx-base-pofile-cover-image-wrp relative overflow-hidden">
                     <div class="bx-base-pofile-cover-image relative h-64 lg:h-40 max-w-7xl mx-auto opacity-100 bg-gradient-to-b from-blue-400 to-blue-500 rounded-t-md __add_class__">
                         <p class="bx-base-pofile-cover-image box-border absolute h-full w-full">
-                            <img class="bx-base-pofile-cover-image relative h-64 mx-auto w-full object-cover bg-cover lg:h-40 rounded-t-md __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />" />
+                            <img alt="" class="bx-base-pofile-cover-image relative h-64 mx-auto w-full object-cover bg-cover lg:h-40 rounded-t-md __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />" />
                         </p>
                     </div>
                 </div>
@@ -16,7 +16,7 @@
                     <bx_if:show_avatar>
                         <div class="bx-base-pofile-cover-thumb relative inline-flex justify-center __add_class__">
                             <a class="bx-base-pofile-cover-thumb" href="__picture_href__" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                                <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 lg:h-32 lg:w-32 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                                <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 lg:h-32 lg:w-32 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                                 <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size flex items-center justify-center box-border m-0 rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 lg:h-32 lg:w-32 text-white font-bold text-5xl __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>" >__letter__</p>
                             </a>
                             <bx_if:show_online>

+ 2 - 2
modules/boonex/artificer/data/template/mod_profile/cover.html

@@ -4,7 +4,7 @@
             <div class="bx-base-pofile-cover-image-wrp relative xl:px-4 overflow-hidden">
                 <div class="bx-base-pofile-cover-image bx-def-page-width relative mx-auto opacity-100 xl:rounded-b-lg <bx_if:show_cover_placeholder>bx-bpci-holder h-32 lg:h-32 bg-gradient-to-b from-blue-400 to-blue-500</bx_if:show_cover_placeholder><bx_if:show_cover>h-64 lg:h-80</bx_if:show_cover> __add_class__">
                     <p class="bx-base-pofile-cover-image box-border absolute h-full w-full">
-                        <img class="bx-base-pofile-cover-image w-full object-cover bg-cover xl:rounded-b-lg h-full relative __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />" />
+                        <img alt="" class="bx-base-pofile-cover-image w-full object-cover bg-cover xl:rounded-b-lg h-full relative __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />" />
                     </p>
                     __cover_tweak__
                 </div>
@@ -14,7 +14,7 @@
                     <bx_if:show_avatar>
                         <div class="bx-base-pofile-cover-thumb bx-def-ava-big-size relative lg:absolute flex self-center lg:flex-none z-10 __add_class__">
                             <a class="bx-base-pofile-cover-thumb hover:no-underline" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                                <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                                <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                                 <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size flex items-center justify-center box-border m-0 rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 text-white font-bold text-5xl __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>" >__letter__</p>
                             </a>
                             <bx_if:show_online>

+ 2 - 2
modules/boonex/artificer/data/template/mod_profile/cover_block.html

@@ -6,7 +6,7 @@
                 <div class="bx-base-pofile-cover-image-wrp relative overflow-hidden">
                     <div class="bx-base-pofile-cover-image relative h-64 lg:h-40 max-w-7xl mx-auto opacity-100 bg-gradient-to-b from-blue-400 to-blue-500 rounded-t-md __add_class__">
                         <p class="bx-base-pofile-cover-image box-border absolute h-full w-full">
-                            <img class="bx-base-pofile-cover-image relative h-64 mx-auto w-full object-cover bg-cover lg:h-40 rounded-t-md __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />" />
+                            <img alt="" class="bx-base-pofile-cover-image relative h-64 mx-auto w-full object-cover bg-cover lg:h-40 rounded-t-md __img_class__ bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__);__cover_settins__" src="<bx_icon_url:spacer.gif />" />
                         </p>
                     </div>
                 </div>
@@ -16,7 +16,7 @@
                     <bx_if:show_avatar>
                         <div class="bx-base-pofile-cover-thumb relative inline-flex justify-center __add_class__">
                             <a class="bx-base-pofile-cover-thumb" href="__picture_href__" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                                <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 lg:h-32 lg:w-32 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                                <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 lg:h-32 lg:w-32 __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__"  <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                                 <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size flex items-center justify-center box-border m-0 rounded-full ring-4 mx-auto ring-white dark:ring-gray-900 h-48 w-48 lg:h-32 lg:w-32 text-white font-bold text-5xl __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>" >__letter__</p>
                             </a>
                             <bx_if:show_online>

+ 2 - 2
modules/boonex/artificer/data/template/system/_block_explore.html

@@ -15,7 +15,7 @@
         <img src="<bx_image_url:cover-dark.svg />" class="object-cover h-full w-full" />
     </div>
     <div class="relative pb-8 z-2">
-        <main class="mt-6">
+        <div class="mt-6">
             <div class="mx-auto max-w-7xl">
                 <div class="lg:grid lg:grid-cols-12 lg:gap-8">
                     <div class="px-6 lg:px-8 text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left lg:flex lg:items-center">
@@ -56,6 +56,6 @@
                     </div>
                 </div>
             </div>
-        </main>
+        </div>
     </div>
 </div>

+ 2 - 2
modules/boonex/artificer/data/template/system/_block_splash.html

@@ -16,7 +16,7 @@
         <div class="absolute top-0 left-0 w-full h-full hidden dark:block z-1">
             <img src="<bx_image_url:cover-dark.svg />" class="object-cover h-full w-full" />
         </div>
-        <main class="relative mt-16 z-2">
+        <div class="relative mt-16 z-2">
             <div class="mx-auto max-w-7xl">
                 <div class="lg:grid lg:grid-cols-12 lg:gap-8">
                     <div class="px-6 lg:px-8 text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left lg:flex lg:items-center">
@@ -57,7 +57,7 @@
                     </div>
                 </div>
             </div>
-        </main>
+        </div>
     </div>
 </div>
 <div class="bg-gray-100 dark:bg-gray-900 pt-12 sm:pt-16">

+ 27 - 6
modules/boonex/artificer/data/template/system/_header_js_custom.html

@@ -45,6 +45,20 @@
                     $(aFocusable[0]).focus();
             });
         }
+
+        //--- Escape closes whichever sidebar is open; bx_sidebar_toggle returns focus to its trigger.
+        $(document).on('keydown.bx-sidebar', function(event) {
+            if(event.which != 27 && event.key != 'Escape')
+                return;
+
+            for(var j in aSidebars) {
+                if(typeof(aSidebars[j]) != 'string' || !bx_sidebar_active(aSidebars[j]))
+                    continue;
+
+                event.preventDefault();
+                bx_sidebar_toggle(aSidebars[j]);
+            }
+        });
     });
 
     function bx_sidebar_get(sType) {
@@ -65,24 +79,31 @@
 
         oSidebar.toggleClass('bx-sidebar-active', !bSidebarActive);
 
+        var oTrigger = $('.bx-sidebar-' + sType + '-trigger');
+        oTrigger.attr('aria-expanded', !bSidebarActive ? 'true' : 'false');
+
         if(!bSidebarActive) {
             var aFocusable = bx_get_focusable(oSidebar.get(0));
             if(aFocusable && aFocusable.length > 0)
                 $(aFocusable[0]).focus();
         }
-        else 
-            $(bx_clicked_stack_pop()).focus();
+        else {
+            var oReturnTo = bx_clicked_stack_pop();
+            $(oReturnTo || oTrigger.filter(':visible').get(0)).focus();
+        }
     }
 
     function bx_sidebar_dropdown_toggle(oLink) {
-        $(oLink).parents('.bx-sidebar-item:first').toggleClass('bx-si-dropdown-open').find('.bx-si-dropdown-icon').toggleClass('rotate-0 rotate-90');
+        var oItem = $(oLink).parents('.bx-sidebar-item:first');
+        oItem.toggleClass('bx-si-dropdown-open').find('.bx-si-dropdown-icon').toggleClass('rotate-0 rotate-90');
+        $(oLink).attr('aria-expanded', oItem.hasClass('bx-si-dropdown-open') ? 'true' : 'false');
 
         return false;
     }
 
     function bx_site_search_show(oButtom) {
         var oButton = $(oButtom).parents('.bx-ti-search-button');
-        oButton.addClass('bx-tis-button-hidden');
+        oButton.addClass('bx-tis-button-hidden').find('button,a').attr('aria-expanded', 'true');
 
         var oBox = oButton.parents('.bx-ti-search').find('.bx-ti-search-box');
         oBox.addClass('bx-tis-box-shown');
@@ -96,7 +117,7 @@
                 else {
                     bx_site_search_close_all_opened();
                     oBox.removeClass('bx-tis-box-shown');
-                    oButton.removeClass('bx-tis-button-hidden').find('a').focus();
+                    oButton.removeClass('bx-tis-button-hidden').find('button,a').attr('aria-expanded', 'false').focus();
                 }
             });
         }, 10);
@@ -142,7 +163,7 @@
         if(oBox.hasClass(sClassBox) && oButton.hasClass(sClassButton)) {
             bx_site_search_close_all_opened();
             oBox.removeClass(sClassBox);
-            oButton.removeClass(sClassButton).find('a').focus();
+            oButton.removeClass(sClassButton).find('button,a').attr('aria-expanded', 'false').focus();
         }
     }
 

+ 1 - 1
modules/boonex/artificer/data/template/system/block_join.html

@@ -9,6 +9,6 @@
     </bx_if:show_login>
     <div class="sys-jf-agreement -mx-4">
         <hr class="bx-def-hr mt-4" />
-        <div class="sys-jf-agreement-cnt pt-4 px-4 text-xs text-gray-500">__agreement__</div>
+        <div class="sys-jf-agreement-cnt pt-4 px-4 text-xs text-gray-600 dark:text-gray-400 [&_a]:underline [&_a]:underline-offset-2">__agreement__</div>
     </div>
 </div>

+ 1 - 1
modules/boonex/artificer/data/template/system/block_login.html

@@ -11,7 +11,7 @@
     </bx_if:show_join>
     <div class="sys-lf-agreement -mx-4">
         <hr class="bx-def-hr mt-4" />
-        <div class="sys-lf-agreement-cnt pt-4 px-4 text-xs text-gray-500">__agreement__</div>
+        <div class="sys-lf-agreement-cnt pt-4 px-4 text-xs text-gray-600 dark:text-gray-400 [&_a]:underline [&_a]:underline-offset-2">__agreement__</div>
     </div>
     <bx_if:show_js_code>
         <script>

+ 4 - 2
modules/boonex/artificer/data/template/system/cover.html

@@ -16,10 +16,12 @@
                     </div>
                 </bx_if:image>
                 <div class="bx-cover-title flex justify-center items-center">
-                    <div class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-50">
+                    <bx_if:title>
+                    <h1 class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-50">
                         <span class="block pb-1 relative">__title__</span>
                         <span class="block pb-1 bg-clip-text text-transparent bg-gradient-to-r from-green-500 to-blue-500"></span>
-                    </div>
+                    </h1>
+                    </bx_if:title>
                 </div>
                 <div class="bx-cover-actions">__actions__</div>
             </div>

+ 2 - 2
modules/boonex/artificer/data/template/system/cover_home.html

@@ -11,10 +11,10 @@
             <div class="bx-cover-gradient bx-def-page-width relative mx-auto"></div>
             <div class="relative mx-auto py-16 text-center">
                 <div>
-                    <div class="text-4xl  sm:text-5xl xl:text-6xl tracking-tight  font-bold text-gray-800 dark:text-white ">
+                    <h1 class="text-4xl  sm:text-5xl xl:text-6xl tracking-tight  font-bold text-gray-800 dark:text-white ">
                         <span class="block pb-1 relative"><bx_text:_bx_artificer_txt_splash_welcome_l1 /></span>
                         <span class="block pb-1  bg-clip-text text-transparent bg-gradient-to-r from-green-500 to-blue-500"><bx_text:_bx_artificer_txt_splash_welcome_l2 /></span>
-                    </div>
+                    </h1>
                     <p class="p-8  font-light text-gray-700 dark:text-gray-300 text-base  sm:text-lg xl:text-xl">
                         <span class="block"><bx_text:_bx_artificer_txt_splash_welcome_l3 /></span>
                     </p>

+ 10 - 3
modules/boonex/artificer/data/template/system/css/forms.css

@@ -128,7 +128,8 @@ import url(../../../../../../../template/css/forms.css);
 
 
 /*-- Form field: Password --*/
-.bx-artificer .bx-form-input-wrapper-password > a {
+.bx-artificer .bx-form-input-wrapper-password > .password-show,
+.bx-artificer .bx-form-input-wrapper-password > .password-hide {
     position: absolute;
     z-index: 1000;
     
@@ -143,9 +144,15 @@ import url(../../../../../../../template/css/forms.css);
     right: 0;    
     height: 100%;
 
-    margin: 0 0.5rem;
+    /* WCAG 2.5.8 (AA): at least 24px wide. 1.75rem is the project's "mini"
+       button size. The side margin is reduced by exactly half the added
+       width, (28px - 18px) / 2 = 5px, so the eye glyph does not move. */
+    min-width: 1.75rem;
+
+    margin: 0 0.1875rem;
 }
-.bx-dir-rtl.bx-artificer .bx-form-input-wrapper-password > a {
+.bx-dir-rtl.bx-artificer .bx-form-input-wrapper-password > .password-show,
+.bx-dir-rtl.bx-artificer .bx-form-input-wrapper-password > .password-hide {
     left: 0;
     right: auto;
 }

+ 1 - 1
modules/boonex/artificer/data/template/system/menu_main_submenu.html

@@ -1,7 +1,7 @@
 <ul id="bx-menu-main-submenu" class="bx-menu-main-submenu bx-menu-object-__object__ inline-flex items-stretch w-max">
     <bx_repeat:menu_items>
         <li class="bx-menu-item bx-menu-item-__name__ __class_add__ flex" __attrs_wrp__>
-            <a class="group block relative py-4 px-4 text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 whitespace-nowrap text-sm font-medium text-center hover:bg-transparent focus:z-10 box-sizing" href="__link__" <bx_if:onclick>onclick="__onclick__"</bx_if:onclick> __attrs__>
+            <a class="group block relative py-4 px-4 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 whitespace-nowrap text-sm font-medium text-center hover:bg-transparent focus:z-10 box-sizing" href="__link__" <bx_if:onclick>onclick="__onclick__"</bx_if:onclick> __attrs__>
                 <bx_if:title>
                     <span class="bx-menu-item-title">__title__</span>
                 </bx_if:title>

+ 1 - 1
modules/boonex/artificer/data/template/system/menu_main_submenu_more_auto.html

@@ -1,7 +1,7 @@
 <ul id="__html_id__" class="bx-menu-main-submenu<bx_if:show_more_auto_class> bx-menu-more-auto</bx_if:show_more_auto_class> bx-menu-object-__object__ flex items-stretch">
     <bx_repeat:menu_items>
         <li class="bx-menu-item bx-menu-item-__name__ __class_add__ flex" __attrs_wrp__>
-            <a href="__link__" <bx_if:onclick>onclick="__onclick__"</bx_if:onclick> class="group block relative py-4 px-4 text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 whitespace-nowrap text-sm font-medium text-center hover:bg-transparent focus:z-10 box-sizing"__attrs__><bx_if:title>__title__</bx_if:title><span class="bx-mi-underline absolute inset-x-0 bottom-0 h-0.5 group-hover:bg-gray-500 rounded-md"></span></a>__popup__
+            <a href="__link__" <bx_if:onclick>onclick="__onclick__"</bx_if:onclick> class="group block relative py-4 px-4 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 whitespace-nowrap text-sm font-medium text-center hover:bg-transparent focus:z-10 box-sizing"__attrs__><bx_if:title>__title__</bx_if:title><span class="bx-mi-underline absolute inset-x-0 bottom-0 h-0.5 group-hover:bg-gray-500 rounded-md"></span></a>__popup__
         </li>
     </bx_repeat:menu_items>
 </ul>

+ 1 - 1
modules/boonex/artificer/data/template/system/menu_meta_item.html

@@ -1,4 +1,4 @@
-<span class="group flex items-center p-1 border-none rounded-full text-gray-500 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600">
+<span class="group flex items-center p-1 border-none rounded-full text-gray-600 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600">
     <span class="sys-action-counter-icon relative flex items-center justify-center ring-1  h-6 w-6 rounded-full ring-white dark:ring-gray-700 bg-gray-200 dark:bg-gray-700">__icon__</span>
     <span class="sys-action-counter-text ltr:ml-1 rtl:mr-1">__caption__</span>
 </span>

+ 1 - 1
modules/boonex/artificer/data/template/system/profile_avatar.html

@@ -6,7 +6,7 @@
                 <div class="bx-pai-line text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-white font-semibold truncate">
                     <a href="__profile_url__" title="__profile_title_attr__">__profile_title__</a>
                 </div>
-                <div class="bx-pai-line text-green-600 dark:text-green-400 text-xs font-normal truncate uppercase tracking-wider">
+                <div class="bx-pai-line text-green-700 dark:text-green-400 text-xs font-normal truncate uppercase tracking-wider">
                     <!--<bx_include_auto:menu_icon.html />--><span>__profile_acl_title__</span>
                 </div>
             </div>

+ 1 - 1
modules/boonex/artificer/data/template/system/profile_stats.html

@@ -6,7 +6,7 @@
                 <div class="bx-pa-info w-full">
                     <div class="bx-pa-info-cnt flex flex-col justify-center h-10 pl-10 box-border">
                         <div class="bx-pai-line text-gray-600 dark:text-gray-300 group-hover:text-gray-800 dark:group-hover:text-white font-semibold truncate">__profile_title__</div>
-                        <div class="bx-pai-line text-green-600 dark:text-green-400 text-xs truncate uppercase tracking-wider font-normal">
+                        <div class="bx-pai-line text-green-700 dark:text-green-400 text-xs truncate uppercase tracking-wider font-normal">
                             <div><!--<bx_include_auto:menu_icon.html />--><span>__profile_acl_title__</span></div>
                         </div>
                     </div>

+ 3 - 3
modules/boonex/artificer/data/template/system/pt_application.html

@@ -64,7 +64,7 @@
 
         <!-- BEG: Main Content -->
         <div id="main-content" class="bx-main-column wide relative w-full h-full overflow-y-auto xl:ml-64">
-            <main>
+            <div>
                 <bx_injection:injection_between_cover_logo />
                 __cover__
                 <bx_injection:injection_between_cover_top_menu />
@@ -91,7 +91,7 @@
                     __informer__
                     <div class="bx-page-wrapper mx-auto px-3 md:px-4 lg:px-6 box-border">
                         <div id="bx-content-container" class="bx-content-container">
-                            <main role="main"  id="bx-content-main" class="bx-content-main my-2 lg:my-3">
+                            <main role="main"  id="bx-content-main" class="bx-content-main my-2 lg:my-3" tabindex="-1">
                                 <bx_injection:injection_content_before />
                                 __page_main_code__
                                 <bx_injection:injection_content_after />
@@ -101,7 +101,7 @@
                         </div> <!-- #bx-content-container -->
                     </div> <!-- .bx-page-wrapper -->
                 </div> <!-- #bx-content-wrapper -->
-            </main>
+            </div>
             <footer class="p-4 m-4 bg-white rounded-lg shadow md:flex md:items-center md:justify-between md:p-6 xl:p-8 dark:bg-gray-800">
                 <bx_injection:injection_footer_before />
                 <div id="bx-menu-bottom">

+ 1 - 0
modules/boonex/artificer/data/template/system/scripts/BxTemplMenuSidebarSite.php

@@ -43,6 +43,7 @@ class BxTemplMenuSidebarSite extends BxTemplMenu
                     'onclick' => "javascript:return bx_sidebar_dropdown_toggle(this)"
             ]];
             $aResult['class_add'] .= ' bx-si-dropdown-has';
+            $aResult['attrs'] .= ' aria-expanded="false"';
 
             $aSubmenuItems = $oSubmenu->getMenuItemsRaw();
             if($oSubmenu->isHtmx() && !$this->_isSelected($a) && !array_key_exists($this->_sPageUri, $aSubmenuItems))

+ 15 - 0
modules/boonex/artificer/data/template/system/scripts/BxTemplMenuToolbar.php

@@ -49,17 +49,22 @@ class BxTemplMenuToolbar extends BxBaseMenuToolbar
             return $a;
 
         $a['class_add_a'] = '';
+
+        //--- Each of these items is a disclosure: it opens a sidebar or a popup instead of following a link.
+        $sAttrsAdd = '';
         switch ($a['name']) {
             case 'main-menu':
                 $a['class_add_a'] = ' bx-sidebar-site-trigger';
                 $a['link'] = 'javascript:void(0)';
                 $a['onclick'] = '';
+                $sAttrsAdd = 'aria-haspopup="dialog" aria-expanded="false" aria-controls="bx-sidebar-site"';
                 break;
 
             case 'account':
                 $a['class_add_a'] = ' bx-sidebar-account-trigger';
                 $a['link'] = 'javascript:void(0)';
                 $a['onclick'] = '';
+                $sAttrsAdd = 'aria-haspopup="dialog" aria-expanded="false" aria-controls="bx-sidebar-account"';
                 break;
 
             case 'add-content':
@@ -67,14 +72,24 @@ class BxTemplMenuToolbar extends BxBaseMenuToolbar
 
                 if(isset($a['bx_if:onclick']['content']['onclick']))
                     $a['bx_if:onclick']['content']['onclick'] = str_replace('bx_menu_slide_inline', 'bx_menu_popup_inline', $a['bx_if:onclick']['content']['onclick']);
+
+                $sAttrsAdd = 'aria-haspopup="true" aria-expanded="false" aria-controls="bx-sliding-menu-sys_add_content"';
                 break;
 
             case 'notifications-preview':
                 if(isset($a['bx_if:onclick']['content']['onclick']))
                     $a['bx_if:onclick']['content']['onclick'] = str_replace(["bx_menu_slide", "'site', "], ['bx_menu_popup'], $a['bx_if:onclick']['content']['onclick']);
+
+                //--- No aria-controls, because bx_menu_popup() creates the popup lazily and the id does not
+                //--- exist yet, and no aria-expanded, because the popup is anchored on the ancestor <ul>
+                //--- rather than on this link, so nothing is in a position to keep the state honest.
+                $sAttrsAdd = 'aria-haspopup="dialog"';
                 break;
         }
 
+        if($sAttrsAdd && isset($a['attrs']))
+            $a['attrs'] .= ' ' . $sAttrsAdd;
+
         return $a;
     }
 }

+ 1 - 1
modules/boonex/artificer/data/template/system/sidebar_account.html

@@ -1,4 +1,4 @@
-<div class="bx-sidebar bx-sidebar-account bx-def-z-index-modal fixed flex flex-row justify-end inset-0" role="dialog" aria-modal="true">
+<div id="bx-sidebar-account" class="bx-sidebar bx-sidebar-account bx-def-z-index-modal fixed flex flex-row justify-end inset-0" role="dialog" aria-modal="true" aria-label="<bx_text:_sys_menu_item_title_system_account_menu />">
     <div class="bx-sidebar-account-bg fixed inset-0 bg-gray-600 opacity-75" aria-hidden="true"></div>
     <div class="flex-shrink-0 w-12" aria-hidden="true"></div>
     <div class="bx-sidebar-cnt relative flex max-w-xs w-full py-4 bg-white dark:bg-gray-900">

+ 2 - 1
modules/boonex/artificer/data/template/system/sidebar_site.html

@@ -1,4 +1,4 @@
-<div class="bx-sidebar bx-sidebar-site bx-def-z-index-modal fixed flex lg:hidden inset-0" role="dialog" aria-modal="true">
+<div id="bx-sidebar-site" class="bx-sidebar bx-sidebar-site bx-def-z-index-modal fixed flex lg:hidden inset-0" role="dialog" aria-modal="true" aria-label="<bx_text:_sys_menu_item_title_system_main_menu />">
     <div class="bx-sidebar-site-bg fixed inset-0 bg-gray-600 opacity-75" aria-hidden="true"></div>
     <div class="bx-sidebar-cnt relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-white dark:bg-gray-900">
         <div class="absolute top-0 ltr:right-0 rtl:left-0 ltr:-mr-12 rtl:-ml-12 pt-2">
@@ -15,6 +15,7 @@
                 <bx_menu:bx_artificer_sidebar_site />
             </nav>
         </div>
+        <div class="bx-focus-trap w-0 h-0 outline-none" tabindex="0"></div>
     </div>
     <div class="flex-shrink-0 w-14" aria-hidden="true"></div>
 </div>

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
modules/boonex/artificer/install/sql/enable.sql


+ 2 - 2
modules/boonex/courses/template/unit_personal.html

@@ -1,11 +1,11 @@
 <div class="__class__ p-2 box-border">
     <div class="bx-base-pofile-unit-cnt bx-def-box bx-def-box-round-corners bx-def-color-bg-box relative h-full overflow-hidden">
         <div class="absolute z-0 w-full h-40 bg-gradient-to-b from-green-400 to-green-500"></div>
-        <a class="bx-base-pofile-unit-cover relative flex items-center justify-start w-full h-40 bg-no-repeat bg-center bg-cover hover:no-underline" style="background-image:url(__cover_url__); __cover_settings__" href="__content_url__" onclick="__content_click__">
+        <a class="bx-base-pofile-unit-cover relative flex items-center justify-start w-full h-40 bg-no-repeat bg-center bg-cover hover:no-underline" style="background-image:url(__cover_url__); __cover_settings__" href="__content_url__" onclick="__content_click__" aria-hidden="true" tabindex="-1">
             <bx_if:show_thumbnail>
                 <div class="bx-def-unit-img bx-def-__size__-size __class_size__ relative ml-5">
                     <bx_if:show_thumb_image>
-                        <img class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ ring-4 ring-white dark:ring-gray-900 rounded-md box-border" src="__thumb_url__" />
+                        <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ ring-4 ring-white dark:ring-gray-900 rounded-md box-border" src="__thumb_url__" />
                     </bx_if:show_thumb_image>
                     <bx_if:show_thumb_letter>
                         <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center m-0 text-white font-bold ring-4 ring-white dark:ring-gray-900 rounded-md box-border" style="background-color:rgba(__color__)">__letter__</p>

+ 2 - 2
modules/boonex/decorous/data/template/bx_organizations/cover.html

@@ -3,7 +3,7 @@
         <div class="bx-base-pofile-cover bx-def-cover-color-bg bx-def-box-sizing">
             <div class="bx-base-pofile-cover-image">
                 <p class="bx-base-pofile-cover-image bx-def-box-sizing flex justify-center items-center">
-                    <img class="bx-base-pofile-cover-image bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />"/>
+                    <img alt="" class="bx-base-pofile-cover-image bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />"/>
                 </p>
                 <div class="bx-cover-gradient"></div>
             </div>
@@ -11,7 +11,7 @@
                 <bx_if:show_avatar>
                     <div class="bx-base-pofile-cover-thumb bx-def-ava-big-size __add_class__">
                         <a class="bx-base-pofile-cover-thumb" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                            <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size __img_class__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                            <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size __img_class__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                             <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size bx-def-border __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>">__letter__</p>
                         </a>
                         <bx_if:show_online>

+ 2 - 2
modules/boonex/decorous/data/template/bx_persons/cover.html

@@ -4,7 +4,7 @@
             <div class="bx-base-pofile-cover-image bx-def-page-width bx-def-centered bx-def-box-sizing ">
                 <div class="bx-base-pofile-cover-image-cnt bx-def-box-sizing">
                     <p class="bx-base-pofile-cover-image bx-def-box-sizing flex justify-center items-center">
-                        <img class="bx-base-pofile-cover-image bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />"/>
+                        <img alt="" class="bx-base-pofile-cover-image bx-image-edit-source-__unique_id__" style="background-image:url(__cover_url__); __cover_settins__" src="<bx_icon_url:spacer.gif />"/>
                     </p>
                     <div class="bx-cover-gradient"></div>
                 </div>
@@ -13,7 +13,7 @@
                 <bx_if:show_avatar>
                     <div class="bx-base-pofile-cover-thumb bx-def-ava-big-size __add_class__">
                         <a class="bx-base-pofile-cover-thumb" href="javascript:" <bx_if:is_avatar>onclick="$('#__picture_popup_id__').dolPopupImage('__picture_url__', $(this).parent()); event.stopPropagation();"</bx_if:is_avatar>>
-                            <img class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
+                            <img alt="__title_attr__" class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size __img_class__ bx-image-edit-source-__unique_id__" src="__ava_url__" <bx_if:show_ava_letter> style="display:none" </bx_if:show_ava_letter> />
                             <p class="bx-base-pofile-unit-thumb bx-def-ava-big bx-def-ava-big-size bx-def-border __img_class__-placeholder" style="background-color:rgba(__color__);<bx_if:show_ava_image>display:none</bx_if:show_ava_image>">__letter__</p>
                         </a>
                         <bx_if:show_online>

+ 6 - 0
modules/boonex/english/data/langs/system/en.xml

@@ -4536,8 +4536,14 @@
     <string name="_sys_txt_quill_tooltip_color"><![CDATA[Select a text color]]></string>
     <string name="_sys_txt_quill_tooltip_background"><![CDATA[Select a background color]]></string>
     <string name="_sys_txt_quill_tooltip_align"><![CDATA[Text alignment]]></string>
+    <string name="_sys_txt_quill_tooltip_align_center"><![CDATA[Align center]]></string>
+    <string name="_sys_txt_quill_tooltip_align_justify"><![CDATA[Justify]]></string>
+    <string name="_sys_txt_quill_tooltip_align_left"><![CDATA[Align left]]></string>
+    <string name="_sys_txt_quill_tooltip_align_right"><![CDATA[Align right]]></string>
     <string name="_sys_txt_quill_tooltip_header"><![CDATA[Select the text style]]></string>
     <string name="_sys_txt_quill_tooltip_embed"><![CDATA[Insert an embed]]></string>
+    <string name="_sys_txt_quill_tooltip_emoji"><![CDATA[Insert an emoji]]></string>
+    <string name="_sys_txt_quill_tooltip_show_html"><![CDATA[Edit HTML source]]></string>
     <string name="_sys_txt_quill_tooltip_embed_popup_header"><![CDATA[Paste an URL for embed]]></string>
     <string name="_sys_txt_quill_tooltip_link_popup_header"><![CDATA[Paste an URL]]></string>
     <string name="_sys_queue_emails"><![CDATA[Emails]]></string>

+ 4 - 0
modules/boonex/forum/classes/BxForumGrid.php

@@ -89,6 +89,10 @@ class BxForumGrid extends BxBaseModGeneralGrid
             $this->_aQueryAppend['filter3'] = $this->_sFilter1Value;
         }
 
+        $this->_aFilterAreaLabels = [
+            $this->_sFilter3Name => '_sys_grid_lbl_order', // "Order": filter3 has no placeholder option to borrow a name from
+        ];
+
         $this->_sDefaultSortingOrder = 'DESC';
         $this->_sDefaultSource = $this->_aOptions['source'];
     }

+ 2 - 0
modules/boonex/massmailer/classes/BxMassMailerTemplate.php

@@ -164,6 +164,7 @@ class BxMassMailerTemplate extends BxBaseModGeneralTemplate
     public function entryBreadcrumb($aContentInfo, $aTmplVarsItems = array())
     {
         $CNF = &$this->getModule()->_oConfig->CNF;
+        $sTitleHome = $this->getModule()->_aModule['title'];
         $aTmplVarsItems = array(array(
             'url' => $aContentInfo['url'],
             'title' => bx_process_output($aContentInfo[$CNF['FIELD_TITLE']])
@@ -172,6 +173,7 @@ class BxMassMailerTemplate extends BxBaseModGeneralTemplate
         return $this->parseHtmlByName('breadcrumb.html', array(
             'url_home' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_MANAGE_CAMPAIGNS'])),
             'icon_home' => $CNF['ICON'],
+            'title_home' => bx_html_attribute(strip_tags(_t($sTitleHome ? $sTitleHome : '_sys_menu_item_title_system_home')), BX_ESCAPE_STR_QUOTE),
             'bx_repeat:items' => $aTmplVarsItems
         ));
     }

+ 1 - 0
modules/boonex/tasks/classes/BxTasksTemplate.php

@@ -648,6 +648,7 @@ class BxTasksTemplate extends BxBaseModTextTemplate
         $aInput = [
             'type' => 'select', 
             'name' => 'filters',
+            'area_label' => '_bx_tasks_filter_title_select',
             'value' => $aParams['filter_selected'] ?? 0,
             'values' => [
                 ['key' => 0, 'value' => _t('_bx_tasks_filter_title_select')]

+ 1 - 0
modules/boonex/timeline/classes/BxTimelineTemplate.php

@@ -1311,6 +1311,7 @@ class BxTimelineTemplate extends BxBaseModNotificationsTemplate
         $aTmplVars = array(
             'style_prefix' => $sStylePrefix,
             'visible' => ($aParams['view'] == BX_TIMELINE_VIEW_TIMELINE && $bVisible) || ($aParams['view'] == BX_TIMELINE_VIEW_OUTLINE && $bEnabled && $bVisible) ? 'block' : 'none',
+            'disabled' => !$bEnabled ? ' disabled="disabled"' : '',
             'bx_if:is_disabled' => array(
                 'condition' => !$bEnabled,
                 'content' => array()

+ 8 - 2
modules/boonex/timeline/template/css/view.css

@@ -449,8 +449,14 @@ div.bx-tl-item-content form {
     padding: 1rem 1rem 0 1rem;
 }
 div.bx-tl-item-content div.bx-tl-title {
-    text-overflow: ellipsis;
-    overflow: hidden;
+    /* This box used to be `overflow: hidden`, which clipped the focus ring of the title link sitting
+       flush inside it. The clipping was really there to stop a long unbroken title from widening the
+       page, and wrapping does that without hiding anything; `text-overflow` never applied here anyway,
+       the box is a flex container. */
+    overflow-wrap: anywhere;
+}
+div.bx-tl-item-content div.bx-tl-title div.bx-tl-title-cnt {
+    min-width: 0;
 }
 div.bx-tl-item-content div.bx-tl-content iframe[src*='video'] {
     width: 100%;

+ 1 - 1
modules/boonex/timeline/template/item_outline.html

@@ -48,7 +48,7 @@
                     </div>
                     <bx_if:show_manage>
                         <div class="__style_prefix__-item-manage">
-                            <a href="javascript:void(0)" onclick="__onclick__">
+                            <a href="javascript:void(0)" onclick="__onclick__" title="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-label="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-haspopup="dialog" aria-expanded="false">
                                 <span class="bx-menu-item-icon bx-def-align-center"><i class="sys-icon ellipsis-v"></i></span>
                             </a>
                         </div>

+ 1 - 1
modules/boonex/timeline/template/item_timeline.html

@@ -47,7 +47,7 @@
                 </div>
                 <bx_if:show_manage>
                     <div class="__style_prefix__-item-manage">
-                        <a href="javascript:void(0)" onclick="__onclick__">
+                        <a href="javascript:void(0)" onclick="__onclick__" title="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-label="<bx_text_attribute:_bx_timeline_menu_title_item_manage />" aria-haspopup="dialog" aria-expanded="false">
                             <span class="bx-menu-item-icon bx-def-align-center"><i class="sys-icon ellipsis-v"></i></span>
                         </a>
                     </div>

+ 1 - 1
modules/boonex/timeline/template/load_more.html

@@ -1,6 +1,6 @@
 <div class="__style_prefix__-load-more bx-def-padding-sec-top bx-clearfix" style="display:__visible__;">
     <div class="__style_prefix__-load-more-cnt flex items-center justify-between">
-        <button class="bx-btn<bx_if:is_disabled> bx-btn-disabled</bx_if:is_disabled>" <bx_if:show_on_click>onclick="__on_click__"</bx_if:show_on_click>>
+        <button class="bx-btn<bx_if:is_disabled> bx-btn-disabled</bx_if:is_disabled>"__disabled__ <bx_if:show_on_click>onclick="__on_click__"</bx_if:show_on_click>>
             <bx_text:_bx_timeline_txt_load_more />
         </button>
         <bx_if:show_jump_to>

+ 1 - 1
modules/boonex/timeline/template/load_more_auto.html

@@ -1,3 +1,3 @@
 <div class="__style_prefix__-load-more __style_prefix__-lm-auto bx-def-padding-sec-topbottom bx-clearfix" style="display:__visible__;">
-    <button class="bx-btn bx-btn-disabled">__loading__</button>
+    <button class="bx-btn bx-btn-disabled" disabled="disabled">__loading__</button>
 </div>

+ 0 - 3
studio/template/scripts/BxBaseStudioMenu.php

@@ -46,9 +46,6 @@ class BxBaseStudioMenu extends BxDolStudioMenu
             $aItem['class_add'] = '';
         $aItem['class_add'] .= ' ' . str_replace('_', '-', $aItem['name']);
 
-        if(!empty($aItem['attrs_add']))
-            $aItem['attrs'] .= ' ' . $aItem['attrs_add'];
-
         // an item may keep its icon as an <img> (icon_inline => false): app tile artwork stays out of the document and its gradient ids never collide
         if($this->_bInlineIcons && ($aItem['icon_inline'] ?? true) && $aItem['bx_if:image']['condition'] && ($sImage = $this->_oTemplate->getIconContent($aItem['icon'])) !== false)
             $aItem = array_merge($aItem, [

+ 1 - 1
template/_page_content_beg.html

@@ -2,4 +2,4 @@ __informer__
 <div class="bx-page-wrapper bx-def-page-width mx-auto px-3 md:px-4 lg:px-6 box-border">
     <div id="bx-content-container" class="bx-content-container">
         <bx_injection:injection_content_before />
-        <main role="main" id="bx-content-main" class="bx-content-main my-4 lg:my-6">
+        <main role="main" id="bx-content-main" class="bx-content-main my-4 lg:my-6" tabindex="-1">

+ 1 - 1
template/_page_submenu.html

@@ -8,7 +8,7 @@
                         <bx_menu:sys_site_submenu />
                     </div>
                 </div>
-                <div class="flex-none">
+                <div class="flex-none z-0">
                     <bx_menu:sys_site_manage />
                 </div>
             </div>

+ 4 - 3
template/_page_toolbar_search.html

@@ -1,12 +1,13 @@
-<div class="bx-ti-search-box flex-auto flex items-start md:min-w-88 ltr:ml-2 rtl:mr-2 bg-white/95 dark:bg-gray-800/95 rounded-md shadow-md hidden">
+<div id="bx-ti-search-box" class="bx-ti-search-box flex-auto flex items-start md:min-w-88 ltr:ml-2 rtl:mr-2 bg-white/95 dark:bg-gray-800/95 rounded-md shadow-md hidden">
     <div class="relative w-full">__sys_site_search__</div>
 </div>
 <div class="bx-ti-search-button group relative inline-flex items-center ltr:ml-2 rtl:mr-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 text-base font-medium leading-6 border border-transparent hover:border-gray-200/50 active:border-gray-300 dark:hover:border-gray-700/50 dark:active:border-gray-700 bg-gray-100 dark:bg-gray-700/50 active:bg-gray-300 dark:active:bg-black/50 hover:bg-gray-200/50 dark:hover:bg-gray-700/50 rounded-full">
-    <a href="javascript:void(0)" onclick="javascript:bx_site_search_show(this)">
+    <button type="button" class="inline-block rounded-full" onclick="javascript:bx_site_search_show(this)" aria-expanded="false" aria-controls="bx-ti-search-box">
         <span class="relative flex items-center justify-center h-10 w-10">
             <div class="bx-icon">
                 <bx_image_auto:search />
             </div>
         </span>
-    </a>
+        <span class="sr-only"><bx_text:_sys_menu_item_title_system_search /></span>
+    </button>
 </div>

+ 1 - 1
template/comments_uploader_bs.html

@@ -4,7 +4,7 @@
 <div class="bx-clear"></div>
 <div class="cmts-uploader px-2">
     <div id="__div_id__" class="filepond bx-uploader-html5-drop-area border-2 border-gray-300 border-dashed rounded-lg hidden"></div>
-    <input type="text" id="__focus_div_id__" class="bx-uploader-html5-paste-catcher" tabindex="1" style="display: none" />
+    <input type="text" id="__focus_div_id__" class="bx-uploader-html5-paste-catcher" tabindex="-1" aria-label="<bx_text_attribute:_sys_uploader_simple_submit_button />" style="display: none" />
     <div id="__errors_container_id__" class="bx-uploader-html5-errors-area bx-def-font-small"></div>
     <div class="bx-clear"></div>
     <div class="relative">

+ 1 - 1
template/connection_counter.html

@@ -2,6 +2,6 @@
     <span class="__class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</span>
 </bx_if:show_text>
 <bx_if:show_link>
-    <a class="__class__ group flex items-center p-1 border-none rounded-full text-gray-500 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 text-sm font-normal" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a>
+    <a class="__class__ group flex items-center p-1 border-none rounded-full text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 text-sm font-normal" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a>
 </bx_if:show_link>
 __script__

+ 4 - 2
template/cover.html

@@ -16,9 +16,11 @@
                             <img class="bx-def-border bx-def-round-corners" style="background-image:url(__icon_url__)" src="<bx_icon_url:spacer.gif />" />
                         </div>
                     </bx_if:image>
-                    <div class="bx-cover-title bx-def-margin-sec-topbottom bx-def-font-h1" style="z-index:2;">
+                    <bx_if:title>
+                    <h1 class="bx-cover-title bx-def-margin-sec-topbottom bx-def-font-h1" style="z-index:2;">
                         <span class="bx-def-font-contrasted">__title__</span>
-                    </div>
+                    </h1>
+                    </bx_if:title>
                     <div class="bx-cover-actions">__actions__</div>
                 </div>
             </div>

+ 1 - 1
template/cover_empty.html

@@ -1 +1 @@
-<div class="bx-menu-toolbar-padding pt-16"></div>
+<div class="bx-menu-toolbar-padding pt-16"></div><bx_if:title><h1 class="sr-only">__title__</h1></bx_if:title>

+ 2 - 1
template/css/forms.css

@@ -201,7 +201,8 @@
 .bx-form-input-wrapper-password {
     padding: 0;
 }
-.bx-form-input-wrapper-password > a {
+.bx-form-input-wrapper-password > .password-show,
+.bx-form-input-wrapper-password > .password-hide {
     position: absolute;
     z-index: 1000;
     

+ 35 - 0
template/css/fullcalendar.css

@@ -3,3 +3,38 @@
     flex-direction: column;
     grid-row-gap: 5px;
 }
+
+/* WCAG 2.1 AA (1.4.3 Contrast Minimum).
+   FullCalendar dims the adjacent-month days to 30% opacity and tints the current
+   day's cell, which pushes both day numbers under 4.5:1 in the light and the dark
+   scheme alike. The month hierarchy is kept, but with explicit colours instead of
+   transparency. FullCalendar injects its own stylesheet from JS after this file,
+   so the opacity reset has to be flagged important. */
+.fc .fc-daygrid-day-number {
+    color: #374151;
+}
+
+.fc .fc-daygrid-day.fc-day-other .fc-daygrid-day-top {
+    opacity: 1 !important;
+}
+
+.fc .fc-daygrid-day.fc-day-other .fc-daygrid-day-number {
+    color: #6b7280;
+}
+
+.fc .fc-daygrid-day.fc-day-today .fc-daygrid-day-number {
+    color: #111827;
+    font-weight: 600;
+}
+
+.dark .fc .fc-daygrid-day-number {
+    color: #e5e7eb;
+}
+
+.dark .fc .fc-daygrid-day.fc-day-other .fc-daygrid-day-number {
+    color: #9ca3af;
+}
+
+.dark .fc .fc-daygrid-day.fc-day-today .fc-daygrid-day-number {
+    color: #f3f4f6;
+}

+ 45 - 6
template/css/tailwind.css

@@ -36,9 +36,37 @@
         @apply rounded;
     }
 
+    /* The logo link wraps a block level <img>, so as a plain inline element it has no inline box
+       of its own and the browser paints no focus ring on it at all. Giving it a box makes the
+       ring below visible without moving the logo. */
+    #bx-logo-container > a {
+        @apply inline-flex items-center align-middle rounded;
+    }
+
+    /* Public site only - Studio has its own .bx-std focus rings further down, and body.bx-std keeps
+       these off it. Anything focusable that is not a .bx-btn (those draw their own ring) would
+       otherwise fall back to the browser default ring, which Safari does not draw for links at all and
+       which Chrome tints with the OS accent colour - so the site showed blue rings on the menus and,
+       say, orange ones on the app tiles. One rule keeps every focus indicator identical. An outline is
+       used rather than a ring box-shadow because the toolbar's more-auto wrapper and the sidebar
+       scroller both clip overflow. */
+    body:not(.bx-std) a:not(.bx-btn):focus-visible,
+    body:not(.bx-std) button:not(.bx-btn):focus-visible,
+    body:not(.bx-std) summary:focus-visible,
+    body:not(.bx-std) [tabindex="0"]:focus-visible {
+        @apply outline outline-2 outline-offset-2 outline-blue-500 dark:outline-blue-400;
+    }
+
+    /* The main menu row is inside .bx-menu-more-auto-wrapper, which sets overflow:hidden, so an
+       offset outline would be clipped top and bottom - draw it on the border edge there. */
+    body:not(.bx-std) .bx-menu-more-auto-wrapper .bx-stl-mil:focus-visible,
+    body:not(.bx-std) .bx-menu-more-auto-wrapper .bx-stl-mil-sm:focus-visible {
+        @apply outline-offset-0;
+    }
+
     .bx-def-vanilla-html {
         @apply prose dark:prose-invert;
-        @apply prose-a:text-blue-500 prose-a:font-medium prose-a:no-underline hover:prose-a:underline visited:prose-a:text-purple-500;
+        @apply prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:font-medium prose-a:no-underline hover:prose-a:underline visited:prose-a:text-purple-700 dark:visited:prose-a:text-purple-400;
     }
     
     .bx-def-vanilla-html.bx-def-vh-sm {
@@ -62,7 +90,7 @@
     }
 
     .bx-artificer .bx-menthion-link {
-        @apply font-normal text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-600;
+        @apply font-normal text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300;
     }
 
     .bx-artificer .bx-def-box-sizing {
@@ -487,7 +515,7 @@
     }
 
     .bx-def-font-grayed {
-        @apply text-gray-700 dark:text-gray-500 text-opacity-60;
+        @apply text-gray-600 dark:text-gray-400;
     }
 
     .bx-def-font-author,
@@ -506,7 +534,7 @@
     }
 
     .bx-artificer .bx-def-a-colored {
-        @apply text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-600;
+        @apply text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300;
     }
 
     .bx-std a.bx-def-font-meta,
@@ -792,6 +820,17 @@
         @apply bg-gray-50 dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-400 hover:text-gray-400 dark:hover:text-gray-400 focus:ring-0 focus:ring-offset-0 shadow-none cursor-default !important;
     }
 
+    /* Help tour (Shepherd): the vendored plugins_public/shepherd/css/shepherd.css paints the primary button #3288e6 with 75% white text (#cce1f9, 2.71:1) and is
+       regenerated by `yarn build:shepherd`, so the fix lives here. The selector names .shepherd-button-primary because Tailwind keeps a component rule only when its
+       selector contains a class it saw while scanning content (that class is in template/homepage_tour.html and modules/boonex/help_tours/template/tour.html).
+       Specificity (0,3,0) beats the vendor .shepherd-button (0,1,0) even though shepherd.css is loaded in a later bundle. */
+    .shepherd-element .shepherd-button.shepherd-button-primary {
+        @apply bg-blue-600 text-white;
+    }
+    .shepherd-element .shepherd-button.shepherd-button-primary:not(:disabled):hover {
+        @apply bg-blue-700 text-white;
+    }
+
     /* Button sizes (height / corner radius): mini 28px/6px with the hit area extended to 44px, small 36px/8px, default 44px/10px, large 52px/12px, xlarge 60px/14px. */
     .bx-btn.bx-btn-mini {
         @apply min-h-7 px-2.5 py-1 text-xs rounded-md;
@@ -825,7 +864,7 @@
     .bx-artificer form a:not(.bx-btn),
     .bx-artificer .sys-jf-agreement a:not(.bx-btn), 
     .bx-artificer .sys-lf-agreement a:not(.bx-btn) {
-        @apply font-normal text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-600;
+        @apply font-normal text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300;
     }
 
     .bx-artificer .bx-form-caption,
@@ -1373,7 +1412,7 @@
     .sys-ac-link .sys-action-counter {
         @apply flex items-center;
         @apply border-none;
-        @apply text-gray-500 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300;
+        @apply text-gray-600 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300;
         @apply rounded-full;
     }
     .sys-action-counter.sys-ac-only.sys-ac-link,

+ 1 - 1
template/designbox_1.html

@@ -9,7 +9,7 @@
                 <div class="bx-dbt-ic grow-0">__icon_html__</div>
             </bx_if:show_db_icon_html>
             <div class="bx-dbt-cnt">
-                <div class="bx-dbt-ttl">__title__</div>
+                <h2 class="bx-dbt-ttl">__title__</h2>
                 <bx_if:show_db_description>
                     <div class="bx-dbt-dsc text-sm font-normal">__description__</div>
                 </bx_if:show_db_description>

+ 1 - 1
template/designbox_11.html

@@ -9,7 +9,7 @@
                 <div class="bx-dbt-ic grow-0">__icon_html__</div>
             </bx_if:show_db_icon_html>
             <div class="bx-dbt-cnt">
-                <div class="bx-dbt-ttl">__title__</div>
+                <h2 class="bx-dbt-ttl">__title__</h2>
                 <bx_if:show_db_description>
                     <div class="bx-dbt-dsc text-sm font-normal">__description__</div>
                 </bx_if:show_db_description>

+ 1 - 1
template/designbox_14.html

@@ -9,7 +9,7 @@
                 <div class="bx-dbt-ic grow-0">__icon_html__</div>
             </bx_if:show_db_icon_html>
             <div class="bx-dbt-cnt">
-                <div class="bx-dbt-ttl">__title__</div>
+                <h2 class="bx-dbt-ttl">__title__</h2>
                 <bx_if:show_db_description>
                     <div class="bx-dbt-dsc text-sm font-normal">__description__</div>
                 </bx_if:show_db_description>

+ 1 - 1
template/designbox_4.html

@@ -9,7 +9,7 @@
                 <div class="bx-dbt-ic grow-0">__icon_html__</div>
             </bx_if:show_db_icon_html>
             <div class="bx-dbt-cnt">
-                <div class="bx-dbt-ttl">__title__</div>
+                <h2 class="bx-dbt-ttl">__title__</h2>
                 <bx_if:show_db_description>
                     <div class="bx-dbt-dsc text-sm font-normal">__description__</div>
                 </bx_if:show_db_description>

+ 1 - 1
template/form_field_password.html

@@ -1,2 +1,2 @@
 __input__
-<a class="password-show" href="javascript:void(0)" title="<bx_text_attribute:_sys_form_input_password_show />"><i class="sys-icon eye"></i></a>
+<button type="button" class="password-show text-blue-600 dark:text-blue-500" title="<bx_text_attribute:_sys_form_input_password_show />" aria-label="<bx_text_attribute:_sys_form_input_password_show />"><i class="sys-icon eye"></i></button>

+ 4 - 4
template/layout_2_columns.html

@@ -1,16 +1,16 @@
 <!-- layout 2 columns [ start ] -->
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-2-columns">
     <div class="bx-layout-row">
-        <aside class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-1">
                 __cell_1__
             </div>
-        </aside>
-        <main class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2" aria-label="Main">
+        </div>
+        <div class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </main>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 8 - 8
template/layout_3_columns.html

@@ -1,23 +1,23 @@
 <!-- layout 3 columns [ start ] -->
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-3-columns">
     <div class="bx-layout-row">
-        <section class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/3 flex-auto md:flex-none" aria-label="Sidebar" >
+        <div class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/3 flex-auto md:flex-none" >
             <div class="bx-layout-cell" id="bx-layout-cell-1">
                 __cell_1__
             </div>
-        </section>
-        <section aria-label="Content" class="max-w-full md:w-2/3 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
-            <main class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-1/2" aria-label="Main">
+        </div>
+        <div class="max-w-full md:w-2/3 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
+            <div class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-1/2">
                 <div class="bx-layout-cell" id="bx-layout-cell-2">
                     __cell_2__
                 </div>
-            </main>
-            <aside class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-1/2 flex-auto" aria-label="Aside">
+            </div>
+            <div class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-1/2 flex-auto">
                 <div class="bx-layout-cell" id="bx-layout-cell-3">
                     __cell_3__
                 </div>
-            </aside>
-        </section>
+            </div>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 8 - 8
template/layout_bar_content_bar.html

@@ -1,23 +1,23 @@
 <!-- layout bar content bar (bcb) [ start ] -->
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-bcb">
     <div class="bx-layout-row">
-        <section class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/4 flex-auto md:flex-none" aria-label="Sidebar" >
+        <div class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/4 flex-auto md:flex-none" >
             <div class="bx-layout-cell" id="bx-layout-cell-1">
                 __cell_1__
             </div>
-        </section>
-        <section aria-label="Content" class="max-w-full md:w-3/4 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
-            <main class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:w-2/3" aria-label="Main">
+        </div>
+        <div class="max-w-full md:w-3/4 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
+            <div class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:w-2/3">
                 <div class="bx-layout-cell" id="bx-layout-cell-2">
                     __cell_2__
                 </div>
-            </main>
-            <aside class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-1/3" aria-label="Aside">
+            </div>
+            <div class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-1/3">
                 <div class="bx-layout-cell" id="bx-layout-cell-3">
                     __cell_3__
                 </div>
-            </aside>
-        </section>
+            </div>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 4 - 4
template/layout_bar_left.html

@@ -1,16 +1,16 @@
 <!-- layout bar left [ start ] -->
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-bar-left">
     <div class="bx-layout-row">
-        <aside class="bx-layout-col bx-layout-bar-left-bar bx-layout-column-thin md:w-1/3" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-bar-left-bar bx-layout-column-thin md:w-1/3">
             <div class="bx-layout-cell" id="bx-layout-cell-1">
                 __cell_1__
             </div>
-        </aside>
-        <main class="bx-layout-col bx-layout-bar-left-content bx-layout-column-wide md:w-2/3" aria-label="Main">
+        </div>
+        <div class="bx-layout-col bx-layout-bar-left-content bx-layout-column-wide md:w-2/3">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </main>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 4 - 4
template/layout_bar_right.html

@@ -1,16 +1,16 @@
 <!-- layout bar right [ start ] -->
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-bar-right">
     <div class="bx-layout-row">
-        <main class="bx-layout-col bx-layout-bar-right-content bx-layout-column-wide md:w-2/3" aria-label="Main">
+        <div class="bx-layout-col bx-layout-bar-right-content bx-layout-column-wide md:w-2/3">
             <div class="bx-layout-cell" id="bx-layout-cell-1">
                 __cell_1__
             </div>
-        </main>
-        <aside class="bx-layout-col bx-layout-bar-right-bar bx-layout-column-thin md:w-1/3" aria-label="Aside">
+        </div>
+        <div class="bx-layout-col bx-layout-bar-right-bar bx-layout-column-thin md:w-1/3">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </aside>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 4 - 4
template/layout_bottom_area_2_columns.html

@@ -1,16 +1,16 @@
 <!-- layout top area 2 columns [ start ] -->
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-bottom-area-2-columns">
     <div class="bx-layout-row">
-        <aside class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </aside>
-        <main class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2" aria-label="Main">
+        </div>
+        <div class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </main>
+        </div>
     </div>
     <bx_include_auto:_row_1_column.html />
     <bx_include_auto:_row_1_column_dump.html />

+ 4 - 4
template/layout_top_area_2_columns.html

@@ -2,16 +2,16 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-top-area-2-columns">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <aside class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </aside>
-        <main class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2" aria-label="Main">
+        </div>
+        <div class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </main>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 8 - 8
template/layout_top_area_3_columns.html

@@ -2,23 +2,23 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-top-area-3-columns">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row" >
-        <section class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/3 flex-auto md:flex-none" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/3 flex-auto md:flex-none">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </section>
-        <section aria-label="Content" class="max-w-full md:w-2/3 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap" >
-            <main class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-1/2" aria-label="Main">
+        </div>
+        <div class="max-w-full md:w-2/3 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap" >
+            <div class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-1/2">
                 <div class="bx-layout-cell" id="bx-layout-cell-3">
                     __cell_3__
                 </div>
-            </main>
-            <aside class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin flex-auto lg:w-1/2 " aria-label="Aside">
+            </div>
+            <div class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin flex-auto lg:w-1/2 ">
                 <div class="bx-layout-cell" id="bx-layout-cell-4">
                     __cell_4__
                 </div>
-            </aside>
-        </section>
+            </div>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 8 - 8
template/layout_top_area_bar_content_bar.html

@@ -2,23 +2,23 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-top-area-bcb">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <section class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/4 flex-auto md:flex-none" aria-label="Sidebar" >
+        <div class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/4 flex-auto md:flex-none" >
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </section>
-        <section aria-label="Content" class="max-w-full md:w-3/4 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
-            <main class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-2/3" aria-label="Main">
+        </div>
+        <div class="max-w-full md:w-3/4 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
+            <div class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-2/3">
                 <div class="bx-layout-cell" id="bx-layout-cell-3">
                     __cell_3__
                 </div>
-            </main>
-            <aside class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin flex-auto lg:w-1/3" aria-label="Aside">
+            </div>
+            <div class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin flex-auto lg:w-1/3">
                 <div class="bx-layout-cell" id="bx-layout-cell-4">
                     __cell_4__
                 </div>
-            </aside>
-        </section>
+            </div>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 4 - 4
template/layout_top_area_bar_left.html

@@ -2,16 +2,16 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-top-area-bar-left">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <main class="bx-layout-col bx-layout-bar-left-bar bx-layout-column-thin md:w-1/3" aria-label="Main">
+        <div class="bx-layout-col bx-layout-bar-left-bar bx-layout-column-thin md:w-1/3">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </main>
-        <aside class="bx-layout-col bx-layout-bar-left-content bx-layout-column-wide md:w-2/3" aria-label="Aside">
+        </div>
+        <div class="bx-layout-col bx-layout-bar-left-content bx-layout-column-wide md:w-2/3">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </aside>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 4 - 4
template/layout_top_area_bar_right.html

@@ -2,16 +2,16 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-top-area-bar-right">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <main class="bx-layout-col bx-layout-bar-right-content bx-layout-column-wide md:w-2/3" aria-label="Main">
+        <div class="bx-layout-col bx-layout-bar-right-content bx-layout-column-wide md:w-2/3">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </main>
-        <aside class="bx-layout-col bx-layout-bar-right-bar bx-layout-column-thin md:w-1/3" aria-label="Aside">
+        </div>
+        <div class="bx-layout-col bx-layout-bar-right-bar bx-layout-column-thin md:w-1/3">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </aside>
+        </div>
     </div>
     <bx_include_auto:_row_1_column_dump.html />
 </div>

+ 4 - 4
template/layout_topbottom_area_2_columns.html

@@ -2,16 +2,16 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-topbottom-area-2-columns">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <aside class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-2-columns-column1 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </aside>
-        <main class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2" aria-label="Main">
+        </div>
+        <div class="bx-layout-col bx-layout-2-columns-column2 bx-layout-column-half md:w-1/2">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </main>
+        </div>
     </div>
     <div class="bx-layout-row">
         <div class="bx-layout-col bx-layout-1-column-column1 bx-layout-column-fullwidth">

+ 4 - 4
template/layout_topbottom_area_bar_left.html

@@ -2,16 +2,16 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-topbottom-area-bar-left">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <aside class="bx-layout-col bx-layout-bar-left-bar bx-layout-column-thin md:w-1/3" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-bar-left-bar bx-layout-column-thin md:w-1/3">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </aside>
-        <main class="bx-layout-col bx-layout-bar-left-content bx-layout-column-wide md:w-2/3" aria-label="Main">
+        </div>
+        <div class="bx-layout-col bx-layout-bar-left-content bx-layout-column-wide md:w-2/3">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </main>
+        </div>
     </div>
     <div class="bx-layout-row">
         <div class="bx-layout-col bx-layout-1-column-column1 bx-layout-column-fullwidth">

+ 4 - 4
template/layout_topbottom_area_bar_right.html

@@ -2,16 +2,16 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-topbottom-area-bar-right">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <main class="bx-layout-col bx-layout-bar-right-content bx-layout-column-wide md:w-2/3" aria-label="Main">
+        <div class="bx-layout-col bx-layout-bar-right-content bx-layout-column-wide md:w-2/3">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </main>
-        <aside class="bx-layout-col bx-layout-bar-right-bar bx-layout-column-thin md:w-1/3" aria-label="Aside">
+        </div>
+        <div class="bx-layout-col bx-layout-bar-right-bar bx-layout-column-thin md:w-1/3">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </aside>
+        </div>
     </div>
     <div class="bx-layout-row">
         <div class="bx-layout-col bx-layout-1-column-column1 bx-layout-column-fullwidth">

+ 8 - 8
template/layout_topbottom_area_col1_col3_col2.html

@@ -3,23 +3,23 @@
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row flex flex-col md:flex-row">
         <div class="bx-layout-row">
-            <section class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/6 flex-auto md:flex-none" aria-label="Sidebar" >
+            <div class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-1/6 flex-auto md:flex-none" >
                 <div class="bx-layout-cell" id="bx-layout-cell-2">
                     __cell_2__
                 </div>
-            </section>
-            <section aria-label="Content" class="max-w-full md:w-5/6 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
-                <main class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto md:flex-none lg:w-3/5" aria-label="Main">
+            </div>
+            <div class="max-w-full md:w-5/6 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
+                <div class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto md:flex-none lg:w-3/5">
                     <div class="bx-layout-cell" id="bx-layout-cell-3">
                         __cell_3__
                     </div>
-                </main>
-                <aside class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-2/5" aria-label="Aside">
+                </div>
+                <div class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-2/5">
                     <div class="bx-layout-cell" id="bx-layout-cell-4">
                         __cell_4__
                     </div>
-                </aside>
-            </section>
+                </div>
+            </div>
         </div>
     </div>
     <div class="bx-layout-row">

+ 4 - 4
template/layout_topbottom_area_col1_col5.html

@@ -2,16 +2,16 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-topbottom-area-col1-col5">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <aside class="bx-layout-col bx-layout-col1-col5-column1 bx-layout-column-thin md:w-1/6" aria-label="Sidebar">
+        <div class="bx-layout-col bx-layout-col1-col5-column1 bx-layout-column-thin md:w-1/6">
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </aside>
-        <main class="bx-layout-col bx-layout-col1-col5-column2 bx-layout-column-wide md:w-5/6" aria-label="Main">
+        </div>
+        <div class="bx-layout-col bx-layout-col1-col5-column2 bx-layout-column-wide md:w-5/6">
             <div class="bx-layout-cell" id="bx-layout-cell-3">
                 __cell_3__
             </div>
-        </main>
+        </div>
     </div>
     <div class="bx-layout-row">
         <div class="bx-layout-col bx-layout-1-column-column1 bx-layout-column-fullwidth">

+ 8 - 8
template/layout_topbottom_area_col2_col5_col3.html

@@ -2,23 +2,23 @@
 <div id="__page_id__" class="bx-layout-wrapper bx-layout-topbottom-area-col2-col5-col3">
     <bx_include_auto:_row_1_column.html />
     <div class="bx-layout-row">
-        <section class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-2/10 flex-auto md:flex-none" aria-label="Sidebar" >
+        <div class="bx-layout-col bx-layout-bcb-bar1 bx-layout-column-thin md:w-2/10 flex-auto md:flex-none" >
             <div class="bx-layout-cell" id="bx-layout-cell-2">
                 __cell_2__
             </div>
-        </section>
-        <section aria-label="Content" class="max-w-full md:w-8/10 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
-            <main class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-3/5" aria-label="Main">
+        </div>
+        <div class="max-w-full md:w-8/10 flex-1 flex flex-wrap lg:flex-nowrap bx-layout-gap">
+            <div class="bx-layout-col bx-layout-bcb-content bx-layout-column-half flex-auto lg:flex-none lg:w-3/5">
                 <div class="bx-layout-cell" id="bx-layout-cell-3">
                     __cell_3__
                 </div>
-            </main>
-            <aside class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-2/5" aria-label="Aside">
+            </div>
+            <div class="bx-layout-col bx-layout-bcb-bar2 bx-layout-column-thin lg:w-2/5">
                 <div class="bx-layout-cell" id="bx-layout-cell-4">
                     __cell_4__
                 </div>
-            </aside>
-        </section>
+            </div>
+        </div>
     </div>
     <div class="bx-layout-row">
         <div class="bx-layout-col bx-layout-1-column-column1 bx-layout-column-fullwidth">

+ 1 - 1
template/pt_application.html

@@ -61,7 +61,7 @@
                 __informer__
                 <div class="bx-page-wrapper bx-def-padding-sec-leftright bx-def-box-sizing">
                     <div id="bx-content-container" class="bx-content-container bx-def-padding-sec-leftright">
-                        <main role="main" id="bx-content-main" class="bx-content-main bx-def-margin-top">
+                        <main role="main" id="bx-content-main" class="bx-content-main bx-def-margin-top" tabindex="-1">
                             <bx_injection:injection_content_before />
                             __page_main_code__
                             <bx_injection:injection_content_after />

+ 15 - 1
template/scripts/BxBaseCover.php

@@ -68,6 +68,13 @@ class BxBaseCover extends BxDolCover
             $aParams = array_merge($this->_aOptiondDefault, $mixedOptions);
         }
 
+        //--- The cover title is the page's <h1>. Content view pages deliberately leave it empty and
+        //--- headline the content instead, so the heading element is only emitted when there is a title.
+        $aParams['bx_if:title'] = array (
+            'condition' => !empty($aParams['title']),
+            'content' => array('title' => isset($aParams['title']) ? $aParams['title'] : ''),
+        );
+
         /**
          * @hooks
          * @hookdef hook-system-display_cover 'system', 'display_cover' - hook to override page cover code to be output
@@ -102,7 +109,14 @@ class BxBaseCover extends BxDolCover
      */
     public function displayEmpty ()
     {
-    	return $this->_oTemplate->parseHtmlByName($this->_sTemplateNameEmpty, array());
+        $sTitle = $this->_oTemplate->getPageHeader();
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTemplateNameEmpty, array(
+            'bx_if:title' => array(
+                'condition' => !empty($sTitle),
+                'content' => array('title' => bx_process_output(strip_tags($sTitle))),
+            ),
+        ));
     }
 
     /**

+ 12 - 0
template/scripts/BxBaseFunctions.php

@@ -553,6 +553,18 @@ class BxBaseFunctions extends BxDolFactory implements iBxDolSingleton
                 if(!empty($aButton['class']))
                     $aAttrs['class'] .= ' ' . trim($aButton['class']);
 
+                // an icon-only button renders no text, so it needs an accessible name of its own
+                if(empty($aButton['title'])) {
+                    $sButtonLabel = _t(!empty($aButton['area_label']) ? $aButton['area_label'] : '_sys_menu_item_title_system_more');
+                    $aAttrs['aria-label'] = $sButtonLabel;
+                }
+
+                // the popup opened by bx_menu_popup* is role="dialog", bx_menu_popup_inline keeps aria-expanded in sync
+                if(!empty($aAttrs['onclick']) && strpos($aAttrs['onclick'], 'bx_menu_popup') !== false) {
+                    $aAttrs['aria-haspopup'] = 'dialog';
+                    $aAttrs['aria-expanded'] = 'false';
+                }
+
                 $bTmplVarsButtonIcon = !empty($aButton['icon']);
                 $aTmplVarsButtonIcon = !$bTmplVarsButtonIcon ? array() : array(
                     'icon' => $aButton['icon']

+ 19 - 0
template/scripts/BxBaseGrid.php

@@ -23,6 +23,7 @@ class BxBaseGrid extends BxDolGrid
     protected $_aConfirmMessages = false;
     protected $_bSelectAll = false;
     protected $_isDisplayPopupOnTextOverflow = true;
+    protected $_aFilterAreaLabels = []; // filter name => accessible name (lang key or plain text); filter controls are rendered without a visible caption
 
     public function __construct ($aOptions, $oTemplate)
     {
@@ -1124,6 +1125,22 @@ class BxBaseGrid extends BxDolGrid
         return '';
     }
 
+    /**
+     * Grid filter controls are rendered without a visible caption, so each one needs its own accessible name:
+     * the name the grid declares in $_aFilterAreaLabels, else the "unfiltered" option the filter starts with
+     * ("All", "Any status", "Select one..."), else the generic "Filters".
+     */
+    protected function _getFilterAreaLabel($sFilterName, $aInputValues = [])
+    {
+        if(!empty($this->_aFilterAreaLabels[$sFilterName]))
+            return $this->_aFilterAreaLabels[$sFilterName];
+
+        if(!empty($aInputValues['']) && is_string($aInputValues['']))
+            return $aInputValues[''];
+
+        return '_Filters';
+    }
+
     protected function _getFilterSelectOne($sFilterName, $sFilterValue, $aFilterValues, $mixedAddSelectOne = true, $bAsArray = false)
     {
         if(empty($sFilterName))
@@ -1146,6 +1163,7 @@ class BxBaseGrid extends BxDolGrid
                 'id' => 'bx-grid-' . $sFilterName . '-' . $this->_sObject,
                 'onChange' => ($sOnChange = $this->_getFilterOnChange()) ? 'javascript:' . $sOnChange : '',
             ],
+            'area_label' => $this->_getFilterAreaLabel($sFilterName, $aInputValues),
             'value' => $sFilterValue,
             'values' => $aInputValues
         ];
@@ -1169,6 +1187,7 @@ class BxBaseGrid extends BxDolGrid
                 'id' => 'bx-grid-' . $sFilterName . '-' . $this->_sObject,
                 'onChange' => ($sOnChange = $this->_getFilterOnChange()) ? 'javascript:' . $sOnChange : '',
             ],
+            'area_label' => $this->_getFilterAreaLabel($sFilterName),
             'value' => $sFilterValue,
         ];
 

+ 53 - 11
template/scripts/BxBaseMenu.php

@@ -357,6 +357,12 @@ class BxBaseMenu extends BxDolMenu
 
         $a['link'] = isset($a['link']) ? $this->_oPermalinks->permalink($a['link']) : 'javascript:void(0);';
 
+        $aTmplVarsAddon = $this->_bDisplayAddons ? $this->_getTmplVarsAddon($mixedAddon, $a) : array('addon' => '', 'addonf' => '');
+        $a['bx_if:addon'] = array (
+            'condition' => $this->_bDisplayAddons && !empty($aTmplVarsAddon['addon']),
+            'content' => $aTmplVarsAddon
+        );
+
         $a['attrs'] = $this->_getMenuAttrs($a);
         $a['attrs_wrp'] = '';
 
@@ -420,12 +426,6 @@ class BxBaseMenu extends BxDolMenu
             'content' => $aOnClick
         ];
 
-        $aTmplVarsAddon = $this->_bDisplayAddons ? $this->_getTmplVarsAddon($mixedAddon, $a) : array('addon' => '', 'addonf' => '');
-        $a['bx_if:addon'] = array (
-            'condition' => $this->_bDisplayAddons && !empty($aTmplVarsAddon['addon']),
-            'content' => $aTmplVarsAddon
-        );
-
         $aTmplVarsSubitems = array('subitems' => '');
         $bTmplVarsSubitems = $this->isMultilevel() && !empty($a['subitems']);
         if($bTmplVarsSubitems) {
@@ -536,15 +536,26 @@ class BxBaseMenu extends BxDolMenu
     {
         $sAttrs = '';
 
-        if($sType == 'image' && ($sTitleAttr = $this->_getMenuTitle($a)))
-            $sAttrs .= ' alt="' . $sTitleAttr . '"';
+        if($sType == 'image')
+            $sAttrs .= ' alt="' . $this->_getMenuTitle($a) . '"';
 
         return $sAttrs;
     }
 
     protected function _getMenuTitle($a)
     {
-        return bx_html_attribute(strip_tags(($sTitleAttr = $a['title_attr'] ?? '') && ($sTitleAttr = _t($sTitleAttr)) ? $sTitleAttr : $a['title']));
+        $sTitle = ($sTitleAttr = $a['title_attr'] ?? '') && ($sTitleAttr = _t($sTitleAttr)) ? $sTitleAttr : ($a['title'] ?? '');
+
+        /**
+         * An icon only menu item (the toolbar hamburger, [+ Add], [Notifications], the 'more' triggers, ...) has an
+         * empty `title` in `sys_menu_items`, so it used to get no accessible name at all. Every menu item does have
+         * `title_system` - the label the Studio menu builder shows - so it is used as the fallback name. No new
+         * language keys are needed and every icon only menu item benefits, not just the toolbar.
+         */
+        if(trim(strip_tags($sTitle)) === '' && ($sTitleSystem = $a['title_system'] ?? ''))
+            $sTitle = _t($sTitleSystem);
+
+        return bx_html_attribute(strip_tags($sTitle));
     }
 
     protected function _getMenuAreaLabel($a)
@@ -552,6 +563,20 @@ class BxBaseMenu extends BxDolMenu
         return ($sAreaLabel = $a['area_label'] ?? '') && ($sAreaLabel = strip_tags(_t($sAreaLabel))) ? bx_html_attribute($sAreaLabel) : '';
     }
 
+    /**
+     * True when the visible content of the menu item link already carries its accessible name AND that content is
+     * richer than the title alone (an addon counter is rendered next to it). In that case an aria-label repeating
+     * only the title would hide the counter from the accessible name and break WCAG 2.5.3 Label in Name, so it is
+     * omitted and the link is named by its own text. Icon only items - and items whose template never prints the
+     * title - keep their aria-label.
+     */
+    protected function _isMenuLabelInContent($a)
+    {
+        $bTitle = !empty($a['title']) && (!isset($a['icon_only']) || (int)$a['icon_only'] == 0);
+
+        return $bTitle && !empty($a['bx_if:addon']['condition']);
+    }
+
     protected function _getMenuCallbackDataAPI($a)
     {
         return [];
@@ -565,7 +590,7 @@ class BxBaseMenu extends BxDolMenu
             'condition' => (bool)$sIconUrl,
             'content' => [
                 'icon_url' => $sIconUrl,
-                'attrs' => ''
+                'attrs' => 'alt=""'
             ],
         ];
         $a['bx_if:icon'] = array (
@@ -630,7 +655,24 @@ class BxBaseMenu extends BxDolMenu
         if($this->_bAddNoFollow && !empty($aMenuItem['link']) && preg_match('@^https?://@', $aMenuItem['link']) && strncmp($aMenuItem['link'], BX_DOL_URL_ROOT, strlen(BX_DOL_URL_ROOT)) !== 0)
             $sAttrs .= ' rel="noreferrer"';
 
-        if(($sAreaLabel = $this->_getMenuAreaLabel($aMenuItem) ?: $sTitleAttr))
+        /**
+         * `attrs_add` lets a menu item declare extra attributes of its own - the same convention as `class_add` -
+         * so that a PHP built item (a disclosure trigger, for example) can add aria-haspopup/aria-expanded without
+         * a mechanism of its own.
+         */
+        if(!empty($aMenuItem['attrs_add']))
+            $sAttrs .= ' ' . $aMenuItem['attrs_add'];
+
+        /**
+         * An explicit `area_label` always wins. Otherwise the item title is used as the accessible name, but only
+         * when the visible content of the link does not already provide one - an aria-label that repeats the title
+         * while the link also renders an addon (a counter) hides that counter from the accessible name and breaks
+         * WCAG 2.5.3 Label in Name.
+         */
+        if(!($sAreaLabel = $this->_getMenuAreaLabel($aMenuItem)) && !$this->_isMenuLabelInContent($aMenuItem))
+            $sAreaLabel = $sTitleAttr;
+
+        if($sAreaLabel)
             $sAttrs .= ' aria-label="' . $sAreaLabel . '"';
 
         return $sAttrs;

+ 1 - 1
template/scripts/BxBaseMenuManage.php

@@ -80,7 +80,7 @@ class BxBaseMenuManage extends BxTemplMenu
             return $this->_oObjectManage->getCode();
 
         $oMenu = new BxTemplMenu(['template' => 'menu_horizontal.html', 'menu_id' => $this->_sObjectManage . '_wrapper', 'menu_items' => [
-            ['id' => $this->_sObjectManage . '_more', 'name' => $this->_sObjectManage . '_more', 'link' => 'javascript:void(0)', 'onclick' => 'bx_menu_popup(\'' . $this->_sObjectManage . '\', this, {pointer: {el: $(this).parents(\'li:first\'), align:\'right\'}, cssClass: \'bx-popup-menu\'}, {id: ' . $this->_iContentId . '});', 'title' => '', 'icon' => $this->_oTemplate->getImageAuto('cog', false)]
+            ['id' => $this->_sObjectManage . '_more', 'name' => $this->_sObjectManage . '_more', 'link' => 'javascript:void(0)', 'onclick' => 'bx_menu_popup(\'' . $this->_sObjectManage . '\', this, {pointer: {el: $(this).parents(\'li:first\'), align:\'right\'}, cssClass: \'bx-popup-menu\'}, {id: ' . $this->_iContentId . '});', 'title' => '', 'title_attr' => '_sys_menu_item_title_system_more', 'attrs_add' => 'aria-haspopup="dialog" aria-expanded="false"', 'icon' => $this->_oTemplate->getImageAuto('cog', false)]
         ]]);
 
         return $oMenu->getCode();

+ 15 - 0
template/scripts/BxBaseMenuMoreAuto.php

@@ -133,6 +133,21 @@ class BxBaseMenuMoreAuto extends BxTemplMenu
         return parent::_getMenuItem($aItem);
     }
 
+    /**
+     * The 'more' trigger opens a popup that is rendered next to it (the __popup__ marker), so it is a disclosure
+     * for a dialog, not a plain link. Its accessible name comes from the generic title/title_system handling in
+     * BxBaseMenu::_getMenuTitle().
+     */
+    protected function _getMenuAttrs ($aMenuItem)
+    {
+        $sAttrs = parent::_getMenuAttrs($aMenuItem);
+
+        if(!empty($aMenuItem['name']) && $aMenuItem['name'] == BX_DEF_MENU_ITEM_MORE_AUTO)
+            $sAttrs .= ' aria-haspopup="dialog" aria-expanded="false" aria-controls="' . bx_html_attribute($this->_aHtmlIds['more_auto_popup']) . '"';
+
+        return $sAttrs;
+    }
+
     protected function _getHtmlIds()
     {
         return $this->_aHtmlIds;

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff