v1.0.0 — redesign complet charte graphique V2
- 8 états météo : arc-en-ciel, ensoleillé, brumeux, nuageux, pluvieux, orageux, tempête, fin du monde - Fonts : Fredoka (titres) + Nunito (texte), palette V2 (bleu #3B82F6, orange #F59E0B, rouge #EF4444) - Nav flottante : Météo / Radar / Bilan / Paramètres (La Carte supprimée) - Thème clair / sombre (toggle dans Paramètres) - Icône app : papier quadrillé + 0 rouge manuscrit - Illustrations météo CSS pures (soleil, nuages, pluie, éclair, champignon nucléaire, arc-en-ciel) - Panel admin centralisé dans Paramètres (flash, classe, évaluations, météo, membres) - Bottom nav flottante avec safe-area pour ne plus toucher la nav système Android - Suppression des anciennes vues (cards.ts, map.ts, plus.ts, exam-form.ts)
This commit is contained in:
parent
8c79476d96
commit
2f2f363650
23 changed files with 1679 additions and 1715 deletions
|
|
@ -1,98 +1,100 @@
|
|||
import './styles.css'
|
||||
import { h, mount } from './dom'
|
||||
import { bootstrap, getState, isAdmin, subscribe } from './store'
|
||||
import { bootstrap, getState, subscribe } from './store'
|
||||
import { renderJoin } from './views/join'
|
||||
import { renderWeek } from './views/week'
|
||||
import { renderRadar } from './views/radar'
|
||||
import { renderMap } from './views/map'
|
||||
import { renderPlus } from './views/plus'
|
||||
import { renderJournal } from './views/journal'
|
||||
import { renderSettings } from './views/settings'
|
||||
import { getBaseUrl } from './config'
|
||||
import { checkForUpdate } from './update'
|
||||
import { showUpdateModal } from './update-modal'
|
||||
|
||||
type Tab = 'meteo' | 'radar' | 'carte' | 'plus'
|
||||
type Tab = 'meteo' | 'radar' | 'bilan' | 'parametres'
|
||||
|
||||
const app = document.getElementById('app')!
|
||||
let activeTab: Tab = 'meteo'
|
||||
|
||||
const TABS: { id: Tab; label: string; icon: string }[] = [
|
||||
{ id: 'meteo', label: 'Météo', icon: 'fa-cloud-sun' },
|
||||
{ id: 'radar', label: 'Radar', icon: 'fa-satellite-dish' },
|
||||
{ id: 'carte', label: 'La Carte', icon: 'fa-map-marked-alt' },
|
||||
{ id: 'plus', label: 'Plus', icon: 'fa-ellipsis-h' },
|
||||
{ id: 'meteo', label: 'Météo', icon: 'cloud-sun' },
|
||||
{ id: 'radar', label: 'Radar', icon: 'satellite-dish' },
|
||||
{ id: 'bilan', label: 'Bilan', icon: 'journal-bookmark' },
|
||||
{ id: 'parametres', label: 'Paramètres', icon: 'gear' },
|
||||
]
|
||||
|
||||
function svgIcon(name: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
'cloud-sun': '<path d="M4 15a4 4 0 0 1 4-4 5 5 0 0 1 9.6 1.8A3.2 3.2 0 1 1 18 19H7a3 3 0 0 1-3-4Z"/><path d="M8 5V3M4.5 6.5 3 5M12 6l1.5-1.5"/>',
|
||||
'satellite-dish': '<circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="4"/><path d="M12 12 18 6M12 2v2M12 20v2M2 12h2M20 12h2"/>',
|
||||
'journal-bookmark': '<path d="M6 3h12v18H6z"/><path d="M9 7h6M9 11h6M9 15h4"/>',
|
||||
'gear': '<circle cx="12" cy="12" r="3"/><path d="M19 12a7 7 0 0 0-.1-1l2-1.5-2-3.4-2.4 1A7 7 0 0 0 15 6l-.3-2.6h-4L10.4 6A7 7 0 0 0 8.9 7.1l-2.4-1-2 3.4 2 1.5a7 7 0 0 0 0 2l-2 1.5 2 3.4 2.4-1A7 7 0 0 0 10.4 18l.3 2.6h4L15 18a7 7 0 0 0 1.5-1.1l2.4 1 2-3.4-2-1.5c.1-.3.1-.7.1-1Z"/>',
|
||||
}
|
||||
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">${icons[name] ?? ''}</svg>`
|
||||
}
|
||||
|
||||
function renderApp(): void {
|
||||
const state = getState()
|
||||
|
||||
// Écran d'accueil : pas de session valide.
|
||||
// Écran d'accueil
|
||||
if (!state.session) {
|
||||
const container = h('div', { class: 'mobile-container' })
|
||||
const main = h('main', { class: 'flex-1 overflow-y-auto no-scrollbar p-5' })
|
||||
const main = h('main', { class: 'app-main' })
|
||||
renderJoin(main, renderApp)
|
||||
container.append(main)
|
||||
mount(app, container)
|
||||
return
|
||||
}
|
||||
|
||||
// App pleine : header + contenu + nav.
|
||||
const container = h('div', { class: 'mobile-container' })
|
||||
|
||||
// Header
|
||||
const header = h(
|
||||
'header',
|
||||
{ class: 'pt-8 pb-3 px-6 bg-slate-900/80 backdrop-blur-md flex justify-between items-center z-20 border-b border-slate-800/60' },
|
||||
{ class: 'app-header' },
|
||||
h(
|
||||
'div',
|
||||
{ class: 'flex items-center gap-3' },
|
||||
h('div', { class: 'w-9 h-9 rounded-2xl bg-gradient-to-tr from-sky-400 to-indigo-500 flex items-center justify-center text-white font-black shadow-md text-sm' }, 'ME'),
|
||||
h('div', {},
|
||||
h('h1', { class: 'text-lg font-extrabold text-white tracking-tight leading-none' }, 'Météo Exam'),
|
||||
h('p', { class: 'text-xs font-bold text-sky-400 mt-0.5' }, `${state.session.klass.name}${state.session.klass.level ? ` · ${state.session.klass.level}` : ''}`),
|
||||
h('div', { class: 'app-mark paper-grid' }, h('div', { class: 'zero' }, '0')),
|
||||
h(
|
||||
'div',
|
||||
{},
|
||||
h('h1', { class: 'font-bold text-lg leading-none' }, 'Météo Exam'),
|
||||
h('p', { class: 'text-[10px] font-black tracking-widest uppercase mt-1', style: 'color:var(--blue2)' },
|
||||
`${state.klass?.name ?? state.session.klass.name}${state.klass?.level ? ` • ${state.klass.level}` : ''}`),
|
||||
),
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{ class: 'flex items-center gap-2' },
|
||||
isAdmin()
|
||||
? h('span', { class: 'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-extrabold bg-sky-500/10 text-sky-400 border border-sky-500/20' },
|
||||
h('i', { class: 'fa-solid fa-user-shield mr-1' }), 'Admin')
|
||||
: null,
|
||||
h('span', { class: 'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-extrabold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' },
|
||||
h('span', { class: 'w-2 h-2 rounded-full bg-emerald-500 animate-ping mr-1.5' }),
|
||||
'Direct'),
|
||||
'button',
|
||||
{ class: 'live-btn', onclick: () => {} },
|
||||
h('span', { class: 'status-dot', style: 'background:var(--good)' }),
|
||||
),
|
||||
)
|
||||
|
||||
const main = h('main', { class: 'flex-1 overflow-y-auto no-scrollbar p-5 pb-28 relative' })
|
||||
// Main content
|
||||
const main = h('main', { class: 'app-main' })
|
||||
const rerender = (): void => renderApp()
|
||||
|
||||
switch (activeTab) {
|
||||
case 'meteo': renderWeek(main, rerender); break
|
||||
case 'radar': renderRadar(main, rerender); break
|
||||
case 'carte': renderMap(main, rerender); break
|
||||
case 'plus': renderPlus(main, rerender); break
|
||||
case 'bilan': renderJournal(main, rerender); break
|
||||
case 'parametres': renderSettings(main, rerender); break
|
||||
}
|
||||
|
||||
// Bottom nav
|
||||
const nav = h(
|
||||
'nav',
|
||||
{ class: 'absolute bottom-0 left-0 right-0 bg-slate-900/95 backdrop-blur-xl border-t border-slate-800 px-4 py-3 flex justify-around items-center z-30' },
|
||||
{ class: 'bottom-nav' },
|
||||
...TABS.map((tab) => {
|
||||
const active = tab.id === activeTab
|
||||
return h(
|
||||
'button',
|
||||
{
|
||||
class: `flex flex-col items-center gap-1 transition-colors ${active ? 'text-sky-400' : 'text-slate-500'}`,
|
||||
onclick: () => {
|
||||
activeTab = tab.id
|
||||
renderApp()
|
||||
},
|
||||
class: active ? 'active' : '',
|
||||
onclick: () => { activeTab = tab.id; renderApp() },
|
||||
},
|
||||
tab.id === 'radar'
|
||||
? h('div', { class: 'relative' },
|
||||
h('i', { class: `fa-solid ${tab.icon} text-xl` }),
|
||||
h('span', { class: 'absolute -top-1 -right-1 w-2 h-2 bg-rose-500 rounded-full' }))
|
||||
: h('i', { class: `fa-solid ${tab.icon} text-xl` }),
|
||||
h('span', { class: 'text-[10px] font-black tracking-wide' }, tab.label),
|
||||
h('span', { html: svgIcon(tab.icon) }),
|
||||
h('span', {}, tab.label),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -101,18 +103,12 @@ function renderApp(): void {
|
|||
mount(app, container)
|
||||
}
|
||||
|
||||
// --- Bootstrapping -----------------------------------------------------------
|
||||
|
||||
subscribe(renderApp)
|
||||
|
||||
void bootstrap().then(() => {
|
||||
renderApp()
|
||||
console.log(`Météo Exam — v${__APP_VERSION__} — serveur : ${getBaseUrl()}`)
|
||||
|
||||
// Vérification de mise à jour (Android natif uniquement).
|
||||
void checkForUpdate().then((update) => {
|
||||
if (update) {
|
||||
showUpdateModal(update, () => renderApp())
|
||||
}
|
||||
if (update) showUpdateModal(update, () => renderApp())
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue