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:
AgentePotente 2026-03-09 14:21:32 +01:00
parent 6ac766172c
commit b79b28034a
5 changed files with 2153 additions and 5 deletions

View file

@ -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)

View 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_

View 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_

View 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>&copy; 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_

View 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_