From 938c77b7a5a61ebff27a99768c5050cd09512cd1 Mon Sep 17 00:00:00 2001 From: Armelle Cogent Date: Wed, 9 Sep 2026 11:58:58 +0200 Subject: [PATCH] nettoyage css + script detection --- package.json | 1 + scripts/check-css-modules.js | 613 ++++++++++++++++++ .../BottomTabbar/BottomTabbar.module.css | 8 - .../components/LeftMenu/LeftMenu.module.css | 35 - .../AuthCallback/AuthCallbackPage.module.css | 2 +- .../pages/AuthCallback/AuthCallbackPage.tsx | 2 +- .../pages/MyAccount/MyAccountPage.module.css | 29 +- .../pages/LegendPage/LegendPage.module.css | 15 +- .../legend/pages/LegendPage/LegendPage.tsx | 12 +- .../GeodesyPointReportWizard.module.css | 14 - .../MapBottomSheet/MapBottomSheet.module.css | 73 +-- .../pointFiche/MapPointSheet.module.css | 5 +- .../pointFiche/PointCoordinatesSection.tsx | 1 - .../pointFiche/PointImageCarousel.module.css | 4 +- .../MapGeodesyFiltersPanel.module.css | 8 - .../MapLayerInfoPanel/MapLayerInfoPanel.tsx | 2 +- .../MapLayersPanel/MapLayersPanel.module.css | 53 -- .../MapOverlaySheet.module.css | 38 -- src/shared/styles/inputs.module.css | 143 ---- src/shared/styles/screen.module.css | 4 - src/shared/ui/Button/Button.module.css | 11 +- 21 files changed, 643 insertions(+), 430 deletions(-) create mode 100644 scripts/check-css-modules.js delete mode 100644 src/shared/styles/inputs.module.css diff --git a/package.json b/package.json index 57620cf..7409289 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "build:qualif": "tsc -b && vite build --mode qualif && cp scripts/htaccess-qualif.example dist/.htaccess", "build:mobile": "VITE_GEODESY_SOURCE=dist tsc -b && vite build", "lint": "eslint .", + "lint:css-modules": "node scripts/check-css-modules.js", "preview": "vite preview", "preview:qualif": "vite preview --mode qualif", "generate-apk": "bash ./scripts/generate-apk.sh", diff --git a/scripts/check-css-modules.js b/scripts/check-css-modules.js new file mode 100644 index 0000000..d3da1f2 --- /dev/null +++ b/scripts/check-css-modules.js @@ -0,0 +1,613 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +const PACKAGE_ROOT = path.resolve(SCRIPT_DIR, '..'); +const SRC_DIR = path.join(PACKAGE_ROOT, 'src'); +const ROOT = path.resolve(process.argv[2] || SRC_DIR); + +const SOURCE_EXTENSIONS = new Set([ + '.js', + '.jsx', + '.ts', + '.tsx', +]); + +const IGNORE_DIRS = new Set([ + 'node_modules', + '.git', + 'dist', + 'build', + 'coverage', + '.next', +]); + +const cssModules = new Map(); +const unresolvedImports = []; + +/** + * Retourne tous les fichiers d'un dossier récursivement. + */ +function walk(dir) { + if (!fs.existsSync(dir)) { + console.error(`Dossier introuvable : ${dir}`); + process.exit(1); + } + + const files = []; + + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (IGNORE_DIRS.has(entry.name)) { + continue; + } + + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + files.push(...walk(fullPath)); + } else { + files.push(fullPath); + } + } + + return files; +} + +/** + * Supprime un préfixe suivi d'un bloc équilibré, ex. :global(...) ou url("..."). + */ +function stripPrefixedBalanced(content, prefixRegex, open, close) { + let result = ''; + let lastIndex = 0; + prefixRegex.lastIndex = 0; + + let match; + + while ((match = prefixRegex.exec(content)) !== null) { + const openAt = match.index + match[0].length - 1; + let depth = 1; + let i = openAt + 1; + + while (i < content.length && depth > 0) { + const ch = content[i]; + + if (ch === open) { + depth++; + } else if (ch === close) { + depth--; + } + + i++; + } + + result += content.slice(lastIndex, match.index); + lastIndex = i; + prefixRegex.lastIndex = i; + } + + return result + content.slice(lastIndex); +} + +/** + * Supprime grossièrement les commentaires CSS. + */ +function removeCssComments(content) { + return content.replace(/\/\*[\s\S]*?\*\//g, ''); +} + +/** + * Supprime url(...) pour ne pas extraire de faux .org / .svg. + */ +function stripCssUrls(content) { + let result = content.replace( + /url\(\s*(['"])[\s\S]*?\1\s*\)/g, + 'url()' + ); + + result = stripPrefixedBalanced(result, /url\s*\(/g, '(', ')'); + + return result; +} + +/** + * Supprime :global(...) et :global { ... } (classes OpenLayers, etc.). + */ +function stripCssGlobals(content) { + let result = stripPrefixedBalanced(content, /:global\s*\(/g, '(', ')'); + result = stripPrefixedBalanced(result, /:global\s*\{/g, '{', '}'); + return result; +} + +/** + * Extrait les classes définies dans un module CSS. + * + * Ex: + * .foo {} + * .foo:hover {} + * .foo.bar {} + * + * => foo, bar + */ +function extractCssClasses(content) { + content = stripCssGlobals(stripCssUrls(removeCssComments(content))); + + const classes = new Set(); + + // On récupère les .classe + // + // Supporte notamment : + // .foo + // .foo-bar + // .foo_bar + // + const regex = /\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)/g; + + let match; + + while ((match = regex.exec(content)) !== null) { + classes.add(match[1]); + } + + return classes; +} + +/** + * Résout un import CSS module, y compris l'alias Vite/TS `@/` → `src/`. + */ +function resolveCssImport(sourceFile, cssImport) { + if (cssImport.startsWith('@/')) { + return path.resolve(SRC_DIR, cssImport.slice(2)); + } + + return path.resolve(path.dirname(sourceFile), cssImport); +} + +/** + * Supprime commentaires // et /* *\/ sans trop casser les URL http://. + */ +function removeJsComments(content) { + return content + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/(^|[^:\/])\/\/.*$/gm, '$1'); +} + +/** + * Extrait les imports CSS modules. + * + * Ex: + * import styles from './Button.module.css' + * import classes from "./foo.module.css"; + * import screen from '@/shared/styles/screen.module.css' + */ +function extractModuleImports(content) { + const imports = []; + + const regex = + /import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]([^'"]+\.module\.css)['"]/g; + + let match; + + while ((match = regex.exec(content)) !== null) { + imports.push({ + variable: match[1], + file: match[2], + }); + } + + return imports; +} + +/** + * Escape pour utilisation dans une RegExp. + */ +function escapeRegex(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * Littéraux string des alias de types (`type Foo = 'a' | 'b'`). + * Sert à résoudre styles[color] / styles[size] dans le même fichier. + */ +function extractTypeStringLiterals(content) { + const literals = new Set(); + const typeAlias = + /(?:export\s+)?type\s+[A-Za-z_$][\w$]*\s*=\s*([^;{]+)/g; + + let match; + + while ((match = typeAlias.exec(content)) !== null) { + const litRegex = /['"]([^'"]+)['"]/g; + let litMatch; + + while ((litMatch = litRegex.exec(match[1])) !== null) { + const value = litMatch[1]; + + if (/^-?[_a-zA-Z][_a-zA-Z0-9-]*$/.test(value)) { + literals.add(value); + } + } + } + + return literals; +} + +/** + * Classes visées par un accès dynamique styles[ident], via unions TS + * et valeurs par défaut (`size = 'md'`). + */ +function extractDynamicClasses(content, variable) { + const classes = new Set(); + const escaped = escapeRegex(variable); + const dynamicRegex = new RegExp( + `(? 0) { + for (const item of unresolvedImports) { + console.log( + `❌ CSS MODULE INTROUVABLE\n` + + ` ${relative(item.sourceFile)}\n` + + ` import ${item.variable} from '${item.file}'\n` + ); + } +} + +for (const module of [...cssModules.values()].sort((a, b) => + a.file.localeCompare(b.file) +)) { + const unused = [...module.defined] + .filter(className => !module.used.has(className)) + .sort(); + + const missing = [...module.missing.keys()].sort(); + const globalStrings = [...module.globalStrings.keys()].sort(); + + if ( + unused.length === 0 && + missing.length === 0 && + globalStrings.length === 0 && + module.importedBy.size > 0 + ) { + continue; + } + + console.log(`\n📄 ${relative(module.file)}`); + + /* + * Module jamais importé + */ + if (module.importedBy.size === 0) { + console.log(' ⚠️ Module CSS jamais importé'); + unimportedCount++; + } + + /* + * Classe du module écrite en string globale (className="foo"). + */ + if (globalStrings.length > 0) { + console.log( + '\n ⚠️ Classes du module utilisées en string globale :\n' + + ' (className="foo" n’applique pas le hash CSS module,' + + ' utiliser styles.foo ou styles[\'foo\'])' + ); + + for (const className of globalStrings) { + console.log(` .${className}`); + + for (const sourceFile of module.globalStrings.get(className)) { + console.log(` ↳ ${relative(sourceFile)}`); + } + + globalStringCount++; + } + } + + /* + * Classes CSS inutilisées + */ + if (unused.length > 0) { + console.log('\n 🟡 Classes définies mais non utilisées :'); + + for (const className of unused) { + console.log(` .${className}`); + unusedCount++; + } + } + + /* + * Classes JS absentes du CSS + */ + if (missing.length > 0) { + console.log('\n 🔴 Classes utilisées mais absentes du CSS :'); + + for (const className of missing) { + console.log(` ${className}`); + + const sourceFiles = module.missing.get(className); + + for (const sourceFile of sourceFiles) { + console.log( + ` ↳ ${relative(sourceFile)}` + ); + } + + missingCount++; + } + } +} + +console.log('\n=========================================='); +console.log(' Résumé'); +console.log('=========================================='); + +console.log(`🟡 Classes CSS inutilisées : ${unusedCount}`); +console.log(`🔴 Classes CSS absentes : ${missingCount}`); +console.log(`⚠️ Modules non importés : ${unimportedCount}`); +console.log(`⚠️ Strings globales : ${globalStringCount}`); +console.log(`❌ Modules introuvables : ${unresolvedImports.length}`); + +if ( + unusedCount === 0 && + missingCount === 0 && + unimportedCount === 0 && + globalStringCount === 0 && + unresolvedImports.length === 0 +) { + console.log('\n✅ Aucun problème détecté.'); +} else { + console.log(''); +} + +/* + * Code retour != 0 uniquement s'il existe + * des références vers des classes / fichiers inexistants. + * + * Pratique pour CI / npm test. + */ +process.exitCode = + missingCount > 0 || unresolvedImports.length > 0 ? 1 : 0; diff --git a/src/app/components/BottomTabbar/BottomTabbar.module.css b/src/app/components/BottomTabbar/BottomTabbar.module.css index 2a7d8a6..32f4583 100644 --- a/src/app/components/BottomTabbar/BottomTabbar.module.css +++ b/src/app/components/BottomTabbar/BottomTabbar.module.css @@ -60,11 +60,3 @@ .active .tabLabel { display: block; } - -.iconCircle { - display: none; -} - -.iconCircleActive { - display: none; -} diff --git a/src/app/components/LeftMenu/LeftMenu.module.css b/src/app/components/LeftMenu/LeftMenu.module.css index f7bc3d7..e17a210 100644 --- a/src/app/components/LeftMenu/LeftMenu.module.css +++ b/src/app/components/LeftMenu/LeftMenu.module.css @@ -165,41 +165,6 @@ fill: var(--color-text); } -.chevronExpanded { - transform: rotate(90deg); -} - -.groupItems { - max-height: 0; - overflow: hidden; - transition: max-height var(--duration-slow) var(--ease-standard); -} - -.groupItemsExpanded { - max-height: 18.75rem; -} - -.menuItem { - display: flex; - align-items: center; - width: 100%; - padding: var(--spacing-sm) var(--spacing-md) var(--spacing-sm) var(--spacing-2xl); - background: none; - border: none; - cursor: pointer; - text-align: left; - font-size: var(--font-size-base); - color: var(--color-border-strong); -} - -.menuItem:hover { - background-color: var(--color-bg-subtle); -} - -.menuItem:active { - background-color: var(--color-bg-active); -} - .standaloneItems { margin-top: auto; padding: var(--spacing-md); diff --git a/src/features/auth/pages/AuthCallback/AuthCallbackPage.module.css b/src/features/auth/pages/AuthCallback/AuthCallbackPage.module.css index 3275faa..c54d868 100644 --- a/src/features/auth/pages/AuthCallback/AuthCallbackPage.module.css +++ b/src/features/auth/pages/AuthCallback/AuthCallbackPage.module.css @@ -9,6 +9,6 @@ margin-top: 1.5rem; } -.text-error { +.textError { color: var(--color-danger); } \ No newline at end of file diff --git a/src/features/auth/pages/AuthCallback/AuthCallbackPage.tsx b/src/features/auth/pages/AuthCallback/AuthCallbackPage.tsx index 4af37c9..8ac8edb 100644 --- a/src/features/auth/pages/AuthCallback/AuthCallbackPage.tsx +++ b/src/features/auth/pages/AuthCallback/AuthCallbackPage.tsx @@ -67,7 +67,7 @@ export function AuthCallbackPage() { return (

Erreur de connexion

-

{error}

+

{error}

diff --git a/src/features/auth/pages/MyAccount/MyAccountPage.module.css b/src/features/auth/pages/MyAccount/MyAccountPage.module.css index e5a4580..058833a 100644 --- a/src/features/auth/pages/MyAccount/MyAccountPage.module.css +++ b/src/features/auth/pages/MyAccount/MyAccountPage.module.css @@ -64,6 +64,10 @@ fill: var(--icon-color-8); } +.photoIcon { + width: var(--icon-base); + height: var(--icon-base); +} .cameraButton { position: absolute; bottom: 0; @@ -157,18 +161,6 @@ box-shadow: var(--shadow-sm); } -.contributionIcon { - width: 2.5rem; - height: 2.5rem; - color: var(--color-text-secondary); - background-color: var(--color-bg-primary-subtle); - border-radius: 1.5rem; -} - -.contributionIcon path { - fill: var(--color-text-primary-8); -} - .contributionValue { font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); @@ -183,19 +175,6 @@ font-weight: var(--font-weight-normal); } -.badge { - position: absolute; - top: var(--spacing-xs); - right: var(--spacing-xs); - width: 1.5rem; - height: 1.5rem; - border-radius: var(--radius-circle); - background-color: var(--color-action-primary); - display: flex; - align-items: center; - justify-content: center; - color: var(--color-bg-surface); -} /* ── Note de bas de page ── */ .footerNote { diff --git a/src/features/legend/pages/LegendPage/LegendPage.module.css b/src/features/legend/pages/LegendPage/LegendPage.module.css index d8d7422..22661ef 100644 --- a/src/features/legend/pages/LegendPage/LegendPage.module.css +++ b/src/features/legend/pages/LegendPage/LegendPage.module.css @@ -1,9 +1,6 @@ -.placeholder { - margin: 1.5rem 0 0; - padding: 1.25rem; - border-radius: 0.75rem; - background: var(--color-bg-surface); - color: var(--color-text-secondary); - font-size: 0.9375rem; - text-align: center; -} + +.content { + display: flex; + flex-direction: column; + gap: 16px; +} \ No newline at end of file diff --git a/src/features/legend/pages/LegendPage/LegendPage.tsx b/src/features/legend/pages/LegendPage/LegendPage.tsx index 3b12c4d..d535d2f 100644 --- a/src/features/legend/pages/LegendPage/LegendPage.tsx +++ b/src/features/legend/pages/LegendPage/LegendPage.tsx @@ -1,5 +1,7 @@ import { MapOverlaySheet } from '@/features/map/components/MapOverlaySheet'; +import styles from './LegendPage.module.css'; + export interface LegendPageProps { isOpen: boolean; onClose: () => void; @@ -8,10 +10,12 @@ export interface LegendPageProps { export function LegendPage({ isOpen, onClose }: LegendPageProps) { return ( -

TODO — Écran pas encore développé

-

- Symboles des repères, clusters et stations affichés sur la carte. -

+
+

TODO — Écran pas encore développé

+

+ Symboles des repères, clusters et stations affichés sur la carte. +

+
); } diff --git a/src/features/map/components/GeodesyPointReportWizard/GeodesyPointReportWizard.module.css b/src/features/map/components/GeodesyPointReportWizard/GeodesyPointReportWizard.module.css index a6bd803..c946fa6 100644 --- a/src/features/map/components/GeodesyPointReportWizard/GeodesyPointReportWizard.module.css +++ b/src/features/map/components/GeodesyPointReportWizard/GeodesyPointReportWizard.module.css @@ -114,17 +114,3 @@ grid-template-columns: 1fr 1fr; gap: 0.75rem; } - -.authHint { - margin: 0.75rem 0 0; - font-size: 13px; - color: var(--color-text-secondary); - text-align: center; -} - -.errorText { - margin: 0.5rem 0 0; - font-size: 13px; - color: var(--color-danger); - text-align: center; -} diff --git a/src/features/map/components/MapBottomSheet/MapBottomSheet.module.css b/src/features/map/components/MapBottomSheet/MapBottomSheet.module.css index 660162c..7513e5f 100644 --- a/src/features/map/components/MapBottomSheet/MapBottomSheet.module.css +++ b/src/features/map/components/MapBottomSheet/MapBottomSheet.module.css @@ -33,41 +33,6 @@ transition: height var(--duration-slow) var(--ease-standard), background var(--duration-slow) var(--ease-standard), box-shadow var(--duration-slow) var(--ease-standard); } -.searchFab { - --search-fab-size: 3rem; - position: absolute; - right: var(--spacing-md); - bottom: var(--spacing-sm); - z-index: 2; - display: flex; - align-items: center; - justify-content: center; - width: var(--search-fab-size); - height: var(--search-fab-size); - padding: 0; - border: none; - border-radius: var(--radius-circle); - background: var(--color-bg-subtle); - box-shadow: var(--shadow-md); - cursor: pointer; - pointer-events: auto; -} - -.searchFab:hover, -.searchFab:focus-visible { - background: var(--color-bg-primary-subtle); - outline: none; -} - -.searchFabIcon { - width: 1.5rem; - height: 1.5rem; -} - -.searchFabIcon path { - fill: var(--color-text); -} - .sheetPointFiche { background: var(--color-fiche-canvas); /* Ne jamais couvrir la pile de FAB (filtre/couches/légende) de MapPage : 3 boutons de @@ -377,11 +342,7 @@ display: none !important; } -.contentCompact { - flex: 0 0 auto; - overflow: visible; - padding: 0 var(--spacing-md) var(--spacing-sm); -} + .searchArea { flex-shrink: 0; @@ -389,13 +350,6 @@ z-index: 1; } -.rgpSection { - flex: 1; - min-height: 0; - margin-top: var(--spacing-md); - overflow: auto; -} - .rgpDispoGroup { display: grid; grid-template-columns: repeat(4, 1.125rem); @@ -542,15 +496,6 @@ color: var(--color-text); } - - -.rgpTitle { - margin: 0 0 var(--spacing-sm); - font-size: var(--font-size-base); - font-weight: var(--font-weight-bold); - color: var(--color-text); -} - .rgpLoading { display: flex; justify-content: center; @@ -598,10 +543,6 @@ background: var(--color-bg-primary-subtle); } -.rgpDotAvailable, -.rgpDotUnavailable { - display: none; -} .rgpName { font-size: var(--font-size-base); @@ -629,18 +570,6 @@ flex: 1; } -.levelContent { - padding-bottom: var(--spacing-xs); -} - -.placeholder { - margin: 0; - padding: var(--spacing-md) 0; - text-align: center; - color: var(--color-text-secondary); - font-size: var(--font-size-base); -} - .sheetSearchActive { /* Étend la sheet pour couvrir la zone tabbar */ margin-bottom: calc(-1 * (6.5rem + var(--safe-bottom))); diff --git a/src/features/map/components/MapBottomSheet/pointFiche/MapPointSheet.module.css b/src/features/map/components/MapBottomSheet/pointFiche/MapPointSheet.module.css index 092f656..76e0f79 100644 --- a/src/features/map/components/MapBottomSheet/pointFiche/MapPointSheet.module.css +++ b/src/features/map/components/MapBottomSheet/pointFiche/MapPointSheet.module.css @@ -96,6 +96,7 @@ fill: var(--figma-gray-11); } +/* Favori désactivé pour cette version — fonctionnalité prévue pour une version ultérieure. .favoriteButton { display: flex; align-items: center; @@ -108,7 +109,7 @@ background: var(--color-bg-primary-hover-subtle); cursor: default; flex-shrink: 0; -} +} .favoriteIcon { width: 1.25rem; @@ -118,7 +119,7 @@ .favoriteIcon path { fill: var(--color-text-primary); } - +*/ .statusMetaRow { display: flex; align-items: center; diff --git a/src/features/map/components/MapBottomSheet/pointFiche/PointCoordinatesSection.tsx b/src/features/map/components/MapBottomSheet/pointFiche/PointCoordinatesSection.tsx index 1b1eecb..4835aa2 100644 --- a/src/features/map/components/MapBottomSheet/pointFiche/PointCoordinatesSection.tsx +++ b/src/features/map/components/MapBottomSheet/pointFiche/PointCoordinatesSection.tsx @@ -70,7 +70,6 @@ export function PointCoordinatesSection({ action }: PointCoordinatesSectionProps
diff --git a/src/features/map/components/MapBottomSheet/pointFiche/PointImageCarousel.module.css b/src/features/map/components/MapBottomSheet/pointFiche/PointImageCarousel.module.css index 1768e96..ff624e5 100644 --- a/src/features/map/components/MapBottomSheet/pointFiche/PointImageCarousel.module.css +++ b/src/features/map/components/MapBottomSheet/pointFiche/PointImageCarousel.module.css @@ -64,13 +64,13 @@ fill: var(--color-white); } -.caption { +/* .caption { padding: 0.5rem 0.75rem; font-size: 0.75rem; color: var(--color-text-secondary); font-style: italic; text-align: center; -} +} */ .dots { display: flex; diff --git a/src/features/map/components/MapGeodesyFiltersPanel/MapGeodesyFiltersPanel.module.css b/src/features/map/components/MapGeodesyFiltersPanel/MapGeodesyFiltersPanel.module.css index 31ba55d..8d73723 100644 --- a/src/features/map/components/MapGeodesyFiltersPanel/MapGeodesyFiltersPanel.module.css +++ b/src/features/map/components/MapGeodesyFiltersPanel/MapGeodesyFiltersPanel.module.css @@ -1,11 +1,3 @@ -.content { - display: flex; - flex-direction: column; - min-height: 0; - flex: 1; - padding: 0; -} - .form { display: flex; flex-direction: column; diff --git a/src/features/map/components/MapLayerInfoPanel/MapLayerInfoPanel.tsx b/src/features/map/components/MapLayerInfoPanel/MapLayerInfoPanel.tsx index 8422388..4b38a92 100644 --- a/src/features/map/components/MapLayerInfoPanel/MapLayerInfoPanel.tsx +++ b/src/features/map/components/MapLayerInfoPanel/MapLayerInfoPanel.tsx @@ -37,7 +37,7 @@ export function MapLayerInfoPanel({

{description}

{legend ? (
-

Légende

+

Légende

Légende
) : null} diff --git a/src/features/map/components/MapLayersPanel/MapLayersPanel.module.css b/src/features/map/components/MapLayersPanel/MapLayersPanel.module.css index 215ddee..fffa72c 100644 --- a/src/features/map/components/MapLayersPanel/MapLayersPanel.module.css +++ b/src/features/map/components/MapLayersPanel/MapLayersPanel.module.css @@ -28,15 +28,6 @@ height: 0.4rem; } - .cardActionButton { - width: 1.75rem; - height: 1.75rem; - } - - .cardActionIcon { - width: 1rem; - height: 1rem; - } .cardTitle { font-size: 0.6875rem; @@ -118,50 +109,6 @@ fill: var(--color-white); } -.cardActions { - position: absolute; - top: 0.5rem; - right: 0.5rem; - z-index: 1; - display: flex; - gap: 0.25rem; - opacity: 0; - transition: opacity 0.2s; -} - -.cardThumbnail:hover .cardActions, -.cardThumbnail:focus-within .cardActions { - opacity: 1; -} - -.cardActionButton { - display: flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - padding: 0; - border: none; - border-radius: 50%; - background: rgba(255, 255, 255, 0.95); - cursor: pointer; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - transition: transform 0.2s; -} - -.cardActionButton:hover { - transform: scale(1.1); - background: var(--color-bg-subtle); -} - -.cardActionIcon { - width: 1.125rem; - height: 1.125rem; -} - -.cardActionIcon path { - fill: var(--color-text); -} .cardContent { display: flex; diff --git a/src/features/map/components/MapOverlaySheet/MapOverlaySheet.module.css b/src/features/map/components/MapOverlaySheet/MapOverlaySheet.module.css index 07334f2..632f78b 100644 --- a/src/features/map/components/MapOverlaySheet/MapOverlaySheet.module.css +++ b/src/features/map/components/MapOverlaySheet/MapOverlaySheet.module.css @@ -61,24 +61,7 @@ flex-shrink: 0; } -.headerTitleLeft { - grid-template-columns: 1fr auto; -} - - -.backButton { - border: none; - background: none; - padding: 0.25rem 0; - font: inherit; - font-size: 0.875rem; - font-weight: 600; - color: var(--color-text-primary); - cursor: pointer; -} - -.headerSpacer, .titleSpacer { display: block; } @@ -131,27 +114,6 @@ align-items: center; } -.closeButton { - display: flex; - align-items: center; - justify-content: center; - width: 1.8rem; - height: 1.8rem; - padding: 0; - border: none; - background: none; - cursor: pointer; -} - -.closeIcon { - width: 1rem; - height: 1rem; -} - -.closeIcon path { - fill: var(--color-text); -} - .footer { flex-shrink: 0; padding: 0 1rem 1rem; diff --git a/src/shared/styles/inputs.module.css b/src/shared/styles/inputs.module.css deleted file mode 100644 index 4ed82e0..0000000 --- a/src/shared/styles/inputs.module.css +++ /dev/null @@ -1,143 +0,0 @@ -/* Shared form input styles — import as `inputs` and use classes */ - -/* Field layout: label + input + error stacked vertically */ -.field { - display: flex; - flex-direction: column; - gap: var(--spacing-2xs); -} - -.label { - font-size: var(--font-size-base); - font-weight: var(--font-weight-semibold); - color: var(--color-text); -} - -.required { - color: var(--color-danger); -} - -.error { - font-size: var(--font-size-xs); - color: var(--color-danger); -} - -/* Text input / number / date */ -.input { - padding: var(--spacing-sm) var(--spacing-s); - border-radius: var(--radius-sm); - border: 1.5px solid var(--color-bg-disabled); - font-size: var(--font-size-lg); - font-family: var(--font-family-base); - background: var(--color-bg-subtle); - transition: border-color var(--duration-base) var(--ease-standard); - box-sizing: border-box; -} - -.input:focus { - outline: none; - border-color: var(--color-text-primary); -} - -.input::placeholder { - color: var(--color-text-secondary); -} - -/* Select */ -.select { - padding: var(--spacing-sm) var(--spacing-s); - padding-right: 2.25rem; - border-radius: var(--radius-sm); - border: 1.5px solid var(--color-bg-disabled); - font-size: var(--font-size-lg); - font-family: var(--font-family-base); - background-color: var(--color-bg-subtle); - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right var(--spacing-sm) center; - cursor: pointer; - transition: border-color var(--duration-base) var(--ease-standard); - box-sizing: border-box; -} - -.select:focus { - outline: none; - border-color: var(--color-text-primary); -} - -/* Textarea */ -.textarea { - padding: var(--spacing-sm) var(--spacing-s); - border-radius: var(--radius-sm); - border: 1.5px solid var(--color-bg-disabled); - font-size: var(--font-size-lg); - font-family: var(--font-family-base); - background: var(--color-bg-subtle); - resize: vertical; - transition: border-color var(--duration-base) var(--ease-standard); - box-sizing: border-box; -} - -.textarea:focus { - outline: none; - border-color: var(--color-text-primary); -} - -.textarea::placeholder { - color: var(--color-text-secondary); -} - -/* Error state — compose with .input, .select, or .textarea */ -.inputError { - border-color: var(--color-danger); -} - -/* Radio */ -.radioInput { - position: absolute; - opacity: 0; - width: 0; - height: 0; - pointer-events: none; -} - -.radioCircle { - width: 1.25rem; - height: 1.25rem; - border-radius: var(--radius-circle); - border: 2px solid var(--color-bg-disabled); - display: flex; - align-items: center; - justify-content: center; - transition: border-color var(--duration-base) var(--ease-standard); - flex-shrink: 0; - box-sizing: border-box; -} - -.radioCircle::after { - content: ''; - width: 0.625rem; - height: 0.625rem; - border-radius: var(--radius-circle); - background: transparent; - transition: background var(--duration-base) var(--ease-standard); -} - -.radioInput:checked + .radioCircle { - border-color: var(--color-action-secondary); -} - -.radioInput:checked + .radioCircle::after { - background: var(--color-action-secondary); -} - -.radioLabel { - display: flex; - align-items: center; - gap: var(--spacing-xs); - cursor: pointer; - font-size: var(--font-size-base); - font-weight: var(--font-weight-medium); - color: var(--color-text); -} diff --git a/src/shared/styles/screen.module.css b/src/shared/styles/screen.module.css index 9394481..e39701d 100644 --- a/src/shared/styles/screen.module.css +++ b/src/shared/styles/screen.module.css @@ -7,10 +7,6 @@ padding-right: var(--spacing-lg); } -.screenExtendedContainer { - padding: var(--spacing-md); -} - /* Shared overlay style for modals and menus */ .overlay { position: fixed; diff --git a/src/shared/ui/Button/Button.module.css b/src/shared/ui/Button/Button.module.css index bc280d9..0d1874c 100644 --- a/src/shared/ui/Button/Button.module.css +++ b/src/shared/ui/Button/Button.module.css @@ -47,6 +47,8 @@ } /* Color mappings - set CSS variables per color */ +.success, +.tertiary, .primary { --btn-color: var(--color-action-primary); --btn-contrast: var(--color-bg-surface); @@ -163,12 +165,3 @@ } } -/* Icon */ -.icon { - width: var(--icon-xl); - height: var(--icon-xl); -} - -.icon path { - fill: currentColor; -}