Files
marketplaces/src/app/components/header/header.component.scss
sdarbinyan fb1afb72d4 fix(storefront): WCAG 2.1 AA accessibility fixes
RC A11Y-01 audit pass, storefront + shared app-shell chrome only. Builds
on RC-Visual-02/RC-Premium-01/RC STORE-01 without redoing that work.

- Skip link: added first-focusable "skip to main content" link (app.html,
  styles.scss .skip-link/.sr-only), targeting new #main-content landmark.
  New app.skipToContent i18n key in en/ru/hy.
- Header: mobile menu items stayed keyboard-focusable and screen-reader
  reachable while visually collapsed (max-height:0 with no visibility
  toggle) - fixed with visibility:hidden + matched transition-delay.
  Desktop search input (readonly, click-to-navigate) had no keyboard
  activation - added aria-label + (keydown.enter).
- Cart payment/bank-payment modals: custom (non-app-dialog) UI had no
  focus trap, no Escape handling, and never returned focus to the
  triggering element - ported app-dialog's confirmed-correct
  focus-trap/Escape/return-focus pattern directly onto cart.component.ts.
  Added role="dialog"/aria-modal/aria-label to both panels and
  role="status"|"alert"/aria-live to every payment-status screen so
  screen readers announce state changes (creating/waiting/success/
  error/timeout).
- Search combobox: suggestion listbox had no role="combobox" wiring on
  the input and suggestion buttons weren't role="option" - added
  aria-autocomplete, aria-controls, aria-activedescendant, aria-selected
  so the existing arrow-key navigation is announced to screen readers.
- Product tabs: tablist/tab pattern was incomplete (no role="tablist",
  no tabpanel) - added role="tablist" + ids to product-tabs.component,
  role="tabpanel"/aria-labelledby to the content panel in
  product-details-container.
- Review form: rating/text validation errors weren't associated with
  their controls (no aria-describedby, no role="alert") - fixed; added
  aria-required to the review textarea.
- delivery-selector: added aria-required to the delivery <select> when
  a selection is mandatory.
- Shared app-icon component: doc comment claimed "decorative by default
  (aria-hidden)" but no aria-hidden was ever applied - fixed to actually
  set aria-hidden="true" when undecorated, and role="img"/aria-label
  when ariaLabel is passed. Shared component, affects every icon-only
  usage app-wide, no visual change.
- Color contrast: --text-light fails WCAG AA 4.5:1 for normal text in
  every theme (dexar 3.39:1, lavero/novo 2.54:1 against white). The two
  in-scope usages (company-details org-short/basis, review-form
  upload-placeholder) switched to --text-secondary (4.55:1-7.56:1,
  passes), same visual family, no layout change.

Flagged, not fixed (design-system decisions, not polish):
- --border-color fails WCAG 1.4.11 3:1 for UI-component boundaries in
  every theme (dexar 1.42:1, lavero/novo 1.24:1 vs white) - pervasive
  token used by hundreds of borders app-wide; needs theme-owner sign-off.
- --success-color/--warning-color/--error-color/--info-color used as
  plain text-on-white in several places (product-information,
  question-card, review-form, compare-page) fail 4.5:1 (2.15-3.76:1) -
  genuine brand semantic colors, changing them to pass would visibly
  shift the palette; needs a deliberate token decision.
- Header mobile-menu max-height/padding transition (pre-existing,
  unrelated to this fix) flagged by design lint as layout-thrashing;
  left as-is per the "no layout/business-logic changes" constraint.

Verified: npx tsc --noEmit clean; npm run build green (only the
pre-existing bundle-budget warning, unrelated to this pass).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 08:46:18 +04:00

