feat: Added agency-web-developer skill
- New skill for building static websites from copy + design system - HTML semantic with accessibility + SEO best practices - CSS layout with Bootstrap or custom support - JS interactivity with jQuery + GSAP for smooth animations - 3 reference files: html_semantics.md, css_layout.md, js_interactivity.md - Updated README with new skill (13 total) and references (23 total) - Workflow updated: agency-ux-copy → agency-web-developer → agency-seo No overlap with existing skills: - agency-visual-generator: generates images (PNG/webp) for social/YouTube - agency-publisher: publishes content via webhook - agency-web-developer: builds complete static websites (HTML/CSS/JS)
This commit is contained in:
parent
6ac766172c
commit
b79b28034a
5 changed files with 2153 additions and 5 deletions
17
README.md
17
README.md
|
|
@ -4,8 +4,8 @@ Suite di AgentSkills per Agency AI OS v3.1 — trasformate da framework multi-ag
|
|||
|
||||
## Panoramica
|
||||
|
||||
- **Skills:** 12
|
||||
- **References:** 17
|
||||
- **Skills:** 13
|
||||
- **References:** 20
|
||||
- **Lingua:** English (con note in italiano)
|
||||
- **Dominio:** Digital Agency / Content Production / Multi-Agent System
|
||||
|
||||
|
|
@ -44,11 +44,13 @@ clawhub install agency_v3_1-skills
|
|||
| 8 | **agency-social** | Social strategy, calendario, post queue | Media |
|
||||
| 9 | **agency-youtube** | YouTube strategy, script retention-first | Media |
|
||||
| 10 | **agency-visual-generator** | Asset visuali (card, carousel, thumbnail) | Media |
|
||||
| 11 | **agency-analytics** | Report KPI settimanali, actions | Bassa |
|
||||
| 12 | **agency-publisher** | Publish gate-based (social/YouTube) | Bassa |
|
||||
| 11 | **agency-web-developer** | Siti web statici (HTML/CSS/JS) | Media |
|
||||
| 12 | **agency-analytics** | Report KPI settimanali, actions | Bassa |
|
||||
| 13 | **agency-publisher** | Publish gate-based (social/YouTube) | Bassa |
|
||||
|
||||
## References Incluse
|
||||
|
||||
### Agency Core (17)
|
||||
1. `design_patterns.md` — Pattern UI riusabili
|
||||
2. `hero_sections.md` — Layout hero efficaci
|
||||
3. `layout_systems.md` — Grid, spacing, density
|
||||
|
|
@ -70,6 +72,11 @@ clawhub install agency_v3_1-skills
|
|||
19. `qa_visual.md` — QA checklist visual
|
||||
20. `weekly_report_template.md` — Template report
|
||||
|
||||
### Web Developer (3)
|
||||
21. `html_semantics.md` — Semantic HTML best practices (in agency-web-developer/references)
|
||||
22. `css_layout.md` — Layout Bootstrap/custom (in agency-web-developer/references)
|
||||
23. `js_interactivity.md` — jQuery + GSAP patterns (in agency-web-developer/references)
|
||||
|
||||
## Workflow Consigliato
|
||||
|
||||
1. **Onboarding:** `agency-orchestrator` → definisci MVP
|
||||
|
|
@ -77,7 +84,7 @@ clawhub install agency_v3_1-skills
|
|||
3. **Strategy:** `agency-strategy` → positioning, messaging
|
||||
4. **Creative:** `agency-creative-director` → direzione visiva
|
||||
5. **Design:** `agency-design-system` → design system
|
||||
6. **Website:** `agency-ux-copy` + `agency-seo` → sitemap, copy, SEO
|
||||
6. **Website:** `agency-ux-copy` → sitemap, copy → `agency-web-developer` → sito HTML/CSS/JS → `agency-seo` → metadata
|
||||
7. **Content:** `agency-social` + `agency-youtube` → calendar, script
|
||||
8. **Visual:** `agency-visual-generator` → asset
|
||||
9. **Publish:** `agency-publisher` → publish (post-approvazione)
|
||||
|
|
|
|||
346
agency-web-developer/SKILL.md
Normal file
346
agency-web-developer/SKILL.md
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
---
|
||||
name: agency-web-developer
|
||||
description: Sviluppare siti web statici o landing page partendo da copy e design system. Usare quando: (1) costruire sito da sitemap + copy, (2) creare landing page, (3) implementare design in HTML/CSS/JS. Output: Sito completo con HTML semantico, CSS (Bootstrap o custom), JS (jQuery + GSAP).
|
||||
---
|
||||
|
||||
# Agency Web Developer — Sviluppo Siti Web Statici
|
||||
|
||||
Trasforma copy e design system in siti web funzionanti con HTML semantico, CSS e JS.
|
||||
|
||||
## Quando Usare
|
||||
|
||||
- **Nuovo sito:** Costruire sito completo da sitemap + copy + design tokens
|
||||
- **Landing page:** Pagina singola campaign-specific
|
||||
- **Design implementation:** Convertire mockup in codice
|
||||
- **Static site:** Sito senza backend (brochure, portfolio, landing)
|
||||
|
||||
---
|
||||
|
||||
## Input
|
||||
|
||||
| Input | Tipo | Validazione |
|
||||
|-------|------|-------------|
|
||||
| `client_path` | string | Percorso client |
|
||||
| `sitemap` | object | Da agency-ux-copy |
|
||||
| `page_copy` | array | Copy completo per ogni pagina |
|
||||
| `design_tokens` | object | Da agency-design-system |
|
||||
| `page_layouts` | array | Layout definitions |
|
||||
| `seo_metadata` | array | SEO notes per pagina |
|
||||
|
||||
---
|
||||
|
||||
## Processo
|
||||
|
||||
### Fase 1: Setup Struttura Progetto
|
||||
|
||||
**Obiettivo:** Creare directory structure per il sito.
|
||||
|
||||
**Azioni:**
|
||||
|
||||
1. Crea cartella `clients/{client}/website/`
|
||||
2. Crea sottocartelle:
|
||||
- `index.html` (homepage)
|
||||
- `pages/` (altre pagine HTML)
|
||||
- `css/` (stylesheet)
|
||||
- `js/` (JavaScript)
|
||||
- `assets/` (immagini, font, etc.)
|
||||
- `assets/img/`
|
||||
- `assets/fonts/`
|
||||
|
||||
3. Inizializza file base:
|
||||
- `css/main.css` (o `css/bootstrap.min.css` + `css/custom.css`)
|
||||
- `js/main.js`
|
||||
- `assets/` (vuoto, pronto per asset)
|
||||
|
||||
**Output:**
|
||||
- Struttura directory pronta
|
||||
|
||||
---
|
||||
|
||||
### Fase 2: HTML Semantico
|
||||
|
||||
**Obiettivo:** Costruire struttura HTML per ogni pagina.
|
||||
|
||||
**Azioni:**
|
||||
|
||||
1. **Per ogni pagina in sitemap:**
|
||||
|
||||
2. **Struttura base:**
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{SEO Title}</title>
|
||||
<meta name="description" content="{SEO Meta Description}">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>{Navigation}</header>
|
||||
<main>{Content}</main>
|
||||
<footer>{Footer}</footer>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
3. **Hero section:**
|
||||
- `<section class="hero">`
|
||||
- `<h1>` per headline
|
||||
- `<p class="hero-sub">` per subcopy
|
||||
- `<a class="btn btn-primary">` per CTA
|
||||
|
||||
4. **Content sections:**
|
||||
- Usa tag semantici: `<section>`, `<article>`, `<aside>`
|
||||
- Heading hierarchy: `<h2>`, `<h3>` (mai saltare livelli)
|
||||
- Liste: `<ul>`, `<ol>` con `<li>`
|
||||
- Immagini: `<img alt="descrizione">`
|
||||
|
||||
5. **Componenti da copy:**
|
||||
- Card grid per services
|
||||
- Testimonial blocks
|
||||
- FAQ accordion
|
||||
- CTA sections
|
||||
- Form di contatto
|
||||
|
||||
6. **Segui `references/html_semantics.md`:**
|
||||
- Semantic HTML best practices
|
||||
- Accessibility (ARIA labels dove necessario)
|
||||
- SEO on-page (heading structure, alt text)
|
||||
|
||||
**Output:**
|
||||
- `clients/{client}/website/index.html`
|
||||
- `clients/{client}/website/pages/*.html`
|
||||
|
||||
---
|
||||
|
||||
### Fase 3: CSS Layout
|
||||
|
||||
**Obiettivo:** Stilizzare il sito con CSS.
|
||||
|
||||
**Decisione: Bootstrap vs Custom**
|
||||
|
||||
**Usa Bootstrap se:**
|
||||
- Design è standard (grid, cards, buttons standard)
|
||||
- Progetto semplice/veloce
|
||||
- Cliente non richiede customizzazione estrema
|
||||
|
||||
**Usa Custom CSS se:**
|
||||
- Design altamente personalizzato
|
||||
- Brand ha linee guida specifiche
|
||||
- Layout non-standard
|
||||
|
||||
**Azioni:**
|
||||
|
||||
1. **Se Bootstrap:**
|
||||
- Includi CDN in HTML
|
||||
- Crea `css/custom.css` per override
|
||||
- Usa classi Bootstrap (.container, .row, .col, .btn, etc.)
|
||||
- Override tokens in custom.css
|
||||
|
||||
2. **Se Custom:**
|
||||
- Crea `css/main.css` da zero
|
||||
- Implementa design tokens (fonts, colors, spacing)
|
||||
- Crea grid system (o usa CSS Grid/Flexbox)
|
||||
- Stilizza componenti uno a uno
|
||||
|
||||
3. **Implementa da `references/css_layout.md`:**
|
||||
- Responsive design (mobile-first)
|
||||
- Breakpoints (mobile, tablet, desktop)
|
||||
- Typography scale
|
||||
- Color system
|
||||
- Spacing system
|
||||
|
||||
4. **Componenti da stilizzare:**
|
||||
- Header/navigation (sticky, mobile menu)
|
||||
- Hero section
|
||||
- Buttons (primary, secondary, hover states)
|
||||
- Cards
|
||||
- Forms
|
||||
- Footer
|
||||
|
||||
**Output:**
|
||||
- `clients/{client}/website/css/main.css` (o `bootstrap.min.css` + `custom.css`)
|
||||
|
||||
---
|
||||
|
||||
### Fase 4: JavaScript Interattività
|
||||
|
||||
**Obiettivo:** Aggiungere interattività al sito.
|
||||
|
||||
**Librerie:**
|
||||
- **jQuery:** Per DOM manipulation, event handling, AJAX (se necessario)
|
||||
- **GSAP:** Per animazioni fluide, timeline, effetti complessi
|
||||
|
||||
**Azioni:**
|
||||
|
||||
1. **Includi librerie in HTML:**
|
||||
```html
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
```
|
||||
|
||||
2. **Funzionalità base (jQuery):**
|
||||
- Mobile menu toggle
|
||||
- Smooth scroll per anchor links
|
||||
- Form validation
|
||||
- Accordion FAQ
|
||||
- Image lazy loading
|
||||
- Back to top button
|
||||
|
||||
3. **Animazioni (GSAP):**
|
||||
- Fade-in on scroll
|
||||
- Hero animations (headline, CTA)
|
||||
- Parallax effects (se richiesti)
|
||||
- Hover animations complesse
|
||||
- Timeline per sequenze animate
|
||||
|
||||
4. **Segui `references/js_interactivity.md`:**
|
||||
- Best practices jQuery
|
||||
- GSAP patterns
|
||||
- Performance considerations
|
||||
- Accessibility (respect reduced-motion)
|
||||
|
||||
**Template `js/main.js`:**
|
||||
```javascript
|
||||
$(document).ready(function() {
|
||||
|
||||
// Mobile menu toggle
|
||||
$('.mobile-menu-toggle').on('click', function() {
|
||||
$('.nav-menu').toggleClass('is-open');
|
||||
});
|
||||
|
||||
// Smooth scroll
|
||||
$('a[href^="#"]').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = $(this.getAttribute('href'));
|
||||
if (target.length) {
|
||||
$('html, body').animate({
|
||||
scrollTop: target.offset().top - 80
|
||||
}, 800);
|
||||
}
|
||||
});
|
||||
|
||||
// FAQ accordion
|
||||
$('.faq-question').on('click', function() {
|
||||
$(this).next('.faq-answer').slideToggle(300);
|
||||
$(this).toggleClass('is-active');
|
||||
});
|
||||
|
||||
// GSAP animations
|
||||
gsap.from('.hero h1', {
|
||||
duration: 1,
|
||||
opacity: 0,
|
||||
y: 30,
|
||||
ease: 'power3.out'
|
||||
});
|
||||
|
||||
gsap.from('.hero .btn', {
|
||||
duration: 1,
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
delay: 0.3,
|
||||
ease: 'power3.out'
|
||||
});
|
||||
|
||||
// Scroll animations
|
||||
gsap.utils.toArray('.fade-on-scroll').forEach(function(elem) {
|
||||
gsap.to(elem, {
|
||||
scrollTrigger: {
|
||||
trigger: elem,
|
||||
start: 'top 80%',
|
||||
toggleActions: 'play none none none'
|
||||
},
|
||||
opacity: 1,
|
||||
y: 0
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- `clients/{client}/website/js/main.js`
|
||||
|
||||
---
|
||||
|
||||
### Fase 5: QA e Testing
|
||||
|
||||
**Obiettivo:** Verificare qualità del sito.
|
||||
|
||||
**Azioni:**
|
||||
|
||||
1. **HTML validation:**
|
||||
- Tag chiusi correttamente
|
||||
- Attributi required presenti
|
||||
- Nesting corretto
|
||||
|
||||
2. **CSS check:**
|
||||
- Responsive su mobile/tablet/desktop
|
||||
- Color contrast (accessibility)
|
||||
- No overflow o layout broken
|
||||
|
||||
3. **JS check:**
|
||||
- Console errors (nessuno)
|
||||
- Funzionalità testate (menu, form, accordion)
|
||||
- Animazioni fluide
|
||||
|
||||
4. **SEO check:**
|
||||
- Title tag unico per pagina
|
||||
- Meta description presente
|
||||
- Heading hierarchy corretta
|
||||
- Alt text su immagini
|
||||
|
||||
5. **Performance:**
|
||||
- Immagini ottimizzate (se presenti)
|
||||
- CSS/JS minified (opzionale)
|
||||
- No console warnings
|
||||
|
||||
**Output:**
|
||||
- `clients/{client}/website/qa/qa_website.md`
|
||||
|
||||
---
|
||||
|
||||
## Output
|
||||
|
||||
| File | Formato | Descrizione |
|
||||
|------|---------|-------------|
|
||||
| `clients/{client}/website/index.html` | HTML | Homepage |
|
||||
| `clients/{client}/website/pages/*.html` | HTML | Altre pagine |
|
||||
| `clients/{client}/website/css/main.css` | CSS | Stylesheet |
|
||||
| `clients/{client}/website/js/main.js` | JS | Interattività |
|
||||
| `clients/{client}/website/assets/` | Folder | Immagini, font, etc. |
|
||||
| `clients/{client}/website/qa/qa_website.md` | Markdown | QA checklist |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [html_semantics.md](./references/html_semantics.md) — Semantic HTML best practices
|
||||
- [css_layout.md](./references/css_layout.md) — Layout, responsive, Bootstrap
|
||||
- [js_interactivity.md](./references/js_interactivity.md) — jQuery + GSAP patterns
|
||||
|
||||
---
|
||||
|
||||
## Note
|
||||
|
||||
**Edge Cases:**
|
||||
|
||||
- **Nessun design system:** Usa Bootstrap default + stile minimal
|
||||
- **Solo landing page:** Crea single-page con anchor navigation
|
||||
- **Cliente fornisce asset:** Copia in `assets/` e usa percorsi corretti
|
||||
- **Animazioni complesse:** Usa GSAP timeline, documenta in decision log
|
||||
|
||||
**Limitazioni:**
|
||||
|
||||
- Siti statici (no backend, no database)
|
||||
- Form richiedono servizio esterno (Formspree, Netlify Forms) o backend
|
||||
- E-commerce non supportato (richiede piattaforma dedicata)
|
||||
|
||||
---
|
||||
|
||||
_Skill creata per agency_v3_1 suite_
|
||||
647
agency-web-developer/references/css_layout.md
Normal file
647
agency-web-developer/references/css_layout.md
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
# CSS Layout — Bootstrap e Custom
|
||||
|
||||
Guida per creare layout responsive con Bootstrap o CSS custom.
|
||||
|
||||
---
|
||||
|
||||
## Decisione: Bootstrap vs Custom
|
||||
|
||||
### Usa Bootstrap Quando:
|
||||
|
||||
✅ Design standard (grid, cards, buttons)
|
||||
✅ Progetto veloce / MVP
|
||||
✅ Cliente non richiede customizzazione estrema
|
||||
✅ Team piccolo, nessun designer dedicato
|
||||
|
||||
### Usa Custom CSS Quando:
|
||||
|
||||
✅ Design altamente personalizzato
|
||||
✅ Brand guidelines specifiche
|
||||
✅ Layout non-standard / creativi
|
||||
✅ Performance critica (no CSS unused)
|
||||
|
||||
---
|
||||
|
||||
## Bootstrap Setup
|
||||
|
||||
### CDN Setup
|
||||
|
||||
```html
|
||||
<head>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
>
|
||||
|
||||
<!-- Custom CSS (override) -->
|
||||
<link rel="stylesheet" href="css/custom.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Bootstrap JS (opzionale, solo se serve JS components) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
```
|
||||
|
||||
### Custom CSS Override
|
||||
|
||||
```css
|
||||
/* css/custom.css */
|
||||
|
||||
:root {
|
||||
/* Override Bootstrap variables */
|
||||
--bs-primary: #005fcc;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-font-sans-serif: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Custom styles */
|
||||
.hero {
|
||||
padding: 120px 0;
|
||||
background: linear-gradient(135deg, #005fcc 0%, #003d99 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 12px 32px;
|
||||
font-weight: 600;
|
||||
border-radius: 4px;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Responsive Design
|
||||
|
||||
### Breakpoints
|
||||
|
||||
```css
|
||||
/* Mobile-first approach */
|
||||
|
||||
/* Mobile: default (< 768px) */
|
||||
.container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/* Tablet (≥ 768px) */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 160px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop (≥ 1024px) */
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Large Desktop (≥ 1440px) */
|
||||
@media (min-width: 1440px) {
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bootstrap Grid
|
||||
|
||||
```html
|
||||
<!-- Container -->
|
||||
<div class="container">
|
||||
<!-- Row -->
|
||||
<div class="row">
|
||||
<!-- Mobile: 1 colonna, Tablet: 2 colonne, Desktop: 3 colonne -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<!-- Content -->
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<!-- Content -->
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<!-- Content -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
### Design Tokens
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Font Families */
|
||||
--font-primary: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--font-secondary: 'Georgia', serif;
|
||||
|
||||
/* Font Sizes (modular scale) */
|
||||
--text-xs: 0.75rem; /* 12px */
|
||||
--text-sm: 0.875rem; /* 14px */
|
||||
--text-base: 1rem; /* 16px */
|
||||
--text-lg: 1.125rem; /* 18px */
|
||||
--text-xl: 1.25rem; /* 20px */
|
||||
--text-2xl: 1.5rem; /* 24px */
|
||||
--text-3xl: 1.875rem; /* 30px */
|
||||
--text-4xl: 2.25rem; /* 36px */
|
||||
--text-5xl: 3rem; /* 48px */
|
||||
|
||||
/* Font Weights */
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
|
||||
/* Line Heights */
|
||||
--leading-tight: 1.25;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.75;
|
||||
|
||||
/* Letter Spacing */
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-normal: 0;
|
||||
--tracking-wide: 0.025em;
|
||||
}
|
||||
|
||||
/* Base Typography */
|
||||
body {
|
||||
font-family: var(--font-primary);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-normal);
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
h1 {
|
||||
font-size: var(--text-5xl);
|
||||
font-weight: var(--font-bold);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--text-4xl);
|
||||
font-weight: var(--font-bold);
|
||||
line-height: var(--leading-tight);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--text-3xl);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: var(--leading-tight);
|
||||
}
|
||||
|
||||
/* Responsive typography */
|
||||
@media (max-width: 767px) {
|
||||
h1 { font-size: var(--text-4xl); }
|
||||
h2 { font-size: var(--text-3xl); }
|
||||
h3 { font-size: var(--text-2xl); }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spacing System
|
||||
|
||||
### Scala Modulare
|
||||
|
||||
```css
|
||||
:root {
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
--space-16: 64px;
|
||||
--space-20: 80px;
|
||||
--space-24: 96px;
|
||||
}
|
||||
|
||||
/* Section padding */
|
||||
.section-padding {
|
||||
padding: var(--space-16) 0;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.section-padding {
|
||||
padding: var(--space-24) 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Component spacing */
|
||||
.card {
|
||||
padding: var(--space-6);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: var(--space-3) var(--space-6);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color System
|
||||
|
||||
### Palette
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Primary Brand Colors */
|
||||
--color-primary: #005fcc;
|
||||
--color-primary-dark: #003d99;
|
||||
--color-primary-light: #3385ff;
|
||||
|
||||
/* Secondary Colors */
|
||||
--color-secondary: #6c757d;
|
||||
--color-secondary-dark: #5a6268;
|
||||
--color-secondary-light: #868e96;
|
||||
|
||||
/* Neutrals */
|
||||
--color-white: #ffffff;
|
||||
--color-gray-50: #f9fafb;
|
||||
--color-gray-100: #f3f4f6;
|
||||
--color-gray-200: #e5e7eb;
|
||||
--color-gray-300: #d1d5db;
|
||||
--color-gray-400: #9ca3af;
|
||||
--color-gray-500: #6b7280;
|
||||
--color-gray-600: #4b5563;
|
||||
--color-gray-700: #374151;
|
||||
--color-gray-800: #1f2937;
|
||||
--color-gray-900: #111827;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Semantic Colors */
|
||||
--color-success: #10b981;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #ef4444;
|
||||
--color-info: #3b82f6;
|
||||
|
||||
/* Text Colors */
|
||||
--text-primary: var(--color-gray-900);
|
||||
--text-secondary: var(--color-gray-600);
|
||||
--text-muted: var(--color-gray-500);
|
||||
--text-inverse: var(--color-white);
|
||||
|
||||
/* Background Colors */
|
||||
--bg-primary: var(--color-white);
|
||||
--bg-secondary: var(--color-gray-50);
|
||||
--bg-tertiary: var(--color-gray-100);
|
||||
}
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```css
|
||||
/* Text */
|
||||
.text-primary { color: var(--text-primary); }
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-muted { color: var(--text-muted); }
|
||||
.text-inverse { color: var(--text-inverse); }
|
||||
|
||||
/* Backgrounds */
|
||||
.bg-primary { background-color: var(--bg-primary); }
|
||||
.bg-secondary { background-color: var(--bg-secondary); }
|
||||
.bg-brand { background-color: var(--color-primary); }
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: transparent;
|
||||
color: var(--color-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Styles
|
||||
|
||||
### Buttons
|
||||
|
||||
```css
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 32px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 95, 204, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: transparent;
|
||||
color: var(--color-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* Responsive buttons */
|
||||
@media (max-width: 767px) {
|
||||
.btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cards
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--color-white);
|
||||
border-radius: 8px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
```
|
||||
|
||||
### Forms
|
||||
|
||||
```css
|
||||
.form-group {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: var(--font-medium);
|
||||
margin-bottom: var(--space-2);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
font-size: var(--text-base);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: 4px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.1);
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.form-error {
|
||||
color: var(--color-error);
|
||||
font-size: var(--text-sm);
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layout Patterns
|
||||
|
||||
### Hero Section
|
||||
|
||||
```css
|
||||
.hero {
|
||||
position: relative;
|
||||
padding: 120px 0;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: var(--text-5xl);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: var(--text-xl);
|
||||
opacity: 0.9;
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Responsive hero */
|
||||
@media (max-width: 767px) {
|
||||
.hero {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: var(--text-4xl);
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Grid Layout
|
||||
|
||||
```css
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.grid-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.grid-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
/* Responsive grid */
|
||||
@media (max-width: 1023px) {
|
||||
.grid-4 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.grid-2,
|
||||
.grid-3,
|
||||
.grid-4 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Split Layout
|
||||
|
||||
```css
|
||||
.split-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-12);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.split-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Responsive split */
|
||||
@media (max-width: 1023px) {
|
||||
.split-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
.split-image {
|
||||
order: -1; /* Image first on mobile */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Utility Classes
|
||||
|
||||
```css
|
||||
/* Display */
|
||||
.d-none { display: none; }
|
||||
.d-block { display: block; }
|
||||
.d-flex { display: flex; }
|
||||
.d-grid { display: grid; }
|
||||
.d-inline-block { display: inline-block; }
|
||||
|
||||
/* Flexbox */
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-column { flex-direction: column; }
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.align-start { align-items: flex-start; }
|
||||
.align-center { align-items: center; }
|
||||
.align-end { align-items: flex-end; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.gap-2 { gap: var(--space-2); }
|
||||
.gap-4 { gap: var(--space-4); }
|
||||
.gap-6 { gap: var(--space-6); }
|
||||
.gap-8 { gap: var(--space-8); }
|
||||
|
||||
/* Text Alignment */
|
||||
.text-left { text-align: left; }
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
/* Spacing */
|
||||
.mt-4 { margin-top: var(--space-4); }
|
||||
.mb-4 { margin-bottom: var(--space-4); }
|
||||
.py-8 { padding-top: var(--space-8); padding-bottom: var(--space-8); }
|
||||
.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); }
|
||||
|
||||
/* Visibility */
|
||||
.visible { visibility: visible; }
|
||||
.invisible { visibility: hidden; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist Layout
|
||||
|
||||
- [ ] Mobile-first approach
|
||||
- [ ] Breakpoints definiti (mobile, tablet, desktop)
|
||||
- [ ] Typography scale coerente
|
||||
- [ ] Spacing system modulare
|
||||
- [ ] Color palette definita
|
||||
- [ ] Buttons stilizzati (hover states)
|
||||
- [ ] Cards con shadow e hover effect
|
||||
- [ ] Forms accessibili e responsive
|
||||
- [ ] Grid system funzionante
|
||||
- [ ] Utility classes per layout veloci
|
||||
- [ ] Test su mobile/tablet/desktop
|
||||
|
||||
---
|
||||
|
||||
_References per agency-web-developer skill_
|
||||
418
agency-web-developer/references/html_semantics.md
Normal file
418
agency-web-developer/references/html_semantics.md
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
# HTML Semantics — Best Practices
|
||||
|
||||
Guida per costruire HTML semantico, accessibile e SEO-friendly.
|
||||
|
||||
---
|
||||
|
||||
## Document Structure
|
||||
|
||||
### Base Template
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="{descrizione pagina}">
|
||||
<title>{Titolo pagina} | {Brand}</title>
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="assets/img/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
<header>{Navigation}</header>
|
||||
<main>{Contenuto principale}</main>
|
||||
<footer>{Footer}</footer>
|
||||
|
||||
<!-- JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Semantic Elements
|
||||
|
||||
### Header
|
||||
|
||||
```html
|
||||
<header>
|
||||
<nav aria-label="Main navigation">
|
||||
<a href="/" class="logo">
|
||||
<img src="assets/img/logo.svg" alt="{Brand Name}">
|
||||
</a>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/pages/services.html">Servizi</a></li>
|
||||
<li><a href="/pages/about.html">Chi Siamo</a></li>
|
||||
<li><a href="/pages/contact.html">Contatti</a></li>
|
||||
</ul>
|
||||
<button class="mobile-menu-toggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
```
|
||||
|
||||
### Main Content
|
||||
|
||||
```html
|
||||
<main>
|
||||
<!-- Hero Section -->
|
||||
<section class="hero" aria-labelledby="hero-title">
|
||||
<h1 id="hero-title">Headline principale</h1>
|
||||
<p class="hero-sub">Subcopy di supporto</p>
|
||||
<a href="#cta" class="btn btn-primary">Call to Action</a>
|
||||
</section>
|
||||
|
||||
<!-- Services Section -->
|
||||
<section class="services" aria-labelledby="services-title">
|
||||
<h2 id="services-title">I Nostri Servizi</h2>
|
||||
<div class="services-grid">
|
||||
<article class="service-card">
|
||||
<h3>Servizio 1</h3>
|
||||
<p>Descrizione servizio</p>
|
||||
<a href="#">Scopri di più</a>
|
||||
</article>
|
||||
<!-- Altre card -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Testimonial Section -->
|
||||
<section class="testimonials" aria-labelledby="testimonials-title">
|
||||
<h2 id="testimonials-title">Dicono di Noi</h2>
|
||||
<article class="testimonial">
|
||||
<blockquote>
|
||||
<p>"Testimonial text"</p>
|
||||
</blockquote>
|
||||
<cite>— Nome Cliente, Azienda</cite>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<section class="faq" aria-labelledby="faq-title">
|
||||
<h2 id="faq-title">Domande Frequenti</h2>
|
||||
<div class="faq-list">
|
||||
<article class="faq-item">
|
||||
<h3>
|
||||
<button class="faq-question" aria-expanded="false">
|
||||
Domanda 1
|
||||
</button>
|
||||
</h3>
|
||||
<div class="faq-answer">
|
||||
<p>Risposta alla domanda</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="cta" aria-labelledby="cta-title">
|
||||
<h2 id="cta-title">Pronto a Iniziare?</h2>
|
||||
<a href="/pages/contact.html" class="btn btn-primary" id="cta">Contattaci</a>
|
||||
</section>
|
||||
</main>
|
||||
```
|
||||
|
||||
### Footer
|
||||
|
||||
```html
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<img src="assets/img/logo.svg" alt="{Brand Name}">
|
||||
<p>Descrizione breve del brand</p>
|
||||
</div>
|
||||
<nav aria-label="Footer navigation">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/pages/services.html">Servizi</a></li>
|
||||
<li><a href="/pages/about.html">Chi Siamo</a></li>
|
||||
<li><a href="/pages/contact.html">Contatti</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="footer-contact">
|
||||
<p>Email: info@example.com</p>
|
||||
<p>Tel: +39 123 456 7890</p>
|
||||
</div>
|
||||
<div class="footer-social">
|
||||
<a href="#" aria-label="Facebook">FB</a>
|
||||
<a href="#" aria-label="Instagram">IG</a>
|
||||
<a href="#" aria-label="LinkedIn">LI</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2024 {Brand Name}. Tutti i diritti riservati.</p>
|
||||
<nav aria-label="Legal navigation">
|
||||
<a href="/pages/privacy.html">Privacy Policy</a>
|
||||
<a href="/pages/cookie.html">Cookie Policy</a>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Heading Hierarchy
|
||||
|
||||
### Regole
|
||||
|
||||
1. **Un solo H1 per pagina** (di solito nella hero)
|
||||
2. **Mai saltare livelli** (H2 → H3 → H4, non H2 → H4)
|
||||
3. **Heading descrittivi** (non "Section 1", ma "I Nostri Servizi")
|
||||
4. **Keyword rilevanti** (SEO-friendly ma naturale)
|
||||
|
||||
### Esempio Corretto
|
||||
|
||||
```html
|
||||
<h1>Consulenza Marketing Digitale</h1>
|
||||
|
||||
<section>
|
||||
<h2>I Nostri Servizi</h2>
|
||||
|
||||
<article>
|
||||
<h3>SEO Optimization</h3>
|
||||
<p>...</p>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h3>Social Media Management</h3>
|
||||
<p>...</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Perché Sceglierci</h2>
|
||||
<h3>Esperienza Decennale</h3>
|
||||
<h3>Team Certificato</h3>
|
||||
</section>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
### ARIA Labels
|
||||
|
||||
```html
|
||||
<!-- Navigation -->
|
||||
<nav aria-label="Main navigation">...</nav>
|
||||
<nav aria-label="Footer navigation">...</nav>
|
||||
|
||||
<!-- Sections -->
|
||||
<section aria-labelledby="services-title">
|
||||
<h2 id="services-title">Servizi</h2>
|
||||
</section>
|
||||
|
||||
<!-- Buttons -->
|
||||
<button aria-label="Close menu" class="close-btn">×</button>
|
||||
<button aria-label="Toggle FAQ" aria-expanded="false" class="faq-question">
|
||||
Domanda
|
||||
</button>
|
||||
|
||||
<!-- Forms -->
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required aria-required="true">
|
||||
|
||||
<!-- Images -->
|
||||
<img src="team-photo.jpg" alt="Team di 5 persone in ufficio">
|
||||
<img src="decorative-pattern.svg" alt="" role="presentation">
|
||||
```
|
||||
|
||||
### Focus States
|
||||
|
||||
```css
|
||||
/* Assicurati che tutti gli elementi focusabili abbiano visible focus */
|
||||
a:focus,
|
||||
button:focus,
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
outline: 2px solid #005fcc;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Skip link per keyboard navigation */
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 0;
|
||||
background: #005fcc;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
top: 0;
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<body>
|
||||
<a href="#main-content" class="skip-link">Vai al contenuto principale</a>
|
||||
<header>...</header>
|
||||
<main id="main-content">...</main>
|
||||
</body>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Images
|
||||
|
||||
### Best Practices
|
||||
|
||||
```html
|
||||
<!-- Immagine con descrizione -->
|
||||
<img src="team.jpg" alt="Team di 5 persone sorridenti in ufficio moderno">
|
||||
|
||||
<!-- Immagine decorativa (no alt text) -->
|
||||
<img src="pattern.svg" alt="" role="presentation">
|
||||
|
||||
<!-- Immagine responsive -->
|
||||
<img
|
||||
src="hero-mobile.jpg"
|
||||
srcset="hero-mobile.jpg 480w, hero-tablet.jpg 768w, hero-desktop.jpg 1200w"
|
||||
sizes="(max-width: 480px) 100vw, (max-width: 768px) 100vw, 100vw"
|
||||
alt="Hero image"
|
||||
>
|
||||
|
||||
<!-- Lazy loading -->
|
||||
<img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy" alt="Descrizione">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Forms
|
||||
|
||||
### Contact Form
|
||||
|
||||
```html
|
||||
<form action="https://formspree.io/f/{id}" method="POST" class="contact-form">
|
||||
<div class="form-group">
|
||||
<label for="name">Nome Completo</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
aria-required="true"
|
||||
autocomplete="name"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
aria-required="true"
|
||||
autocomplete="email"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="message">Messaggio</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
rows="5"
|
||||
required
|
||||
aria-required="true"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Invia Messaggio</button>
|
||||
|
||||
<p class="form-note">
|
||||
<small>Inviando questo form accetti la nostra
|
||||
<a href="/pages/privacy.html">privacy policy</a>.</small>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SEO On-Page
|
||||
|
||||
### Meta Tags
|
||||
|
||||
```html
|
||||
<head>
|
||||
<!-- Required -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{Keyword Primaria} | {Brand Name}</title>
|
||||
<meta name="description" content="{150-160 caratteri con keyword}">
|
||||
|
||||
<!-- Open Graph (social sharing) -->
|
||||
<meta property="og:title" content="{Titolo pagina}">
|
||||
<meta property="og:description" content="{Descrizione}">
|
||||
<meta property="og:image" content="https://example.com/assets/img/og-image.jpg">
|
||||
<meta property="og:url" content="https://example.com/pagina.html">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{Titolo}">
|
||||
<meta name="twitter:description" content="{Descrizione}">
|
||||
<meta name="twitter:image" content="https://example.com/assets/img/twitter-image.jpg">
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="https://example.com/pagina.html">
|
||||
</head>
|
||||
```
|
||||
|
||||
### Structured Data (JSON-LD)
|
||||
|
||||
```html
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "LocalBusiness",
|
||||
"name": "{Brand Name}",
|
||||
"image": "https://example.com/assets/img/logo.png",
|
||||
"description": "{Descrizione business}",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": "Via Roma 123",
|
||||
"addressLocality": "Milano",
|
||||
"postalCode": "20100",
|
||||
"addressCountry": "IT"
|
||||
},
|
||||
"telephone": "+391234567890",
|
||||
"url": "https://example.com"
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist Semantica
|
||||
|
||||
- [ ] Un solo H1 per pagina
|
||||
- [ ] Heading hierarchy corretta (no salti)
|
||||
- [ ] Tag semantici usati (`<header>`, `<main>`, `<footer>`, `<section>`, `<article>`, `<nav>`)
|
||||
- [ ] ARIA labels dove necessario
|
||||
- [ ] Alt text su tutte le immagini (o vuoto per decorative)
|
||||
- [ ] Form label associati correttamente
|
||||
- [ ] Focus states visibili
|
||||
- [ ] Skip link per keyboard navigation
|
||||
- [ ] Meta title + description unici per pagina
|
||||
- [ ] Canonical URL impostata
|
||||
- [ ] Open Graph tags per social sharing
|
||||
|
||||
---
|
||||
|
||||
_References per agency-web-developer skill_
|
||||
730
agency-web-developer/references/js_interactivity.md
Normal file
730
agency-web-developer/references/js_interactivity.md
Normal file
|
|
@ -0,0 +1,730 @@
|
|||
# JavaScript Interactivity — jQuery + GSAP
|
||||
|
||||
Guida per aggiungere interattività e animazioni fluide ai siti web.
|
||||
|
||||
---
|
||||
|
||||
## Setup Librerie
|
||||
|
||||
### CDN Links
|
||||
|
||||
```html
|
||||
<head>
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
|
||||
<!-- GSAP -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
|
||||
<!-- GSAP ScrollTrigger (opzionale, per scroll animations) -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Main JS -->
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## jQuery Patterns
|
||||
|
||||
### Document Ready
|
||||
|
||||
```javascript
|
||||
$(document).ready(function() {
|
||||
// Tutto il codice va qui dentro
|
||||
// o usa la shorthand:
|
||||
});
|
||||
|
||||
// Shorthand (equivalente)
|
||||
$(function() {
|
||||
// Codice qui
|
||||
});
|
||||
```
|
||||
|
||||
### Mobile Menu Toggle
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
const $menuToggle = $('.mobile-menu-toggle');
|
||||
const $navMenu = $('.nav-menu');
|
||||
const $body = $('body');
|
||||
|
||||
$menuToggle.on('click', function() {
|
||||
$navMenu.toggleClass('is-open');
|
||||
$menuToggle.toggleClass('is-active');
|
||||
$body.toggleClass('menu-open');
|
||||
|
||||
// Update ARIA
|
||||
const expanded = $menuToggle.attr('aria-expanded') === 'true';
|
||||
$menuToggle.attr('aria-expanded', !expanded);
|
||||
});
|
||||
|
||||
// Close menu on link click (mobile)
|
||||
$navMenu.find('a').on('click', function() {
|
||||
if ($(window).width() < 768) {
|
||||
$navMenu.removeClass('is-open');
|
||||
$menuToggle.removeClass('is-active');
|
||||
$body.removeClass('menu-open');
|
||||
$menuToggle.attr('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
// Close menu on outside click
|
||||
$(document).on('click', function(e) {
|
||||
if (!$(e.target).closest('.nav-menu, .mobile-menu-toggle').length) {
|
||||
$navMenu.removeClass('is-open');
|
||||
$menuToggle.removeClass('is-active');
|
||||
$body.removeClass('menu-open');
|
||||
$menuToggle.attr('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Smooth Scroll
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
$('a[href^="#"]').on('click', function(e) {
|
||||
const targetId = this.getAttribute('href');
|
||||
|
||||
// Ignora link vuoti o non-anchor
|
||||
if (targetId === '#' || !targetId.startsWith('#')) return;
|
||||
|
||||
const $target = $(targetId);
|
||||
|
||||
if ($target.length) {
|
||||
e.preventDefault();
|
||||
|
||||
const offsetTop = $target.offset().top;
|
||||
const headerHeight = $('header').outerHeight() || 0;
|
||||
const scrollPosition = offsetTop - headerHeight - 20;
|
||||
|
||||
$('html, body').stop().animate({
|
||||
scrollTop: scrollPosition
|
||||
}, 800, 'easeInOutQuad');
|
||||
|
||||
// Update URL without jumping
|
||||
history.pushState(null, null, targetId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Easing function (se non usi jQuery easing plugin)
|
||||
$.easing.easeInOutQuad = function(x, t, b, c, d) {
|
||||
t /= d / 2;
|
||||
if (t < 1) return c / 2 * t * t + b;
|
||||
t--;
|
||||
return -c / 2 * (t * (t - 2) - 1) + b;
|
||||
};
|
||||
```
|
||||
|
||||
### FAQ Accordion
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
const $faqQuestions = $('.faq-question');
|
||||
|
||||
$faqQuestions.on('click', function() {
|
||||
const $question = $(this);
|
||||
const $answer = $question.next('.faq-answer');
|
||||
const $item = $question.closest('.faq-item');
|
||||
const isActive = $question.hasClass('is-active');
|
||||
|
||||
// Close all other items (accordion style)
|
||||
$faqQuestions.not(this).removeClass('is-active');
|
||||
$('.faq-answer').not($answer).slideUp(300);
|
||||
$('.faq-item').not($item).removeClass('is-active');
|
||||
|
||||
// Toggle current item
|
||||
$question.toggleClass('is-active');
|
||||
$answer.slideToggle(300);
|
||||
$item.toggleClass('is-active');
|
||||
|
||||
// Update ARIA
|
||||
const expanded = $question.attr('aria-expanded') === 'true';
|
||||
$question.attr('aria-expanded', !expanded);
|
||||
});
|
||||
|
||||
// Keyboard accessibility
|
||||
$faqQuestions.on('keydown', function(e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
$(this).click();
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Form Validation
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
const $form = $('.contact-form');
|
||||
const $submitBtn = $form.find('button[type="submit"]');
|
||||
|
||||
$form.on('submit', function(e) {
|
||||
let isValid = true;
|
||||
|
||||
// Validate required fields
|
||||
$form.find('[required]').each(function() {
|
||||
const $field = $(this);
|
||||
const value = $field.val().trim();
|
||||
const $error = $field.siblings('.form-error');
|
||||
|
||||
if (!value) {
|
||||
isValid = false;
|
||||
$field.addClass('is-invalid');
|
||||
if ($error.length) {
|
||||
$error.text('Questo campo è obbligatorio').show();
|
||||
} else {
|
||||
$field.after('<span class="form-error">Questo campo è obbligatorio</span>');
|
||||
}
|
||||
} else {
|
||||
$field.removeClass('is-invalid');
|
||||
$error.hide();
|
||||
|
||||
// Email validation
|
||||
if ($field.attr('type') === 'email') {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(value)) {
|
||||
isValid = false;
|
||||
$field.addClass('is-invalid');
|
||||
if ($error.length) {
|
||||
$error.text('Inserisci un\'email valida').show();
|
||||
} else {
|
||||
$field.after('<span class="form-error">Inserisci un\'email valida</span>');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
e.preventDefault();
|
||||
// Scroll to first error
|
||||
const $firstError = $form.find('.is-invalid').first();
|
||||
if ($firstError.length) {
|
||||
$('html, body').animate({
|
||||
scrollTop: $firstError.offset().top - 100
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear error on input
|
||||
$form.find('input, textarea').on('input', function() {
|
||||
$(this).removeClass('is-invalid');
|
||||
$(this).siblings('.form-error').hide();
|
||||
});
|
||||
|
||||
// Form submission success (AJAX example with Formspree)
|
||||
$form.on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$submitBtn.prop('disabled', true).text('Invio in corso...');
|
||||
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
method: 'POST',
|
||||
data: $form.serialize(),
|
||||
headers: { 'Accept': 'application/json' }
|
||||
})
|
||||
.done(function(response) {
|
||||
$form.trigger('reset');
|
||||
$submitBtn.text('Messaggio Inviato!');
|
||||
|
||||
// Show success message
|
||||
$form.after('<div class="form-success">Grazie! Ti contatteremo presto.</div>');
|
||||
|
||||
setTimeout(function() {
|
||||
$submitBtn.prop('disabled', false).text('Invia Messaggio');
|
||||
$('.form-success').fadeOut();
|
||||
}, 3000);
|
||||
})
|
||||
.fail(function() {
|
||||
$submitBtn.prop('disabled', false).text('Riprova');
|
||||
$form.after('<div class="form-error">Errore nell\'invio. Riprova più tardi.</div>');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Back to Top Button
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
const $backToTop = $('.back-to-top');
|
||||
|
||||
// Show/hide on scroll
|
||||
$(window).on('scroll', function() {
|
||||
if ($(window).scrollTop() > 300) {
|
||||
$backToTop.addClass('is-visible');
|
||||
} else {
|
||||
$backToTop.removeClass('is-visible');
|
||||
}
|
||||
});
|
||||
|
||||
// Smooth scroll to top
|
||||
$backToTop.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$('html, body').animate({
|
||||
scrollTop: 0
|
||||
}, 800);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- HTML for back to top -->
|
||||
<a href="#top" class="back-to-top" aria-label="Torna su">
|
||||
<svg><!-- icon --></svg>
|
||||
</a>
|
||||
```
|
||||
|
||||
```css
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
right: 32px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.back-to-top.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.back-to-top:hover {
|
||||
background: var(--color-primary-dark);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
```
|
||||
|
||||
### Image Lazy Loading
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
const $lazyImages = $('img[loading="lazy"]');
|
||||
|
||||
// Native lazy loading support check
|
||||
if ('loading' in HTMLImageElement.prototype) {
|
||||
// Browser supports native lazy loading
|
||||
$lazyImages.each(function() {
|
||||
const src = $(this).data('src');
|
||||
if (src) {
|
||||
$(this).attr('src', src);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback for browsers without support
|
||||
const imageObserver = new IntersectionObserver(function(entries, observer) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
const $img = $(entry.target);
|
||||
const src = $img.data('src');
|
||||
if (src) {
|
||||
$img.attr('src', src);
|
||||
$img.on('load', function() {
|
||||
$img.addClass('is-loaded');
|
||||
});
|
||||
}
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$lazyImages.each(function() {
|
||||
imageObserver.observe(this);
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GSAP Animations
|
||||
|
||||
### Basic Animations
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
// Fade in element
|
||||
gsap.from('.hero h1', {
|
||||
duration: 1,
|
||||
opacity: 0,
|
||||
y: 30,
|
||||
ease: 'power3.out'
|
||||
});
|
||||
|
||||
// Stagger animation
|
||||
gsap.from('.service-card', {
|
||||
duration: 0.8,
|
||||
opacity: 0,
|
||||
y: 40,
|
||||
stagger: 0.15,
|
||||
ease: 'power3.out',
|
||||
delay: 0.3
|
||||
});
|
||||
|
||||
// Multiple properties
|
||||
gsap.from('.hero .btn', {
|
||||
duration: 1,
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
scale: 0.95,
|
||||
delay: 0.5,
|
||||
ease: 'back.out(1.7)'
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Timeline
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
const tl = gsap.timeline({ defaults: { ease: 'power3.out' } });
|
||||
|
||||
tl.from('.hero h1', {
|
||||
duration: 1,
|
||||
opacity: 0,
|
||||
y: 50
|
||||
})
|
||||
.from('.hero-sub', {
|
||||
duration: 0.8,
|
||||
opacity: 0,
|
||||
y: 30
|
||||
}, '-=0.5')
|
||||
.from('.hero .btn', {
|
||||
duration: 0.6,
|
||||
opacity: 0,
|
||||
y: 20
|
||||
}, '-=0.4')
|
||||
.from('.logo-wall', {
|
||||
duration: 1,
|
||||
opacity: 0
|
||||
}, '-=0.3');
|
||||
});
|
||||
```
|
||||
|
||||
### ScrollTrigger Animations
|
||||
|
||||
```javascript
|
||||
// Register ScrollTrigger
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
$(function() {
|
||||
// Fade in on scroll
|
||||
gsap.utils.toArray('.fade-on-scroll').forEach(function(elem) {
|
||||
gsap.to(elem, {
|
||||
scrollTrigger: {
|
||||
trigger: elem,
|
||||
start: 'top 85%',
|
||||
toggleActions: 'play none none none'
|
||||
},
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: 1,
|
||||
ease: 'power3.out'
|
||||
});
|
||||
});
|
||||
|
||||
// Slide in from left
|
||||
gsap.utils.toArray('.slide-in-left').forEach(function(elem) {
|
||||
gsap.from(elem, {
|
||||
scrollTrigger: {
|
||||
trigger: elem,
|
||||
start: 'top 80%',
|
||||
toggleActions: 'play none none none'
|
||||
},
|
||||
opacity: 0,
|
||||
x: -50,
|
||||
duration: 1,
|
||||
ease: 'power3.out'
|
||||
});
|
||||
});
|
||||
|
||||
// Slide in from right
|
||||
gsap.utils.toArray('.slide-in-right').forEach(function(elem) {
|
||||
gsap.from(elem, {
|
||||
scrollTrigger: {
|
||||
trigger: elem,
|
||||
start: 'top 80%',
|
||||
toggleActions: 'play none none none'
|
||||
},
|
||||
opacity: 0,
|
||||
x: 50,
|
||||
duration: 1,
|
||||
ease: 'power3.out'
|
||||
});
|
||||
});
|
||||
|
||||
// Scale up
|
||||
gsap.utils.toArray('.scale-up').forEach(function(elem) {
|
||||
gsap.from(elem, {
|
||||
scrollTrigger: {
|
||||
trigger: elem,
|
||||
start: 'top 85%',
|
||||
toggleActions: 'play none none none'
|
||||
},
|
||||
opacity: 0,
|
||||
scale: 0.8,
|
||||
duration: 1,
|
||||
ease: 'back.out(1.7)'
|
||||
});
|
||||
});
|
||||
|
||||
// Parallax effect
|
||||
gsap.to('.parallax-bg', {
|
||||
scrollTrigger: {
|
||||
trigger: '.hero',
|
||||
start: 'top top',
|
||||
end: 'bottom top',
|
||||
scrub: true
|
||||
},
|
||||
y: 100,
|
||||
ease: 'none'
|
||||
});
|
||||
|
||||
// Pin section (sticky)
|
||||
gsap.to('.sticky-section', {
|
||||
scrollTrigger: {
|
||||
trigger: '.sticky-section',
|
||||
start: 'top top',
|
||||
end: '+=100%',
|
||||
pin: true,
|
||||
scrub: true
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Hover Animations
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
// Card hover with GSAP
|
||||
const $cards = $('.service-card');
|
||||
|
||||
$cards.each(function() {
|
||||
const $card = $(this);
|
||||
|
||||
$card.on('mouseenter', function() {
|
||||
gsap.to($card, {
|
||||
duration: 0.3,
|
||||
y: -8,
|
||||
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.15)',
|
||||
ease: 'power2.out'
|
||||
});
|
||||
});
|
||||
|
||||
$card.on('mouseleave', function() {
|
||||
gsap.to($card, {
|
||||
duration: 0.3,
|
||||
y: 0,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
ease: 'power2.out'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Button hover
|
||||
const $buttons = $('.btn');
|
||||
|
||||
$buttons.each(function() {
|
||||
const $btn = $(this);
|
||||
|
||||
$btn.on('mouseenter', function() {
|
||||
gsap.to($btn, {
|
||||
duration: 0.2,
|
||||
scale: 1.05,
|
||||
ease: 'power2.out'
|
||||
});
|
||||
});
|
||||
|
||||
$btn.on('mouseleave', function() {
|
||||
gsap.to($btn, {
|
||||
duration: 0.2,
|
||||
scale: 1,
|
||||
ease: 'power2.out'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Complex Animation Sequence
|
||||
|
||||
```javascript
|
||||
$(function() {
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: '.feature-section',
|
||||
start: 'top 70%',
|
||||
toggleActions: 'play none none none'
|
||||
}
|
||||
});
|
||||
|
||||
tl.from('.feature-section h2', {
|
||||
duration: 0.8,
|
||||
opacity: 0,
|
||||
y: 30,
|
||||
ease: 'power3.out'
|
||||
})
|
||||
.from('.feature-section p', {
|
||||
duration: 0.6,
|
||||
opacity: 0,
|
||||
y: 20
|
||||
}, '-=0.4')
|
||||
.from('.feature-grid .feature-card', {
|
||||
duration: 0.6,
|
||||
opacity: 0,
|
||||
y: 40,
|
||||
stagger: 0.1,
|
||||
ease: 'back.out(1.7)'
|
||||
}, '-=0.3')
|
||||
.from('.feature-section .btn', {
|
||||
duration: 0.5,
|
||||
opacity: 0,
|
||||
scale: 0.9
|
||||
}, '-=0.3');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Best Practices
|
||||
|
||||
### Reduce Motion
|
||||
|
||||
```javascript
|
||||
// Check for reduced motion preference
|
||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
// Disable GSAP animations
|
||||
gsap.globalTimeline.timeScale(0);
|
||||
|
||||
// Or set duration to 0
|
||||
gsap.defaults({ duration: 0 });
|
||||
}
|
||||
```
|
||||
|
||||
### Debounce Scroll Events
|
||||
|
||||
```javascript
|
||||
// Debounce function
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Usage
|
||||
$(window).on('scroll', debounce(function() {
|
||||
// Scroll logic here
|
||||
}, 100));
|
||||
```
|
||||
|
||||
### Use CSS for Simple Animations
|
||||
|
||||
```css
|
||||
/* Prefer CSS transitions for simple hover effects */
|
||||
.btn {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Use GSAP only for complex animations */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Respect User Preferences
|
||||
|
||||
```javascript
|
||||
// Check reduced motion
|
||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
if (!prefersReducedMotion) {
|
||||
// Initialize animations
|
||||
initAnimations();
|
||||
}
|
||||
|
||||
function initAnimations() {
|
||||
// GSAP animations here
|
||||
}
|
||||
```
|
||||
|
||||
### Focus Management
|
||||
|
||||
```javascript
|
||||
// Trap focus in mobile menu
|
||||
function trapFocus($element) {
|
||||
const $focusable = $element.find('a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])');
|
||||
const $first = $focusable.first();
|
||||
const $last = $focusable.last();
|
||||
|
||||
$element.on('keydown', function(e) {
|
||||
if (e.key === 'Tab') {
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === $first[0]) {
|
||||
e.preventDefault();
|
||||
$last.focus();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === $last[0]) {
|
||||
e.preventDefault();
|
||||
$first.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist Interattività
|
||||
|
||||
- [ ] Mobile menu toggle funzionante
|
||||
- [ ] Smooth scroll per anchor links
|
||||
- [ ] FAQ accordion accessibile
|
||||
- [ ] Form validation con feedback
|
||||
- [ ] Back to top button (se pagine lunghe)
|
||||
- [ ] Lazy loading immagini
|
||||
- [ ] GSAP animations (hero, scroll)
|
||||
- [ ] Hover effects su cards/buttons
|
||||
- [ ] Respect reduced motion preference
|
||||
- [ ] Keyboard navigation testata
|
||||
- [ ] No console errors
|
||||
- [ ] Performance ottimizzata (debounce, CSS where possible)
|
||||
|
||||
---
|
||||
|
||||
_References per agency-web-developer skill_
|
||||
Loading…
Add table
Add a link
Reference in a new issue