Visual element updates.
Icons for site and cleanup. Update text moved to toasts. Localstorage added.
This commit is contained in:
BIN
osrs-grid-master/Grid_Master_Raw.png
Normal file
BIN
osrs-grid-master/Grid_Master_Raw.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
File diff suppressed because it is too large
Load Diff
BIN
osrs-grid-master/public/cell_tile.png
Normal file
BIN
osrs-grid-master/public/cell_tile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
osrs-grid-master/public/cell_tile_nontransparent.png
Normal file
BIN
osrs-grid-master/public/cell_tile_nontransparent.png
Normal file
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 |
@@ -13,8 +13,11 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "OSRS Grid Master Tracker",
|
||||||
description: "Generated by create next app",
|
description: "A tracker for OSRS Grid Master",
|
||||||
|
icons: {
|
||||||
|
icon: "/favicon.ico",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import CharacterLookup from '@/components/CharacterLookup';
|
import CharacterLookup from '@/components/CharacterLookup';
|
||||||
import PassphraseLogin from '@/components/PassphraseLogin';
|
import PassphraseLogin from '@/components/PassphraseLogin';
|
||||||
import GridMaster from '@/components/GridMaster';
|
import GridMaster from '@/components/GridMaster';
|
||||||
|
import Toast from '@/components/Toast';
|
||||||
import { UserProgress, OSRSHiscoreStats, GridTask } from '@/types/osrs';
|
import { UserProgress, OSRSHiscoreStats, GridTask } from '@/types/osrs';
|
||||||
import { defaultGridTasks } from '@/data/grid-tasks';
|
import { defaultGridTasks } from '@/data/grid-tasks';
|
||||||
import { saveProgress, loadProgress } from '@/lib/progress-api';
|
import { saveProgress, loadProgress } from '@/lib/progress-api';
|
||||||
@@ -14,6 +15,15 @@ export default function Home() {
|
|||||||
const [currentPlayer, setCurrentPlayer] = useState<string>('');
|
const [currentPlayer, setCurrentPlayer] = useState<string>('');
|
||||||
const [playerStats, setPlayerStats] = useState<OSRSHiscoreStats | null>(null);
|
const [playerStats, setPlayerStats] = useState<OSRSHiscoreStats | null>(null);
|
||||||
const [saving, setSaving] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
if (currentPassphrase) {
|
if (currentPassphrase) {
|
||||||
@@ -46,8 +56,10 @@ export default function Home() {
|
|||||||
setUserProgress(null);
|
setUserProgress(null);
|
||||||
setCurrentPlayer('');
|
setCurrentPlayer('');
|
||||||
setPlayerStats(null);
|
setPlayerStats(null);
|
||||||
|
localStorage.removeItem('osrs-grid-passphrase');
|
||||||
} else {
|
} else {
|
||||||
setCurrentPassphrase(passphrase);
|
setCurrentPassphrase(passphrase);
|
||||||
|
localStorage.setItem('osrs-grid-passphrase', passphrase);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,8 +81,16 @@ export default function Home() {
|
|||||||
if (!currentPassphrase) return;
|
if (!currentPassphrase) return;
|
||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
await saveProgress(currentPassphrase, progress);
|
setToastMessage('Saving progress...');
|
||||||
setSaving(false);
|
|
||||||
|
try {
|
||||||
|
await saveProgress(currentPassphrase, progress);
|
||||||
|
setToastMessage('Progress saved!');
|
||||||
|
} catch (error) {
|
||||||
|
setToastMessage('Failed to save progress');
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTaskToggle = (taskId: string) => {
|
const handleTaskToggle = (taskId: string) => {
|
||||||
@@ -134,11 +154,6 @@ export default function Home() {
|
|||||||
<p className="text-lg text-gray-600">
|
<p className="text-lg text-gray-600">
|
||||||
Track your progress for the Grid Master event (Oct 15 - Nov 12, 2025)
|
Track your progress for the Grid Master event (Oct 15 - Nov 12, 2025)
|
||||||
</p>
|
</p>
|
||||||
{saving && (
|
|
||||||
<div className="mt-2 text-sm text-blue-600">
|
|
||||||
Saving progress...
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
@@ -176,6 +191,15 @@ export default function Home() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Toast notification */}
|
||||||
|
{toastMessage && (
|
||||||
|
<Toast
|
||||||
|
message={toastMessage}
|
||||||
|
type={toastMessage.includes('Failed') ? 'error' : toastMessage.includes('saved') ? 'success' : 'info'}
|
||||||
|
onClose={() => setToastMessage(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -74,9 +74,11 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
|
|||||||
const completedTasks = gridTasks.filter(task => task.completed).length;
|
const completedTasks = gridTasks.filter(task => task.completed).length;
|
||||||
|
|
||||||
return (
|
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">
|
<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="flex flex-wrap gap-4 text-sm">
|
||||||
<div className="bg-blue-50 px-3 py-1 rounded-lg">
|
<div className="bg-blue-50 px-3 py-1 rounded-lg">
|
||||||
<span className="font-semibold text-blue-800">Total Points:</span>
|
<span className="font-semibold text-blue-800">Total Points:</span>
|
||||||
@@ -84,7 +86,7 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
|
|||||||
</div>
|
</div>
|
||||||
<div className="bg-green-50 px-3 py-1 rounded-lg">
|
<div className="bg-green-50 px-3 py-1 rounded-lg">
|
||||||
<span className="font-semibold text-green-800">Completed Tasks:</span>
|
<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>
|
||||||
<div className="bg-purple-50 px-3 py-1 rounded-lg">
|
<div className="bg-purple-50 px-3 py-1 rounded-lg">
|
||||||
<span className="font-semibold text-purple-800">Complete Rows:</span>
|
<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>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-7 gap-2 mb-4">
|
<div className="flex gap-2 mb-4">
|
||||||
{Array.from({ length: 7 }, (_, row) =>
|
{/* Side nodes column */}
|
||||||
Array.from({ length: 7 }, (_, col) => {
|
<div className="flex flex-col gap-2">
|
||||||
const task = gridTasks.find(t => t.row === row && t.col === col);
|
{Array.from({ length: 7 }, (_, row) => {
|
||||||
if (!task) return null;
|
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>;
|
||||||
const isRowComplete = completedRows.includes(row);
|
|
||||||
const isColComplete = completedCols.includes(col);
|
|
||||||
const hasBonus = isRowComplete || isColComplete;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
onClick={() => handleTaskClick(task.id)}
|
onClick={() => handleTaskClick(task.id)}
|
||||||
className={`
|
className={`
|
||||||
relative p-3 border-2 rounded-lg transition-all duration-200
|
relative p-2 border-2 rounded-lg transition-all duration-200
|
||||||
${task.completed ? 'bg-green-50 border-green-400' : getDifficultyColor(task.difficulty)}
|
${task.completed ? 'bg-green-50 border-green-400' : 'bg-gray-800 border-gray-600'}
|
||||||
${readonly ? 'cursor-default' : 'cursor-pointer hover:shadow-md'}
|
${readonly ? 'cursor-default' : 'cursor-pointer hover:shadow-md'}
|
||||||
${hasBonus ? 'ring-2 ring-yellow-400 ring-opacity-60' : ''}
|
w-16 h-16 flex items-center justify-center
|
||||||
min-h-[120px] flex flex-col justify-between
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{task.completed && (
|
{task.completed ? (
|
||||||
<div className="absolute top-1 right-1 text-green-600">
|
<div className="text-green-600">
|
||||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</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>
|
||||||
);
|
);
|
||||||
})
|
})}
|
||||||
)}
|
</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>
|
||||||
|
|
||||||
<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 flex-wrap gap-4 mb-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 bg-green-100 border border-green-300 rounded"></div>
|
<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>
|
<span>Expert (6-15 pts)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-amber-700">
|
||||||
Complete full rows or columns for bonus rewards!
|
Complete full rows or columns for bonus rewards!
|
||||||
Tasks with the ⭐ symbol are part of a completed line.
|
Tasks with the ⭐ symbol are part of a completed line.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
55
osrs-grid-master/src/components/Toast.tsx
Normal file
55
osrs-grid-master/src/components/Toast.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,65 +2,83 @@ import { GridTask } from '@/types/osrs';
|
|||||||
|
|
||||||
export const defaultGridTasks: GridTask[] = [
|
export const defaultGridTasks: GridTask[] = [
|
||||||
// Row 0
|
// Row 0
|
||||||
{ id: '0-0', title: 'First Steps', description: 'Complete Tutorial Island', difficulty: 'easy', points: 1, completed: false, row: 0, col: 0 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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
|
// 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-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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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
|
// 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-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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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
|
// 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-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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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
|
// 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-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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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
|
// 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-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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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
|
// 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-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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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 },
|
{ 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' },
|
||||||
];
|
];
|
||||||
@@ -40,6 +40,7 @@ export interface GridTask {
|
|||||||
completed: boolean;
|
completed: boolean;
|
||||||
row: number;
|
row: number;
|
||||||
col: number;
|
col: number;
|
||||||
|
type?: 'main' | 'side' | 'bottom';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserProgress {
|
export interface UserProgress {
|
||||||
|
|||||||
Reference in New Issue
Block a user