refactor[ frontend / components ]: aggiornamento UI globale, ottimizzazione header e gestione errori

Header: Rifatto il componente con menu mobile ed effetti di background.

Cleanup: Rimossi i componenti Hero e Services non più necessari.

Layout: Migliorata la struttura e lo stile della pagina Contacts.

Core: Implementata la gestione globale degli errori in main.js.

Styles: Aggiornati gli stili globali e le media queries per una migliore responsività.
This commit is contained in:
2026-05-06 23:05:17 +02:00
parent 575e2b4779
commit e3098d3004
25 changed files with 1426 additions and 565 deletions
+493 -128
View File
@@ -1,20 +1,24 @@
# Cima Progetti - Sito Aziendale
Sito web moderno e responsivo sviluppato con **Svelte + Vite** per presentare servizi, metodologia e contatti aziendali.
Sito web moderno, performante e accessibile sviluppato con **Svelte 5 + Vite** per presentare servizi, metodologia e contatti aziendali. Completamente responsivo con supporto completo per telefoni e tablet.
## ✨ Funzionalità principali
- 🌓 **Tema light/dark** con persistenza locale
- 📱 **Design completamente responsivo**
- ⬆️ **Navbar collapsibile** con hide/show al scroll
- 🎨 **Sistema di componenti riusabili**
- 🚀 **Routing client-side** con pagine dedicate e fallback 404
- **Accessibilità** e buone pratiche web
- 🌓 **Tema light/dark** con persistenza dual-storage (localStorage + cookie backup)
- 📱 **Design completamente responsivo** con breakpoint intelligenti
- ⬆️ **Header smart** — trasparente all'inizio, sfondo blur opaco dopo scroll
- 🍔 **Menu hamburger** con animazione fluida di apertura/chiusura
- 🎨 **Sistema di componenti riusabili** e theme-aware
- 🚀 **Routing client-side** con History API e fallback 404 per pagine non create
- 🔐 **Cookie consent** con personalizzazione categorie
-**Accessibilità** — focus-visible preservato, tap highlight rimosso su touch
-**Svelte 5 Runes** — reattività state-driven con $state(), $derived()
- 🐛 **Global error handling** — trap di SyntaxError visibile su dev server
## 🛠️ Requisiti
- `Node.js` 18+ (consigliato LTS)
- `npm` 9+
- **Node.js** 18+ (consigliato LTS)
- **npm** 9+
## 🚀 Avvio rapido
@@ -23,76 +27,201 @@ npm install
npm run dev
```
Applicazione disponibile su `http://localhost:5173`.
Applicazione disponibile su `http://localhost:5173` (accesso locale) e `http://192.168.XX.XXX:5173` (rete locale).
## 📝 Script disponibili
- `npm run dev` - avvia il server di sviluppo con hot reload
- `npm run build` - genera la build di produzione
- `npm run preview` - avvia un'anteprima della build
| Script | Descrizione |
|--------|-------------|
| `npm run dev` | Avvia il server di sviluppo con hot reload (esposto su 0.0.0.0:5173) |
| `npm run build` | Genera la build di produzione ottimizzata in `dist/` |
| `npm run preview` | Avvia preview della build (simula produzione in locale) |
## 📁 Struttura del progetto
```
src/
├── App.svelte # Componente root con routing
├── main.js # Punto di ingresso
├── styles/
│ ├── global.css # Stili globali e variabili tema
├── App.css # Layout principale app
│ └── error-pages.css # Stili pagine errore
├── components/
│ ├── Header/ # Navbar con scroll hide/show
├── Hero/ # Sezione hero principale
│ ├── Services/ # Griglia servizi
│ ├── Footer/ # Footer con info aziendali
├── Button/ # Componente button riusabile
└── ThemeToggle/ # Toggle tema light/dark
└── pages/
├── Contacts.svelte # Pagina contatti
├── 403.svelte # Accesso negato
├── 404.svelte # Pagina non trovata
├── 500.svelte # Errore server
└── ErrorGeneric.svelte # Errore generico
cimaprogetti/
├── index.html # Template HTML entry point
├── package.json # Dependencies e scripts
├── vite.config.js # Configurazione Vite (SPA + host 0.0.0.0)
├── svelte.config.js # Configurazione Svelte
├── src/
│ ├── main.js # Entry point con error trap globale
│ ├── App.svelte # Root component con routing e layout
│ ├── components/
│ ├── Header/
├── Header.svelte # Navbar sticky con tema + scroll detection
│ │ └── Header.css # Stili header (blur backdrop, absolute menu)
│ │ │
│ │ ├── Button/
│ │ │ ├── Button.svelte # Button/link riusabile (16 prop)
│ │ └── Button.css # Stili + focus-visible + no tap highlight
│ │ │
│ │ ├── CookiePopUp/
│ │ │ ├── CookiePopUp.svelte # Cookie consent con toggle personalizzato
│ │ │ ├── CookiePopUp.css # Dialog mobile + desktop
│ │ │ └── assets/ # SVG cookie icon
│ │ │
│ │ ├── Toggle/
│ │ │ ├── Toggle.svelte # Switch accessibile
│ │ │ └── Toggle.css # Stili toggle (animated knob)
│ │ │
│ │ ├── ThemeToggle/
│ │ │ ├── ThemeToggle.svelte
│ │ │ └── ThemeToggle.css
│ │ │
│ │ ├── Footer/
│ │ │ ├── Footer.svelte
│ │ │ └── Footer.css
│ │ │
│ │ └── Hero/, Services/ # Componenti home
│ │
│ ├── pages/
│ │ ├── index.svelte # Home page
│ │ ├── Contacts.svelte # Pagina contatti con team profiles
│ │ ├── 403.svelte, 404.svelte, 500.svelte, errore.svelte # Error pages
│ │
│ ├── lib/
│ │ ├── theme.js # Theme persistence (localStorage + cookie)
│ │ ├── navigation.js # Client-side routing utilities
│ │ ├── cookieConsent.js # Cookie storage & consent management
│ │ └── errorRouting.js # Path normalization & aliases
│ │
│ └── styles/
│ ├── global.css # CSS variables, typography, resets
│ ├── App.css # Layout principale
│ ├── contacts.css # Pagina contatti (flex + absolute positioning)
│ └── error-pages.css # Pagine errore (clamp typography)
└── public/
└── images/
├── background/ # Background patterns
├── icons/ # Logo, UI icons
└── contacts/ # Hero images (light/dark theme)
```
## 🌐 Configurazione Network
Il server Vite è configurato per essere **accessibile dalla rete locale**:
```js
// vite.config.js
server: {
host: '0.0.0.0', // Ascolta su tutte le interfacce
port: 5173,
}
```
### Accesso dal telefono sulla stessa Wi-Fi
1. Avvia il dev server: `npm run dev`
2. Nel terminale vedrai l'indirizzo IP locale (es. `192.168.68.119`)
3. Dal telefono apri: `http://192.168.68.119:5173`
**Nota:** Per produzione, usa `npm run build && npm run preview` per servire la versione compilata su `http://192.168.68.119:4173` (compatibile con browser vecchi).
## 🗺️ Routing
Il routing è gestito in `src/App.svelte` leggendo `window.location.pathname` e reagendo ai cambi di History API.
Il routing è **client-side only** — nessuna pagina server. Gestito in `App.svelte` leggendo `window.location.pathname` e reagendo ai cambi di History API.
| Route | Componente | Descrizione |
|-------|-----------|-------------|
| `/` | `Hero` + `Services` | Home page |
| `/contatti`, `/contacts` | `Contacts` | Pagina contatti |
| `/403` | `Forbidden` | Accesso negato |
| `/404` | `NotFound` | Pagina non trovata |
| `/500` | `ServerError` | Errore server |
| `/errore` | `ErrorGeneric` | Errore generico |
| qualsiasi altra route | `NotFound` | Fallback 404 per pagine non ancora create |
### Tabella rotte
Le voci della navbar puntano a route reali come `/servizi`, `/metodo`, `/progetti` e `/chi-siamo`: se una pagina non esiste ancora, il contenuto mostrato è il 404 ma l'URL resta quello richiesto.
| Route | Pagina | Comportamento |
|-------|--------|---------------|
| `/` | `index.svelte` | Home page (Hero + Services) |
| `/contatti` o `/contacts` | `Contacts.svelte` | Info contatti + team profiles |
| `/servizi`, `/metodo`, `/progetti`, `/chi-siamo` | `404.svelte` | Non create — mostra 404 ma mantiene URL |
| `/403`, `/404`, `/500`, `/errore` | Error pages | Pagine errore dedicate |
| Qualsiasi altra rotta | `404.svelte` | Fallback automatico |
## 🎨 Tema e Stili
### Meccanismo di routing
### Variabili CSS globali
```js
// src/App.svelte
const getRoute = (path) => {
const normalized = resolvePathname(path) // Normalizza trailing slash
if (normalized === '/') return 'home'
if (normalized === '/contatti' || normalized === '/contacts') return 'contacts'
// ...
return '404' // Fallback
}
let currentRoute = $derived(getRoute(pathname))
```
### Come aggiungere una nuova pagina
1. **Crea il componente** in `src/pages/MyPage.svelte`
2. **Importa in App.svelte**:
```svelte
import MyPage from "./pages/MyPage.svelte"
```
3. **Aggiungi la route** in `getRoute()`:
```js
if (normalized === '/my-page') return 'mypage'
```
4. **Renderizza nel template**:
```svelte
{:else if currentRoute === 'mypage'}
<MyPage />
```
## 🎨 Sistema di Tema
### Tema Persistente Dual-Storage
Il tema viene salvato su **localStorage** (primario) e **cookie** (backup) simultaneamente. Questo assicura che il tema sopravviva anche se l'utente cancella parte dello storage del browser.
```js
// src/lib/theme.js
export const getSavedTheme = () => {
// Prova localStorage prima, poi cookie come fallback
return localStorage.getItem('theme') || getCookie('theme')
}
export const saveTheme = (theme) => {
localStorage.setItem('theme', theme)
setCookie('theme', theme) // Backup redundante
}
export const applyTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme)
}
```
### Variabili CSS Globali
Tutte le variabili tema sono in `src/styles/global.css`:
```css
:root {
--primary-color: #1343F0; /* Colore primario */
--text-color: #111827; /* Testo light mode */
--surface: #ffffff; /* Sfondo light mode */
--navbar-height: 140px;
--primary-color: #1343F0; /* Blu Cima */
--text-color: #111827; /* Grigio scuro */
--surface: #ffffff; /* Background carta */
--background: rgba(246, 249, 255, 0.5);
/* ... altre variabili */
--background-opaque: #00000033;
--muted-color: #6b7280;
--lateral-margin: 12rem;
}
:root[data-theme='dark'] {
--primary-color: #4983F2;
--text-color: #e6eef8;
--primary-color: #4983F2; /* Blu chiaro */
--text-color: #e6eef8; /* Grigio molto chiaro */
--surface: #000;
--background: rgba(0, 0, 0, 0.8);
/* ... altre variabili */
--background-opaque: rgba(255, 255, 255, 0.2);
}
/* Mobile breakpoint */
@media (max-width: 900px) {
:root {
--navbar-height: 110px;
--lateral-margin: 1.5rem;
}
}
```
@@ -100,126 +229,362 @@ Tutte le variabili tema sono in `src/styles/global.css`:
```svelte
<div style="color: var(--text-color); background: var(--surface);">
Contenuto che segue il tema
Contenuto automaticamente theme-aware
</div>
```
## 🧩 Componenti principali
## 🧩 Componenti Principali
### Button
### Header (Navbar Intelligente)
Componente button/link riusabile con tema integrato.
La navbar è **sticky** e reagisce allo scroll con una transizione elegante:
- **Trasparente all'inizio** (`background-color: transparent`)
- **Sfondo blur dopo 100px di scroll** (`backdrop-filter: blur(8px)`)
- **Scompare al scroll verso il basso** (nascosto con `translateY(-100%)`)
- **Riappare al scroll verso l'alto** (velocità rilevata)
```svelte
<!-- Button base -->
<Button text="Click me" />
<Header />
```
<!-- Button con link -->
<Button text="HOME" href="/" />
**Modifiche in Header.svelte:**
- Stato `hasBackground` che attiva a `scrollY > 100`
- Classe `with-bg` per applicare blur e shadow
- Mobile menu hamburger con animazione da basso
<!-- Button con bordo interno -->
**CSS Features:**
- `backdrop-filter: blur(8px)` per effetto frosted glass
- `position: fixed` con `transition: transform 0.28s`
- Menu mobile `transform: translateY(100%)` → `translateY(0)` con cubic-bezier smoothing
### Button (Componente Riusabile)
Componente ultra-flessibile che renderizza come `<a>` se ha `href`, altrimenti `<button>`.
```svelte
<Button
text="Custom Button"
href="/"
borderWidth="2px"
/>
<!-- Stili avanzati -->
<Button
text="Premium"
color="var(--primary-color)"
textColor="var(--surface)"
text="PRENOTA UNA CALL"
href="/contatti"
borderWidth="1.5px"
borderColor="var(--text-color)"
color="transparent"
textColor="var(--text-color)"
padding="12px 32px"
round="12px"
borderWidth="1px"
round="5px"
margin="1rem 0 0"
bold
/>
```
**Prop disponibili:**
- `text` - testo del button (default: `'Button'`)
- `color` - colore sfondo (default: `var(--primary-color)`)
- `textColor` - colore testo (default: `var(--surface)`)
- `padding` - padding interno (default: `'8px 24px'`)
- `round` - border-radius (default: `'8px'`)
- `href` - se presente, renderizza come `<a>` tag
- `borderWidth` - spessore bordo interno (default: `'0px'`)
**Tutte le prop:**
Se `href` punta a una route interna, il click usa la navigazione client-side e aggiorna il contenuto senza ricaricare la pagina.
| Prop | Default | Descrizione |
|------|---------|-------------|
| `text` | `'Button'` | Testo del pulsante |
| `color` | `var(--primary-color)` | Background |
| `textColor` | `var(--surface)` | Colore testo |
| `padding` | `'10px 20px'` | Padding interno |
| `round` | `'5px'` | border-radius |
| `href` | — | Se presente, renderizza come `<a>` con navigazione client-side |
| `borderWidth` | `'0px'` | Spessore border interno (inset) |
| `borderColor` | `var(--primary-color)` | Colore border |
| `bold` | `true` | Font-weight bold |
| `margin` | `'0'` | **NEW** — margini esterni |
### Header
**Accessibilità:**
- Outline su `:focus-visible` (tastiera)
- Tap highlight rimosso su touch (`:focus:not(:focus-visible)`)
Navbar sticky che si nasconde al scroll verso il basso e riappare al scroll verso l'alto.
### CookiePopUp (Cookie Consent Dialog)
Le voci della navbar usano route interne. Le pagine non ancora create devono restituire 404.
Dialog modale con due view:
Modifica il contenuto in `src/components/Header/Header.svelte`.
### ThemeToggle
Pulsante per switchare tra tema light e dark. Il tema viene salvato in `localStorage` e come backup in cookie, così resta disponibile anche dopo operazioni che puliscono parte dello storage del browser.
## 📱 Modificare i contenuti
- **Header/Navigazione:** `src/components/Header/Header.svelte`
- **Routing e redirect:** `src/App.svelte`, `src/lib/navigation.js`, `src/lib/errorRouting.js`
- **Sezione hero:** `src/components/Hero/Hero.svelte`
- **Servizi:** `src/components/Services/Services.svelte`
- **Footer:** `src/components/Footer/Footer.svelte`
- **Pagine errore:** `src/pages/*.svelte`
## Aggiungere nuove pagine
1. Crea un nuovo file `.svelte` in `src/pages/`
2. Aggiungi una route in `src/App.svelte` nello script:
1. **Main** — Descrizione generica + bottoni accetta/rifiuta/personalizza
2. **Prefs** — Toggle per tre categorie: Tecnici (always on), Analitici, Profilazione
```svelte
import MyPage from "./pages/MyPage.svelte"
<CookiePopUp />
```
const getRoute = (path) => {
// ...
if (normalized === '/my-page') return 'mypage'
// ...
**Varianti layout:**
- **Desktop** (> 900px): Fixed al bottom, full-width
- **Tablet** (768900px): Drawer da destra (clip-path animation)
- **Mobile** (< 768px): Full-page dal basso (translateY animation)
**Accessibilità:**
- `role="dialog"` + `aria-modal="true"`
- Focus trap — primo elemento focusabile automaticamente
- Escape key per chiudere (con reject)
- Tap highlight disabilitato
### Contacts Page (Pagina Contatti)
Layout two-column con immagine decorativa e team profiles.
```svelte
<div class="contact-us">
<div class="contact-us-container">
<!-- Titolo, testo, email/WhatsApp, button -->
</div>
<div class="contact-image">
<!-- SVG tema-aware (light/dark) -->
</div>
</div>
<div class="behind-cima">
<h2>PEOPLE BEHIND CIMA</h2>
<div class="profiles-container">
<!-- Due profile cards con flex: 1 1 0 -->
</div>
</div>
```
**Responsive:**
- **Desktop** (> 900px): Due colonne con gap 40px
- **Mobile** (< 900px):
- `contact-image` diventa `position: absolute` centrata
- `contact-us-container` full width
- Profili stacked verticalmente
### Toggle (Accessibile Switch)
Switch accessibile con label opzionale e stato animato.
```svelte
<Toggle
bind:checked={analitici}
label="Analitici"
disabled={false}
/>
```
**CSS Variables** (customizzabile):
- `--toggle-on-bg` — colore quando ON
- `--toggle-off-bg` — colore quando OFF
- `--toggle-knob` — colore pallina
- `--toggle-width` / `--toggle-height` — dimensioni
## 📱 Responsive Design
### Breakpoint Principale
L'unico breakpoint è **900px** (tablet). Sotto questo valore attivano:
- Margini orizzontali ridotti: `12rem` → `1.5rem`
- Navbar height ridotta: `140px` → `110px`
- Menu hamburger (al posto di nav desktop)
- Header flexibile trasparente con blur
- Contatti layout single-column
- Profiles stacked
- Error pages con tipografia clamp()
### Clamp() per Tipografia Fluida
Errori, heading e font-size critici usano `clamp()` per scalare fluidamente:
```css
/* Error code number */
.error-code {
font-size: clamp(2rem, 5vw, 4rem);
}
/* Error page title background */
.error-text {
font-size: clamp(10rem, 20vw, 15rem) !important;
}
/* Links in contacts */
.contact-info-item a {
font-size: clamp(1.25rem, 2.5vw, 2rem);
}
```
3. Aggiungi il rendering nel template:
## 🔐 Cookie Consent & Privacy
```svelte
{:else if currentRoute === 'mypage'}
<MyPage />
### Gestione Categoria Cookie
```js
// src/lib/cookieConsent.js
const consent = {
value: 'custom', // 'accepted_all', 'rejected_all', 'custom'
preferences: {
tecnici: true, // Obbligatorio
analitici: false, // Personalizzabile
profilazione: false // Personalizzabile
}
}
```
Se non aggiungi la route in `getRoute`, il path continuerà a mostrare il fallback 404.
### Persistenza
## 🚀 Build e Deploy
- Cookie di consenso salvato in `cookie-consent` (via `js-cookie`)
- Configurazione letta al mount di `App.svelte`
- Toggle popup non riappare se esiste consenso valido
### Build di produzione
## 🐛 Error Handling & Debugging
### Global Error Trap (main.js)
```js
// Cattura SyntaxError e runtime errors prima del mount
window.addEventListener('error', (event) => {
const msg = `ERROR: ${event.error?.message || event.message}`;
console.error(msg, event.error);
alert(msg); // Visibile su telefono per debug
}, true);
try {
const app = mount(App, { target: document.getElementById('app') })
console.log("App mounted successfully!")
} catch (e) {
alert(`Mount Error: ${e.message}`)
}
```
### Diagnostica Schermo Bianco
Se il telefono mostra schermo bianco:
1. **Apri DevTools** → Console → controlla errori
2. **Prova build di produzione**:
```bash
npm run build
npm run preview
# Accedi da http://192.168.XX.XXX:4173
```
3. **Se l'alert appare**, c'è un SyntaxError nei moduli ES
4. **Prova altro browser** (Chrome vs Safari su iOS)
## 🔄 Svelte 5 & Modern JavaScript
### Svelte 5 Runes
Il progetto usa **Svelte 5 Runes** per reattività state-driven:
```svelte
<script>
let activeNav = $state(window.location.pathname)
let isMenuOpen = $state(false)
let hasBackground = $state(false)
let currentRoute = $derived(getRoute(pathname))
</script>
```
**Evitare:**
- ~~`on:click`~~ → Usare `onclick={...}`
- ~~Stores `writable()`~~ → Usare `$state()`
- ~~`reactive`~~ → Usare `$derived()`
### Rune Disponibili
- `$state(value)` — Mutabile, reattivo
- `$derived(expr)` — Calcolato e cached
- `$effect(fn)` — Side effect reattivo
- `$props()` — Default props type-safe
## 🚀 Build & Deploy
### Build di Produzione
```bash
npm run build
```
Genera i file ottimizzati in `dist/`.
Genera file ottimizzati in `dist/`:
- `.html` minificato
- `.js` bundle (tree-shaken)
- `.css` minificato
- Source map opzionali
### Preview locale
### Preview Locale
```bash
npm run preview
```
Simula il server di produzione su `http://localhost:4173` — ottimo per testare la build prima di deployare.
### Deployment
Il progetto è una **SPA statica** — deployabile su:
- **Vercel**, **Netlify** (drag & drop `dist/`)
- **GitHub Pages** (aggiungere `base: "/repo-name/"` in vite.config.js)
- **AWS S3 + CloudFront**
- **Qualsiasi server web** (serve `dist/` con fallback 404 → `/index.html`)
## 📦 Dipendenze
- **Svelte** - Framework UI
- **Vite** - Build tool e dev server
- **@sveltejs/vite-plugin-svelte** - Plugin Svelte per Vite
```json
{
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"svelte": "^5.55.5",
"vite": "^8.0.10"
}
}
```
## 🎓 Tecnologie utilizzate
**Zero runtime dependencies** — tutta la logica è vanilla JavaScript.
- **Svelte 5** - UI framework reattivo
- **Vite** - Build tool moderno e veloce
- **CSS3** - Styling con variabili CSS
- **Vanilla JS** - JavaScript puro
## 🎓 Tecnologie & Approcci
| Aspetto | Soluzione |
|---------|----------|
| **Framework UI** | Svelte 5 (Runes + reactivity) |
| **Build** | Vite 8 (zero-config, super veloce) |
| **CSS** | CSS3 nativo (custom properties + clamp) |
| **Routing** | History API (client-side only) |
| **Tema** | Data attribute + CSS variables |
| **Cookies** | Vanilla JS (no library) |
| **Accessibilità** | ARIA roles + focus-visible + keyboard nav |
| **Mobile** | Responsive design + touch optimized |
## 🎯 Convenzioni Codice
### Naming
- **Components**: PascalCase (`Header.svelte`, `Button.svelte`)
- **Pages**: PascalCase (`Contacts.svelte`, `index.svelte`)
- **Styles**: kebab-case (`.contact-us-container`, `.mobile-menu`)
- **JS utils**: camelCase (`getSavedTheme`, `navigateTo`)
### Organizzazione File
```
Component/
├── Component.svelte # Logica + template
├── Component.css # Stili isolati
└── assets/ # SVG, immagini specifiche
```
### CSS Classi
- `.component-name` per root
- `.component-name-child` per nested (BEM-like)
- Aggiungere media query alla fine del file
- Commentare sezioni con `/* ===== SECTION NAME ===== */`
## 🤝 Contribuire
### Workflow
1. **Crea branch** per feature: `git checkout -b feature/nome`
2. **Test locale**: `npm run dev` + mobile preview
3. **Build test**: `npm run build && npm run preview`
4. **Commit**: Messaggi chiari (`feat:`, `fix:`, `docs:`)
5. **Push** e open PR
## 🔗 Link Utili
- [Svelte 5 Docs](https://svelte.dev)
- [Vite Docs](https://vitejs.dev)
- [Svelte REPL](https://svelte.dev/repl)
- [Can I Use](https://caniuse.com) — Compatibilità browser
## 📄 Licenza
Progetto proprietario © CIMA PROGETTI
Progetto proprietario © **CIMA PROGETTI**