560 lines
10 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.platform-header {
background: rgba(117, 121, 124, 0.1);
padding: 8px 0;
position: sticky;
top: 0;
z-index: 1000;
backdrop-filter: blur(10px);
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
&--static {
position: static;
}
}
.platform-header--centered .platform-header-container {
justify-content: center;
}
.platform-header-container {
max-width: 1440px;
margin: 0 auto;
padding: 0 40px;
display: flex;
align-items: center;
gap: 32px;
min-height: 48px;
}
.platform-logo {
display: flex;
align-items: center;
text-decoration: none;
flex-shrink: 0;
::ng-deep .logo-img {
width: 120px;
height: 38px;
object-fit: contain;
}
}
// Navigation Buttons Group
.platform-nav {
display: flex;
flex-shrink: 0;
}
.platform-nav-group {
display: flex;
align-items: center;
}
.platform-nav-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 6px 32px;
height: 38px;
border: 1px solid #d3dad9;
background: rgba(255, 255, 255, 0.74);
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: var(--font-weight-semibold, 600);
font-size: var(--font-size-md, 0.9375rem);
color: #1e3c38;
text-decoration: none;
cursor: pointer;
transition: background 0.3s ease;
white-space: nowrap;
&:not(:first-child) {
border-left: none;
}
&:first-child {
border-radius: 10px 0 0 10px;
}
&:last-child {
border-radius: 0 10px 10px 0;
}
&:hover {
background: #a1b4b5;
color: #1e3c38;
}
&.platform-active {
background: #497671;
color: #ffffff;
}
}
// Search Box
.platform-search-wrapper {
flex: 1;
max-width: 200px;
margin-left: auto;
}
.platform-search-box {
position: relative;
width: 100%;
height: 38px;
background: rgba(255, 255, 255, 0.74);
border: 1px solid #d2dad9;
border-radius: 19px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.12);
display: flex;
align-items: center;
padding: 0 14px;
gap: 8px;
}
.platform-search-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
}
.platform-search-input {
flex: 1;
border: none;
background: transparent;
outline: none;
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: var(--font-weight-semibold, 600);
font-size: var(--font-size-md, 0.9375rem);
color: #828e8d;
cursor: pointer;
&::placeholder {
color: #828e8d;
}
}
// Actions
.platform-actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.platform-ux-btn {
position: relative;
width: 34px;
height: 34px;
border: 1px solid #d3dad9;
border-radius: 50%;
background: rgba(255, 255, 255, 0.84);
color: #1e3c38;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.18s ease, border-color 0.2s ease, background 0.2s ease;
}
.platform-ux-btn:hover {
transform: translateY(-1px);
border-color: #497671;
background: #ffffff;
}
.platform-ux-icon {
font-size: var(--font-size-md, 0.9375rem);
line-height: 1;
font-weight: var(--font-weight-bold, 700);
}
.platform-ux-badge {
position: absolute;
right: -6px;
top: -6px;
min-width: 16px;
height: 16px;
border-radius: var(--radius-full, 999px);
background: #497671;
color: #ffffff;
font-size: 10px;
font-weight: var(--font-weight-bold, 700);
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 4px;
}
.platform-cart-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 36px;
text-decoration: none;
cursor: pointer;
transition: opacity 0.3s ease;
gap: 2px;
svg {
width: 36px;
height: 28px;
}
&:hover {
opacity: 0.85;
}
&.platform-cart-active {
opacity: 1;
}
}
.platform-cart-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 28px;
}
.platform-cart-badge {
position: absolute;
top: -8px;
right: -10px;
background: #497671;
color: #ffffff;
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: var(--font-weight-bold, 700);
font-size: 10px;
line-height: 1;
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.platform-cart-total {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: var(--font-weight-bold, 700);
font-size: 10px;
line-height: 1;
color: #1e3c38;
white-space: nowrap;
}
.platform-lang-selector {
display: flex;
align-items: center;
}
// Mobile Menu Backdrop
.platform-menu-backdrop {
display: none;
}
// Mobile Menu Panel
.platform-mobile-menu {
display: none;
}
// Mobile Menu Items
.platform-mobile-item {
display: flex;
align-items: center;
gap: 14px;
width: 395px;
max-width: 100%;
height: 42px;
padding: 15px 26px;
background: #e9edf1;
border: 1px solid #d3dad9;
border-radius: var(--radius-lg, 13px);
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15);
font-family: 'DM Sans', sans-serif;
font-size: var(--font-size-lg, 1rem);
font-weight: var(--font-weight-medium, 500);
color: #1e3c38;
text-decoration: none;
text-align: left;
appearance: none;
cursor: pointer;
transition: background 0.2s ease, transform 0.15s ease;
box-sizing: border-box;
&:hover {
background: #dce2e7;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
&.platform-mobile-active {
background: #497671;
color: #ffffff;
border-color: #497671;
}
app-icon:first-child {
flex-shrink: 0;
}
span {
flex: 1;
}
.platform-mobile-chevron {
flex-shrink: 0;
margin-left: auto;
}
}
.platform-mobile-lang {
display: flex;
align-items: center;
justify-content: center;
}
.platform-mobile-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
width: 100%;
margin-top: 4px;
}
.platform-menu-toggle {
position: relative;
z-index: 1001;
display: none;
flex-direction: column;
gap: 5px;
background: none;
border: none;
cursor: pointer;
padding: 8px;
span {
width: 25px;
height: 3px;
background: #1e3c38;
border-radius: 2px;
transition: all 0.3s;
}
&.active {
span:nth-child(1) {
transform: rotate(45deg) translate(7px, 4.5px);
}
span:nth-child(2) {
opacity: 0;
}
span:nth-child(3) {
transform: rotate(-45deg) translate(7px, -4.5px);
}
}
}
// Mobile Search Icon
.platform-search-mobile {
display: none;
align-items: center;
justify-content: center;
background: none;
border: none;
cursor: pointer;
padding: 6px;
margin-left: auto;
transition: opacity 0.2s ease;
&:hover {
opacity: 0.7;
}
}
// Responsive Design
@media (max-width: 1200px) {
.platform-header-container {
padding: 0 32px;
gap: 32px;
}
.platform-nav-btn {
font-size: var(--font-size-xl, 1.125rem);
padding: 8px 32px !important;
}
.platform-nav-btn-left {
padding: 8px 32px !important;
}
.platform-nav-btn-middle {
padding: 8px 40px !important;
}
.platform-nav-btn-right {
padding: 8px 28px !important;
}
}
@media (max-width: 992px) {
.platform-header-container {
padding: 0 20px;
gap: 20px;
}
.platform-logo ::ng-deep .logo-img {
width: 120px;
height: 40px;
}
.platform-nav-btn {
font-size: var(--font-size-lg, 1rem);
padding: 8px 24px !important;
}
.platform-nav-btn-left {
padding: 8px 24px !important;
}
.platform-nav-btn-middle {
padding: 8px 28px !important;
}
.platform-nav-btn-right {
padding: 8px 20px !important;
}
.platform-search-box {
height: 42px;
}
.platform-search-input {
font-size: var(--font-size-xl, 1.125rem);
}
}
@media (max-width: 768px) {
.platform-header-container {
gap: 12px;
padding: 0 16px;
}
// Hide desktop nav
.platform-nav {
display: none;
}
// Hide desktop language selector
.platform-lang-desktop {
display: none !important;
}
.platform-search-wrapper {
display: none;
}
.platform-search-mobile {
display: flex;
}
.platform-menu-toggle {
display: flex;
}
.platform-logo ::ng-deep .logo-img {
width: 100px;
height: 32px;
}
// Mobile Menu Backdrop full-screen, blurred, blocks interaction
.platform-menu-backdrop {
display: block;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.35);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
z-index: 998;
animation: platform-fade-in 0.25s ease;
}
// Mobile Menu Panel
.platform-mobile-menu {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
position: fixed;
top: 84px;
right: 0;
width: 485px;
max-width: 100vw;
padding: 28px 20px 32px;
background: rgba(245, 242, 249, 0.93);
border-radius: 0 0 13px 13px;
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.25);
z-index: 999;
box-sizing: border-box;
// Hide by default, animate in. `visibility: hidden` (with a matched
// transition-delay so it only applies once the collapse finishes) keeps
// the menu's links/buttons out of the Tab order and the accessibility
// tree while closed — without it they stayed keyboard-focusable and
// screen-reader-reachable despite being visually collapsed to 0 height.
max-height: 0;
padding-top: 0;
padding-bottom: 0;
overflow: hidden;
opacity: 0;
visibility: hidden;
transition: max-height 0.35s ease, opacity 0.25s ease, padding 0.35s ease, visibility 0s linear 0.35s;
&.platform-mobile-menu-open {
max-height: calc(100dvh - 84px);
padding: 28px 20px 32px;
opacity: 1;
visibility: visible;
overflow-y: auto;
transition: max-height 0.35s ease, opacity 0.25s ease, padding 0.35s ease, visibility 0s linear 0s;
}
}
.platform-mobile-item {
width: 395px;
max-width: 100%;
}
.platform-mobile-controls {
margin-top: 4px;
}
}
@media (max-width: 480px) {
.platform-mobile-menu {
width: 100vw;
border-radius: 0 0 13px 13px;
}
.platform-mobile-item {
width: 100%;
}
}
@keyframes platform-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}