From 0b6bb6cc07d8ca7b9d59edc883071e0f97cb1dc5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 16 Dec 2025 22:47:17 -0500 Subject: [PATCH] j essais de fix --- index.html | 46 +--- package-lock.json | 20 ++ package.json | 1 + src/api/client.js | 2 +- src/auth/AuthContext.jsx | 17 +- src/components/Filters/FilterButtons.jsx | 6 +- src/components/Filters/TimeFilterButtons.jsx | 14 +- src/components/Layout/TopBar.jsx | 106 ++------ src/components/Map/MapView.jsx | 129 ++------- src/components/Map/mapConfig.js | 11 +- src/components/Map/mapFilter.js | 30 +-- src/components/Map/mapGeo.js | 7 +- src/components/Map/markerManager.js | 88 ++---- src/components/Map/useMapCore.js | 78 +----- src/components/Map/usePostsEngine.js | 77 ++---- src/components/Map/useUserPosition.js | 25 +- src/components/Posts/PostCard.jsx | 23 +- src/components/Posts/PostList.jsx | 25 +- src/main.jsx | 5 +- src/styles/auth-modal.css | 118 +++----- src/styles/base.css | 9 +- src/styles/filters.css | 135 ++-------- src/styles/game-filters.css | 89 +----- src/styles/layout.css | 27 +- src/styles/map.css | 19 +- src/styles/mapMarkers.css | 169 +++--------- src/styles/markers.css | 109 +------- src/styles/overlays.css | 269 +++---------------- src/styles/popup.css | 71 +---- src/styles/posts.css | 184 +++---------- src/styles/theme.css | 78 +----- src/styles/topbar.css | 32 +-- vite.config.js | 10 +- 33 files changed, 342 insertions(+), 1687 deletions(-) diff --git a/index.html b/index.html index a2e1a88..aaab769 100644 --- a/index.html +++ b/index.html @@ -1,53 +1,9 @@ - - SocioWire — Wired to Life - - - - - - - - - - - - - - - - - -SocioWire — Wired to Life - - - - - - - - - - - - - - - - - - - - - - - - sociowire-frontend - + SocioWire
diff --git a/package-lock.json b/package-lock.json index 92bd153..c9e6ef6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "sociowire-frontend", "version": "0.0.0", "dependencies": { + "@fortawesome/fontawesome-free": "^7.1.0", "axios": "^1.13.2", "keycloak-js": "^26.2.1", "maplibre-gl": "^5.14.0", @@ -58,6 +59,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -500,6 +502,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.1.0.tgz", + "integrity": "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA==", + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "engines": { + "node": ">=6" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1074,6 +1085,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1124,6 +1136,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1246,6 +1259,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1547,6 +1561,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2767,6 +2782,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2852,6 +2868,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2861,6 +2878,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3163,6 +3181,7 @@ "integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/runtime": "0.97.0", "fdir": "^6.5.0", @@ -3285,6 +3304,7 @@ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index a247088..51e10fb 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@fortawesome/fontawesome-free": "^7.1.0", "axios": "^1.13.2", "keycloak-js": "^26.2.1", "maplibre-gl": "^5.14.0", diff --git a/src/api/client.js b/src/api/client.js index 7ee2c61..f6459df 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -113,7 +113,7 @@ export async function createPost(payload, token) { /** * Crée un nouvel utilisateur via le backend * (backend: POST /api/signup) - * payload: { username, email, password } + * payload: { username, email, password, firstName, lastName } */ export async function registerUser(payload) { const res = await fetch(`${API_BASE}/signup`, { diff --git a/src/auth/AuthContext.jsx b/src/auth/AuthContext.jsx index 552007b..695ed74 100644 --- a/src/auth/AuthContext.jsx +++ b/src/auth/AuthContext.jsx @@ -56,22 +56,20 @@ async function refreshWithKeycloak(refreshToken) { if (!data.access_token) throw new Error("No access_token in refresh response"); return { accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, // parfois KC renvoie un nouveau refresh + refreshToken: data.refresh_token || refreshToken, }; } export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); // UX: restore session - const [loading, setLoading] = useState(false); // login in progress + const [booting, setBooting] = useState(true); + const [loading, setLoading] = useState(false); const [authenticated, setAuthenticated] = useState(false); const [username, setUsername] = useState(""); const [token, setToken] = useState(null); const [lastError, setLastError] = useState(""); - const refreshRef = useRef(null); const usernameRef = useRef(""); - // persist helper function persistAuth({ accessToken, refreshToken, user }) { setToken(accessToken); setAuthenticated(true); @@ -105,7 +103,6 @@ export function AuthProvider({ children }) { if (!storedToken || !storedRefresh) return false; - // si token encore bon, on le charge tel quel if (!willExpireSoon(storedToken, 30)) { setToken(storedToken); setUsername(storedUser); @@ -114,7 +111,6 @@ export function AuthProvider({ children }) { return true; } - // sinon refresh const { accessToken, refreshToken } = await refreshWithKeycloak(storedRefresh); persistAuth({ accessToken, refreshToken, user: storedUser }); return true; @@ -125,14 +121,11 @@ export function AuthProvider({ children }) { } } - // Boot: restore session useEffect(() => { (async () => { try { - // si on a un token/refresh en storage => restore/refresh const ok = await trySilentRefreshIfNeeded(); if (!ok) { - // fallback: si token sans refresh, on garde “loggé” mais ça expirera vite const storedToken = localStorage.getItem(TOKEN_KEY); const storedUser = localStorage.getItem(USERNAME_KEY) || ""; if (storedToken) { @@ -148,7 +141,6 @@ export function AuthProvider({ children }) { })(); }, []); - // Auto-refresh loop (silencieux) useEffect(() => { if (!authenticated) return; @@ -172,7 +164,6 @@ export function AuthProvider({ children }) { return () => clearInterval(interval); }, [authenticated]); - // Login via password grant async function login(user, pass) { setLastError(""); const u = (user || "").trim(); @@ -252,4 +243,4 @@ export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used inside "); return ctx; -} \ No newline at end of file +} diff --git a/src/components/Filters/FilterButtons.jsx b/src/components/Filters/FilterButtons.jsx index 12d1ef1..b5710d7 100644 --- a/src/components/Filters/FilterButtons.jsx +++ b/src/components/Filters/FilterButtons.jsx @@ -1,5 +1,4 @@ import React from "react"; -import "../../styles/filters.css"; export default function FilterButtons({ active, onSelect }) { const buttons = [ @@ -16,10 +15,7 @@ export default function FilterButtons({ active, onSelect }) { - @@ -164,34 +137,23 @@ export default function TopBar({ theme = "dark", onChangeTheme }) { {showAuth && (
-
e.stopPropagation()} - > +
e.stopPropagation()}>
-
@@ -216,11 +178,7 @@ export default function TopBar({ theme = "dark", onChangeTheme }) { autoComplete="current-password" /> - @@ -229,55 +187,29 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
- {signupError && ( -
{signupError}
- )} + {signupError &&
{signupError}
} - diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index ae1011a..b297d21 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -1,16 +1,13 @@ import React, { useEffect, useState } from "react"; import "maplibre-gl/dist/maplibre-gl.css"; + import "../../styles/mapMarkers.css"; import "../../styles/overlays.css"; import "../../styles/posts.css"; import "../../styles/filters.css"; import { createPost } from "../../api/client"; -import { - FILTER_MAIN_CATEGORIES, - FILTER_CATEGORY_MAP, - CREATE_CATEGORY_MAP, -} from "./mapConfig"; +import { FILTER_MAIN_CATEGORIES, FILTER_CATEGORY_MAP, CREATE_CATEGORY_MAP } from "./mapConfig"; import { useMapCore } from "./useMapCore"; import { useUserPosition } from "./useUserPosition"; import { usePostsEngine } from "./usePostsEngine"; @@ -22,14 +19,7 @@ import TimeFilterButtons from "../Filters/TimeFilterButtons"; export default function MapView({ theme = "dark" }) { const { authenticated, username, token } = useAuth(); - const { - containerRef, - mapRef, - markersRef, - expandedElRef, - viewParams, - hasLastView, - } = useMapCore(theme); + const { containerRef, mapRef, markersRef, expandedElRef, viewParams, hasLastView } = useMapCore(theme); const [mainFilter, setMainFilter] = useState("All"); const [timeFilter, setTimeFilter] = useState("RECENT"); @@ -37,13 +27,7 @@ export default function MapView({ theme = "dark" }) { const userPosition = useUserPosition(mapRef, hasLastView); - const { - status, - visiblePosts, - loadingPosts, - loadError, - handleIncomingPost, - } = usePostsEngine({ + const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } = usePostsEngine({ mapRef, viewParams, mainFilter, @@ -59,9 +43,7 @@ export default function MapView({ theme = "dark" }) { const [draftTitle, setDraftTitle] = useState(""); const [draftBody, setDraftBody] = useState(""); const [draftCategory, setDraftCategory] = useState("News"); - const [draftSubCategory, setDraftSubCategory] = useState( - CREATE_CATEGORY_MAP.News[0] - ); + const [draftSubCategory, setDraftSubCategory] = useState(CREATE_CATEGORY_MAP.News[0]); const [draftCoords, setDraftCoords] = useState(null); const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(""); @@ -70,12 +52,9 @@ export default function MapView({ theme = "dark" }) { useEffect(() => { if (!bottomCategories.length) return; - if (!subFilter || !bottomCategories.includes(subFilter)) { - setSubFilter("All"); - } + if (!subFilter || !bottomCategories.includes(subFilter)) setSubFilter("All"); }, [mainFilter, bottomCategories, subFilter]); - // WebSocket live useEffect(() => { const proto = window.location.protocol === "https:" ? "wss" : "ws"; const host = @@ -83,22 +62,21 @@ export default function MapView({ theme = "dark" }) { ? `${window.location.hostname}:8081` : window.location.host; const wsUrl = `${proto}://${host}/ws`; + let ws; try { ws = new WebSocket(wsUrl); } catch { return; } + ws.onmessage = (ev) => { try { const msg = JSON.parse(ev.data); - if (msg.type === "new_post" && msg.post) { - handleIncomingPost(msg.post); - } - } catch { - // ignore - } + if (msg.type === "new_post" && msg.post) handleIncomingPost(msg.post); + } catch {} }; + return () => ws && ws.close(); }, [handleIncomingPost]); @@ -140,7 +118,6 @@ export default function MapView({ theme = "dark" }) { const authorName = (username || "").trim() || "anon"; - // Crosshair offset so the big bubble appears above the pointer let baseLng; let baseLat; @@ -156,10 +133,7 @@ export default function MapView({ theme = "dark" }) { const pixelOffsetY = -20; const screenPoint = map.project([baseLng, baseLat]); - const correctedPoint = { - x: screenPoint.x, - y: screenPoint.y + pixelOffsetY, - }; + const correctedPoint = { x: screenPoint.x, y: screenPoint.y + pixelOffsetY }; const correctedLngLat = map.unproject([correctedPoint.x, correctedPoint.y]); const lng = correctedLngLat.lng; const lat = correctedLngLat.lat; @@ -176,7 +150,7 @@ export default function MapView({ theme = "dark" }) { { title: draftTitle.trim(), snippet: draftBody.trim() || draftTitle.trim(), - category: (draftCategory === "Events" ? "EVENT" : draftCategory.toUpperCase()), + category: draftCategory === "Events" ? "EVENT" : draftCategory.toUpperCase(), sub_category: draftSubCategory || "ALL", lat, lon: lng, @@ -185,9 +159,7 @@ export default function MapView({ theme = "dark" }) { token ); - if (newPost && newPost.id) { - handleIncomingPost(newPost); - } + if (newPost && newPost.id) handleIncomingPost(newPost); setIsSaving(false); setIsCreating(false); @@ -204,9 +176,7 @@ export default function MapView({ theme = "dark" }) { if (!map) return; const lng = post.lon ?? post.lng; const lat = post.lat ?? post.latitude; - if (typeof lng === "number" && typeof lat === "number") { - map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 }); - } + if (typeof lng === "number" && typeof lat === "number") map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 }); }; const handleMainFilterSelect = (code) => { @@ -219,87 +189,55 @@ export default function MapView({ theme = "dark" }) {
{status &&
{status}
} - {/* TOP */}
- - + +
- {/* LEFT: time filters as icon+label */}
- setTimeFilter(code)} - /> + setTimeFilter(code)} />
- {/* RIGHT: main categories as icon+label */}
- +
- {/* BOTTOM: subcategories icons */}
{bottomCategories.map((c) => ( ))}
- {/* MY SPOT */}
-
- {/* CROSSHAIR when creating a post */} {isCreating && (
)} - {/* CREATE POST PANEL */} {isCreating && (
Create wire - +
- Move the map, aim with the crosshair — your wire will be pinned - there. + Move the map, aim with the crosshair — your wire will be pinned there.
- setDraftSubCategory(e.target.value)}> + {(CREATE_CATEGORY_MAP[draftCategory] || CREATE_CATEGORY_MAP.News).map((sub) => ( ))} @@ -341,18 +273,13 @@ export default function MapView({ theme = "dark" }) { /> {saveError &&
{saveError}
}
-
)} - {/* SOCIOWALL + CHAT */}
Sociowall
diff --git a/src/components/Map/mapConfig.js b/src/components/Map/mapConfig.js index 8d814bf..aba8264 100644 --- a/src/components/Map/mapConfig.js +++ b/src/components/Map/mapConfig.js @@ -1,16 +1,8 @@ export const LAST_POS_KEY = "sociowire:lastPosition"; // position GPS/IP user export const LAST_VIEW_KEY = "sociowire:lastView"; // centre+zoom carte -// Boutons de filtre principaux (droite) -export const FILTER_MAIN_CATEGORIES = [ - "All", - "News", - "Friends", - "Events", - "Market", -]; +export const FILTER_MAIN_CATEGORIES = ["All", "News", "Friends", "Events", "Market"]; -// Sous-catégories pour les filtres (UI du bas) – AVEC "All" export const FILTER_CATEGORY_MAP = { All: ["All"], News: ["All", "World", "Politics", "Tech", "Finance", "Local"], @@ -19,7 +11,6 @@ export const FILTER_CATEGORY_MAP = { Market: ["All", "Actu", "Tech", "Finan", "Sport", "Deals"], }; -// Sous-catégories pour la création de post – SANS "All" export const CREATE_CATEGORY_MAP = { News: ["World", "Politics", "Tech", "Finance", "Local"], Friends: ["Nearby", "Chats", "Groups", "Stories", "Favs"], diff --git a/src/components/Map/mapFilter.js b/src/components/Map/mapFilter.js index 4dd2889..511bf4a 100644 --- a/src/components/Map/mapFilter.js +++ b/src/components/Map/mapFilter.js @@ -1,6 +1,5 @@ import { haversineKm } from "./mapGeo"; -// map bouton -> code backend export function categoryCode(mainFilter) { switch (mainFilter) { case "News": @@ -8,7 +7,7 @@ export function categoryCode(mainFilter) { case "Friends": return "FRIENDS"; case "Events": - return "EVENT"; // backend attendu + return "EVENT"; case "Market": return "MARKET"; case "All": @@ -17,7 +16,6 @@ export function categoryCode(mainFilter) { } } -// Transforme un post en petits textes pour les bulles export function extractPostText(post) { const title = post.title || post.text || post.body || "Post"; const body = @@ -30,7 +28,6 @@ export function extractPostText(post) { return { title, body, short }; } -// sous-catégorie: "All" = tout export function matchesSubFilter(post, subFilter) { if (!subFilter || subFilter.toUpperCase() === "ALL" || subFilter === "All") { return true; @@ -40,20 +37,14 @@ export function matchesSubFilter(post, subFilter) { return sub === subFilter.toLowerCase(); } -// Parse date backend safely: -// - "YYYY-MM-DD HH:MM:SS" => treated as LOCAL (pas de "Z" qui décale) -// - ISO string garde ce que JS sait faire function parseCreatedAt(createdAt) { if (!createdAt) return null; - if (createdAt instanceof Date) return createdAt; const s = String(createdAt).trim(); if (!s) return null; - // "YYYY-MM-DD HH:MM:SS" (sans timezone) if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(s)) { - // local time const isoLocal = s.replace(" ", "T"); const d = new Date(isoLocal); return Number.isNaN(d.getTime()) ? null : d; @@ -63,15 +54,8 @@ function parseCreatedAt(createdAt) { return Number.isNaN(d.getTime()) ? null : d; } -// time filter local – règles demandées : -// NOW = dernière 1h -// TODAY = dernière 24h -// RECENT = dernier 3 jours -// PAST = ALL (pas de filtre de temps) export function matchesTimeFilter(createdAt, timeFilter) { if (!timeFilter) return true; - - // PAST = ALL => aucun filtre temps if (timeFilter === "PAST") return true; const d = parseCreatedAt(createdAt); @@ -80,31 +64,27 @@ export function matchesTimeFilter(createdAt, timeFilter) { const now = Date.now(); const diffMs = now - d.getTime(); if (!Number.isFinite(diffMs)) return true; - - // si date future (clock/UTC weird) -> on laisse passer if (diffMs < 0) return true; const diffHours = diffMs / 3600000; const diffDays = diffMs / 86400000; switch (timeFilter) { - case "NOW": // 1h + case "NOW": return diffHours <= 1; - case "TODAY": // 24h + case "TODAY": return diffHours <= 24; - case "RECENT": // 3 jours + case "RECENT": return diffDays <= 3; default: return true; } } -// Vérifie si un post est dans le rayon courant export function isInViewRadius(post, center, radiusKm) { if (!center || !radiusKm) return true; const [lngC, latC] = center; - const coords = - post.coords || [post.lon ?? post.lng, post.lat ?? post.latitude]; + const coords = post.coords || [post.lon ?? post.lng, post.lat ?? post.latitude]; const [lngP, latP] = coords; if ( typeof lngP !== "number" || diff --git a/src/components/Map/mapGeo.js b/src/components/Map/mapGeo.js index 9b8a805..0e4ccbe 100644 --- a/src/components/Map/mapGeo.js +++ b/src/components/Map/mapGeo.js @@ -1,4 +1,3 @@ -// Haversine pour calculer un rayon approx (km) export function haversineKm(lat1, lon1, lat2, lon2) { const R = 6371; const toRad = (deg) => (deg * Math.PI) / 180; @@ -14,7 +13,6 @@ export function haversineKm(lat1, lon1, lat2, lon2) { return R * c; } -// centre + rayon (km) de la vue actuelle export function getViewFromMap(map) { const bounds = map.getBounds(); const center = bounds.getCenter(); @@ -22,12 +20,10 @@ export function getViewFromMap(map) { let radiusKm = haversineKm(center.lat, center.lng, ne.lat, ne.lng); - // si calcul weird if (!Number.isFinite(radiusKm) || radiusKm <= 0) { - radiusKm = 750; // ✅ fallback plus large + radiusKm = 750; } - // clamp (si ton backend limite à 1000, mets 1000 ici) radiusKm = Math.min(Math.max(radiusKm, 25), 1500); return { @@ -36,7 +32,6 @@ export function getViewFromMap(map) { }; } -// extraction [lng, lat] depuis un post export function getCoords(post) { const lat = post.lat ?? diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index e8c8506..277f7f9 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -1,50 +1,30 @@ import maplibregl from "maplibre-gl"; -/** - * Supprime tous les marqueurs - */ export function clearAllMarkers(markersRef, expandedElRef) { if (markersRef.current) { for (const m of markersRef.current) { try { m.marker.remove(); - } catch { - // ignore - } + } catch {} } } markersRef.current = []; - if (expandedElRef) { - expandedElRef.current = null; - } + if (expandedElRef) expandedElRef.current = null; } -/** - * Util: rectangles overlap? - */ function rectsOverlap(a, b) { - return !( - a.right < b.left || - a.left > b.right || - a.bottom < b.top || - a.top > b.bottom - ); + return !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom); } -/** - * Hide only markers that overlap the expanded card. - * IMPORTANT: we hide ONLY markers that are BELOW the expanded card - * (so markers above stay visible). - */ export function applyOcclusionForExpanded(markersRef, expandedEl) { if (!expandedEl) return; const expandedCard = expandedEl.querySelector(".post-card"); if (!expandedCard) return; - const PAD_X = 26; // left/right padding - const PAD_TOP = 20; // top padding - const PAD_BOTTOM = 140; // EXTRA bottom padding (image + buttons + comments) + const PAD_X = 26; + const PAD_TOP = 20; + const PAD_BOTTOM = 140; const raw = expandedCard.getBoundingClientRect(); const expandedRect = { @@ -55,14 +35,11 @@ export function applyOcclusionForExpanded(markersRef, expandedEl) { }; const list = markersRef.current || []; - for (const item of list) { const el = item?.el; if (!el || el === expandedEl) continue; const r = el.getBoundingClientRect(); - - // ✅ only hide stuff that is BELOW the expanded card zone const isBelow = r.top >= raw.top; if (!isBelow) { el.style.visibility = "visible"; @@ -76,9 +53,6 @@ export function applyOcclusionForExpanded(markersRef, expandedEl) { } } -/** - * Reset all markers visible - */ export function clearOcclusion(markersRef) { const list = markersRef.current || []; for (const item of list) { @@ -89,21 +63,13 @@ export function clearOcclusion(markersRef) { } } -/** - * Crée un marker stable: - * - root 0x0 anchored by MapLibre (anchor bottom) - * - UI is ABSOLUTE above the anchor, so expanded doesn't "shift" other markers - */ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { const map = mapRef.current; if (!map) return; - // --- coords --- const lat = typeof post.lat === "number" ? post.lat - : typeof post.Lat === "number" - ? post.Lat : typeof post.latitude === "number" ? post.latitude : null; @@ -111,10 +77,8 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { const lon = typeof post.lon === "number" ? post.lon - : typeof post.Lon === "number" - ? post.Lon - : typeof post.longitude === "number" - ? post.longitude + : typeof post.lng === "number" + ? post.lng : null; if (lat == null || lon == null) { @@ -122,17 +86,13 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { return; } - const title = (post.title || post.Title || "").trim() || "Untitled"; - const body = - (post.snippet || post.Snippet || "").trim() || - (post.body || post.Body || "").trim() || - title; + const title = (post.title || "").trim() || "Untitled"; + const body = (post.snippet || "").trim() || (post.body || "").trim() || title; - const author = (post.author || post.Author || "").trim() || "unknown"; - const created = - (post.created_at || post.CreatedAt || "").toString().slice(0, 19); - const category = (post.category || post.Category || "").toUpperCase() || ""; - const subCat = (post.sub_category || post.SubCategory || "").toString(); + const author = (post.author || "").trim() || "unknown"; + const created = (post.created_at || "").toString().slice(0, 19); + const category = (post.category || "").toUpperCase() || ""; + const subCat = (post.sub_category || "").toString(); const root = document.createElement("div"); root.className = "post-pin post-pin--compact"; @@ -161,9 +121,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { ${escapeHtml(category || "NEWS")} ${subCat ? " · " + escapeHtml(subCat) : ""}
-
- ${escapeHtml(created || "")} -
+
${escapeHtml(created || "")}
@@ -197,16 +155,10 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { }); } - // initial compact renderCompact(); - - // used by map click to close root.__renderCompact = renderCompact; - const marker = new maplibregl.Marker({ - element: root, - anchor: "bottom", - }) + const marker = new maplibregl.Marker({ element: root, anchor: "bottom" }) .setLngLat([lon, lat]) .addTo(map); @@ -215,11 +167,8 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { root.addEventListener("click", (ev) => { ev.stopPropagation(); - // close previous expanded if (expandedElRef.current && expandedElRef.current !== root) { - if (expandedElRef.current.__renderCompact) { - expandedElRef.current.__renderCompact(); - } + if (expandedElRef.current.__renderCompact) expandedElRef.current.__renderCompact(); expandedElRef.current = null; } @@ -234,11 +183,10 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { }); } -/* escape html */ function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); -} \ No newline at end of file +} diff --git a/src/components/Map/useMapCore.js b/src/components/Map/useMapCore.js index 2387ece..85f09a2 100644 --- a/src/components/Map/useMapCore.js +++ b/src/components/Map/useMapCore.js @@ -4,7 +4,6 @@ import { LAST_VIEW_KEY } from "./mapConfig"; import { getViewFromMap } from "./mapGeo"; import { clearAllMarkers, applyOcclusionForExpanded } from "./markerManager"; -// MapLibre base style by theme function getBaseStyle(theme) { const t = theme || "dark"; let tiles; @@ -32,13 +31,7 @@ function getBaseStyle(theme) { attribution: "© OpenStreetMap contributors © CARTO", }, }, - layers: [ - { - id: "carto-base-layer", - type: "raster", - source: "carto-base", - }, - ], + layers: [{ id: "carto-base-layer", type: "raster", source: "carto-base" }], }; } @@ -49,12 +42,7 @@ export function useMapCore(theme) { const markersRef = useRef([]); const expandedElRef = useRef(null); - // default large radius - const [viewParams, setViewParams] = useState({ - center: null, - radiusKm: 750, - }); - + const [viewParams, setViewParams] = useState({ center: null, radiusKm: 750 }); const [hasLastView, setHasLastView] = useState(false); function closeExpandedIfAny() { @@ -63,17 +51,6 @@ export function useMapCore(theme) { expandedElRef.current = null; } - function safeUpdateViewParams(map) { - try { - const vp = getViewFromMap(map); - if (vp && Array.isArray(vp.center) && vp.center.length === 2) { - setViewParams(vp); - } - } catch { - // ignore - } - } - useEffect(() => { if (!containerRef.current) return; @@ -87,17 +64,12 @@ export function useMapCore(theme) { antialias: true, }); - // Restore last view if available let hadLastView = false; try { const raw = localStorage.getItem(LAST_VIEW_KEY); if (raw) { const v = JSON.parse(raw); - if ( - typeof v.lat === "number" && - typeof v.lon === "number" && - typeof v.zoom === "number" - ) { + if (typeof v.lat === "number" && typeof v.lon === "number" && typeof v.zoom === "number") { map.setCenter([v.lon, v.lat]); map.setZoom(v.zoom); hadLastView = true; @@ -109,7 +81,6 @@ export function useMapCore(theme) { map.addControl(new maplibregl.NavigationControl(), "top-right"); mapRef.current = map; - // Re-occlude markers when post expanded const reOcclude = () => { const el = expandedElRef.current; if (el) applyOcclusionForExpanded(markersRef, el); @@ -117,36 +88,20 @@ export function useMapCore(theme) { map.on("move", reOcclude); map.on("zoom", reOcclude); - // Close expanded when clicking/dragging map map.on("click", () => closeExpandedIfAny()); map.on("dragstart", () => closeExpandedIfAny()); - // ✅ CRITICAL FIX: - // Set view params ASAP and also on load/idle/moveend. - // On some devices/prod, relying on 'load' only can miss initial fetch triggers. - const update = () => safeUpdateViewParams(map); + map.on("load", () => setViewParams(getViewFromMap(map))); - map.on("load", update); - map.on("idle", update); map.on("moveend", () => { - update(); + const vp = getViewFromMap(map); + setViewParams(vp); try { - const vp = getViewFromMap(map); const center = vp.center; - localStorage.setItem( - LAST_VIEW_KEY, - JSON.stringify({ - lat: center[1], - lon: center[0], - zoom: map.getZoom(), - }) - ); + localStorage.setItem(LAST_VIEW_KEY, JSON.stringify({ lat: center[1], lon: center[0], zoom: map.getZoom() })); } catch {} }); - // Also do an early update next frame (helps first load) - requestAnimationFrame(update); - return () => { clearAllMarkers(markersRef, expandedElRef); map.remove(); @@ -155,28 +110,11 @@ export function useMapCore(theme) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // Theme style switch useEffect(() => { const map = mapRef.current; if (!map) return; - - // setStyle triggers style reload; we still want viewParams updated after map.setStyle(getBaseStyle(theme || "dark")); - - // update view params after style swap (next frame) - requestAnimationFrame(() => { - try { - safeUpdateViewParams(map); - } catch {} - }); }, [theme]); - return { - containerRef, - mapRef, - markersRef, - expandedElRef, - viewParams, - hasLastView, - }; + return { containerRef, mapRef, markersRef, expandedElRef, viewParams, hasLastView }; } diff --git a/src/components/Map/usePostsEngine.js b/src/components/Map/usePostsEngine.js index ce88eca..d45be49 100644 --- a/src/components/Map/usePostsEngine.js +++ b/src/components/Map/usePostsEngine.js @@ -1,18 +1,9 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { fetchPosts } from "../../api/client"; // keep existing endpoint for now +import { fetchPosts } from "../../api/client"; import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter"; import { haversineKm } from "./mapGeo"; import { createMarkerForPost, clearAllMarkers } from "./markerManager"; -/** - * Posts engine: - * - keeps a cache (allPostsRef) - * - applies filters (cat/sub/time) for map + Sociowall - * - fetches by zone - * - * ✅ FIX: first page load must fetch even if viewParams.center is null. - * We fallback to map.getCenter() and default radius 750. - */ export function usePostsEngine({ mapRef, viewParams, @@ -23,12 +14,7 @@ export function usePostsEngine({ expandedElRef, }) { const allPostsRef = useRef([]); - - const lastFetchRef = useRef({ - center: null, - radiusKm: null, - filterKey: "", - }); + const lastFetchRef = useRef({ center: null, radiusKm: null, filterKey: "" }); const [status, setStatus] = useState("Loading posts..."); const [visiblePosts, setVisiblePosts] = useState([]); @@ -55,9 +41,7 @@ export function usePostsEngine({ return true; }); - visible.forEach((post) => { - createMarkerForPost(post, mapRef, markersRef, expandedElRef); - }); + visible.forEach((post) => createMarkerForPost(post, mapRef, markersRef, expandedElRef)); setVisiblePosts(visible); setStatus(visible.length ? "" : "No posts found."); @@ -72,42 +56,27 @@ export function usePostsEngine({ useEffect(() => { const map = mapRef.current; if (!map) return; + if (!viewParams.center) return; let cancelled = false; async function load() { - // ✅ fallback center/radius if viewParams not ready yet - const centerArr = - (viewParams && Array.isArray(viewParams.center) && viewParams.center.length === 2) - ? viewParams.center - : (() => { - const c = map.getCenter(); - return [c.lng, c.lat]; - })(); + const [lng, lat] = viewParams.center; + const radiusKm = viewParams.radiusKm || 750; + const filterKey = `${mainFilter}|${subFilter}`; - const radiusKm = - (viewParams && typeof viewParams.radiusKm === "number" && viewParams.radiusKm > 0) - ? viewParams.radiusKm - : 750; - - const [lng, lat] = centerArr; - - const filterKey = `${mainFilter}|${subFilter}|${timeFilter}`; const last = lastFetchRef.current; if (last.center && last.filterKey === filterKey) { const [lastLng, lastLat] = last.center; - const distKm = haversineKm(lastLat, lastLng, lat, lng); + const distKm = haversineKm(lastLat, lastLng, viewParams.center[1], lng); const lastR = last.radiusKm || 0; - const radiusChanged = - !lastR || Math.abs(radiusKm - lastR) / Math.max(lastR, 1) >= 0.25; + const radiusChanged = !lastR || Math.abs(radiusKm - lastR) / Math.max(lastR, 1) >= 0.25; const minMoveKm = Math.max(50, radiusKm * 0.25); - if (!radiusChanged && distKm < minMoveKm) { - return; - } + if (!radiusChanged && distKm < minMoveKm) return; } try { @@ -117,9 +86,7 @@ export function usePostsEngine({ const catCode = categoryCode(mainFilter); const subCatParam = - subFilter && subFilter.toUpperCase() !== "ALL" && subFilter !== "All" - ? subFilter - : ""; + subFilter && subFilter.toUpperCase() !== "ALL" && subFilter !== "All" ? subFilter : ""; const newPosts = await fetchPosts({ category: catCode, @@ -138,21 +105,16 @@ export function usePostsEngine({ for (const p of existing) { if (p && typeof p.id !== "undefined") byId.set(p.id, p); } + if (Array.isArray(newPosts)) { for (const p of newPosts) { - if (p && typeof p.id !== "undefined" && !byId.has(p.id)) { - byId.set(p.id, p); - } + if (p && typeof p.id !== "undefined" && !byId.has(p.id)) byId.set(p.id, p); } } allPostsRef.current = Array.from(byId.values()); - lastFetchRef.current = { - center: [...centerArr], - radiusKm, - filterKey, - }; + lastFetchRef.current = { center: [...viewParams.center], radiusKm, filterKey }; rebuildMarkersForFilters(timeFilter); setLoadingPosts(false); @@ -167,11 +129,10 @@ export function usePostsEngine({ } load(); - return () => { cancelled = true; }; - }, [viewParams, mainFilter, subFilter, timeFilter, mapRef, rebuildMarkersForFilters, markersRef, expandedElRef]); + }, [viewParams, mainFilter, subFilter, mapRef, rebuildMarkersForFilters, timeFilter]); const handleIncomingPost = useCallback( (p) => { @@ -191,11 +152,5 @@ export function usePostsEngine({ [mainFilter, subFilter, timeFilter, mapRef, markersRef, expandedElRef] ); - return { - status, - visiblePosts, - loadingPosts, - loadError, - handleIncomingPost, - }; + return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost }; } diff --git a/src/components/Map/useUserPosition.js b/src/components/Map/useUserPosition.js index 892da1a..da2cf4c 100644 --- a/src/components/Map/useUserPosition.js +++ b/src/components/Map/useUserPosition.js @@ -19,22 +19,13 @@ export function useUserPosition(mapRef, hasLastView) { setUserPosition(coords); try { localStorage.setItem(LAST_POS_KEY, JSON.stringify({ lat, lon })); - } catch (e) { - // ignore - } + } catch {} if (!hasLastView) { - map.flyTo({ - center: coords, - zoom: 9, - speed: 1.2, - curve: 1.5, - essential: true, - }); + map.flyTo({ center: coords, zoom: 9, speed: 1.2, curve: 1.5, essential: true }); } } - // 1) dernière position user dans localStorage try { const raw = localStorage.getItem(LAST_POS_KEY); if (raw) { @@ -44,11 +35,8 @@ export function useUserPosition(mapRef, hasLastView) { setUserPosition([p.lon, p.lat]); } } - } catch (e) { - // ignore - } + } catch {} - // 2) GPS → 3) IP backend (uniquement si on n'a VRAIMENT rien) async function viaIpFallback() { if (cancelled || hasPos) return; const ipCoords = await fetchIpLocation(); @@ -67,14 +55,9 @@ export function useUserPosition(mapRef, hasLastView) { if (cancelled || hasPos) return; viaIpFallback(); }, - { - enableHighAccuracy: true, - timeout: 8000, - maximumAge: 30000, - } + { enableHighAccuracy: true, timeout: 8000, maximumAge: 30000 } ); } else if (!hasPos) { - // pas de geolocation et aucune position connue → IP fallback viaIpFallback(); } diff --git a/src/components/Posts/PostCard.jsx b/src/components/Posts/PostCard.jsx index 52ca087..0a2d5a4 100644 --- a/src/components/Posts/PostCard.jsx +++ b/src/components/Posts/PostCard.jsx @@ -1,25 +1,13 @@ import React from "react"; function getKmFromPost(post) { - return ( - post.km ?? - post.distance_km ?? - post.distanceKm ?? - post.dist_km ?? - null - ); + return post.km ?? post.distance_km ?? post.distanceKm ?? post.dist_km ?? null; } export default function PostCard({ post, selected, onSelect }) { const title = post.title || post.name || "Post"; - - const fullBody = - post.body || post.content || post.text || ""; - - // texte court dans le Sociowall - const body = - fullBody.length > 80 ? fullBody.slice(0, 77) + "..." : fullBody; - + const fullBody = post.body || post.content || post.text || ""; + const body = fullBody.length > 80 ? fullBody.slice(0, 77) + "..." : fullBody; const km = getKmFromPost(post); function handleClick() { @@ -27,10 +15,7 @@ export default function PostCard({ post, selected, onSelect }) { } return ( -
+

{title}

{body}

{km != null && {km} km} diff --git a/src/components/Posts/PostList.jsx b/src/components/Posts/PostList.jsx index e7fba56..3094a15 100644 --- a/src/components/Posts/PostList.jsx +++ b/src/components/Posts/PostList.jsx @@ -2,22 +2,10 @@ import React, { useState, useMemo } from "react"; import PostCard from "./PostCard"; function getKmFromPost(post) { - return ( - post.km ?? - post.distance_km ?? - post.distanceKm ?? - post.dist_km ?? - null - ); + return post.km ?? post.distance_km ?? post.distanceKm ?? post.dist_km ?? null; } -export default function PostList({ - posts, - loading, - error, - selectedPost, - onSelectPost, -}) { +export default function PostList({ posts, loading, error, selectedPost, onSelectPost }) { const [kmFilter, setKmFilter] = useState(1000); const filtered = useMemo( @@ -54,17 +42,12 @@ export default function PostList({ ))} - {!loading && filtered.length === 0 && ( -

Aucun post dans ce rayon.

- )} + {!loading && filtered.length === 0 &&

Aucun post dans ce rayon.

}
); } diff --git a/src/main.jsx b/src/main.jsx index c5b7ea5..ebce1bc 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,3 +1,4 @@ +import "@fortawesome/fontawesome-free/css/all.min.css"; import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.jsx"; @@ -11,9 +12,11 @@ import "./styles/popup.css"; import "./styles/overlays.css"; import "./styles/posts.css"; import "./styles/theme.css"; -// dans main.jsx, avec les autres styles import "./styles/auth-modal.css"; import "./styles/game-filters.css"; +import "./styles/filters.css"; +import "./styles/mapMarkers.css"; + import { AuthProvider } from "./auth/AuthContext.jsx"; ReactDOM.createRoot(document.getElementById("root")).render( diff --git a/src/styles/auth-modal.css b/src/styles/auth-modal.css index f560da2..696056f 100644 --- a/src/styles/auth-modal.css +++ b/src/styles/auth-modal.css @@ -1,8 +1,3 @@ -/* ========================================== - SocioWire auth modal (login / signup) - Follows body[data-theme]: dark | blue | light - ========================================== */ - .auth-modal-backdrop { position: fixed; inset: 0; @@ -14,7 +9,6 @@ backdrop-filter: blur(8px); } -/* Base modal box */ .auth-modal { width: min(640px, 95vw); border-radius: 22px; @@ -23,134 +17,82 @@ border: 1px solid rgba(148, 163, 184, 0.7); } -/* Theme variants */ body[data-theme="dark"] .auth-modal { background: radial-gradient(circle at top left, #0f172a, #020617 70%); } - body[data-theme="blue"] .auth-modal { background: radial-gradient(circle at top left, #0ea5e9, #020617 65%); border-color: rgba(96, 165, 250, 0.9); } - body[data-theme="light"] .auth-modal { background: radial-gradient(circle at top left, #ffffff, #e5e7eb 70%); color: #020617; border-color: rgba(148, 163, 184, 0.7); } -/* Header: tabs + close */ -.auth-modal-header { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.9rem; -} +.auth-modal-header { display:flex; align-items:center; gap:.5rem; margin-bottom:.9rem; } .auth-tab { - padding: 0.45rem 0.95rem; + padding: .45rem .95rem; border-radius: 999px; border: 1px solid transparent; background: transparent; - font-size: 0.85rem; + font-size: .85rem; color: #e5e7eb; cursor: pointer; } - .auth-tab-active { background: linear-gradient(135deg, #1d4ed8, #38bdf8); border-color: rgba(191, 219, 254, 0.9); color: #f9fafb; } - -/* light theme override for tab text */ -body[data-theme="light"] .auth-tab { - color: #0f172a; -} -body[data-theme="light"] .auth-tab-active { - color: #f9fafb; -} +body[data-theme="light"] .auth-tab { color:#0f172a; } +body[data-theme="light"] .auth-tab-active { color:#f9fafb; } .auth-close { - margin-left: auto; - width: 32px; - height: 32px; - border-radius: 999px; - border: 1px solid rgba(148, 163, 184, 0.6); + margin-left:auto; + width:32px; height:32px; + border-radius:999px; + border:1px solid rgba(148, 163, 184, 0.6); background: rgba(15, 23, 42, 0.85); - color: #e5e7eb; - font-size: 0.8rem; - cursor: pointer; + color:#e5e7eb; + font-size:.8rem; + cursor:pointer; } +body[data-theme="light"] .auth-close { background:#e5e7eb; color:#020617; } -/* Close button for light theme */ -body[data-theme="light"] .auth-close { - background: #e5e7eb; - color: #020617; -} +.auth-form { display:flex; flex-direction:column; gap:.7rem; } +.auth-row { display:flex; gap:.6rem; } +.auth-row label { flex:1; } +.auth-form label { display:flex; flex-direction:column; font-size:.8rem; gap:.25rem; } -/* Form layout */ -.auth-form { - display: flex; - flex-direction: column; - gap: 0.7rem; -} - -.auth-row { - display: flex; - gap: 0.6rem; -} -.auth-row label { - flex: 1; -} - -.auth-form label { - display: flex; - flex-direction: column; - font-size: 0.8rem; - gap: 0.25rem; -} - -/* Inputs */ .auth-form input { border-radius: 999px; border: 1px solid rgba(51, 65, 85, 0.9); background: rgba(15, 23, 42, 0.96); color: #e5e7eb; - padding: 0.55rem 0.85rem; - font-size: 0.85rem; + padding: .55rem .85rem; + font-size: .85rem; } - -/* Light theme inputs */ body[data-theme="light"] .auth-form input { - background: #f9fafb; - color: #020617; + background:#f9fafb; + color:#020617; border-color: rgba(148, 163, 184, 0.9); } -/* Full-width primary button */ -.btn-full { - width: 100%; - margin-top: 0.4rem; -} +.btn-full { width:100%; margin-top:.4rem; } -/* Error message */ .auth-error { - margin-top: 0.25rem; - font-size: 0.8rem; - color: #fecaca; + margin-top:.25rem; + font-size:.8rem; + color:#fecaca; background: rgba(127, 29, 29, 0.55); - border-radius: 0.5rem; - padding: 0.4rem 0.6rem; + border-radius:.5rem; + padding:.4rem .6rem; border: 1px solid rgba(248, 113, 113, 0.7); } -/* Small screen tweaks */ -@media (max-width: 480px) { - .auth-modal { - padding: 0.95rem 0.9rem 1.1rem; - } - .auth-row { - flex-direction: column; - } +@media (max-width:480px){ + .auth-modal { padding:.95rem .9rem 1.1rem; } + .auth-row { flex-direction:column; } } diff --git a/src/styles/base.css b/src/styles/base.css index 94e5932..d00b648 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -1,12 +1,7 @@ -/* ========== BASE / RESET ========== */ -* { - box-sizing: border-box; -} - +* { box-sizing: border-box; } body { margin: 0; background: #050816; color: #ffffff; - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", - sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } diff --git a/src/styles/filters.css b/src/styles/filters.css index 1f4ad77..760ac24 100644 --- a/src/styles/filters.css +++ b/src/styles/filters.css @@ -1,126 +1,35 @@ -/* ===== Generic vertical icon column (right & left) ===== */ - -.sw-icon-column { - display: flex; - flex-direction: column; - gap: 0.55rem; - align-items: center; -} - -.sw-icon-btn { - background: none; - border: none; - padding: 0; - cursor: pointer; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.15rem; - color: #e5e7eb; -} - +.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%; + 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; - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.75); - transition: - transform 0.12s ease, - box-shadow 0.12s ease, - border-color 0.12s ease, - background 0.12s ease; + display:flex; align-items:center; justify-content:center; + box-shadow:0 3px 10px rgba(0,0,0,.75); + transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease, background .12s ease; } - -.sw-icon-circle i { - font-size: 22px; -} - -.sw-icon-label { - font-size: 0.7rem; - line-height: 1; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9); -} - -.sw-icon-btn:hover .sw-icon-circle { - transform: translateY(-1px); - box-shadow: 0 5px 14px rgba(0, 0, 0, 0.85); -} - -.sw-icon-btn:active .sw-icon-circle { - transform: scale(0.96); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.9); -} - -/* active state: same color for icon + background, more glow */ -.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, 0.95); -} - -.sw-icon-active .sw-icon-label { - color: #bfdbfe; - font-weight: 600; -} - -/* ===== Bottom sub-category icons (row) ===== */ +.sw-icon-circle i { font-size:22px; } +.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; } .sw-bottom-row { - display: flex; - gap: 0.35rem; - padding: 0.2rem 0.6rem; + display:flex; gap:.35rem; padding:.2rem .6rem; background: rgba(15, 23, 42, 0.94); border-radius: 999px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.85); + box-shadow: 0 4px 16px rgba(0,0,0,.85); } - -.sw-bottom-item { - border: none; - background: none; - padding: 0; - cursor: pointer; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.12rem; - min-width: 54px; -} - +.sw-bottom-item { border:none; background:none; padding:0; cursor:pointer; display:flex; flex-direction:column; align-items:center; gap:.12rem; min-width:54px; } .sw-bottom-circle { - width: 40px; - height: 40px; - border-radius: 50%; + width:40px; height:40px; border-radius:50%; background: rgba(15, 23, 42, 0.86); border: 1.5px solid rgba(148, 163, 184, 0.6); - display: flex; - align-items: center; - justify-content: center; - font-size: 0.78rem; - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.75); -} - -.sw-bottom-label { - font-size: 0.65rem; - color: #e5e7eb; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9); -} - -.sw-bottom-item:hover .sw-bottom-circle { - transform: translateY(-1px); - box-shadow: 0 5px 14px rgba(0, 0, 0, 0.85); -} - -.sw-bottom-active .sw-bottom-circle { - background: rgba(37, 99, 235, 0.82); - border-color: #60a5fa; -} - -.sw-bottom-active .sw-bottom-label { - color: #bfdbfe; - font-weight: 600; + display:flex; align-items:center; justify-content:center; + font-size:.78rem; + box-shadow:0 3px 10px rgba(0,0,0,.75); } +.sw-bottom-label { font-size:.65rem; color:#e5e7eb; text-shadow:0 1px 2px rgba(0,0,0,.9); } +.sw-bottom-active .sw-bottom-circle { background: rgba(37, 99, 235, 0.82); border-color:#60a5fa; } +.sw-bottom-active .sw-bottom-label { color:#bfdbfe; font-weight:600; } diff --git a/src/styles/game-filters.css b/src/styles/game-filters.css index dd20133..a2f2f26 100644 --- a/src/styles/game-filters.css +++ b/src/styles/game-filters.css @@ -1,88 +1 @@ -/* ===== SOCIOWIRE – GAME STYLE FILTER BUTTONS ===== */ - -/* Columns on left/right of the map */ -.game-filter-column { - position: absolute; - top: 50%; - transform: translateY(-50%); - display: flex; - flex-direction: column; - gap: 0.55rem; - z-index: 25; - pointer-events: none; /* overlay container */ -} - -.game-filter-column * { - pointer-events: auto; /* real buttons clickable */ -} - -.game-filter-column-left { - left: 0.45rem; -} - -.game-filter-column-right { - right: 0.45rem; -} - -/* One line: circle + vertical label */ -.game-filter-item { - display: flex; - align-items: center; - gap: 0.15rem; -} - -/* Round “avatar style” button */ -.game-filter-circle { - width: 52px; - height: 52px; - border-radius: 50%; - background: radial-gradient(circle at 30% 30%, #3f9dfd, #1d4ed8, #020617); - border: 3px solid rgba(255, 255, 255, 0.35); - box-shadow: - 0 4px 14px rgba(0, 0, 0, 0.7), - 0 0 10px rgba(59, 130, 246, 0.9); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: - transform 0.15s ease, - box-shadow 0.2s ease, - background 0.2s ease, - border-color 0.2s ease; -} - -.game-filter-circle span { - font-size: 0.8rem; - font-weight: 700; - color: #e5f2ff; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); -} - -/* Selected state */ -.game-filter-circle-active { - background: radial-gradient(circle at 30% 30%, #4ade80, #16a34a, #052e16); - border-color: #22c55e; - box-shadow: - 0 0 16px rgba(34, 197, 94, 0.9), - 0 6px 18px rgba(0, 0, 0, 0.85); -} - -/* Vertical label like AVATAR / QUEST */ -.game-filter-label { - font-size: 0.55rem; - letter-spacing: 1px; - text-transform: uppercase; - color: #f9fafb; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85); - writing-mode: vertical-rl; - text-orientation: mixed; -} - -/* Tiny tweak for small screens */ -@media (max-width: 480px) { - .game-filter-circle { - width: 46px; - height: 46px; - } -} +/* legacy placeholder (kept if imported) */ diff --git a/src/styles/layout.css b/src/styles/layout.css index a40c6f9..aa6e899 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -1,24 +1,3 @@ -/* ========== LAYOUT GLOBAL (structure) ========== */ - -.app-root { - height: 100vh; - display: flex; - flex-direction: column; -} - -/* plus aucun espace entre la topbar et la carte */ -.main-shell { - flex: 1; - display: flex; - padding: 0; -} - -/* Conteneur de la carte plein écran collé aux bords */ -.map-shell { - position: relative; - flex: 1; - border-radius: 0; - overflow: hidden; - border: none; - background: #020617; -} +.app-root { height: 100vh; display: flex; flex-direction: column; } +.main-shell { flex: 1; display: flex; padding: 0; } +.map-shell { position: relative; flex: 1; border-radius: 0; overflow: hidden; border: none; background: #020617; } diff --git a/src/styles/map.css b/src/styles/map.css index 84a9ad4..2860dc8 100644 --- a/src/styles/map.css +++ b/src/styles/map.css @@ -1,20 +1,5 @@ -/* ========== CARTE / CONTAINER MAPLIBRE ========== */ - -.map-view { - position: relative; - width: 100%; - height: 100%; -} - -.map-container, -.maplibre-container { - position: absolute; - inset: 0; - width: 100%; - height: 100%; -} - -/* Petit statut en bas à gauche (ex: "Loading posts...") */ +.map-view { position: relative; width: 100%; height: 100%; } +.map-container, .maplibre-container { position: absolute; inset: 0; width: 100%; height: 100%; } .map-status { position: absolute; left: 0.5rem; diff --git a/src/styles/mapMarkers.css b/src/styles/mapMarkers.css index 8fc15c4..c64bfdb 100644 --- a/src/styles/mapMarkers.css +++ b/src/styles/mapMarkers.css @@ -1,26 +1,12 @@ -/* ========================================================== - SOCIOWIRE MARKERS — ANCHOR STABLE (NO SHIFT) - Root = 0x0 on the exact geo point. - Bubble/Card are ABSOLUTE above the anchor. - ========================================================== */ - -/* Root anchored by MapLibre at the geo point */ .post-pin { position: relative; width: 0; height: 0; - pointer-events: none; /* children clickable */ + pointer-events: none; transform: translate3d(0, 0, 0); } +.post-pin * { pointer-events: auto; box-sizing: border-box; } -.post-pin * { - pointer-events: auto; - box-sizing: border-box; -} - -/* ========================= - COMPACT - ========================= */ .post-pin--compact .post-pin-bubble { position: absolute; left: 0; @@ -35,27 +21,9 @@ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); max-width: 180px; } +.post-pin-dot { width:10px; height:10px; border-radius:999px; background:#ff8a00; margin-right:6px; flex-shrink:0; } +.post-pin-text { color:#e8f5ff; font-size:11px; font-weight:500; max-width:160px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } -.post-pin-dot { - width: 10px; - height: 10px; - border-radius: 999px; - background: #ff8a00; - margin-right: 6px; - flex-shrink: 0; -} - -.post-pin-text { - color: #e8f5ff; - font-size: 11px; - font-weight: 500; - max-width: 160px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* little pointer tip */ .post-pin--compact .post-pin-pointer-small { position: absolute; left: 0; @@ -69,12 +37,7 @@ filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.6)); } -/* ========================= - EXPANDED - ========================= */ -.post-pin--expanded { - z-index: 999999; -} +.post-pin--expanded { z-index: 999999; } .post-pin--expanded .post-card { position: absolute; @@ -90,110 +53,38 @@ border: 1px solid rgba(90, 190, 255, 0.9); color: #e8f5ff; } - -/* header : catégorie + heure/date */ -.post-card-header { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 11px; - margin-bottom: 6px; -} - -.post-card-cat { - font-weight: 600; - color: #8fc5ff; -} - -.post-card-time { - color: #9eb7ff; -} - -/* corps : media + texte */ -.post-card-main { - display: flex; - flex-direction: row; - gap: 8px; - margin-bottom: 6px; -} - +.post-card-header { display:flex; justify-content:space-between; align-items:center; font-size:11px; margin-bottom:6px; } +.post-card-cat { font-weight:600; color:#8fc5ff; } +.post-card-time { color:#9eb7ff; } +.post-card-main { display:flex; flex-direction:row; gap:8px; margin-bottom:6px; } .post-card-media { - width: 72px; - height: 72px; - border-radius: 10px; + width:72px; height:72px; border-radius:10px; background: radial-gradient(circle at 30% 30%, #4a9dff, #07152b); - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; + display:flex; align-items:center; justify-content:center; flex-shrink:0; } - -.post-card-media-label { - font-size: 9px; - text-align: center; - color: #e8f5ff; -} - -.post-card-info { - flex: 1; - min-width: 0; -} - -.post-card-title { - font-size: 13px; - font-weight: 600; - margin-bottom: 2px; -} - -.post-card-meta { - font-size: 10px; - color: #9eb7ff; - margin-bottom: 4px; -} - -.post-card-body { - font-size: 11px; - color: #c7e3ff; - max-height: 70px; - overflow: hidden; -} - -/* footer boutons */ -.post-card-footer { - display: flex; - gap: 6px; - margin-bottom: 6px; - flex-wrap: wrap; -} - +.post-card-media-label { font-size:9px; text-align:center; color:#e8f5ff; } +.post-card-info { flex:1; min-width:0; } +.post-card-title { font-size:13px; font-weight:600; margin-bottom:2px; } +.post-card-meta { font-size:10px; color:#9eb7ff; margin-bottom:4px; } +.post-card-body { font-size:11px; color:#c7e3ff; max-height:70px; overflow:hidden; } +.post-card-footer { display:flex; gap:6px; margin-bottom:6px; flex-wrap:wrap; } .post-card-btn { - border: none; - border-radius: 999px; - padding: 3px 8px; - font-size: 10px; + border:none; border-radius:999px; padding:3px 8px; font-size:10px; background: rgba(5, 35, 70, 0.95); - color: #e8f5ff; - cursor: pointer; + color:#e8f5ff; + cursor:pointer; } - .post-card-comments { - font-size: 10px; - color: #9eb7ff; - padding: 4px 6px; - border-radius: 8px; - background: rgba(3, 14, 32, 0.95); + font-size:10px; color:#9eb7ff; padding:4px 6px; + border-radius:8px; background: rgba(3, 14, 32, 0.95); } - -/* pointer under expanded card */ .post-pin--expanded .post-card-pointer { - position: absolute; - left: 0; - top: 0; + position:absolute; + left:0; top:0; transform: translate(-50%, -100%); - width: 0; - height: 0; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 11px solid rgba(8, 20, 40, 0.97); - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8)); -} \ No newline at end of file + width:0; height:0; + border-left:10px solid transparent; + border-right:10px solid transparent; + border-top:11px solid rgba(8, 20, 40, 0.97); + filter: drop-shadow(0 2px 4px rgba(0,0,0,.8)); +} diff --git a/src/styles/markers.css b/src/styles/markers.css index 92f52bf..a2f2f26 100644 --- a/src/styles/markers.css +++ b/src/styles/markers.css @@ -1,108 +1 @@ -/* ========== MARKERS (PETITS & GROS POSTS SUR LA MAP) ========== */ - -/* base commune */ -.post-marker { - position: relative; - color: #ffffff; - font-size: 12px; - display: inline-flex; - align-items: stretch; - cursor: pointer; - transform: translateY(0); /* important pour garder le même ancrage */ -} - -/* --- PETIT POST (COMPACT) --- */ -.post-marker-compact { - background: rgba(0, 0, 0, 0.78); - padding: 4px 10px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.35); - box-shadow: - 0 0 0 1px rgba(15, 23, 42, 0.7), - 0 6px 18px rgba(0, 0, 0, 0.8), - 0 0 16px rgba(56, 189, 248, 0.4); -} - -.post-marker-compact::after { - content: ""; - position: absolute; - left: 50%; - bottom: -7px; - transform: translateX(-50%); - width: 0; - height: 0; - border-style: solid; - border-width: 7px 7px 0 7px; - border-color: rgba(0, 0, 0, 0.78) transparent transparent transparent; -} - -.marker-inner { - display: flex; - align-items: center; - gap: 6px; -} - -.marker-dot { - width: 16px; - height: 16px; - border-radius: 50%; - background: radial-gradient(circle at 30% 30%, #4ade80, #22c55e, #0284c7); -} - -.marker-text { - max-width: 140px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* --- GROS POST (EXPANDED) --- */ -.post-marker-expanded { - background: rgba(15, 23, 42, 0.98); - padding: 8px 10px 10px 10px; - border-radius: 16px; - border: 1px solid rgba(148, 163, 184, 0.9); - box-shadow: - 0 10px 30px rgba(0, 0, 0, 0.9), - 0 0 24px rgba(56, 189, 248, 0.55); -} - -.post-marker-expanded::after { - content: ""; - position: absolute; - left: 50%; - bottom: -10px; - transform: translateX(-50%); - width: 0; - height: 0; - border-style: solid; - border-width: 10px 10px 0 10px; - border-color: rgba(15, 23, 42, 0.98) transparent transparent transparent; -} - -/* contenu du gros post */ -.marker-expanded-inner { - display: flex; - flex-direction: column; - gap: 4px; - max-width: 230px; -} - -.marker-expanded-header { - display: flex; - align-items: center; - gap: 6px; -} - -.marker-expanded-title { - font-size: 13px; - font-weight: 600; -} - -.marker-expanded-body { - font-size: 12px; - line-height: 1.35; - opacity: 0.95; - max-height: 120px; - overflow-y: auto; -} +/* legacy placeholder (kept if imported) */ diff --git a/src/styles/overlays.css b/src/styles/overlays.css index 3b2ca6c..b5a0945 100644 --- a/src/styles/overlays.css +++ b/src/styles/overlays.css @@ -1,239 +1,70 @@ -/* ========================================== - SOCIOWIRE — OVERLAYS (TOP / LEFT / RIGHT / - BOTTOM / MY SPOT / CREATE PANEL) - ========================================== */ - -.map-overlay { - position: absolute; - pointer-events: none; - z-index: 20; -} - -.map-overlay * { - pointer-events: auto; -} - -/* --------- TOP --------- */ +.map-overlay { position:absolute; pointer-events:none; z-index:20; } +.map-overlay * { pointer-events:auto; } .map-overlay-top { - top: 0.7rem; - left: 50%; - transform: translateX(-50%); - display: flex; - gap: 0.6rem; - align-items: center; + top: .7rem; left:50%; transform:translateX(-50%); + display:flex; gap:.6rem; align-items:center; } -.lookat-box { - font-size: 0.75rem; - color: #93c5fd; -} - -.wire-title { - font-size: 0.75rem; - opacity: 0.9; - color: #93c5fd; -} - -/* --------- LEFT --------- */ - .map-overlay-left { - left: 0.7rem; - top: 44%; /* avant 50% → plus haut */ - transform: translateY(-50%); - display: flex; - flex-direction: column; - gap: 0.5rem; + left:.7rem; top:44%; transform:translateY(-50%); + display:flex; flex-direction:column; gap:.5rem; } - -/* --------- RIGHT --------- */ - .map-overlay-right { - right: 0.7rem; - top: 44%; /* avant 50% → plus haut */ - transform: translateY(-50%); - display: flex; - flex-direction: column; - gap: 0.5rem; + right:.7rem; top:44%; transform:translateY(-50%); + display:flex; flex-direction:column; gap:.5rem; } -/* Bouton principal sélectionné */ -.chip-selected { - background: linear-gradient(135deg, #2563eb, #0ea5e9); - border-color: #7dd3fc; - box-shadow: 0 0 20px rgba(56, 189, 248, 0.55); -} +.map-overlay-bottom { bottom: 3.2rem; left:50%; transform:translateX(-50%); display:flex; gap:.4rem; } -/* --------- BOTTOM — ARC AUTOUR DU GLOBE --------- */ - -.map-overlay-bottom { - bottom: 3.2rem; - left: 50%; - transform: translateX(-50%); - display: flex; - gap: 0.4rem; -} - -/* courbure inversée : centre plus bas, côtés plus hauts */ -.map-overlay-bottom .chip-egg:nth-child(1) { - transform: translateY(-8px) rotate(-12deg); -} -.map-overlay-bottom .chip-egg:nth-child(2) { - transform: translateY(-4px) rotate(-6deg); -} -.map-overlay-bottom .chip-egg:nth-child(3) { - transform: translateY(0px) rotate(0deg); -} -.map-overlay-bottom .chip-egg:nth-child(4) { - transform: translateY(-4px) rotate(6deg); -} -.map-overlay-bottom .chip-egg:nth-child(5) { - transform: translateY(-8px) rotate(12deg); -} - -/* --------- BOUTON "MY SPOT" --------- */ - -.map-overlay-myloc { - right: 0.7rem; - bottom: 6rem; -} - -/* --------- CROSSHAIR SUR LA MAP --------- */ - -.map-crosshair { - left: 50%; - top: 40%; - transform: translate(-50%, -50%); -} +.map-overlay-myloc { right:.7rem; bottom:6rem; } +.map-crosshair { left:50%; top:40%; transform:translate(-50%,-50%); } .crosshair-aim { - width: 34px; - height: 34px; - border-radius: 999px; - border: 1px solid #38bdf8; - position: relative; - box-shadow: 0 0 14px rgba(56, 189, 248, 0.9); + width:34px; height:34px; border-radius:999px; + border:1px solid #38bdf8; position:relative; + box-shadow:0 0 14px rgba(56,189,248,.9); } - -.crosshair-aim::before, -.crosshair-aim::after { - content: ""; - position: absolute; - left: 50%; - top: 50%; - background: #38bdf8; - transform: translate(-50%, -50%); +.crosshair-aim::before, .crosshair-aim::after{ + content:""; position:absolute; left:50%; top:50%; + background:#38bdf8; transform:translate(-50%,-50%); } - -.crosshair-aim::before { - width: 70%; - height: 1px; -} - -.crosshair-aim::after { - width: 1px; - height: 70%; -} - -/* --------- CREATE POST PANEL --------- */ +.crosshair-aim::before{ width:70%; height:1px; } +.crosshair-aim::after{ width:1px; height:70%; } .create-post-panel { - left: 50%; - bottom: 20%; - transform: translateX(-50%); - width: 92%; - max-width: 520px; - + left:50%; bottom:20%; transform:translateX(-50%); + width:92%; max-width:520px; background: rgba(15, 23, 42, 0.96); - border-radius: 16px; - border: 1px solid rgba(148, 163, 184, 0.9); - box-shadow: - 0 16px 40px rgba(0, 0, 0, 0.9), - 0 0 24px rgba(56, 189, 248, 0.6); - - padding: 0.6rem 0.7rem 0.7rem 0.7rem; -} - -.create-post-header { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.8rem; - margin-bottom: 0.4rem; - color: #e5e7eb; -} - -.create-close { - background: transparent; - border: none; - color: #9ca3af; - font-size: 0.85rem; - padding: 0.1rem 0.3rem; - cursor: pointer; -} - -.create-row { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.4rem; -} - -.crosshair-label { - font-size: 0.75rem; - color: #cbd5f5; + border-radius:16px; + border:1px solid rgba(148, 163, 184, 0.9); + box-shadow: 0 16px 40px rgba(0,0,0,.9), 0 0 24px rgba(56,189,248,.6); + padding: .6rem .7rem .7rem; } +.create-post-header { display:flex; justify-content:space-between; align-items:center; font-size:.8rem; margin-bottom:.4rem; color:#e5e7eb; } +.create-close { background:transparent; border:none; color:#9ca3af; font-size:.85rem; padding:.1rem .3rem; cursor:pointer; } +.create-row { display:flex; align-items:center; gap:.5rem; margin-bottom:.4rem; } +.crosshair-label { font-size:.75rem; color:#cbd5f5; } .create-row select { - flex: 1; - background: #020617; - border-radius: 999px; - border: 1px solid #4b5563; - padding: 0.35rem 0.7rem; - font-size: 0.75rem; - color: #e5e7eb; + flex:1; background:#020617; border-radius:999px; + border:1px solid #4b5563; padding:.35rem .7rem; + font-size:.75rem; color:#e5e7eb; } - .create-input { - width: 100%; - margin-bottom: 0.3rem; - border-radius: 999px; - border: 1px solid #4b5563; - padding: 0.35rem 0.7rem; - background: #020617; - color: #e5e7eb; - font-size: 0.8rem; + width:100%; margin-bottom:.3rem; border-radius:999px; + border:1px solid #4b5563; padding:.35rem .7rem; + background:#020617; color:#e5e7eb; font-size:.8rem; } - .create-textarea { - width: 100%; - border-radius: 0.75rem; - border: 1px solid #4b5563; - padding: 0.35rem 0.7rem; - background: #020617; - color: #e5e7eb; - resize: none; - font-size: 0.78rem; + width:100%; border-radius:.75rem; border:1px solid #4b5563; + padding:.35rem .7rem; background:#020617; color:#e5e7eb; + resize:none; font-size:.78rem; } +.create-error { margin-top:.25rem; font-size:.7rem; color:#f97373; } +.create-actions { margin-top:.4rem; display:flex; justify-content:flex-end; } -.create-error { - margin-top: 0.25rem; - font-size: 0.7rem; - color: #f97373; -} - -.create-actions { - margin-top: 0.4rem; - display: flex; - justify-content: flex-end; -} - -/* --------- STYLES DES CHIPS --------- */ - -.chip-round, -.chip-pill, -.chip-egg, -.chip-hex { +.chip-pill { background: rgba(15, 23, 42, 0.96); border: 1px solid rgba(148, 163, 184, 0.85); color: #e5e7eb; @@ -241,21 +72,5 @@ font-size: 0.78rem; border-radius: 999px; cursor: pointer; - box-shadow: - 0 5px 12px rgba(0, 0, 0, 0.7), - 0 0 12px rgba(56, 189, 248, 0.35); -} - -.chip-egg { - background: rgba(0, 10, 30, 0.9); - border: 1px solid rgba(56, 189, 248, 0.55); -} - -.chip-egg-active { - background: linear-gradient(135deg, #1d4ed8, #0ea5e9); - border-color: #7dd3fc; -} - -.chip-hex { - border-radius: 0.65rem; + box-shadow: 0 5px 12px rgba(0,0,0,.7), 0 0 12px rgba(56,189,248,.35); } diff --git a/src/styles/popup.css b/src/styles/popup.css index afac9aa..a2f2f26 100644 --- a/src/styles/popup.css +++ b/src/styles/popup.css @@ -1,70 +1 @@ -/* ========== POPUP (GROS POST AU CLIC) ========== */ - -.maplibregl-popup-content { - padding: 0; - border-radius: 16px; - overflow: visible; - background: transparent; - border: none; -} - -/* conteneur du gros post */ -.popup-post-card { - background: rgba(15, 23, 42, 0.98); - border-radius: 16px; - border: 1px solid rgba(148, 163, 184, 0.9); - box-shadow: - 0 10px 30px rgba(0, 0, 0, 0.9), - 0 0 26px rgba(56, 189, 248, 0.55); - color: #e5e7eb; -} - -/* crochet du gros post */ -.popup-post-card::after { - content: ""; - position: absolute; - left: 50%; - bottom: -10px; - transform: translateX(-50%); - width: 0; - height: 0; - border-style: solid; - border-width: 10px 10px 0 10px; - border-color: rgba(15, 23, 42, 0.98) transparent transparent transparent; -} - -.popup-post-inner { - padding: 8px 10px 10px 10px; -} - -.popup-post-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 6px; -} - -/* petit rond bleu dans le gros post, pour garder le lien visuel */ -.popup-post-dot { - width: 18px; - height: 18px; - border-radius: 999px; - background: radial-gradient(circle at 30% 30%, #4ade80, #22c55e, #0284c7); -} - -.popup-post-title { - font-size: 14px; - font-weight: 600; -} - -.popup-post-body { - font-size: 13px; - line-height: 1.35; - max-height: 180px; - overflow-y: auto; -} - -.popup-post-empty { - opacity: 0.8; - font-size: 12px; -} +/* legacy placeholder (kept if imported) */ diff --git a/src/styles/posts.css b/src/styles/posts.css index 6b2f513..704a732 100644 --- a/src/styles/posts.css +++ b/src/styles/posts.css @@ -1,184 +1,80 @@ -/* ========================================== - SOCIOWALL + CHAT (bas de l'écran) - ========================================== */ - .map-overlay-wall { - position: absolute; - left: 0.7rem; - right: 0.7rem; - bottom: 0.6rem; - display: flex; - gap: 0.6rem; - align-items: stretch; - pointer-events: none; - z-index: 10; + position:absolute; + left:.7rem; right:.7rem; bottom:.6rem; + display:flex; gap:.6rem; align-items:stretch; + pointer-events:none; z-index:10; } - -.map-overlay-wall * { - pointer-events: auto; -} - -/* --------- SOCIOWALL (liste de posts) --------- */ +.map-overlay-wall * { pointer-events:auto; } .sociowall-panel { flex: 1.5; min-height: 80px; - max-height: 150px; /* ≈ moitié de ce que tu avais */ + max-height: 150px; background: rgba(15, 23, 42, 0.96); border-radius: 14px; border: 1px solid rgba(148, 163, 184, 0.9); - box-shadow: - 0 10px 28px rgba(0, 0, 0, 0.9), - 0 0 18px rgba(56, 189, 248, 0.45); - padding: 0.45rem 0.55rem; - display: flex; - flex-direction: column; + box-shadow: 0 10px 28px rgba(0,0,0,.9), 0 0 18px rgba(56,189,248,.45); + padding: .45rem .55rem; + display:flex; flex-direction:column; } - .sociowall-header { - font-size: 0.78rem; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - color: #bfdbfe; - margin-bottom: 0.25rem; + font-size: .78rem; font-weight: 600; + letter-spacing: .06em; text-transform: uppercase; + color:#bfdbfe; margin-bottom:.25rem; } -/* --------- LISTE DES POSTS --------- */ - -.post-list { - flex: 1; - overflow-y: auto; - padding-right: 0.15rem; -} - -.post-list-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.5rem; - margin-bottom: 0.2rem; - font-size: 0.7rem; - color: #cbd5f5; -} - -.km-filter label { - font-size: 0.7rem; -} - -.km-filter span { - font-weight: 600; -} - -/* slider 0–1000 km */ -.km-filter input[type="range"] { - width: 130px; -} - -/* texte de statut (chargement / erreur / vide) */ -.status-text { - font-size: 0.72rem; - opacity: 0.85; - margin: 0.15rem 0; -} - -.status-error { - color: #fecaca; -} - -/* --------- CARTE D’UN POST --------- */ +.post-list { flex:1; overflow-y:auto; padding-right:.15rem; } +.post-list-header { display:flex; align-items:center; gap:.5rem; margin-bottom:.2rem; font-size:.7rem; color:#cbd5f5; } +.km-filter input[type="range"] { width:130px; } +.status-text { font-size:.72rem; opacity:.85; margin:.15rem 0; } +.status-error { color:#fecaca; } .post-card { - border-radius: 10px; - border: 1px solid rgba(55, 65, 81, 0.9); + border-radius:10px; + border:1px solid rgba(55, 65, 81, 0.9); background: rgba(15, 23, 42, 0.92); - padding: 0.3rem 0.45rem; - margin-bottom: 0.22rem; - font-size: 0.76rem; - cursor: pointer; - transition: background 0.15s ease, border-color 0.15s ease, - box-shadow 0.15s ease, transform 0.1s ease; + padding:.3rem .45rem; + margin-bottom:.22rem; + font-size:.76rem; + cursor:pointer; + transition: background .15s ease, border-color .15s ease, box-shadow .15s ease, transform .1s ease; } - .post-card:hover { background: rgba(17, 24, 39, 0.95); border-color: rgba(96, 165, 250, 0.9); - box-shadow: 0 0 12px rgba(56, 189, 248, 0.4); + box-shadow: 0 0 12px rgba(56,189,248,.4); transform: translateY(-1px); } - .post-card.selected { background: linear-gradient(135deg, #1d4ed8, #0ea5e9); border-color: #bfdbfe; - box-shadow: - 0 0 16px rgba(56, 189, 248, 0.6), - 0 10px 22px rgba(15, 23, 42, 0.98); -} - -.post-card h3 { - margin: 0 0 0.12rem 0; - font-size: 0.8rem; -} - -.post-card p { - margin: 0; - font-size: 0.72rem; - opacity: 0.9; + box-shadow: 0 0 16px rgba(56,189,248,.6), 0 10px 22px rgba(15,23,42,.98); } +.post-card h3 { margin:0 0 .12rem 0; font-size:.8rem; } +.post-card p { margin:0; font-size:.72rem; opacity:.9; } .post-km { - display: inline-block; - margin-top: 0.12rem; - font-size: 0.68rem; - padding: 0.05rem 0.35rem; - border-radius: 999px; + display:inline-block; margin-top:.12rem; font-size:.68rem; + padding:.05rem .35rem; border-radius:999px; background: rgba(15, 118, 110, 0.2); border: 1px solid rgba(45, 212, 191, 0.7); color: #a5f3fc; } -/* --------- PANEL CHAT (à droite) --------- */ - .chat-panel { - width: 150px; - min-width: 130px; - max-height: 150px; /* même hauteur que Sociowall */ + width:150px; min-width:130px; + max-height:150px; background: rgba(15, 23, 42, 0.96); border-radius: 14px; border: 1px solid rgba(148, 163, 184, 0.9); - box-shadow: - 0 10px 28px rgba(0, 0, 0, 0.9), - 0 0 18px rgba(56, 189, 248, 0.45); - padding: 0.4rem 0.45rem; - display: flex; - flex-direction: column; + box-shadow: 0 10px 28px rgba(0,0,0,.9), 0 0 18px rgba(56,189,248,.45); + padding:.4rem .45rem; + display:flex; flex-direction:column; } - .chat-header { - font-size: 0.78rem; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - color: #bfdbfe; - margin-bottom: 0.2rem; -} - -.chat-users { - list-style: none; - padding: 0; - margin: 0; - font-size: 0.76rem; -} - -.chat-users li { - display: flex; - align-items: center; - gap: 0.3rem; - padding: 0.18rem 0.1rem; -} - -.chat-users li::before { - content: "👤"; - font-size: 0.72rem; - opacity: 0.9; + font-size:.78rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase; + color:#bfdbfe; margin-bottom:.2rem; } +.chat-users { list-style:none; padding:0; margin:0; font-size:.76rem; } +.chat-users li { display:flex; align-items:center; gap:.3rem; padding:.18rem .1rem; } +.chat-users li::before { content:"👤"; font-size:.72rem; opacity:.9; } diff --git a/src/styles/theme.css b/src/styles/theme.css index d8cc270..5d27ffe 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -1,89 +1,21 @@ -/* ========================================== - THEME SYSTEM — body[data-theme="..."] - Trois thèmes : dark (par défaut), blue, light - ========================================== */ - -/* Thème noir (par défaut) */ -body[data-theme="dark"] { - background: #050816; - color: #ffffff; -} - -/* ===================== BLUE ===================== */ +body[data-theme="dark"] { background:#050816; color:#ffffff; } body[data-theme="blue"] .sociowall-panel, body[data-theme="blue"] .chat-panel, body[data-theme="blue"] .post-card, body[data-theme="blue"] .post-pin--expanded .post-card, -body[data-theme="blue"] .post-pin--compact .post-pin-bubble, -body[data-theme="blue"] .popup-post-card { +body[data-theme="blue"] .post-pin--compact .post-pin-bubble { background: rgba(12, 20, 60, 0.96); border-color: rgba(96, 165, 250, 0.95); } -body[data-theme="blue"] .chip-round, -body[data-theme="blue"] .chip-pill, -body[data-theme="blue"] .chip-egg, -body[data-theme="blue"] .chip-hex { - background: rgba(10, 22, 60, 0.96); - border-color: rgba(96, 165, 250, 0.9); -} +body[data-theme="light"] { background:#e5e7eb; color:#111827; } -body[data-theme="blue"] .chip-selected, -body[data-theme="blue"] .chip-egg-active { - background: linear-gradient(135deg, #1d4ed8, #38bdf8); - border-color: #bfdbfe; -} - -/* ===================== LIGHT ===================== */ - -body[data-theme="light"] { - background: #e5e7eb; - color: #111827; -} - -/* panneaux principaux clairs */ body[data-theme="light"] .sociowall-panel, body[data-theme="light"] .chat-panel, body[data-theme="light"] .post-card, -body[data-theme="light"] .post-pin--expanded .post-card, -body[data-theme="light"] .popup-post-card { +body[data-theme="light"] .post-pin--expanded .post-card { background: rgba(248, 250, 252, 0.97); border-color: rgba(148, 163, 184, 0.9); - color: #111827; -} - -/* petits marqueurs compacts */ -body[data-theme="light"] .post-pin--compact .post-pin-bubble { - background: rgba(255, 255, 255, 0.96); - border-color: rgba(148, 163, 184, 0.9); - color: #0f172a; -} - -/* chips claires */ -body[data-theme="light"] .chip-round, -body[data-theme="light"] .chip-pill, -body[data-theme="light"] .chip-egg, -body[data-theme="light"] .chip-hex { - background: rgba(255, 255, 255, 0.98); - border-color: rgba(148, 163, 184, 0.9); - color: #111827; -} - -body[data-theme="light"] .chip-selected, -body[data-theme="light"] .chip-egg-active { - background: linear-gradient(135deg, #38bdf8, #0ea5e9); - border-color: #0ea5e9; - color: #0f172a; -} - -/* topbar clair */ -body[data-theme="light"] .topbar { - background: #f9fafb; - border-bottom-color: #e5e7eb; -} - -body[data-theme="light"] .main-title, -body[data-theme="light"] .sub-title { - color: #0f172a; + color:#111827; } diff --git a/src/styles/topbar.css b/src/styles/topbar.css index 1834e23..979e0d1 100644 --- a/src/styles/topbar.css +++ b/src/styles/topbar.css @@ -1,5 +1,3 @@ -/* ===== SOCIOWIRE Topbar — clean gradient, tight spacing, solid mobile ===== */ - .topbar{ position: sticky; top:0; z-index:100; width:100%; @@ -11,20 +9,18 @@ overflow:hidden; } -/* LEFT: logo + titles */ .logo-circle{ width:40px; height:40px; border-radius:50%; flex-shrink:0; background-image:url("/icons/logo-master.png"); background-size:cover; background-position:center; background-repeat:no-repeat; box-shadow: 0 0 10px rgba(37,99,235,.8), 0 0 18px rgba(56,189,248,.6); - color:transparent; /* cache texte éventuel */ + color:transparent; } .title-block{ display:flex; flex-direction:column; line-height:1.05; min-width:0; } .main-title{ font-size:1.02rem; font-weight:800; letter-spacing:.04em; color:#f9fafb; } .sub-title{ font-size:.74rem; color:#dbeafe; opacity:.95; } -/* RIGHT: theme dots + auth buttons */ .topbar-right{ display:flex; align-items:center; gap:.5rem; margin-left:auto; flex-wrap:wrap; } .theme-switch{ display:flex; gap:.28rem; } @@ -42,30 +38,24 @@ padding:.32rem .85rem; font-size:.78rem; border-radius:999px; font-weight:700; cursor:pointer; white-space:nowrap; line-height:1; } -.btn-primary{ border:1px solid #22c55e; background:radial-gradient(circle at 30% 30%,#22c55e,#16a34a); color:#f9fafb; +.btn-primary{ + border:1px solid #22c55e; + background:radial-gradient(circle at 30% 30%,#22c55e,#16a34a); + color:#f9fafb; box-shadow:0 4px 10px rgba(22,163,74,.5),0 0 8px rgba(22,163,74,.6); } .btn-primary:disabled{ opacity:.6; cursor:default; } -.btn-secondary{ border:1px solid #bfdbfe; background:rgba(15,23,42,.25); color:#e0f2fe; box-shadow:0 3px 10px rgba(15,23,42,.6); } +.btn-secondary{ + border:1px solid #bfdbfe; + background:rgba(15,23,42,.25); + color:#e0f2fe; + box-shadow:0 3px 10px rgba(15,23,42,.6); +} .btn-secondary:hover{ background:rgba(15,23,42,.45); } -/* Auth label (AUTH: anon) plus discret, ne casse pas la ligne */ -.topbar .title-block + div, -.topbar .auth-label{ - font-size:.65rem; opacity:.7; color:#dbeafe; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -} - -/* Mobile tweaks */ @media (max-width:640px){ .topbar{ padding:.5rem .7rem; border-bottom-left-radius:16px; border-bottom-right-radius:16px; } .main-title{ font-size:1rem; } .sub-title{ font-size:.7rem; } .topbar-right{ width:100%; justify-content:flex-start; gap:.45rem; } } - -/* Light theme compatibility */ -body[data-theme="light"] .topbar{ - background:linear-gradient(135deg,#e6f0ff 0%,#f9fafb 100%); - box-shadow:0 8px 20px rgba(148,163,184,.45),0 0 10px rgba(129,140,248,.25); -} -body[data-theme="light"] .main-title, body[data-theme="light"] .sub-title{ color:#0f172a; } diff --git a/vite.config.js b/vite.config.js index 5a1d145..d0a1a7d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,15 +1,19 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -// https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], server: { - host: "0.0.0.0", + host: true, port: 5173, proxy: { "/api": { - target: "http://localhost:8081", // ton backend + target: "http://127.0.0.1:8081", + changeOrigin: true, + }, + "/ws": { + target: "ws://127.0.0.1:8081", + ws: true, changeOrigin: true, }, },