diff --git a/assets/css/table.css b/assets/css/table.css index 0c5a7b0..c498584 100644 --- a/assets/css/table.css +++ b/assets/css/table.css @@ -455,6 +455,43 @@ } .profile-dropdown-close:hover { color: #1e1208; background: rgba(176, 152, 112, 0.16); } +/* Tablet + desktop: the floating bottom button is mobile-only, so the panel + hangs off the bottom edge of the active-profile-row instead — dropping + down out of that row and shrinking back into it on close. + --profile-anchor-top is the row's live viewport bottom, set by + ConfigPanel.js. Phones (<=640px) keep the bottom sheet untouched. */ +@media (min-width: 641px) { + .profile-dropdown { + top: var(--profile-anchor-top, 0px); + bottom: auto; + max-height: calc(100vh - var(--profile-anchor-top, 0px) - 16px); + border: 1.5px solid #c9b08a; + border-radius: 12px; + overflow: hidden; /* keeps the scrolling body inside the rounded corners */ + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18); + transform-origin: 50% 0; + } + .profile-dropdown-body { + max-height: calc(100vh - var(--profile-anchor-top, 0px) - 16px); + } + + /* Too little room under the row for the content — revert to the mobile + bottom sheet (set by ConfigPanel.js) so the panel gets the full height. */ + .profile-dropdown.is-bottom-sheet { + top: auto; + bottom: 0; + max-height: calc(100vh - 24px); + border: none; + border-top: 1.5px solid #c9b08a; + border-radius: 12px 12px 0 0; + box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.18); + transform-origin: 50% 100%; + } + .profile-dropdown.is-bottom-sheet .profile-dropdown-body { + max-height: calc(100vh - 24px); + } +} + /* Read-only "what am I looking at" row above the day tabs — states the active profile and the thermal basis (SunSoak env / vehicle / building / fur colour) driving the numbers below. Clicking/Enter opens the same @@ -465,15 +502,16 @@ justify-content: center; flex-wrap: wrap; gap: 12px; - padding: 2px 16px; - margin-bottom: 6px; + padding: 8px 16px; + margin-bottom: 4px; /* --row-bg is set inline per the exact selected option (see ROW_GRADIENTS in DayTabs.js) — falls back to a flat parchment tone if unset. */ background: var(--row-bg, #fdf8ee); - border: 1.5px solid #d4c0a0; - border-radius: 6px; + border: 2px solid #d4c0a0; + border-radius: 9px; cursor: pointer; - transition: filter 0.15s, border-color 0.15s; + box-shadow: inset 0 -1px 0 rgba(0,0,0,0.05), 0 1px 3px rgba(0,0,0,0.04); + transition: filter 0.2s, border-color 0.2s, box-shadow 0.2s; } /* Remove alpha from the default fallback color and any potential inherited semi-transparent backgrounds while keeping the hue. */ @@ -494,13 +532,16 @@ } .active-profile-row:hover, .active-profile-row:focus-visible { - filter: brightness(1.06); + filter: brightness(1.04); border-color: #c8922a; + box-shadow: inset 0 -1px 0 rgba(0,0,0,0.05), 0 3px 8px rgba(200,146,42,0.18); outline: none; } + +/* Breathing room between each "PROFILE" / "VIEWING" label and its value. */ .active-profile-row-item { display: inline-flex; - align-items: center; + align-items: baseline; gap: 7px; } .active-profile-row-label { @@ -521,12 +562,22 @@ } .active-profile-row-sep { color: #c0a880; font-size: 16px; } .active-profile-row-edit { + display: inline-flex; + align-items: center; + gap: 5px; font-family: Manrope, sans-serif; - font-size: 13px; + font-size: 12.5px; font-weight: 700; letter-spacing: 0.04em; color: #9a6a1a; - margin-left: 4px; + margin-left: 8px; + opacity: 0.7; + transition: opacity 0.2s, transform 0.2s, color 0.2s; +} +.active-profile-row:hover .active-profile-row-edit, +.active-profile-row:focus-visible .active-profile-row-edit { + opacity: 1; + color: #c8922a; } /* Border + text accent per thermal-model group, reusing the same colour diff --git a/assets/js/components/ConfigPanel.js b/assets/js/components/ConfigPanel.js index 6e65d4e..2630282 100644 --- a/assets/js/components/ConfigPanel.js +++ b/assets/js/components/ConfigPanel.js @@ -55,6 +55,7 @@ export function ConfigPanel({ }) { const profileScrollRef = useRef(null); const profileWrapRef = useRef(null); + const dropdownRef = useRef(null); const { mainConfigKey, mainLabel } = deriveProfileMain(activeProfile, outdoorsVariant); @@ -77,6 +78,34 @@ export function ConfigPanel({ return () => document.removeEventListener('keydown', onKey); }, [open, onClose]); + // From tablet up the panel drops *out of* the active-profile-row rather + // than sliding up from the floating button (which is mobile-only), so it + // has to be anchored to that row's live bottom edge — it's sticky on + // desktop and scrolls away on tablet, hence remeasuring on scroll/resize. + // Mobile (<=640px) ignores the var entirely and keeps the bottom sheet. + useEffect(() => { + const el = dropdownRef.current; + if (!el) return; + const measure = () => { + const row = document.querySelector('.active-profile-row'); + const top = row ? Math.max(0, row.getBoundingClientRect().bottom) : 0; + el.style.setProperty('--profile-anchor-top', `${Math.round(top)}px`); + // Not enough room below the row for the panel's content? Fall back to + // the mobile bottom-sheet layout, which gets the full viewport height. + const body = el.querySelector('.profile-dropdown-body'); + const content = body ? body.scrollHeight : 0; + el.classList.toggle('is-bottom-sheet', content > window.innerHeight - top - 16); + }; + measure(); + if (!open) return; + window.addEventListener('scroll', measure, true); + window.addEventListener('resize', measure); + return () => { + window.removeEventListener('scroll', measure, true); + window.removeEventListener('resize', measure); + }; + }, [open, activeTab, activeProfile, outdoorsVariant]); + // Drag-to-scroll + fade edges for the horizontal profile card row. useEffect(() => { const el = profileScrollRef.current; @@ -226,7 +255,7 @@ export function ConfigPanel({ return html` <${Fragment}>
-