- App.tsx: full navigation (Auth stack + Main tabs with 5 screens) - Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen - HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors - MapScreen: interactive map with layer toggles (6 layers) - MarketplaceScreen: categories (6), products (5), search - ChatScreen: AI chat with quick prompts (4), bot responses - ProfileScreen: user info, stats, menu (9 items), logout - AlertsScreen: alert list with severity, acknowledge - SensorsScreen: sensor list with type filters (6 types), search - ZonesScreen: zone cards with stats - SettingsScreen: language picker (FR/EN/ES/DE), privacy, about - Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n - Hooks: useSensors, useAlerts, useNotifications, useLocation - Components: Card, Button, LoadingSpinner, ErrorBoundary, Header - Services: iotService, notificationService (with axios API client) - Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN) - Theme: colors.ts with full design system (Blue Ocean palette) - Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
451 lines
11 KiB
CSS
451 lines
11 KiB
CSS
/* Smart App City — Design System v2 BLUE */
|
|
/* i18n: FR / EN / ES / DE */
|
|
/* Platform: React Native → mobile-first */
|
|
/* Palette: Blue Ocean / Indigo / Cyan — inspired by sea & sky of Martinique */
|
|
|
|
:root {
|
|
/* ── Primary Palette — Blue Ocean ── */
|
|
--primary-50: #E3F2FD;
|
|
--primary-100: #BBDEFB;
|
|
--primary-200: #90CAF9;
|
|
--primary-300: #64B5F6;
|
|
--primary-400: #42A5F5;
|
|
--primary-500: #1565C0;
|
|
--primary-600: #0D47A1;
|
|
--primary-700: #0A3470;
|
|
--primary-800: #082854;
|
|
--primary-900: #051C38;
|
|
|
|
/* ── Deep Ocean ── */
|
|
--ocean-50: #E0F7FA;
|
|
--ocean-100: #B2EBF2;
|
|
--ocean-200: #80DEEA;
|
|
--ocean-300: #4DD0E1;
|
|
--ocean-400: #26C6DA;
|
|
--ocean-500: #00ACC1;
|
|
--ocean-600: #00838F;
|
|
--ocean-700: #006064;
|
|
|
|
/* ── Indigo Accent ── */
|
|
--indigo-50: #E8EAF6;
|
|
--indigo-100: #C5CAE9;
|
|
--indigo-200: #9FA8DA;
|
|
--indigo-300: #7986CB;
|
|
--indigo-400: #5C6BC0;
|
|
--indigo-500: #3949AB;
|
|
--indigo-600: #283593;
|
|
--indigo-700: #1A237E;
|
|
|
|
/* ── Alert Colors ── */
|
|
--alert-danger: #D32F2F;
|
|
--alert-warning: #F57C00;
|
|
--alert-success: #2E7D32;
|
|
--alert-info: #0288D1;
|
|
|
|
/* ── Neutrals ── */
|
|
--neutral-0: #FFFFFF;
|
|
--neutral-50: #FAFAFA;
|
|
--neutral-100: #F5F5F5;
|
|
--neutral-200: #EEEEEE;
|
|
--neutral-300: #E0E0E0;
|
|
--neutral-400: #BDBDBD;
|
|
--neutral-500: #9E9E9E;
|
|
--neutral-600: #757575;
|
|
--neutral-700: #616161;
|
|
--neutral-800: #424242;
|
|
--neutral-900: #212121;
|
|
--neutral-1000: #1A1A1A;
|
|
|
|
/* ── Typography ── */
|
|
--font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
--font-family-mono: 'JetBrains Mono', 'SF Mono', monospace;
|
|
|
|
--text-xs: 11px;
|
|
--text-sm: 13px;
|
|
--text-base: 15px;
|
|
--text-md: 17px;
|
|
--text-lg: 20px;
|
|
--text-xl: 24px;
|
|
--text-2xl: 28px;
|
|
--text-3xl: 34px;
|
|
--text-4xl: 40px;
|
|
|
|
--weight-regular: 400;
|
|
--weight-medium: 500;
|
|
--weight-semibold: 600;
|
|
--weight-bold: 700;
|
|
|
|
/* ── Spacing ── */
|
|
--space-2xs: 2px;
|
|
--space-xs: 4px;
|
|
--space-sm: 8px;
|
|
--space-md: 12px;
|
|
--space-base: 16px;
|
|
--space-lg: 20px;
|
|
--space-xl: 24px;
|
|
--space-2xl: 32px;
|
|
--space-3xl: 40px;
|
|
--space-4xl: 48px;
|
|
|
|
/* ── Border Radius ── */
|
|
--radius-sm: 8px;
|
|
--radius-md: 12px;
|
|
--radius-lg: 16px;
|
|
--radius-xl: 20px;
|
|
--radius-2xl: 24px;
|
|
--radius-full: 9999px;
|
|
|
|
/* ── Shadows ── */
|
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
|
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.06);
|
|
--shadow-lg: 0 8px 24px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.06);
|
|
--shadow-xl: 0 16px 48px rgba(0,0,0,0.16);
|
|
|
|
/* ── Dark Mode ── */
|
|
--bg-primary-dark: #0D1B2A;
|
|
--bg-secondary-dark: #1B2838;
|
|
--bg-card-dark: #1E3350;
|
|
--text-primary-dark: #E8EAF6;
|
|
--text-secondary-dark: #9FA8DA;
|
|
--text-tertiary-dark: #5C6BC0;
|
|
}
|
|
|
|
/* ── Base Reset ── */
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: var(--font-family);
|
|
font-size: var(--text-base);
|
|
color: var(--neutral-900);
|
|
background: var(--neutral-50);
|
|
line-height: 1.5;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
body.dark {
|
|
background: var(--bg-primary-dark);
|
|
color: var(--text-primary-dark);
|
|
}
|
|
|
|
/* ── Utility Classes ── */
|
|
.flex { display: flex; }
|
|
.flex-col { flex-direction: column; }
|
|
.items-center { align-items: center; }
|
|
.justify-center { justify-content: center; }
|
|
.justify-between { justify-content: space-between; }
|
|
.gap-xs { gap: var(--space-xs); }
|
|
.gap-sm { gap: var(--space-sm); }
|
|
.gap-md { gap: var(--space-md); }
|
|
.gap-base { gap: var(--space-base); }
|
|
.gap-lg { gap: var(--space-lg); }
|
|
.text-center { text-align: center; }
|
|
.w-full { width: 100%; }
|
|
.h-full { height: 100%; }
|
|
.relative { position: relative; }
|
|
.absolute { position: absolute; }
|
|
.rounded-md { border-radius: var(--radius-md); }
|
|
.rounded-lg { border-radius: var(--radius-lg); }
|
|
.rounded-full { border-radius: var(--radius-full); }
|
|
.shadow-sm { box-shadow: var(--shadow-sm); }
|
|
.shadow-md { box-shadow: var(--shadow-md); }
|
|
.shadow-lg { box-shadow: var(--shadow-lg); }
|
|
|
|
/* ── Component: Mobile Frame ── */
|
|
.mobile-frame {
|
|
width: 390px;
|
|
height: 844px;
|
|
border-radius: 40px;
|
|
border: 3px solid var(--neutral-800);
|
|
overflow: hidden;
|
|
position: relative;
|
|
background: var(--neutral-0);
|
|
box-shadow: var(--shadow-xl), 0 0 0 1px rgba(0,0,0,0.04);
|
|
}
|
|
.dark .mobile-frame {
|
|
border-color: var(--neutral-600);
|
|
background: var(--bg-primary-dark);
|
|
}
|
|
|
|
/* ── Notch ── */
|
|
.notch {
|
|
width: 120px;
|
|
height: 30px;
|
|
background: var(--neutral-900);
|
|
border-radius: 0 0 20px 20px;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 100;
|
|
}
|
|
.dark .notch { background: var(--bg-primary-dark); border: 2px solid var(--neutral-600); border-top: none; }
|
|
|
|
/* ── Status Bar ── */
|
|
.status-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 24px 4px;
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--weight-semibold);
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 50;
|
|
}
|
|
|
|
/* ── Bottom Nav ── */
|
|
.bottom-nav {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 80px;
|
|
background: var(--neutral-0);
|
|
border-top: 1px solid var(--neutral-200);
|
|
display: flex;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
padding-bottom: 20px;
|
|
z-index: 50;
|
|
}
|
|
.dark .bottom-nav {
|
|
background: var(--bg-secondary-dark);
|
|
border-color: var(--neutral-700);
|
|
}
|
|
.bottom-nav-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: var(--text-xs);
|
|
color: var(--neutral-500);
|
|
cursor: pointer;
|
|
transition: color 0.2s;
|
|
}
|
|
.bottom-nav-item.active { color: var(--primary-500); }
|
|
.bottom-nav-item svg { width: 24px; height: 24px; }
|
|
|
|
/* ── Content Area ── */
|
|
.content-area {
|
|
position: absolute;
|
|
top: 44px;
|
|
bottom: 80px;
|
|
left: 0;
|
|
right: 0;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
scrollbar-width: none;
|
|
}
|
|
.content-area::-webkit-scrollbar { display: none; }
|
|
|
|
/* ── Cards ── */
|
|
.card {
|
|
background: var(--neutral-0);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-base);
|
|
box-shadow: var(--shadow-sm);
|
|
border: 1px solid var(--neutral-100);
|
|
}
|
|
.dark .card {
|
|
background: var(--bg-card-dark);
|
|
border-color: var(--neutral-700);
|
|
}
|
|
|
|
/* ── Buttons ── */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-xs);
|
|
padding: 12px 24px;
|
|
border-radius: var(--radius-full);
|
|
font-weight: var(--weight-semibold);
|
|
font-size: var(--text-base);
|
|
cursor: pointer;
|
|
border: none;
|
|
transition: all 0.2s;
|
|
min-height: 48px;
|
|
}
|
|
.btn-primary {
|
|
background: var(--primary-500);
|
|
color: white;
|
|
}
|
|
.btn-primary:hover { background: var(--primary-600); }
|
|
.btn-secondary {
|
|
background: var(--neutral-200);
|
|
color: var(--neutral-800);
|
|
}
|
|
.btn-ghost {
|
|
background: transparent;
|
|
color: var(--primary-500);
|
|
}
|
|
.btn-block { width: 100%; }
|
|
|
|
/* ── Metric Cards ── */
|
|
.metric-card {
|
|
padding: var(--space-base);
|
|
border-radius: var(--radius-lg);
|
|
background: var(--neutral-0);
|
|
border: 1px solid var(--neutral-200);
|
|
text-align: center;
|
|
}
|
|
.dark .metric-card {
|
|
background: var(--bg-card-dark);
|
|
border-color: var(--neutral-700);
|
|
}
|
|
.metric-value {
|
|
font-size: var(--text-2xl);
|
|
font-weight: var(--weight-bold);
|
|
color: var(--primary-500);
|
|
}
|
|
.metric-label {
|
|
font-size: var(--text-xs);
|
|
color: var(--neutral-500);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* ── Label ── */
|
|
.label {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--weight-medium);
|
|
color: var(--neutral-600);
|
|
margin-bottom: var(--space-xs);
|
|
}
|
|
.dark .label { color: var(--text-secondary-dark); }
|
|
|
|
/* ── Page Title ── */
|
|
.page-title {
|
|
font-size: var(--text-lg);
|
|
font-weight: var(--weight-bold);
|
|
color: var(--neutral-900);
|
|
}
|
|
.dark .page-title { color: var(--text-primary-dark); }
|
|
|
|
/* ── Section Header ── */
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--space-base);
|
|
}
|
|
.section-title {
|
|
font-size: var(--text-md);
|
|
font-weight: var(--weight-semibold);
|
|
}
|
|
|
|
/* ── Online Indicator ── */
|
|
.online-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--alert-success);
|
|
display: inline-block;
|
|
}
|
|
|
|
/* ── Badge ── */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 20px;
|
|
height: 20px;
|
|
border-radius: var(--radius-full);
|
|
background: var(--alert-danger);
|
|
color: white;
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--weight-bold);
|
|
padding: 0 6px;
|
|
}
|
|
|
|
/* ── Chip ── */
|
|
.chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 4px 12px;
|
|
border-radius: var(--radius-full);
|
|
background: var(--primary-50);
|
|
color: var(--primary-600);
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--weight-medium);
|
|
}
|
|
.dark .chip { background: rgba(21,101,192,0.15); }
|
|
|
|
/* ── Gradient Overlays ── */
|
|
.gradient-blue {
|
|
background: linear-gradient(135deg, var(--primary-500), var(--ocean-500));
|
|
}
|
|
.gradient-ocean {
|
|
background: linear-gradient(135deg, var(--ocean-400), var(--ocean-600));
|
|
}
|
|
.gradient-indigo {
|
|
background: linear-gradient(135deg, var(--indigo-500), var(--primary-500));
|
|
}
|
|
.gradient-deep {
|
|
background: linear-gradient(160deg, var(--primary-700) 0%, var(--ocean-600) 50%, var(--ocean-500) 100%);
|
|
}
|
|
|
|
/* ── SVG Icon Base ── */
|
|
.icon { width: 24px; height: 24px; stroke-width: 2; fill: none; stroke: currentColor; }
|
|
.icon-sm { width: 18px; height: 18px; }
|
|
.icon-lg { width: 32px; height: 32px; }
|
|
.icon-xl { width: 48px; height: 48px; }
|
|
|
|
/* ── Thumbnail / Avatar ── */
|
|
.avatar {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: var(--radius-full);
|
|
object-fit: cover;
|
|
background: var(--neutral-200);
|
|
}
|
|
.avatar-sm { width: 32px; height: 32px; }
|
|
.avatar-lg { width: 56px; height: 56px; }
|
|
|
|
/* ── Spacing Utilities ── */
|
|
.px-base { padding-left: var(--space-base); padding-right: var(--space-base); }
|
|
.py-sm { padding-top: var(--space-sm); padding-bottom: var(--space-sm); }
|
|
.py-base { padding-top: var(--space-base); padding-bottom: var(--space-base); }
|
|
.p-base { padding: var(--space-base); }
|
|
.p-lg { padding: var(--space-lg); }
|
|
.mt-sm { margin-top: var(--space-sm); }
|
|
.mt-base { margin-top: var(--space-base); }
|
|
.mt-lg { margin-top: var(--space-lg); }
|
|
.mb-sm { margin-bottom: var(--space-sm); }
|
|
.mb-base { margin-bottom: var(--space-base); }
|
|
|
|
/* ── Grid ── */
|
|
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); }
|
|
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-sm); }
|
|
.grid-auto { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: var(--space-md); }
|
|
|
|
/* ── Scrollable Row ── */
|
|
.scroll-row {
|
|
display: flex;
|
|
gap: var(--space-md);
|
|
overflow-x: auto;
|
|
padding: 0 var(--space-base) var(--space-base);
|
|
scrollbar-width: none;
|
|
}
|
|
.scroll-row::-webkit-scrollbar { display: none; }
|
|
|
|
/* ── Language Picker ── */
|
|
.lang-btn {
|
|
padding: 6px 12px;
|
|
border-radius: var(--radius-md);
|
|
border: 2px solid var(--neutral-300);
|
|
background: transparent;
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--weight-semibold);
|
|
cursor: pointer;
|
|
color: var(--neutral-700);
|
|
transition: all 0.2s;
|
|
}
|
|
.lang-btn.active {
|
|
border-color: var(--primary-500);
|
|
background: var(--primary-500);
|
|
color: white;
|
|
}
|