<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FantaMasterChef 2025</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
</style>
</head>
<body>
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect } = React;
// Icone SVG
const Edit2Icon = () => (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
</svg>
);
const SaveIcon = () => (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
);
const XIcon = () => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
);
const TrophyIcon = ({ size = 32 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"></path>
<path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"></path>
<path d="M4 22h16"></path>
<path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"></path>
<path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"></path>
<path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"></path>
</svg>
);
const CrownIcon = ({ size = 24 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="m2 4 3 12h14l3-12-6 7-4-7-4 7-6-7zm3 16h14"></path>
</svg>
);
function FantacalcioTable() {
const [players] = useState([
'AISHE', 'FABIANO', 'LUCA', 'STEFANO', 'PAOLO', 'JAMES'
]);
const [data, setData] = useState(() => {
const saved = localStorage.getItem('fantacalcio-data');
return saved ? JSON.parse(saved) : {
'TOT 1-2': ['', '', '', '', '', ''],
'TOT 3-4': ['', '', '', '', '', ''],
'TOT 5-6': ['', '', '', '', '', ''],
'TOT 7-8': ['', '', '', '', '', ''],
'TOT 9-10': ['', '', '', '', '', ''],
'TOT 11-12': ['', '', '', '', '', ''],
'TOT 13-14': ['', '', '', '', '', ''],
'TOT 15-16': ['', '', '', '', '', ''],
'TOT 17-18': ['', '', '', '', '', ''],
'TOT 19-20': ['', '', '', '', '', ''],
'TOT 21-22': ['', '', '', '', '', ''],
'TOT 23-24': ['', '', '', '', '', ''],
'TOTALE FINALE': [0, 0, 0, 0, 0, 0]
};
});
const [subtitles, setSubtitles] = useState(() => {
const saved = localStorage.getItem('fantacalcio-subtitles');
return saved ? JSON.parse(saved) : {
row1: ['', '', '', '', '', ''],
row2: ['', '', '', '', '', '']
};
});
const [editMode, setEditMode] = useState(false);
const [editingCell, setEditingCell] = useState(null);
const [tempValue, setTempValue] = useState('');
const rows = Object.keys(data);
const totRows = rows.filter(row => row.startsWith('TOT') && row !== 'TOTALE FINALE');
useEffect(() => {
const newTotals = [0, 0, 0, 0, 0, 0];
totRows.forEach(rowKey => {
data[rowKey].forEach((value, index) => {
if (value !== null && value !== '') {
const numValue = parseFloat(value);
if (!isNaN(numValue)) {
newTotals[index] += numValue;
}
}
});
});
setData(prevData => ({
...prevData,
'TOTALE FINALE': newTotals
}));
}, [JSON.stringify(totRows.map(row => data[row]))]);
useEffect(() => {
localStorage.setItem('fantacalcio-data', JSON.stringify(data));
}, [data]);
useEffect(() => {
localStorage.setItem('fantacalcio-subtitles', JSON.stringify(subtitles));
}, [subtitles]);
const getWinner = () => {
const totals = data['TOTALE FINALE'];
const maxScore = Math.max(...totals);
if (maxScore === 0) return null;
const winnerIndex = totals.indexOf(maxScore);
return { index: winnerIndex, name: players[winnerIndex], score: maxScore };
};
const winner = getWinner();
const handleCellClick = (rowKey, playerIndex, type = 'data') => {
if (editMode) {
if (rowKey === 'TOTALE FINALE') return;
setEditingCell({ rowKey, playerIndex, type });
if (type === 'data') {
setTempValue(data[rowKey][playerIndex] || '');
} else {
setTempValue(subtitles[rowKey][playerIndex] || '');
}
}
};
const handleSaveCell = () => {
if (editingCell) {
if (editingCell.type === 'data') {
const newData = { ...data };
newData[editingCell.rowKey][editingCell.playerIndex] = tempValue || '';
setData(newData);
} else {
const newSubtitles = { ...subtitles };
newSubtitles[editingCell.rowKey][editingCell.playerIndex] = tempValue || '';
setSubtitles(newSubtitles);
}
setEditingCell(null);
setTempValue('');
}
};
const handleCancelEdit = () => {
setEditingCell(null);
setTempValue('');
};
const getCellColor = (rowKey) => {
if (rowKey === 'TOTALE FINALE') {
return 'bg-gradient-to-r from-rose-200 to-pink-200';
}
return 'bg-gradient-to-r from-amber-100 to-yellow-100';
};
const getHeaderColor = (index) => {
const colors = [
'from-purple-500 to-indigo-500',
'from-blue-500 to-cyan-500',
'from-teal-500 to-emerald-500',
'from-orange-500 to-amber-500',
'from-rose-500 to-pink-500',
'from-fuchsia-500 to-purple-500'
];
return colors[index % colors.length];
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 p-8">
<div className="max-w-7xl mx-auto">
<div className="bg-white/95 backdrop-blur-sm rounded-2xl shadow-2xl p-8">
<div className="flex flex-col md:flex-row justify-between items-center mb-8 gap-4">
<div className="flex items-center gap-4">
<div className="relative">
<div className="w-20 h-20 bg-gradient-to-br from-red-600 to-red-800 rounded-full flex items-center justify-center shadow-xl transform hover:scale-110 transition-transform">
<svg viewBox="0 0 100 100" className="w-14 h-14">
<path d="M50 20 L55 35 L70 35 L58 45 L63 60 L50 50 L37 60 L42 45 L30 35 L45 35 Z" fill="white"/>
<circle cx="50" cy="70" r="3" fill="white"/>
<circle cx="40" cy="72" r="2" fill="white"/>
<circle cx="60" cy="72" r="2" fill="white"/>
</svg>
</div>
</div>
<div>
<h1 className="text-5xl font-black bg-gradient-to-r from-red-600 via-orange-500 to-yellow-500 bg-clip-text text-transparent drop-shadow-lg">
FANTAMASTERCHEF
</h1>
<p className="text-2xl font-bold text-gray-700 mt-1">2025</p>
<p className="text-gray-500 text-sm mt-1">Classifica Ufficiale • Stagione 2025</p>
</div>
</div>
<button
onClick={() => {
setEditMode(!editMode);
setEditingCell(null);
}}
className={`flex items-center gap-2 px-6 py-3 rounded-xl font-semibold transition-all transform hover:scale-105 shadow-lg ${
editMode
? 'bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white'
: 'bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 text-white'
}`}
>
{editMode ? (
<>
<SaveIcon />
Salva e Chiudi
</>
) : (
<>
<Edit2Icon />
Modifica Tabella
</>
)}
</button>
</div>
<div className="overflow-x-auto rounded-xl shadow-inner">
<table className="w-full border-collapse">
<thead>
<tr>
<th className="bg-gradient-to-r from-slate-700 to-slate-600 text-white p-4 text-left font-bold border border-slate-500">
Punteggio
</th>
{players.map((player, idx) => (
<th
key={idx}
className={`bg-gradient-to-r ${getHeaderColor(idx)} text-white border border-white/20 min-w-[140px]`}
>
<div className="p-3">
<div className="font-bold text-lg">{player}</div>
<div
className={`text-xs mt-1 italic ${editMode ? 'cursor-pointer hover:bg-white/20 rounded p-1' : ''}`}
onClick={() => handleCellClick('row1', idx, 'subtitle')}
>
{editingCell?.rowKey === 'row1' && editingCell?.playerIndex === idx && editingCell?.type === 'subtitle' ? (
<input
type="text"
value={tempValue}
onChange={(e) => setTempValue(e.target.value)}
className="w-full px-2 py-1 text-gray-800 rounded"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') handleSaveCell();
if (e.key === 'Escape') handleCancelEdit();
}}
/>
) : (
subtitles.row1[idx] || (editMode ? 'Clicca per modificare' : '')
)}
</div>
<div
className={`text-xs italic ${editMode ? 'cursor-pointer hover:bg-white/20 rounded p-1' : ''}`}
onClick={() => handleCellClick('row2', idx, 'subtitle')}
>
{editingCell?.rowKey === 'row2' && editingCell?.playerIndex === idx && editingCell?.type === 'subtitle' ? (
<input
type="text"
value={tempValue}
onChange={(e) => setTempValue(e.target.value)}
className="w-full px-2 py-1 text-gray-800 rounded"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') handleSaveCell();
if (e.key === 'Escape') handleCancelEdit();
}}
/>
) : (
subtitles.row2[idx] || (editMode ? 'Clicca per modificare' : '')
)}
</div>
</div>
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((rowKey) => (
<tr key={rowKey} className="hover:bg-slate-50 transition-colors">
<td className={`${getCellColor(rowKey)} p-4 font-bold text-gray-800 border border-slate-300`}>
{rowKey}
</td>
{players.map((_, playerIndex) => {
const isEditing = editingCell?.rowKey === rowKey && editingCell?.playerIndex === playerIndex && editingCell?.type === 'data';
const cellValue = data[rowKey][playerIndex];
const isTotal = rowKey === 'TOTALE FINALE';
return (
<td
key={playerIndex}
className={`${getCellColor(rowKey)} p-4 text-center font-semibold text-gray-700 border border-slate-300 ${
editMode && !isEditing && !isTotal ? 'cursor-pointer hover:bg-blue-100 hover:shadow-inner' : ''
} ${isTotal ? 'text-xl font-bold' : ''}`}
onClick={() => handleCellClick(rowKey, playerIndex, 'data')}
>
{isEditing ? (
<div className="flex items-center justify-center gap-2">
<input
type="text"
value={tempValue}
onChange={(e) => setTempValue(e.target.value)}
className="w-full px-3 py-2 border-2 border-blue-500 rounded-lg text-center font-semibold focus:outline-none focus:ring-2 focus:ring-blue-400"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') handleSaveCell();
if (e.key === 'Escape') handleCancelEdit();
}}
/>
<button
onClick={handleSaveCell}
className="p-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors"
>
<SaveIcon />
</button>
<button
onClick={handleCancelEdit}
className="p-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
>
<XIcon />
</button>
</div>
) : (
<span className={isTotal ? 'text-2xl' : 'text-lg'}>
{cellValue !== null && cellValue !== '' ? cellValue : '—'}
</span>
)}
</td>
);
})}
</tr>
))}
<tr className="bg-gradient-to-r from-yellow-300 via-yellow-400 to-yellow-500">
<td className="p-4 font-bold text-gray-800 border border-yellow-600">
<div className="flex items-center gap-2">
<CrownIcon />
<span>VINCITORE</span>
</div>
</td>
{players.map((player, idx) => (
<td
key={idx}
className={`p-4 text-center font-bold border border-yellow-600 ${
winner && winner.index === idx ? 'bg-gradient-to-r from-yellow-200 to-amber-300' : ''
}`}
>
{winner && winner.index === idx ? (
<div className="flex flex-col items-center justify-center">
<div className="animate-bounce">
<TrophyIcon />
</div>
<span className="text-xl font-black text-yellow-800 mt-1">{player}</span>
<span className="text-sm text-yellow-700">({winner.score} punti)</span>
</div>
) : (
<span className="text-gray-400">—</span>
)}
</td>
))}
</tr>
</tbody>
</table>
</div>
<div className="mt-6 p-4 bg-gradient-to-r from-blue-50 to-purple-50 rounded-xl border border-blue-200">
{editMode ? (
<div className="flex items-center gap-2 text-blue-700">
<Edit2Icon />
<p className="font-medium">
Modalità modifica attiva: clicca su celle e sottotitoli per modificarli. I dati vengono salvati automaticamente nel browser.
</p>
</div>
) : (
<div className="flex items-center gap-2 text-purple-700">
<TrophyIcon size={20} />
<p className="font-medium">
Visualizzazione in sola lettura. Clicca su "Modifica Tabella" per aggiornare i punteggi.
</p>
</div>
)}
</div>
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<FantacalcioTable />);
</script>
</body>
</html>