mirror of
https://github.com/LucasKalil-Programador/world-2026-hub.git
synced 2026-07-04 17:41:28 -03:00
fix(header): two-row layout with scrollable tabs below 1100px
This commit is contained in:
parent
6e33142c96
commit
ad6d7ea616
4 changed files with 95 additions and 4 deletions
|
|
@ -129,6 +129,7 @@ button {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
flex-shrink: 0;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
font-size: 1.05rem;
|
||||
|
|
@ -159,6 +160,22 @@ button {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* edge fades — applied by JS only on a side that has more tabs to scroll to */
|
||||
.tabs.fade-right {
|
||||
-webkit-mask-image: linear-gradient(to left, transparent 0, #000 28px);
|
||||
mask-image: linear-gradient(to left, transparent 0, #000 28px);
|
||||
}
|
||||
|
||||
.tabs.fade-left {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 28px);
|
||||
mask-image: linear-gradient(to right, transparent 0, #000 28px);
|
||||
}
|
||||
|
||||
.tabs.fade-left.fade-right {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%);
|
||||
mask-image: linear-gradient(to right, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%);
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.5rem 0.95rem;
|
||||
border-radius: 999px;
|
||||
|
|
@ -183,9 +200,13 @@ button {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.35rem 0.8rem;
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
|
|
@ -195,6 +216,10 @@ button {
|
|||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.time-icon {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent-gold);
|
||||
|
|
@ -1048,8 +1073,22 @@ dialog.match-modal::backdrop {
|
|||
}
|
||||
}
|
||||
|
||||
/* tablet 768–1439px: single-row header with centered (reduced) menu */
|
||||
@media (min-width: 768px) {
|
||||
/* very narrow: collapse the time toggle to its icon so logo + controls stay on
|
||||
one row and the tab strip gets its own full-width scrollable row below */
|
||||
@media (max-width: 420px) {
|
||||
.time-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 0.35rem 0.55rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* single-row header only once logo + 6 tabs + controls genuinely fit (~950px of
|
||||
content + container padding); below this the tabs keep their own scroll row,
|
||||
so the controls never wrap into a broken second line. */
|
||||
@media (min-width: 1100px) {
|
||||
.tabs {
|
||||
flex: 0 1 auto;
|
||||
order: 0;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ function activateTab(id, { updateHash = true } = {}) {
|
|||
}
|
||||
setPref('lastTab', tab);
|
||||
if (updateHash) history.replaceState(null, '', `#${tab}`);
|
||||
scrollActiveTabIntoView(true);
|
||||
}
|
||||
|
||||
// programmatic navigation for cross-view links (e.g. stadium → its matches)
|
||||
|
|
@ -101,7 +102,46 @@ function initTabs() {
|
|||
});
|
||||
window.addEventListener('hashchange', () =>
|
||||
activateTab(location.hash.slice(1), { updateHash: false }));
|
||||
|
||||
// edge fades + keep the active tab visible while the nav scrolls horizontally
|
||||
// (below the 1100px single-row breakpoint the tab strip is a scroll container)
|
||||
const tabsEl = document.querySelector('.tabs');
|
||||
tabsEl.addEventListener('scroll', updateTabFades, { passive: true });
|
||||
let resizeRaf = 0;
|
||||
window.addEventListener('resize', () => {
|
||||
cancelAnimationFrame(resizeRaf);
|
||||
resizeRaf = requestAnimationFrame(() => { scrollActiveTabIntoView(false); updateTabFades(); });
|
||||
});
|
||||
// language toggle changes label widths → re-measure overflow and recenter
|
||||
document.addEventListener('langchange', () => { scrollActiveTabIntoView(false); updateTabFades(); });
|
||||
|
||||
activateTab(location.hash.slice(1) || getPrefs().lastTab || 'home');
|
||||
updateTabFades();
|
||||
}
|
||||
|
||||
// Toggle edge-fade masks on the tab strip: a fade only shows on a side that has
|
||||
// more tabs to scroll toward, so the cut-off tab no longer looks like a bug.
|
||||
function updateTabFades() {
|
||||
const tabs = document.querySelector('.tabs');
|
||||
if (!tabs) return;
|
||||
const overflowing = tabs.scrollWidth - tabs.clientWidth > 1;
|
||||
const atStart = tabs.scrollLeft <= 1;
|
||||
const atEnd = tabs.scrollLeft >= tabs.scrollWidth - tabs.clientWidth - 1;
|
||||
tabs.classList.toggle('fade-left', overflowing && !atStart);
|
||||
tabs.classList.toggle('fade-right', overflowing && !atEnd);
|
||||
}
|
||||
|
||||
// Horizontally scroll the active tab to the center of the strip (no page jump).
|
||||
function scrollActiveTabIntoView(smooth) {
|
||||
const tabs = document.querySelector('.tabs');
|
||||
if (!tabs) return;
|
||||
const active = tabs.querySelector('.tab-btn.active');
|
||||
if (!active || tabs.scrollWidth <= tabs.clientWidth) { updateTabFades(); return; }
|
||||
const tabsRect = tabs.getBoundingClientRect();
|
||||
const aRect = active.getBoundingClientRect();
|
||||
const target = tabs.scrollLeft + (aRect.left - tabsRect.left) - (tabs.clientWidth - aRect.width) / 2;
|
||||
tabs.scrollTo({ left: Math.max(0, target), behavior: smooth ? 'smooth' : 'auto' });
|
||||
requestAnimationFrame(updateTabFades);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- hero
|
||||
|
|
@ -367,7 +407,10 @@ function initFavorites() {
|
|||
function syncTimeToggle() {
|
||||
const btn = document.getElementById('time-toggle');
|
||||
const mode = getPrefs().timeMode ?? 'local';
|
||||
btn.textContent = `🕐 ${t(mode === 'local' ? 'time.local' : 'time.stadium')}`;
|
||||
// icon + label split so the label can collapse on narrow screens (the
|
||||
// accessible name comes from data-i18n-aria, so hiding the text is a11y-safe).
|
||||
btn.innerHTML = `<span class="time-icon" aria-hidden="true">🕐</span>` +
|
||||
`<span class="time-label">${t(mode === 'local' ? 'time.local' : 'time.stadium')}</span>`;
|
||||
btn.setAttribute('aria-pressed', String(mode === 'stadium'));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue