diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index 5289d8d..16122ac 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -18,8 +18,7 @@ import { useUserPosition } from "./useUserPosition"; import { usePostsEngine } from "./usePostsEngine"; import { openCenteredOverlay } from "./markerManager"; import { useAuth } from "../../auth/AuthContext"; -import FilterButtons from "../Filters/FilterButtons"; -import TimeFilterButtons from "../Filters/TimeFilterButtons"; +import RadialMenu from "./RadialMenu"; import SmartSearchBar from "../Search/SmartSearchBar"; import UserProfile from "../Profile/UserProfile"; import { fetchPostById, fetchPostByUrl } from "../../api/client"; @@ -136,14 +135,37 @@ export default function MapView({ const SUB_FILTER_KEY = "sociowire:subFilter"; const TIME_FILTER_KEY = "sociowire:timeHours"; - const [mainFilter, setMainFilter] = useState(() => { + const [mainFilters, setMainFilters] = useState(() => { try { const saved = localStorage.getItem(MAIN_FILTER_KEY); - if (saved && FILTER_MAIN_CATEGORIES.includes(saved)) return saved; + if (saved) { + const parsed = JSON.parse(saved); + if (Array.isArray(parsed)) return parsed; + // Legacy: single string + if (typeof parsed === "string" && parsed !== "All") return [parsed]; + } } catch {} - return "All"; + return []; // Empty = All }); + // Computed mainFilter for backend compatibility + // Empty array = "All", single item = that item, multiple = first (or "All") + const mainFilter = mainFilters.length === 0 ? "All" : + mainFilters.length === 1 ? mainFilters[0] : "All"; + + // Toggle a category in the multi-select array + const handleCategoryToggle = (catId) => { + setMainFilters((prev) => { + if (prev.includes(catId)) { + // Remove it + return prev.filter((c) => c !== catId); + } else { + // Add it + return [...prev, catId]; + } + }); + }; + const [timeHours, setTimeHours] = useState(() => { try { const v = localStorage.getItem(TIME_FILTER_KEY); @@ -154,13 +176,32 @@ export default function MapView({ } }); - const [subFilter, setSubFilter] = useState(() => { + const [subFilters, setSubFilters] = useState(() => { try { const saved = localStorage.getItem(SUB_FILTER_KEY); - if (saved) return saved; + if (saved) { + const parsed = JSON.parse(saved); + if (Array.isArray(parsed)) return parsed; + } } catch {} - return "All"; + return []; // Empty = All }); + + // Computed subFilter for backend compatibility + const subFilter = subFilters.length === 0 ? "All" : + subFilters.length === 1 ? subFilters[0].split(":")[1] : "All"; + + // Toggle a subcategory + const handleSubcategoryToggle = (catId, subId) => { + const key = `${catId}:${subId}`; + setSubFilters((prev) => { + if (prev.includes(key)) { + return prev.filter((s) => s !== key); + } else { + return [...prev, key]; + } + }); + }; const [mainNewsOnly, setMainNewsOnly] = useState(false); const WEATHER_VIEW_KEY = "sociowire:weatherView"; const [weatherViewEnabled, setWeatherViewEnabled] = useState(() => { @@ -189,15 +230,15 @@ export default function MapView({ useEffect(() => { try { - localStorage.setItem(MAIN_FILTER_KEY, mainFilter); + localStorage.setItem(MAIN_FILTER_KEY, JSON.stringify(mainFilters)); } catch {} - }, [mainFilter]); + }, [mainFilters]); useEffect(() => { try { - localStorage.setItem(SUB_FILTER_KEY, subFilter); + localStorage.setItem(SUB_FILTER_KEY, JSON.stringify(subFilters)); } catch {} - }, [subFilter]); + }, [subFilters]); useEffect(() => { try { @@ -1795,10 +1836,7 @@ export default function MapView({ } }; - const handleMainFilterSelect = (code) => { - if (!FILTER_MAIN_CATEGORIES.includes(code)) return; - setMainFilter(code); - }; + // Legacy handler - now using handleCategoryToggle for multi-select const handleSelectPost = (post) => { if (onSelectPost) onSelectPost(post); @@ -1886,22 +1924,10 @@ export default function MapView({ onPlaceTourWire={handleOpenCreate} />
- {CLUSTERS_ENABLED && ( - - )} -
@@ -1950,6 +1968,7 @@ export default function MapView({ alignItems: 'center', gap: '5px', marginTop: '8px', + marginLeft: '70px', // Offset from left FAB padding: '5px 12px', borderRadius: '16px', background: bg, @@ -1970,47 +1989,17 @@ export default function MapView({ })()} -
- setTimeHours(hours)} - /> -
- -
- -
- - {mainFilter !== "All" && ( -
-
- {bottomCategories.map((c) => ( - - ))} -
-
- )} + {/* Dual FAB Menu - Left: Categories, Right: Time/Weather */} + setTimeHours(h)} + onWeather={() => setWeatherViewEnabled((prev) => !prev)} + activeCategories={mainFilters} + activeSubcategories={subFilters} + activeTime={timeHours} + weatherEnabled={weatherViewEnabled} + /> {isCreating && modeChosen && (
diff --git a/src/components/Map/RadialMenu.css b/src/components/Map/RadialMenu.css new file mode 100644 index 0000000..76e5da1 --- /dev/null +++ b/src/components/Map/RadialMenu.css @@ -0,0 +1,296 @@ +/* Dual FAB Menu - Left (Categories) + Right (Time/Actions) */ + +/* Backdrop */ +.fab-backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.15); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + z-index: 998; + animation: fab-fade-in 0.2s ease; +} + +@keyframes fab-fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +/* FAB Containers */ +.fab-left, +.fab-right { + position: fixed; + top: 120px; + z-index: 999; +} + +.fab-left { + left: 24px; +} + +.fab-right { + right: 12px; +} + +/* Main FAB Button */ +.fab-btn { + width: 48px; + height: 48px; + border-radius: 50%; + border: none; + background: linear-gradient(135deg, var(--fab-color, #6366f1) 0%, color-mix(in srgb, var(--fab-color, #6366f1) 80%, #000) 100%); + color: #fff; + font-size: 18px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + transition: transform 0.2s ease, box-shadow 0.2s ease; + -webkit-tap-highlight-color: transparent; + position: relative; + z-index: 1001; +} + +.fab-btn-right { + --fab-color: #6366f1; +} + +.fab-btn-split { + border: 2px solid rgba(255,255,255,0.3); +} + +.fab-count { + font-size: 16px; + font-weight: 800; +} + +.fab-btn:hover { + transform: scale(1.06); +} + +.fab-btn:active { + transform: scale(0.94); +} + +/* FAB Label */ +.fab-label { + position: absolute; + top: 54px; + left: 50%; + transform: translateX(-50%); + font-size: 10px; + font-weight: 700; + color: #e2e8f0; + text-shadow: 0 1px 4px rgba(0,0,0,0.8); + white-space: nowrap; + pointer-events: none; +} + +.fab-label-time { + background: rgba(15, 23, 42, 0.85); + padding: 2px 6px; + border-radius: 4px; +} + +/* Ring containers */ +.fab-ring { + position: absolute; + top: 0; + left: 0; + width: 48px; + height: 48px; + pointer-events: none; +} + +/* Individual items */ +.fab-item { + position: absolute; + top: 4px; + left: 4px; + width: 40px; + height: 40px; + border-radius: 50%; + border: 1.5px solid rgba(255, 255, 255, 0.15); + background: rgba(15, 23, 42, 0.92); + color: #cbd5e1; + font-size: 14px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35); + opacity: 0; + transform: translate(0, 0) scale(0.3); + pointer-events: none; + transition: + transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), + opacity 0.2s ease, + background 0.15s ease, + border-color 0.15s ease, + color 0.15s ease; + -webkit-tap-highlight-color: transparent; +} + +.fab-item.is-visible { + opacity: 1; + transform: translate(var(--x), var(--y)) scale(1); + pointer-events: auto; + transition-delay: var(--delay); +} + +.fab-item:hover { + background: var(--color, #3b82f6); + border-color: var(--color, #3b82f6); + color: #fff; +} + +.fab-item:active { + transform: translate(var(--x), var(--y)) scale(0.9) !important; +} + +.fab-item.is-active { + background: var(--color, #3b82f6); + border-color: var(--color, #3b82f6); + color: #fff; + box-shadow: 0 4px 16px color-mix(in srgb, var(--color, #3b82f6) 50%, transparent); +} + +/* Time chips - smaller text */ +.fab-time { + font-size: 11px; + font-weight: 700; + --color: #f59e0b; +} + +/* Subcategory items */ +.fab-sub { + width: 36px; + height: 36px; + font-size: 13px; + opacity: 1; + transform: translate(var(--x), var(--y)) scale(1); + pointer-events: auto; + animation: fab-pop-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; + animation-delay: var(--delay); + --color: #64748b; +} + +.fab-sub.is-active { + --color: #3b82f6; +} + +@keyframes fab-pop-in { + from { + opacity: 0; + transform: translate(calc(var(--x) * 0.3), calc(var(--y) * 0.3)) scale(0.5); + } + to { + opacity: 1; + transform: translate(var(--x), var(--y)) scale(1); + } +} + +/* Action items */ +.fab-action { + width: 42px; + height: 42px; + font-size: 16px; +} + +/* ========== LIGHT THEME ========== */ +body[data-theme="light"] .fab-backdrop { + background: rgba(255, 255, 255, 0.2); +} + +body[data-theme="light"] .fab-btn { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +body[data-theme="light"] .fab-label { + color: #1e293b; + text-shadow: 0 1px 4px rgba(255,255,255,0.8); +} + +body[data-theme="light"] .fab-label-time { + background: rgba(255, 255, 255, 0.9); + color: #1e293b; +} + +body[data-theme="light"] .fab-item { + background: rgba(255, 255, 255, 0.95); + border-color: rgba(148, 163, 184, 0.4); + color: #475569; + box-shadow: 0 3px 12px rgba(0, 0, 0, 0.1); +} + +body[data-theme="light"] .fab-item:hover, +body[data-theme="light"] .fab-item.is-active { + color: #fff; +} + +/* ========== MOBILE ========== */ +@media (max-width: 480px) { + .fab-left, + .fab-right { + top: 100px; + } + + .fab-left { + left: 18px; + } + + .fab-right { + right: 8px; + } + + .fab-btn { + width: 44px; + height: 44px; + font-size: 16px; + } + + .fab-item { + width: 36px; + height: 36px; + font-size: 12px; + } + + .fab-sub { + width: 32px; + height: 32px; + font-size: 11px; + } + + .fab-action { + width: 38px; + height: 38px; + font-size: 14px; + } + + .fab-time { + font-size: 10px; + } + + .fab-label { + font-size: 9px; + top: 50px; + } +} + +/* Safe area */ +@supports (padding-top: env(safe-area-inset-top)) { + .fab-left, + .fab-right { + top: calc(120px + env(safe-area-inset-top)); + } + + .fab-left { + left: calc(24px + env(safe-area-inset-left)); + } + + .fab-right { + right: calc(12px + env(safe-area-inset-right)); + } +} diff --git a/src/components/Map/RadialMenu.jsx b/src/components/Map/RadialMenu.jsx new file mode 100644 index 0000000..e60a679 --- /dev/null +++ b/src/components/Map/RadialMenu.jsx @@ -0,0 +1,229 @@ +import React, { useState, useCallback } from "react"; +import "./RadialMenu.css"; + +const CATEGORIES = [ + { id: "News", icon: "fa-solid fa-newspaper", color: "#3b82f6" }, + { id: "Friends", icon: "fa-solid fa-users", color: "#10b981" }, + { id: "Events", icon: "fa-solid fa-calendar", color: "#f59e0b" }, + { id: "Market", icon: "fa-solid fa-store", color: "#8b5cf6" }, +]; + +const SUBCATEGORIES = { + News: [ + { id: "World", icon: "fa-solid fa-globe" }, + { id: "Politics", icon: "fa-solid fa-landmark" }, + { id: "Tech", icon: "fa-solid fa-microchip" }, + { id: "Finance", icon: "fa-solid fa-chart-line" }, + { id: "Local", icon: "fa-solid fa-location-dot" }, + ], + Friends: [ + { id: "Nearby", icon: "fa-solid fa-location-crosshairs" }, + { id: "Chats", icon: "fa-solid fa-comments" }, + { id: "Groups", icon: "fa-solid fa-user-group" }, + ], + Events: [ + { id: "Today", icon: "fa-solid fa-calendar-day" }, + { id: "Music", icon: "fa-solid fa-music" }, + { id: "Sports", icon: "fa-solid fa-football" }, + ], + Market: [ + { id: "Deals", icon: "fa-solid fa-tags" }, + { id: "Tech", icon: "fa-solid fa-laptop" }, + { id: "Auto", icon: "fa-solid fa-car" }, + ], +}; + +const TIME_OPTIONS = [ + { label: "1h", hours: 1 }, + { label: "6h", hours: 6 }, + { label: "24h", hours: 24 }, + { label: "7d", hours: 168 }, + { label: "2w", hours: 336 }, +]; + +export default function RadialMenu({ + onCategoryToggle, + onSubcategoryToggle, + onTime, + onWeather, + activeCategories = [], + activeSubcategories = [], + activeTime, + weatherEnabled, +}) { + const [leftOpen, setLeftOpen] = useState(false); + const [rightOpen, setRightOpen] = useState(false); + + // Get colors for split FAB + const activeColors = CATEGORIES + .filter(c => activeCategories.includes(c.id)) + .map(c => c.color); + + const handleLeftToggle = () => { + setLeftOpen(!leftOpen); + if (!leftOpen) setRightOpen(false); + }; + + const handleRightToggle = () => { + setRightOpen(!rightOpen); + if (!rightOpen) setLeftOpen(false); + }; + + const handleCategoryClick = (catId) => { + onCategoryToggle?.(catId); + }; + + const handleSubcatClick = (catId, subId) => { + onSubcategoryToggle?.(catId, subId); + }; + + const handleTimeClick = (hours) => { + onTime?.(hours); + }; + + const handleWeatherClick = () => { + onWeather?.(); + }; + + // Build gradient for multi-select FAB + const fabGradient = activeColors.length === 0 + ? "linear-gradient(135deg, #6366f1 0%, #4f46e5 100%)" + : activeColors.length === 1 + ? `linear-gradient(135deg, ${activeColors[0]} 0%, ${activeColors[0]}dd 100%)` + : `conic-gradient(${activeColors.map((c, i) => `${c} ${i * (360 / activeColors.length)}deg ${(i + 1) * (360 / activeColors.length)}deg`).join(", ")})`; + + return ( + <> + {/* Backdrop */} + {(leftOpen || rightOpen) && ( +
{ setLeftOpen(false); setRightOpen(false); }} + /> + )} + + {/* LEFT FAB - Categories */} +
+ {/* Categories ring - expand DOWN-RIGHT */} +
+ {CATEGORIES.map((cat, i) => { + // Find index of first active category to rotate + const firstActiveIdx = CATEGORIES.findIndex(c => activeCategories.includes(c.id)); + // Rotate so active category is at top-right (angle 20) + const rotateOffset = firstActiveIdx >= 0 ? -firstActiveIdx * 42 : 0; + const angle = 20 + (i * 42) + rotateOffset; // Rotated based on selection + const rad = (angle * Math.PI) / 180; + const r = 62; + const x = Math.cos(rad) * r; + const y = Math.sin(rad) * r; + const isActive = activeCategories.includes(cat.id); + + // Subcategories for this category + const subcats = SUBCATEGORIES[cat.id] || []; + const subRadius = 55; + + return ( + + {/* Category button */} + + + {/* Subcategories - appear around the category when active */} + {isActive && leftOpen && subcats.map((sub, si) => { + const subAngle = angle - 25 + (si * 38); // More spacing + const subRad = (subAngle * Math.PI) / 180; + const subX = x + Math.cos(subRad) * subRadius; + const subY = y + Math.sin(subRad) * subRadius; + const isSubActive = activeSubcategories.includes(`${cat.id}:${sub.id}`); + + return ( + + ); + })} + + ); + })} +
+ + {/* Main FAB - split colors */} + +
+ + {/* RIGHT FAB - Time & Weather */} +
+
+ {/* Time options */} + {TIME_OPTIONS.map((t, i) => { + const angle = 110 + (i * 35); + const rad = (angle * Math.PI) / 180; + const r = 60; + const x = Math.cos(rad) * r; + const y = Math.sin(rad) * r; + return ( + + ); + })} + + {/* Weather toggle */} + +
+ + {/* Main FAB */} + + {TIME_OPTIONS.find(t => t.hours === activeTime)?.label || "2w"} +
+ + ); +} diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index 8af6da9..6c68cc7 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -1503,32 +1503,19 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { } }); }); + // Animate in using CSS classes only (no inline transitions that persist) requestAnimationFrame(() => { - try { backdrop.classList.add("sw-enter-active"); } catch {} - try { modalWrap.classList.add("sw-enter-active"); } catch {} + backdrop.classList.add("sw-open"); + modalWrap.classList.add("sw-open"); + // After animation completes, remove any inline transition to prevent jumps + setTimeout(() => { + try { + modalWrap.style.transition = "none"; + backdrop.style.transition = "none"; + } catch {} + }, 350); }); - // animate in - backdrop.style.opacity = "0"; - modalWrap.style.opacity = "0"; - modalWrap.style.transform = "scale(0.96)"; - requestAnimationFrame(() => { - backdrop.style.transition = "opacity 260ms ease"; - modalWrap.style.transition = "opacity 260ms ease, transform 320ms cubic-bezier(.2,.9,.2,1)"; - backdrop.style.opacity = "1"; - modalWrap.style.opacity = "1"; - modalWrap.style.transform = "scale(1)"; - }); - - - // ✅ animate in (fade + scale) - try { - requestAnimationFrame(() => { - backdrop.classList.add("sw-open"); - modalWrap.classList.add("sw-open"); - }); - } catch {} - // outside click closes (ignore drag/scroll on map) let backdropDown = null; const trackDown = (e) => { diff --git a/src/styles/filters.css b/src/styles/filters.css index 12a22cb..60f9ae8 100644 --- a/src/styles/filters.css +++ b/src/styles/filters.css @@ -1,20 +1,79 @@ -.sw-icon-column { display:flex; flex-direction:column; gap:.55rem; align-items:center; } -.sw-icon-btn { background:none; border:none; padding:0; cursor:pointer; display:flex; flex-direction:column; align-items:center; gap:.15rem; color:#e5e7eb; } -.sw-icon-circle { - width:54px; height:54px; border-radius:50%; - background: rgba(15, 23, 42, 0.86); - border: 1.5px solid rgba(148, 163, 184, 0.7); - display:flex; align-items:center; justify-content:center; - color:#e5e7eb; - box-shadow:0 3px 10px rgba(0,0,0,.75); - transition: transform 0.36s ease, box-shadow 0.36s ease, border-color 0.36s ease, background 0.36s ease; +/* Filter buttons - Modern glassmorphism style */ +.sw-icon-column { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.sw-icon-btn { + background: none; + border: none; + padding: 0; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.2rem; + color: #e5e7eb; + -webkit-tap-highlight-color: transparent; +} + +.sw-icon-circle { + width: 48px; + height: 48px; + border-radius: 50%; + background: rgba(15, 23, 42, 0.75); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border: 1.5px solid rgba(148, 163, 184, 0.4); + display: flex; + align-items: center; + justify-content: center; + color: #cbd5e1; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sw-icon-circle i { + font-size: 18px; + color: currentColor; + transition: transform 0.2s ease; +} + +.sw-icon-label { + font-size: 0.65rem; + font-weight: 500; + line-height: 1; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); + opacity: 0.9; +} + +.sw-icon-btn:hover .sw-icon-circle { + transform: scale(1.08); + border-color: rgba(148, 163, 184, 0.6); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5); +} + +.sw-icon-btn:hover .sw-icon-circle i { + transform: scale(1.1); +} + +.sw-icon-btn:active .sw-icon-circle { + transform: scale(0.95); +} + +.sw-icon-active .sw-icon-circle { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.9), rgba(37, 99, 235, 0.9)); + border-color: rgba(96, 165, 250, 0.8); + color: #fff; + box-shadow: 0 4px 20px rgba(59, 130, 246, 0.5), 0 0 0 3px rgba(59, 130, 246, 0.2); +} + +.sw-icon-active .sw-icon-label { + color: #93c5fd; + font-weight: 600; } -.sw-icon-circle i { font-size:22px; color: currentColor; } -.sw-icon-label { font-size:.7rem; line-height:1; text-shadow:0 1px 2px rgba(0,0,0,.9); } -.sw-icon-btn:hover .sw-icon-circle { transform: translateY(-1px); box-shadow:0 5px 14px rgba(0,0,0,.85); } -.sw-icon-btn:active .sw-icon-circle { transform: scale(.96); box-shadow:0 2px 8px rgba(0,0,0,.9); } -.sw-icon-active .sw-icon-circle { background: rgba(37, 99, 235, 0.82); border-color:#60a5fa; box-shadow:0 0 14px rgba(56,189,248,.95); } -.sw-icon-active .sw-icon-label { color:#bfdbfe; font-weight:600; } /* Right-side filter stack alignment */ .map-overlay-right .sw-icon-column { align-items:center; } diff --git a/src/styles/map.css b/src/styles/map.css index d9f614e..bfc2687 100644 --- a/src/styles/map.css +++ b/src/styles/map.css @@ -183,20 +183,20 @@ /* Left / right: filtres */ .map-overlay-left{ - left:.7rem; - top:50%; - transform:translateY(-50%); - display:flex; - flex-direction:column; - gap:.5rem; + left: 0.6rem; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + gap: 0.5rem; } .map-overlay-right{ - right:.7rem; - top:50%; - transform:translateY(-50%); - display:flex; - flex-direction:column; - gap:.5rem; + right: 0.6rem; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + gap: 0.5rem; } diff --git a/src/styles/mapMarkers.css b/src/styles/mapMarkers.css index ab5dbc5..aebaa9a 100644 --- a/src/styles/mapMarkers.css +++ b/src/styles/mapMarkers.css @@ -70,6 +70,12 @@ gap: 8px; overflow-y: auto; -webkit-overflow-scrolling: touch; + /* Mobile stability fixes */ + -webkit-tap-highlight-color: transparent; + touch-action: pan-y; + -webkit-touch-callout: none; + transform: translateZ(0); + backface-visibility: hidden; --sw-modal-accent: #38bdf8; --sw-modal-accent-soft: rgba(56,189,248,0.28); --sw-modal-accent-glow: rgba(56,189,248,0.5); @@ -79,6 +85,14 @@ --sw-modal-pill-text: #c7e3ff; } +/* Prevent any tap effects on interactive elements inside modal */ +.sw-centered-modal button, +.sw-centered-modal a, +.sw-centered-modal [data-action] { + -webkit-tap-highlight-color: transparent; + touch-action: manipulation; +} + /* Couleur principale par catégorie/template pour grosse carte */ .sw-centered-modal [data-template="news"]{ @@ -536,10 +550,25 @@ body[data-theme="light"] .sw-chat-bubble::before{ color: #e8f5ff; font-weight: 900; padding: 6px 12px; + border-radius: 8px; + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease; + -webkit-tap-highlight-color: transparent; + user-select: none; +} + +.post-card-btn:hover{ + background: var(--sw-modal-accent-soft, rgba(56,189,248,0.25)); + border-color: var(--sw-modal-accent, rgba(56,189,248,0.5)); } .post-card-btn:active{ - transform: scale(0.98); + opacity: 0.8; +} + +.post-card-btn:disabled{ + opacity: 0.5; + cursor: not-allowed; } /* Center modal content */ @@ -1017,6 +1046,15 @@ body[data-theme="light"] .sw-modal-hero-fallback{ border: 1px solid var(--sw-modal-accent, rgba(90, 190, 255, 0.22)); padding: 6px 10px; border-radius: 999px; + min-width: 52px; + text-align: center; + white-space: nowrap; +} + +.sw-stat-pill .sw-stat-num{ + display: inline-block; + min-width: 1.2em; + transition: opacity 0.15s ease; } .sw-modal-cta-row{ @@ -1087,7 +1125,11 @@ body[data-theme="light"] .sw-modal-hero-fallback{ padding: 12px; pointer-events: auto; opacity: 0; - transition: opacity 440ms ease; + transition: opacity 300ms ease; + /* Mobile stability */ + -webkit-tap-highlight-color: transparent; + touch-action: none; + overscroll-behavior: contain; } .sw-centered-backdrop.sw-modal-fullscreen{ @@ -1095,10 +1137,14 @@ body[data-theme="light"] .sw-modal-hero-fallback{ } .sw-centered-modal{ - transform: scale(0.94); - opacity: 0.0; - transition: transform 440ms ease, opacity 440ms ease; + transform: scale(0.96); + opacity: 0; + transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 200ms ease; will-change: transform, opacity; + /* Prevent any bouncing/jumping */ + -webkit-tap-highlight-color: transparent; + backface-visibility: hidden; + perspective: 1000px; } .sw-centered-backdrop.sw-open{ @@ -1106,8 +1152,38 @@ body[data-theme="light"] .sw-modal-hero-fallback{ } .sw-centered-modal.sw-open{ - transform: scale(1); - opacity: 1.0; + transform: scale(1) translateZ(0); + opacity: 1; + /* Once open, disable ALL transitions to prevent flash/jump */ + transition: none !important; + will-change: auto; +} + +/* Prevent layout shifts during interactions */ +.sw-centered-modal.sw-open .post-card.sw-expanded-shell{ + contain: layout style paint; + transform: translateZ(0); + isolation: isolate; +} + +/* Smooth stat updates without layout shift */ +.sw-stat-num{ + display: inline-block; + min-width: 1.5em; + text-align: center; +} + +/* Mobile: prevent any visual feedback that causes "jumping" */ +@media (hover: none) and (pointer: coarse) { + .sw-centered-modal * { + -webkit-tap-highlight-color: transparent !important; + } + + .sw-centered-modal button:active, + .sw-centered-modal [data-action]:active { + transform: none !important; + opacity: 0.85; + } } body[data-theme="blue"] .sw-centered-backdrop{ diff --git a/src/styles/smartSearchBar.css b/src/styles/smartSearchBar.css index e69984d..598e911 100644 --- a/src/styles/smartSearchBar.css +++ b/src/styles/smartSearchBar.css @@ -247,3 +247,53 @@ body[data-theme="light"] { color: var(--sw-danger, #ff6b6b); font-size: 12.5px; } + +/* Time filter chips */ +.sw-time-chips { + display: flex; + gap: 3px; +} + +.sw-time-chip { + border: none; + outline: none; + cursor: pointer; + padding: 5px 8px; + border-radius: 8px; + background: var(--sw-chip-bg, rgba(255,255,255,0.08)); + color: var(--sw-muted, rgba(255,255,255,0.7)); + font-size: 11px; + font-weight: 700; + transition: all 0.15s ease; + -webkit-tap-highlight-color: transparent; +} + +.sw-time-chip:hover { + background: rgba(59, 130, 246, 0.25); + color: var(--sw-text, #fff); +} + +.sw-time-chip.is-active { + background: linear-gradient(135deg, #3b82f6, #6366f1); + color: #fff; + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4); +} + +.sw-time-chip:active { + transform: scale(0.95); +} + +body[data-theme="light"] .sw-time-chip { + background: rgba(148, 163, 184, 0.2); + color: #475569; +} + +body[data-theme="light"] .sw-time-chip:hover { + background: rgba(59, 130, 246, 0.15); + color: #1e40af; +} + +body[data-theme="light"] .sw-time-chip.is-active { + background: linear-gradient(135deg, #2563eb, #4f46e5); + color: #fff; +}