Visual element updates.

Icons for site and cleanup.
Update text moved to toasts.
Localstorage added.
This commit is contained in:
2025-10-02 03:15:56 +00:00
parent 5b7e133d76
commit b9e0f470c5
11 changed files with 4781 additions and 106 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -13,8 +13,11 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "OSRS Grid Master Tracker",
description: "A tracker for OSRS Grid Master",
icons: {
icon: "/favicon.ico",
},
};
export default function RootLayout({

View File

@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import CharacterLookup from '@/components/CharacterLookup';
import PassphraseLogin from '@/components/PassphraseLogin';
import GridMaster from '@/components/GridMaster';
import Toast from '@/components/Toast';
import { UserProgress, OSRSHiscoreStats, GridTask } from '@/types/osrs';
import { defaultGridTasks } from '@/data/grid-tasks';
import { saveProgress, loadProgress } from '@/lib/progress-api';
@@ -14,6 +15,15 @@ export default function Home() {
const [currentPlayer, setCurrentPlayer] = useState<string>('');
const [playerStats, setPlayerStats] = useState<OSRSHiscoreStats | null>(null);
const [saving, setSaving] = useState(false);
const [toastMessage, setToastMessage] = useState<string | null>(null);
// Load passphrase from localStorage on mount
useEffect(() => {
const savedPassphrase = localStorage.getItem('osrs-grid-passphrase');
if (savedPassphrase) {
setCurrentPassphrase(savedPassphrase);
}
}, []);
useEffect(() => {
if (currentPassphrase) {
@@ -46,8 +56,10 @@ export default function Home() {
setUserProgress(null);
setCurrentPlayer('');
setPlayerStats(null);
localStorage.removeItem('osrs-grid-passphrase');
} else {
setCurrentPassphrase(passphrase);
localStorage.setItem('osrs-grid-passphrase', passphrase);
}
};
@@ -69,8 +81,16 @@ export default function Home() {
if (!currentPassphrase) return;
setSaving(true);
await saveProgress(currentPassphrase, progress);
setSaving(false);
setToastMessage('Saving progress...');
try {
await saveProgress(currentPassphrase, progress);
setToastMessage('Progress saved!');
} catch (error) {
setToastMessage('Failed to save progress');
} finally {
setSaving(false);
}
};
const handleTaskToggle = (taskId: string) => {
@@ -134,11 +154,6 @@ export default function Home() {
<p className="text-lg text-gray-600">
Track your progress for the Grid Master event (Oct 15 - Nov 12, 2025)
</p>
{saving && (
<div className="mt-2 text-sm text-blue-600">
Saving progress...
</div>
)}
</div>
<div className="space-y-8">
@@ -176,6 +191,15 @@ export default function Home() {
</>
)}
</div>
{/* Toast notification */}
{toastMessage && (
<Toast
message={toastMessage}
type={toastMessage.includes('Failed') ? 'error' : toastMessage.includes('saved') ? 'success' : 'info'}
onClose={() => setToastMessage(null)}
/>
)}
</div>
</div>
);

View File

@@ -74,9 +74,11 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
const completedTasks = gridTasks.filter(task => task.completed).length;
return (
<div className="bg-white rounded-lg shadow-md p-6">
<div className="bg-amber-100 rounded-lg shadow-md p-6 border-4 border-amber-800" style={{
background: 'linear-gradient(135deg, #D2B48C 0%, #DEB887 50%, #F5DEB3 100%)'
}}>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-2 text-gray-800">Grid Master Progress</h2>
<h2 className="text-2xl font-bold mb-2 text-amber-900 text-center">Grid Master</h2>
<div className="flex flex-wrap gap-4 text-sm">
<div className="bg-blue-50 px-3 py-1 rounded-lg">
<span className="font-semibold text-blue-800">Total Points:</span>
@@ -84,7 +86,7 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
</div>
<div className="bg-green-50 px-3 py-1 rounded-lg">
<span className="font-semibold text-green-800">Completed Tasks:</span>
<span className="ml-1 text-green-900">{completedTasks}/49</span>
<span className="ml-1 text-green-900">{completedTasks}/{gridTasks.length}</span>
</div>
<div className="bg-purple-50 px-3 py-1 rounded-lg">
<span className="font-semibold text-purple-800">Complete Rows:</span>
@@ -97,67 +99,122 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
</div>
</div>
<div className="grid grid-cols-7 gap-2 mb-4">
{Array.from({ length: 7 }, (_, row) =>
Array.from({ length: 7 }, (_, col) => {
const task = gridTasks.find(t => t.row === row && t.col === col);
if (!task) return null;
const isRowComplete = completedRows.includes(row);
const isColComplete = completedCols.includes(col);
const hasBonus = isRowComplete || isColComplete;
<div className="flex gap-2 mb-4">
{/* Side nodes column */}
<div className="flex flex-col gap-2">
{Array.from({ length: 7 }, (_, row) => {
const task = gridTasks.find(t => t.row === row && t.col === -1 && t.type === 'side');
if (!task) return <div key={`side-${row}`} className="w-16 h-16"></div>;
return (
<div
key={task.id}
onClick={() => handleTaskClick(task.id)}
className={`
relative p-3 border-2 rounded-lg transition-all duration-200
${task.completed ? 'bg-green-50 border-green-400' : getDifficultyColor(task.difficulty)}
relative p-2 border-2 rounded-lg transition-all duration-200
${task.completed ? 'bg-green-50 border-green-400' : 'bg-gray-800 border-gray-600'}
${readonly ? 'cursor-default' : 'cursor-pointer hover:shadow-md'}
${hasBonus ? 'ring-2 ring-yellow-400 ring-opacity-60' : ''}
min-h-[120px] flex flex-col justify-between
w-16 h-16 flex items-center justify-center
`}
>
{task.completed && (
<div className="absolute top-1 right-1 text-green-600">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
{task.completed ? (
<div className="text-green-600">
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
) : (
<div className="text-gray-400 text-2xl">?</div>
)}
{hasBonus && (
<div className="absolute top-1 left-1 text-yellow-500">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
</div>
)}
<div className="text-xs font-semibold mb-1 truncate" title={task.title}>
{task.title}
</div>
<div className="text-xs text-gray-600 mb-2 flex-1 leading-tight" title={task.description}>
{task.description}
</div>
<div className="flex justify-between items-center text-xs">
<span className={`px-2 py-1 rounded text-xs font-medium ${getDifficultyColor(task.difficulty)}`}>
{task.difficulty}
</span>
<span className="font-bold text-gray-700">
{task.points}pt{hasBonus ? ' +bonus' : ''}
</span>
</div>
</div>
);
})
)}
})}
</div>
{/* Main 7x7 grid */}
<div className="grid grid-cols-7 gap-2">
{Array.from({ length: 7 }, (_, row) =>
Array.from({ length: 7 }, (_, col) => {
const task = gridTasks.find(t => t.row === row && t.col === col && t.type === 'main');
if (!task) return null;
const isRowComplete = completedRows.includes(row);
const isColComplete = completedCols.includes(col);
const hasBonus = isRowComplete || isColComplete;
return (
<div
key={task.id}
onClick={() => handleTaskClick(task.id)}
className={`
relative p-2 border-2 rounded-lg transition-all duration-200
${task.completed ? 'bg-green-50 border-green-400' : 'bg-blue-900 border-blue-600'}
${readonly ? 'cursor-default' : 'cursor-pointer hover:shadow-md'}
${hasBonus ? 'ring-2 ring-yellow-400 ring-opacity-60' : ''}
w-16 h-16 flex flex-col justify-center items-center
`}
>
{task.completed ? (
<div className="text-green-600">
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
) : (
<img
src="/cell_tile.png"
alt="Bingo tile"
className="w-12 h-12 pixel-art"
style={{ imageRendering: 'pixelated' }}
/>
)}
{hasBonus && (
<div className="absolute top-1 left-1 text-yellow-500">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
</div>
)}
</div>
);
})
)}
</div>
</div>
<div className="mt-4 text-sm text-gray-600">
{/* Bottom nodes row */}
<div className="flex gap-2" style={{ marginLeft: '72px' }}>
{Array.from({ length: 7 }, (_, col) => {
const task = gridTasks.find(t => t.row === 7 && t.col === col && t.type === 'bottom');
if (!task) return <div key={`bottom-${col}`} className="w-16 h-16"></div>;
return (
<div
key={task.id}
onClick={() => handleTaskClick(task.id)}
className={`
relative p-2 border-2 rounded-lg transition-all duration-200
${task.completed ? 'bg-green-50 border-green-400' : 'bg-gray-800 border-gray-600'}
${readonly ? 'cursor-default' : 'cursor-pointer hover:shadow-md'}
w-16 h-16 flex items-center justify-center
`}
>
{task.completed ? (
<div className="text-green-600">
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
) : (
<div className="text-gray-400 text-2xl">?</div>
)}
</div>
);
})}
</div>
<div className="mt-4 text-sm text-amber-800">
<div className="flex flex-wrap gap-4 mb-2">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-green-100 border border-green-300 rounded"></div>
@@ -176,7 +233,7 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
<span>Expert (6-15 pts)</span>
</div>
</div>
<p className="text-xs text-gray-500">
<p className="text-xs text-amber-700">
Complete full rows or columns for bonus rewards!
Tasks with the symbol are part of a completed line.
</p>

View File

@@ -0,0 +1,55 @@
'use client';
import { useEffect, useState } from 'react';
interface ToastProps {
message: string;
type?: 'success' | 'error' | 'info';
duration?: number;
onClose?: () => void;
}
export default function Toast({ message, type = 'info', duration = 3000, onClose }: ToastProps) {
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(false);
setTimeout(() => onClose?.(), 300); // Wait for fade out animation
}, duration);
return () => clearTimeout(timer);
}, [duration, onClose]);
const getStyles = () => {
switch (type) {
case 'success':
return 'bg-green-500 text-white';
case 'error':
return 'bg-red-500 text-white';
default:
return 'bg-blue-500 text-white';
}
};
return (
<div
className={`fixed top-4 right-4 z-50 px-4 py-2 rounded-lg shadow-lg transition-all duration-300 ${
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2'
} ${getStyles()}`}
>
<div className="flex items-center gap-2">
<span>{message}</span>
<button
onClick={() => {
setIsVisible(false);
setTimeout(() => onClose?.(), 300);
}}
className="text-white hover:text-gray-200 ml-2"
>
×
</button>
</div>
</div>
);
}

View File

@@ -2,65 +2,83 @@ import { GridTask } from '@/types/osrs';
export const defaultGridTasks: GridTask[] = [
// Row 0
{ id: '0-0', title: 'First Steps', description: 'Complete Tutorial Island', difficulty: 'easy', points: 1, completed: false, row: 0, col: 0 },
{ id: '0-1', title: 'Combat Ready', description: 'Reach 10 Attack', difficulty: 'easy', points: 1, completed: false, row: 0, col: 1 },
{ id: '0-2', title: 'Magic Apprentice', description: 'Reach 10 Magic', difficulty: 'easy', points: 1, completed: false, row: 0, col: 2 },
{ id: '0-3', title: 'Archer Training', description: 'Reach 10 Ranged', difficulty: 'easy', points: 1, completed: false, row: 0, col: 3 },
{ id: '0-4', title: 'Prayer Novice', description: 'Reach 10 Prayer', difficulty: 'easy', points: 1, completed: false, row: 0, col: 4 },
{ id: '0-5', title: 'Crafting Start', description: 'Reach 10 Crafting', difficulty: 'easy', points: 1, completed: false, row: 0, col: 5 },
{ id: '0-6', title: 'Cook\'s Assistant', description: 'Complete Cook\'s Assistant quest', difficulty: 'easy', points: 2, completed: false, row: 0, col: 6 },
{ id: '0-0', title: 'First Steps', description: 'Complete Tutorial Island', difficulty: 'easy', points: 1, completed: false, row:0, col: 0, type: 'main' , type: 'main' },
{ id: '0-1', title: 'Combat Ready', description: 'Reach 10 Attack', difficulty: 'easy', points: 1, completed: false, row:0, col: 1, type: 'main' , type: 'main' },
{ id: '0-2', title: 'Magic Apprentice', description: 'Reach 10 Magic', difficulty: 'easy', points: 1, completed: false, row:0, col: 2, type: 'main' , type: 'main' },
{ id: '0-3', title: 'Archer Training', description: 'Reach 10 Ranged', difficulty: 'easy', points: 1, completed: false, row:0, col: 3, type: 'main' , type: 'main' },
{ id: '0-4', title: 'Prayer Novice', description: 'Reach 10 Prayer', difficulty: 'easy', points: 1, completed: false, row:0, col: 4, type: 'main' , type: 'main' },
{ id: '0-5', title: 'Crafting Start', description: 'Reach 10 Crafting', difficulty: 'easy', points: 1, completed: false, row:0, col: 5, type: 'main' , type: 'main' },
{ id: '0-6', title: 'Cook\'s Assistant', description: 'Complete Cook\'s Assistant quest', difficulty: 'easy', points: 2, completed: false, row:0, col: 6, type: 'main' , type: 'main' },
// Row 1
{ id: '1-0', title: 'Quest Points', description: 'Earn 10 Quest Points', difficulty: 'easy', points: 2, completed: false, row: 1, col: 0 },
{ id: '1-1', title: 'Combat Training', description: 'Reach 20 Attack', difficulty: 'medium', points: 2, completed: false, row: 1, col: 1 },
{ id: '1-2', title: 'Mage Training', description: 'Reach 20 Magic', difficulty: 'medium', points: 2, completed: false, row: 1, col: 2 },
{ id: '1-3', title: 'Archer Progress', description: 'Reach 20 Ranged', difficulty: 'medium', points: 2, completed: false, row: 1, col: 3 },
{ id: '1-4', title: 'Holy Training', description: 'Reach 20 Prayer', difficulty: 'medium', points: 2, completed: false, row: 1, col: 4 },
{ id: '1-5', title: 'Artisan Skills', description: 'Reach 20 Crafting', difficulty: 'medium', points: 2, completed: false, row: 1, col: 5 },
{ id: '1-6', title: 'Demon Slayer', description: 'Complete Demon Slayer quest', difficulty: 'medium', points: 3, completed: false, row: 1, col: 6 },
{ id: '1-0', title: 'Quest Points', description: 'Earn 10 Quest Points', difficulty: 'easy', points: 2, completed: false, row:1, col: 0, type: 'main' , type: 'main' },
{ id: '1-1', title: 'Combat Training', description: 'Reach 20 Attack', difficulty: 'medium', points: 2, completed: false, row:1, col: 1, type: 'main' , type: 'main' },
{ id: '1-2', title: 'Mage Training', description: 'Reach 20 Magic', difficulty: 'medium', points: 2, completed: false, row:1, col: 2, type: 'main' , type: 'main' },
{ id: '1-3', title: 'Archer Progress', description: 'Reach 20 Ranged', difficulty: 'medium', points: 2, completed: false, row:1, col: 3, type: 'main' , type: 'main' },
{ id: '1-4', title: 'Holy Training', description: 'Reach 20 Prayer', difficulty: 'medium', points: 2, completed: false, row:1, col: 4, type: 'main' , type: 'main' },
{ id: '1-5', title: 'Artisan Skills', description: 'Reach 20 Crafting', difficulty: 'medium', points: 2, completed: false, row:1, col: 5, type: 'main' , type: 'main' },
{ id: '1-6', title: 'Demon Slayer', description: 'Complete Demon Slayer quest', difficulty: 'medium', points: 3, completed: false, row:1, col: 6, type: 'main' , type: 'main' },
// Row 2
{ id: '2-0', title: 'Quest Master', description: 'Earn 25 Quest Points', difficulty: 'medium', points: 3, completed: false, row: 2, col: 0 },
{ id: '2-1', title: 'Warrior', description: 'Reach 30 Attack', difficulty: 'medium', points: 3, completed: false, row: 2, col: 1 },
{ id: '2-2', title: 'Wizard', description: 'Reach 30 Magic', difficulty: 'medium', points: 3, completed: false, row: 2, col: 2 },
{ id: '2-3', title: 'Marksman', description: 'Reach 30 Ranged', difficulty: 'medium', points: 3, completed: false, row: 2, col: 3 },
{ id: '2-4', title: 'Devout', description: 'Reach 30 Prayer', difficulty: 'medium', points: 3, completed: false, row: 2, col: 4 },
{ id: '2-5', title: 'Artisan', description: 'Reach 30 Crafting', difficulty: 'medium', points: 3, completed: false, row: 2, col: 5 },
{ id: '2-6', title: 'Vampire Slayer', description: 'Complete Vampire Slayer quest', difficulty: 'medium', points: 3, completed: false, row: 2, col: 6 },
{ id: '2-0', title: 'Quest Master', description: 'Earn 25 Quest Points', difficulty: 'medium', points: 3, completed: false, row:2, col: 0, type: 'main' , type: 'main' },
{ id: '2-1', title: 'Warrior', description: 'Reach 30 Attack', difficulty: 'medium', points: 3, completed: false, row:2, col: 1, type: 'main' , type: 'main' },
{ id: '2-2', title: 'Wizard', description: 'Reach 30 Magic', difficulty: 'medium', points: 3, completed: false, row:2, col: 2 , type: 'main' },
{ id: '2-3', title: 'Marksman', description: 'Reach 30 Ranged', difficulty: 'medium', points: 3, completed: false, row:2, col: 3 , type: 'main' },
{ id: '2-4', title: 'Devout', description: 'Reach 30 Prayer', difficulty: 'medium', points: 3, completed: false, row:2, col: 4 , type: 'main' },
{ id: '2-5', title: 'Artisan', description: 'Reach 30 Crafting', difficulty: 'medium', points: 3, completed: false, row:2, col: 5 , type: 'main' },
{ id: '2-6', title: 'Vampire Slayer', description: 'Complete Vampire Slayer quest', difficulty: 'medium', points: 3, completed: false, row:2, col: 6 , type: 'main' },
// Row 3
{ id: '3-0', title: 'Quest Veteran', description: 'Earn 50 Quest Points', difficulty: 'hard', points: 4, completed: false, row: 3, col: 0 },
{ id: '3-1', title: 'Elite Warrior', description: 'Reach 40 Attack', difficulty: 'hard', points: 4, completed: false, row: 3, col: 1 },
{ id: '3-2', title: 'Elite Mage', description: 'Reach 40 Magic', difficulty: 'hard', points: 4, completed: false, row: 3, col: 2 },
{ id: '3-3', title: 'Elite Archer', description: 'Reach 40 Ranged', difficulty: 'hard', points: 4, completed: false, row: 3, col: 3 },
{ id: '3-4', title: 'High Priest', description: 'Reach 43 Prayer', difficulty: 'hard', points: 4, completed: false, row: 3, col: 4 },
{ id: '3-5', title: 'Master Crafter', description: 'Reach 40 Crafting', difficulty: 'hard', points: 4, completed: false, row: 3, col: 5 },
{ id: '3-6', title: 'Dragon Slayer', description: 'Complete Dragon Slayer I', difficulty: 'hard', points: 5, completed: false, row: 3, col: 6 },
{ id: '3-0', title: 'Quest Veteran', description: 'Earn 50 Quest Points', difficulty: 'hard', points: 4, completed: false, row:3, col: 0 , type: 'main' },
{ id: '3-1', title: 'Elite Warrior', description: 'Reach 40 Attack', difficulty: 'hard', points: 4, completed: false, row:3, col: 1 , type: 'main' },
{ id: '3-2', title: 'Elite Mage', description: 'Reach 40 Magic', difficulty: 'hard', points: 4, completed: false, row:3, col: 2 , type: 'main' },
{ id: '3-3', title: 'Elite Archer', description: 'Reach 40 Ranged', difficulty: 'hard', points: 4, completed: false, row:3, col: 3 , type: 'main' },
{ id: '3-4', title: 'High Priest', description: 'Reach 43 Prayer', difficulty: 'hard', points: 4, completed: false, row:3, col: 4 , type: 'main' },
{ id: '3-5', title: 'Master Crafter', description: 'Reach 40 Crafting', difficulty: 'hard', points: 4, completed: false, row:3, col: 5 , type: 'main' },
{ id: '3-6', title: 'Dragon Slayer', description: 'Complete Dragon Slayer I', difficulty: 'hard', points: 5, completed: false, row:3, col: 6 , type: 'main' },
// Row 4
{ id: '4-0', title: 'Quest Champion', description: 'Earn 100 Quest Points', difficulty: 'hard', points: 5, completed: false, row: 4, col: 0 },
{ id: '4-1', title: 'Rune Warrior', description: 'Reach 50 Attack', difficulty: 'hard', points: 5, completed: false, row: 4, col: 1 },
{ id: '4-2', title: 'High Mage', description: 'Reach 50 Magic', difficulty: 'hard', points: 5, completed: false, row: 4, col: 2 },
{ id: '4-3', title: 'Master Archer', description: 'Reach 50 Ranged', difficulty: 'hard', points: 5, completed: false, row: 4, col: 3 },
{ id: '4-4', title: 'Prayer Master', description: 'Reach 50 Prayer', difficulty: 'hard', points: 5, completed: false, row: 4, col: 4 },
{ id: '4-5', title: 'Craft Master', description: 'Reach 50 Crafting', difficulty: 'hard', points: 5, completed: false, row: 4, col: 5 },
{ id: '4-6', title: 'Barrows Gloves', description: 'Complete Recipe for Disaster', difficulty: 'expert', points: 8, completed: false, row: 4, col: 6 },
{ id: '4-0', title: 'Quest Champion', description: 'Earn 100 Quest Points', difficulty: 'hard', points: 5, completed: false, row:4, col: 0 , type: 'main' },
{ id: '4-1', title: 'Rune Warrior', description: 'Reach 50 Attack', difficulty: 'hard', points: 5, completed: false, row:4, col: 1 , type: 'main' },
{ id: '4-2', title: 'High Mage', description: 'Reach 50 Magic', difficulty: 'hard', points: 5, completed: false, row:4, col: 2 , type: 'main' },
{ id: '4-3', title: 'Master Archer', description: 'Reach 50 Ranged', difficulty: 'hard', points: 5, completed: false, row:4, col: 3 , type: 'main' },
{ id: '4-4', title: 'Prayer Master', description: 'Reach 50 Prayer', difficulty: 'hard', points: 5, completed: false, row:4, col: 4 , type: 'main' },
{ id: '4-5', title: 'Craft Master', description: 'Reach 50 Crafting', difficulty: 'hard', points: 5, completed: false, row:4, col: 5 , type: 'main' },
{ id: '4-6', title: 'Barrows Gloves', description: 'Complete Recipe for Disaster', difficulty: 'expert', points: 8, completed: false, row:4, col: 6 , type: 'main' },
// Row 5
{ id: '5-0', title: 'Quest Legend', description: 'Earn 150 Quest Points', difficulty: 'expert', points: 6, completed: false, row: 5, col: 0 },
{ id: '5-1', title: 'Dragon Warrior', description: 'Reach 60 Attack', difficulty: 'expert', points: 6, completed: false, row: 5, col: 1 },
{ id: '5-2', title: 'Ancient Mage', description: 'Reach 60 Magic', difficulty: 'expert', points: 6, completed: false, row: 5, col: 2 },
{ id: '5-3', title: 'Expert Archer', description: 'Reach 60 Ranged', difficulty: 'expert', points: 6, completed: false, row: 5, col: 3 },
{ id: '5-4', title: 'Holy Master', description: 'Reach 60 Prayer', difficulty: 'expert', points: 6, completed: false, row: 5, col: 4 },
{ id: '5-5', title: 'Elite Crafter', description: 'Reach 60 Crafting', difficulty: 'expert', points: 6, completed: false, row: 5, col: 5 },
{ id: '5-6', title: 'Fire Cape', description: 'Obtain Fire Cape from Fight Caves', difficulty: 'expert', points: 10, completed: false, row: 5, col: 6 },
{ id: '5-0', title: 'Quest Legend', description: 'Earn 150 Quest Points', difficulty: 'expert', points: 6, completed: false, row:5, col: 0 , type: 'main' },
{ id: '5-1', title: 'Dragon Warrior', description: 'Reach 60 Attack', difficulty: 'expert', points: 6, completed: false, row:5, col: 1 , type: 'main' },
{ id: '5-2', title: 'Ancient Mage', description: 'Reach 60 Magic', difficulty: 'expert', points: 6, completed: false, row:5, col: 2 , type: 'main' },
{ id: '5-3', title: 'Expert Archer', description: 'Reach 60 Ranged', difficulty: 'expert', points: 6, completed: false, row:5, col: 3 , type: 'main' },
{ id: '5-4', title: 'Holy Master', description: 'Reach 60 Prayer', difficulty: 'expert', points: 6, completed: false, row:5, col: 4 , type: 'main' },
{ id: '5-5', title: 'Elite Crafter', description: 'Reach 60 Crafting', difficulty: 'expert', points: 6, completed: false, row:5, col: 5 , type: 'main' },
{ id: '5-6', title: 'Fire Cape', description: 'Obtain Fire Cape from Fight Caves', difficulty: 'expert', points: 10, completed: false, row:5, col: 6 , type: 'main' },
// Row 6
{ id: '6-0', title: 'Quest Grandmaster', description: 'Earn 200 Quest Points', difficulty: 'expert', points: 8, completed: false, row: 6, col: 0 },
{ id: '6-1', title: 'Whip Master', description: 'Reach 70 Attack', difficulty: 'expert', points: 8, completed: false, row: 6, col: 1 },
{ id: '6-2', title: 'Arcane Master', description: 'Reach 70 Magic', difficulty: 'expert', points: 8, completed: false, row: 6, col: 2 },
{ id: '6-3', title: 'Legendary Archer', description: 'Reach 70 Ranged', difficulty: 'expert', points: 8, completed: false, row: 6, col: 3 },
{ id: '6-4', title: 'Divine Master', description: 'Reach 70 Prayer', difficulty: 'expert', points: 8, completed: false, row: 6, col: 4 },
{ id: '6-5', title: 'Legendary Crafter', description: 'Reach 70 Crafting', difficulty: 'expert', points: 8, completed: false, row: 6, col: 5 },
{ id: '6-6', title: 'ToA Completion', description: 'Complete Tombs of Amascut', difficulty: 'expert', points: 15, completed: false, row: 6, col: 6 },
{ id: '6-0', title: 'Quest Grandmaster', description: 'Earn 200 Quest Points', difficulty: 'expert', points: 8, completed: false, row:6, col: 0 , type: 'main' },
{ id: '6-1', title: 'Whip Master', description: 'Reach 70 Attack', difficulty: 'expert', points: 8, completed: false, row:6, col: 1 , type: 'main' },
{ id: '6-2', title: 'Arcane Master', description: 'Reach 70 Magic', difficulty: 'expert', points: 8, completed: false, row:6, col: 2 , type: 'main' },
{ id: '6-3', title: 'Legendary Archer', description: 'Reach 70 Ranged', difficulty: 'expert', points: 8, completed: false, row:6, col: 3 , type: 'main' },
{ id: '6-4', title: 'Divine Master', description: 'Reach 70 Prayer', difficulty: 'expert', points: 8, completed: false, row:6, col: 4 , type: 'main' },
{ id: '6-5', title: 'Legendary Crafter', description: 'Reach 70 Crafting', difficulty: 'expert', points: 8, completed: false, row:6, col: 5 , type: 'main' },
{ id: '6-6', title: 'ToA Completion', description: 'Complete Tombs of Amascut', difficulty: 'expert', points: 15, completed: false, row:6, col: 6, type: 'main' , type: 'main' },
// Side nodes (left column)
{ id: 'side-0', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:0, col: -1, type: 'side' },
{ id: 'side-1', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:1, col: -1, type: 'side' },
{ id: 'side-2', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:2, col: -1, type: 'side' },
{ id: 'side-3', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:3, col: -1, type: 'side' },
{ id: 'side-4', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:4, col: -1, type: 'side' },
{ id: 'side-5', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:5, col: -1, type: 'side' },
{ id: 'side-6', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:6, col: -1, type: 'side' },
// Bottom nodes
{ id: 'bottom-0', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:7, col: 0, type: 'bottom' },
{ id: 'bottom-1', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:7, col: 1, type: 'bottom' },
{ id: 'bottom-2', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:7, col: 2, type: 'bottom' },
{ id: 'bottom-3', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:7, col: 3, type: 'bottom' },
{ id: 'bottom-4', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:7, col: 4, type: 'bottom' },
{ id: 'bottom-5', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:7, col: 5, type: 'bottom' },
{ id: 'bottom-6', title: 'Custom Goal', description: 'Set your own challenge', difficulty: 'easy', points: 1, completed: false, row:7, col: 6, type: 'bottom' },
];

View File

@@ -40,6 +40,7 @@ export interface GridTask {
completed: boolean;
row: number;
col: number;
type?: 'main' | 'side' | 'bottom';
}
export interface UserProgress {