From 2a88e79869ded3a82dc7c57c4c01ae11f4849510 Mon Sep 17 00:00:00 2001 From: phyce Date: Fri, 31 Jul 2026 08:45:40 +0000 Subject: [PATCH 1/2] Fix contrast and table headers flagged by Lighthouse (a11y) Metric: Accessibility (90-93 -> expected 100 on all ten audited pages) Three axe failures repeat across every page: 1. color-contrast (10/10 pages). text-gray-500 (#6a7282) is used for secondary text on the dark backgrounds, giving 3.13-4.02:1 against the six backgrounds in use (#0d0d0d, #141414, #171717, #1c1c1c, #222222, #262626) where 4.5:1 is required. text-gray-600 (#4a5565) on the co-author line was worse at 2.00:1. Both are replaced with text-gray-400 (#99a1af), which measures 5.82:1 in the worst case (#262626) and 7.47:1 in the best. 2. link-in-text-block (10/10 pages). The footer link is #ff6900 inside #6a7282 text, 1.67:1 where 3:1 is required, and had no non-colour affordance. Recolouring cannot fix this - even against the new gray-400 the ratio is 1.11:1 - so the link is now permanently underlined instead of only on hover. 3. td-has-header (7/10 pages). Every ranking table ends with an empty above the Stats/Profile link column, leaving those data cells without a header. Each now carries a visually hidden label. The hidden labels follow the existing BEM + @apply convention rather than putting a bare sr-only utility in the markup. Co-Authored-By: Claude Opus 5 --- resources/js/components/AppFooter.vue | 4 ++-- resources/js/pages/Developers/Growing.vue | 12 ++++++++---- resources/js/pages/Developers/Index.vue | 10 +++++++--- resources/js/pages/Developers/Popular.vue | 12 ++++++++---- resources/js/pages/Developers/Show.vue | 18 +++++++++--------- resources/js/pages/Developers/Top.vue | 8 ++++++-- resources/js/pages/Index.vue | 10 +++++++--- resources/js/pages/PluginDetail.vue | 10 +++++----- resources/js/pages/TopAbsolute.vue | 8 ++++++-- resources/js/pages/TopRelative.vue | 8 ++++++-- 10 files changed, 64 insertions(+), 36 deletions(-) diff --git a/resources/js/components/AppFooter.vue b/resources/js/components/AppFooter.vue index 6486cf3..8f906b6 100644 --- a/resources/js/components/AppFooter.vue +++ b/resources/js/components/AppFooter.vue @@ -11,10 +11,10 @@ @reference "tailwindcss"; .app-footer { - @apply py-4 text-center text-sm text-gray-500; + @apply py-4 text-center text-sm text-gray-400; } .app-footer__link { - @apply text-orange-500 hover:underline; + @apply text-orange-500 underline; } diff --git a/resources/js/pages/Developers/Growing.vue b/resources/js/pages/Developers/Growing.vue index eb39f9e..57c82d0 100644 --- a/resources/js/pages/Developers/Growing.vue +++ b/resources/js/pages/Developers/Growing.vue @@ -69,7 +69,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) Plugins Growth Installs gained - + Profile @@ -189,7 +189,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) } .dev-growing__head-cell { - @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-500 sm:px-4 sm:py-3; + @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-400 sm:px-4 sm:py-3; white-space: nowrap; } @@ -268,7 +268,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) } .dev-growing__pct-base { - @apply text-xs tabular-nums text-gray-500; + @apply text-xs tabular-nums text-gray-400; } .dev-growing__installs { @@ -280,7 +280,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) } .dev-growing__installs-total { - @apply text-xs tabular-nums text-gray-500; + @apply text-xs tabular-nums text-gray-400; } .dev-growing__profile-link { @@ -295,4 +295,8 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) border-color: #c54704; color: #fff; } + +.dev-growing__head-cell-label { + @apply sr-only; +} diff --git a/resources/js/pages/Developers/Index.vue b/resources/js/pages/Developers/Index.vue index fb99560..1b65889 100644 --- a/resources/js/pages/Developers/Index.vue +++ b/resources/js/pages/Developers/Index.vue @@ -127,7 +127,7 @@ const columns: { field: SortField; label: string; numeric: boolean; title?: stri {{ col.label }} {{ sortIndicator(col.field) }} - + Profile @@ -186,7 +186,7 @@ const columns: { field: SortField; label: string; numeric: boolean; title?: stri } .developers__count { - @apply flex-shrink-0 text-xs tabular-nums text-gray-500; + @apply flex-shrink-0 text-xs tabular-nums text-gray-400; } .developers__empty { @@ -222,7 +222,7 @@ const columns: { field: SortField; label: string; numeric: boolean; title?: stri } .developers__head-cell { - @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-500 sm:px-4 sm:py-3; + @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-400 sm:px-4 sm:py-3; white-space: nowrap; } @@ -316,4 +316,8 @@ const columns: { field: SortField; label: string; numeric: boolean; title?: stri border-color: #c54704; color: #fff; } + +.developers__head-cell-label { + @apply sr-only; +} diff --git a/resources/js/pages/Developers/Popular.vue b/resources/js/pages/Developers/Popular.vue index e9cd2f6..b76c91f 100644 --- a/resources/js/pages/Developers/Popular.vue +++ b/resources/js/pages/Developers/Popular.vue @@ -77,7 +77,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) Plugins {{ growthLabel }} % Change - + Profile @@ -200,7 +200,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) } .dev-popular__head-cell { - @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-500 sm:px-4 sm:py-3; + @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-400 sm:px-4 sm:py-3; white-space: nowrap; } @@ -283,7 +283,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) } .dev-popular__installs-total-sub { - @apply text-xs tabular-nums text-gray-500; + @apply text-xs tabular-nums text-gray-400; } .dev-popular__pct { @@ -295,7 +295,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) } .dev-popular__pct-base { - @apply text-xs tabular-nums text-gray-500; + @apply text-xs tabular-nums text-gray-400; } .dev-popular__profile-link { @@ -310,4 +310,8 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) border-color: #c54704; color: #fff; } + +.dev-popular__head-cell-label { + @apply sr-only; +} diff --git a/resources/js/pages/Developers/Show.vue b/resources/js/pages/Developers/Show.vue index da86a60..926acef 100644 --- a/resources/js/pages/Developers/Show.vue +++ b/resources/js/pages/Developers/Show.vue @@ -184,7 +184,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__aliases { - @apply text-xs text-gray-500; + @apply text-xs text-gray-400; } .dev-profile__figures { @@ -196,7 +196,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__figure-label { - @apply text-xs font-medium uppercase tracking-wider text-gray-500; + @apply text-xs font-medium uppercase tracking-wider text-gray-400; } .dev-profile__figure-value { @@ -204,7 +204,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__figure-value--peak { - @apply text-gray-500; + @apply text-gray-400; } .dev-profile__facts { @@ -217,7 +217,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__fact-label { - @apply text-xs font-medium text-gray-500; + @apply text-xs font-medium text-gray-400; } .dev-profile__fact-value { @@ -225,7 +225,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__fact-note { - @apply truncate text-xs text-gray-500; + @apply truncate text-xs text-gray-400; } .dev-profile__fact-link { @@ -261,7 +261,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__card-count { - @apply text-xs font-medium tabular-nums text-gray-600; + @apply text-xs font-medium tabular-nums text-gray-400; } .dev-profile__table-wrapper { @@ -293,7 +293,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__th { - @apply px-3 py-2 text-xs font-medium uppercase tracking-wider text-gray-500 sm:px-4; + @apply px-3 py-2 text-xs font-medium uppercase tracking-wider text-gray-400 sm:px-4; white-space: nowrap; } @@ -325,7 +325,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__td--muted { - @apply font-normal text-gray-500; + @apply font-normal text-gray-400; } .dev-profile__td--action { @@ -343,7 +343,7 @@ function coauthorLabel(entry: DeveloperPluginEntry): string | null { } .dev-profile__plugin-coauthors { - @apply text-xs text-gray-600; + @apply text-xs text-gray-400; } .dev-profile__link-btn { diff --git a/resources/js/pages/Developers/Top.vue b/resources/js/pages/Developers/Top.vue index f840b90..5d4628b 100644 --- a/resources/js/pages/Developers/Top.vue +++ b/resources/js/pages/Developers/Top.vue @@ -39,7 +39,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) Developer Plugins Installs - + Profile @@ -121,7 +121,7 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) } .dev-top__head-cell { - @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-500 sm:px-4 sm:py-3; + @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-400 sm:px-4 sm:py-3; white-space: nowrap; } @@ -189,4 +189,8 @@ const rows = computed((): DeveloperRankEntry[] => props.rankings?.entries ?? []) border-color: #c54704; color: #fff; } + +.dev-top__head-cell-label { + @apply sr-only; +} diff --git a/resources/js/pages/Index.vue b/resources/js/pages/Index.vue index 3e83c29..a7179e5 100644 --- a/resources/js/pages/Index.vue +++ b/resources/js/pages/Index.vue @@ -110,7 +110,7 @@ const columns: { field: SortField; label: string }[] = [ {{ col.label }} {{ sortIndicator(col.field) }} - + Stats @@ -169,7 +169,7 @@ const columns: { field: SortField; label: string }[] = [ } .plugin-table__head-cell { - @apply px-4 py-3 text-xs font-medium uppercase tracking-wider text-gray-500; + @apply px-4 py-3 text-xs font-medium uppercase tracking-wider text-gray-400; white-space: nowrap; } @@ -238,7 +238,7 @@ const columns: { field: SortField; label: string }[] = [ } .plugin-table__author { - @apply ml-1.5 text-xs text-gray-500; + @apply ml-1.5 text-xs text-gray-400; } .plugin-table__stats-link { @@ -276,4 +276,8 @@ const columns: { field: SortField; label: string }[] = [ .plugin-table__scrollbar-spacer { @apply h-px; } + +.plugin-table__head-cell-label { + @apply sr-only; +} diff --git a/resources/js/pages/PluginDetail.vue b/resources/js/pages/PluginDetail.vue index 43d570f..b6d039f 100644 --- a/resources/js/pages/PluginDetail.vue +++ b/resources/js/pages/PluginDetail.vue @@ -669,7 +669,7 @@ onUnmounted(() => { } .plugin-detail__related-meta { - @apply truncate text-xs text-gray-500; + @apply truncate text-xs text-gray-400; } .plugin-detail__related-tags { @@ -684,7 +684,7 @@ onUnmounted(() => { } .plugin-detail__related-reason { - @apply mt-0.5 text-xs text-gray-600; + @apply mt-0.5 text-xs text-gray-400; } .plugin-detail__developer { @@ -733,7 +733,7 @@ onUnmounted(() => { } .plugin-detail__dev-stat-label { - @apply text-xs text-gray-500; + @apply text-xs text-gray-400; } .plugin-detail__contributors { @@ -742,7 +742,7 @@ onUnmounted(() => { } .plugin-detail__contributors-title { - @apply text-xs font-medium uppercase tracking-wider text-gray-500; + @apply text-xs font-medium uppercase tracking-wider text-gray-400; } .plugin-detail__contributor-list { @@ -762,7 +762,7 @@ onUnmounted(() => { } .plugin-detail__contributor-meta { - @apply text-xs tabular-nums text-gray-500; + @apply text-xs tabular-nums text-gray-400; } .plugin-detail__dev-link { diff --git a/resources/js/pages/TopAbsolute.vue b/resources/js/pages/TopAbsolute.vue index 9472ac3..a02a5ac 100644 --- a/resources/js/pages/TopAbsolute.vue +++ b/resources/js/pages/TopAbsolute.vue @@ -73,7 +73,7 @@ const rows = computed((): GrowthEntry[] => props.entries ?? []); Plugin Installs % Change - + Stats @@ -206,7 +206,7 @@ const rows = computed((): GrowthEntry[] => props.entries ?? []); } .top-absolute__head-cell { - @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-500 sm:px-4 sm:py-3; + @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-400 sm:px-4 sm:py-3; } .top-absolute__head-cell--rank { @@ -308,4 +308,8 @@ const rows = computed((): GrowthEntry[] => props.entries ?? []); border-color: #c54704; color: #fff; } + +.top-absolute__head-cell-label { + @apply sr-only; +} diff --git a/resources/js/pages/TopRelative.vue b/resources/js/pages/TopRelative.vue index 8faae7b..36e9740 100644 --- a/resources/js/pages/TopRelative.vue +++ b/resources/js/pages/TopRelative.vue @@ -73,7 +73,7 @@ const rows = computed((): GrowthEntry[] => props.entries ?? []); Plugin % Change Installs - + Stats @@ -206,7 +206,7 @@ const rows = computed((): GrowthEntry[] => props.entries ?? []); } .top-relative__head-cell { - @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-500 sm:px-4 sm:py-3; + @apply px-2 py-2 text-xs font-medium uppercase tracking-wider text-gray-400 sm:px-4 sm:py-3; } .top-relative__head-cell--rank { @@ -309,4 +309,8 @@ const rows = computed((): GrowthEntry[] => props.entries ?? []); border-color: #c54704; color: #fff; } + +.top-relative__head-cell-label { + @apply sr-only; +} From 0e0ababd9a1ffae075c8feef5edec23f2c93b3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronaldas=20Turevi=C4=8D?= Date: Fri, 31 Jul 2026 19:10:31 +0200 Subject: [PATCH 2/2] Update AppFooter.vue --- resources/js/components/AppFooter.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/AppFooter.vue b/resources/js/components/AppFooter.vue index 8f906b6..055834a 100644 --- a/resources/js/components/AppFooter.vue +++ b/resources/js/components/AppFooter.vue @@ -15,6 +15,6 @@ } .app-footer__link { - @apply text-orange-500 underline; + @apply text-orange-500 hover:underline; }