Cima Progetti - Sito Aziendale
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 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+
🚀 Avvio rapido
npm install
npm run dev
Applicazione disponibile su http://localhost:5173 (accesso locale) e http://192.168.XX.XXX:5173 (rete locale).
📝 Script disponibili
| 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
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:
// vite.config.js
server: {
host: '0.0.0.0', // Ascolta su tutte le interfacce
port: 5173,
}
Accesso dal telefono sulla stessa Wi-Fi
- Avvia il dev server:
npm run dev - Nel terminale vedrai l'indirizzo IP locale (es.
192.168.68.119) - 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 è client-side only — nessuna pagina server. Gestito in App.svelte leggendo window.location.pathname e reagendo ai cambi di History API.
Tabella rotte
| 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 |
Meccanismo di routing
// 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
- Crea il componente in
src/pages/MyPage.svelte - Importa in App.svelte:
import MyPage from "./pages/MyPage.svelte" - Aggiungi la route in
getRoute():if (normalized === '/my-page') return 'mypage' - Renderizza nel template:
{: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.
// 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:
:root {
--navbar-height: 140px;
--primary-color: #1343F0; /* Blu Cima */
--text-color: #111827; /* Grigio scuro */
--surface: #ffffff; /* Background carta */
--background: rgba(246, 249, 255, 0.5);
--background-opaque: #00000033;
--muted-color: #6b7280;
--lateral-margin: 12rem;
}
:root[data-theme='dark'] {
--primary-color: #4983F2; /* Blu chiaro */
--text-color: #e6eef8; /* Grigio molto chiaro */
--surface: #000;
--background: rgba(0, 0, 0, 0.8);
--background-opaque: rgba(255, 255, 255, 0.2);
}
/* Mobile breakpoint */
@media (max-width: 900px) {
:root {
--navbar-height: 110px;
--lateral-margin: 1.5rem;
}
}
Come usare i colori tema
<div style="color: var(--text-color); background: var(--surface);">
Contenuto automaticamente theme-aware
</div>
🧩 Componenti Principali
Header (Navbar Intelligente)
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)
<Header />
Modifiche in Header.svelte:
- Stato
hasBackgroundche attiva ascrollY > 100 - Classe
with-bgper applicare blur e shadow - Mobile menu hamburger con animazione da basso
CSS Features:
backdrop-filter: blur(8px)per effetto frosted glassposition: fixedcontransition: 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>.
<Button
text="PRENOTA UNA CALL"
href="/contatti"
borderWidth="1.5px"
borderColor="var(--text-color)"
color="transparent"
textColor="var(--text-color)"
padding="12px 32px"
round="5px"
margin="1rem 0 0"
bold
/>
Tutte le prop:
| 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 |
Accessibilità:
- Outline su
:focus-visible(tastiera) - Tap highlight rimosso su touch (
:focus:not(:focus-visible))
CookiePopUp (Cookie Consent Dialog)
Dialog modale con due view:
- Main — Descrizione generica + bottoni accetta/rifiuta/personalizza
- Prefs — Toggle per tre categorie: Tecnici (always on), Analitici, Profilazione
<CookiePopUp />
Varianti layout:
- Desktop (> 900px): Fixed al bottom, full-width
- Tablet (768–900px): 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.
<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-imagediventaposition: absolutecentratacontact-us-containerfull width- Profili stacked verticalmente
Toggle (Accessibile Switch)
Switch accessibile con label opzionale e stato animato.
<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:
/* 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);
}
🔐 Cookie Consent & Privacy
Gestione Categoria Cookie
// src/lib/cookieConsent.js
const consent = {
value: 'custom', // 'accepted_all', 'rejected_all', 'custom'
preferences: {
tecnici: true, // Obbligatorio
analitici: false, // Personalizzabile
profilazione: false // Personalizzabile
}
}
Persistenza
- Cookie di consenso salvato in
cookie-consent(viajs-cookie) - Configurazione letta al mount di
App.svelte - Toggle popup non riappare se esiste consenso valido
🐛 Error Handling & Debugging
Global Error Trap (main.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:
- Apri DevTools → Console → controlla errori
- Prova build di produzione:
npm run build npm run preview # Accedi da http://192.168.XX.XXX:4173 - Se l'alert appare, c'è un SyntaxError nei moduli ES
- 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:
<script>
let activeNav = $state(window.location.pathname)
let isMenuOpen = $state(false)
let hasBackground = $state(false)
let currentRoute = $derived(getRoute(pathname))
</script>
Evitare:
→ Usareon:clickonclick={...}Stores→ Usarewritable()$state()→ Usarereactive$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
npm run build
Genera file ottimizzati in dist/:
.htmlminificato.jsbundle (tree-shaken).cssminificato- Source map opzionali
Preview Locale
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
{
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"svelte": "^5.55.5",
"vite": "^8.0.10"
}
}
Zero runtime dependencies — tutta la logica è vanilla JavaScript.
🎓 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-nameper root.component-name-childper nested (BEM-like)- Aggiungere media query alla fine del file
- Commentare sezioni con
/* ===== SECTION NAME ===== */
🤝 Contribuire
Workflow
- Crea branch per feature:
git checkout -b feature/nome - Test locale:
npm run dev+ mobile preview - Build test:
npm run build && npm run preview - Commit: Messaggi chiari (
feat:,fix:,docs:) - Push e open PR
🔗 Link Utili
- Svelte 5 Docs
- Vite Docs
- Svelte REPL
- Can I Use — Compatibilità browser
📄 Licenza
Progetto proprietario © CIMA PROGETTI