From b9e0f470c54a4aeafed484e99da1e440f4e17a97 Mon Sep 17 00:00:00 2001 From: Bailey Taylor Date: Thu, 2 Oct 2025 03:15:56 +0000 Subject: [PATCH] Visual element updates. Icons for site and cleanup. Update text moved to toasts. Localstorage added. --- osrs-grid-master/Grid_Master_Raw.png | Bin 0 -> 10849 bytes osrs-grid-master/data/user-progress.json | 4517 +++++++++++++++++ osrs-grid-master/public/cell_tile.png | Bin 0 -> 1115 bytes .../public/cell_tile_nontransparent.png | Bin 0 -> 415 bytes osrs-grid-master/src/app/favicon.ico | Bin 25931 -> 3086 bytes osrs-grid-master/src/app/layout.tsx | 7 +- osrs-grid-master/src/app/page.tsx | 38 +- .../src/components/GridMaster.tsx | 153 +- osrs-grid-master/src/components/Toast.tsx | 55 + osrs-grid-master/src/data/grid-tasks.ts | 116 +- osrs-grid-master/src/types/osrs.ts | 1 + 11 files changed, 4781 insertions(+), 106 deletions(-) create mode 100644 osrs-grid-master/Grid_Master_Raw.png create mode 100644 osrs-grid-master/public/cell_tile.png create mode 100644 osrs-grid-master/public/cell_tile_nontransparent.png create mode 100644 osrs-grid-master/src/components/Toast.tsx diff --git a/osrs-grid-master/Grid_Master_Raw.png b/osrs-grid-master/Grid_Master_Raw.png new file mode 100644 index 0000000000000000000000000000000000000000..f24845d074a36fa36f367b872ae7386f48f9d9e8 GIT binary patch literal 10849 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 J+CBSF_zlrwUW)(# literal 25931 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 LrJugUA?W`A8`#=m diff --git a/osrs-grid-master/src/app/layout.tsx b/osrs-grid-master/src/app/layout.tsx index f7fa87e..732dd4c 100644 --- a/osrs-grid-master/src/app/layout.tsx +++ b/osrs-grid-master/src/app/layout.tsx @@ -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({ diff --git a/osrs-grid-master/src/app/page.tsx b/osrs-grid-master/src/app/page.tsx index 0e22b5d..84c3246 100644 --- a/osrs-grid-master/src/app/page.tsx +++ b/osrs-grid-master/src/app/page.tsx @@ -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(''); const [playerStats, setPlayerStats] = useState(null); const [saving, setSaving] = useState(false); + const [toastMessage, setToastMessage] = useState(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() {

Track your progress for the Grid Master event (Oct 15 - Nov 12, 2025)

- {saving && ( -
- Saving progress... -
- )}
@@ -176,6 +191,15 @@ export default function Home() { )}
+ + {/* Toast notification */} + {toastMessage && ( + setToastMessage(null)} + /> + )} ); diff --git a/osrs-grid-master/src/components/GridMaster.tsx b/osrs-grid-master/src/components/GridMaster.tsx index 37989af..198e7e5 100644 --- a/osrs-grid-master/src/components/GridMaster.tsx +++ b/osrs-grid-master/src/components/GridMaster.tsx @@ -74,9 +74,11 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea const completedTasks = gridTasks.filter(task => task.completed).length; return ( -
+
-

Grid Master Progress

+

Grid Master

Total Points: @@ -84,7 +86,7 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
Completed Tasks: - {completedTasks}/49 + {completedTasks}/{gridTasks.length}
Complete Rows: @@ -97,67 +99,122 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea
-
- {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; +
+ {/* Side nodes column */} +
+ {Array.from({ length: 7 }, (_, row) => { + const task = gridTasks.find(t => t.row === row && t.col === -1 && t.type === 'side'); + if (!task) return
; return (
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 && ( -
- + {task.completed ? ( +
+
+ ) : ( +
?
)} - - {hasBonus && ( -
- - - -
- )} - -
- {task.title} -
- -
- {task.description} -
- -
- - {task.difficulty} - - - {task.points}pt{hasBonus ? ' +bonus' : ''} - -
); - }) - )} + })} +
+ + {/* Main 7x7 grid */} +
+ {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 ( +
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 ? ( +
+ + + +
+ ) : ( + Bingo tile + )} + + {hasBonus && ( +
+ + + +
+ )} +
+ ); + }) + )} +
-
+ {/* Bottom nodes row */} +
+ {Array.from({ length: 7 }, (_, col) => { + const task = gridTasks.find(t => t.row === 7 && t.col === col && t.type === 'bottom'); + if (!task) return
; + + return ( +
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 ? ( +
+ + + +
+ ) : ( +
?
+ )} +
+ ); + })} +
+ +
@@ -176,7 +233,7 @@ export default function GridMaster({ tasks = defaultGridTasks, onTaskToggle, rea Expert (6-15 pts)
-

+

Complete full rows or columns for bonus rewards! Tasks with the ⭐ symbol are part of a completed line.

diff --git a/osrs-grid-master/src/components/Toast.tsx b/osrs-grid-master/src/components/Toast.tsx new file mode 100644 index 0000000..f9ab25c --- /dev/null +++ b/osrs-grid-master/src/components/Toast.tsx @@ -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 ( +
+
+ {message} + +
+
+ ); +} \ No newline at end of file diff --git a/osrs-grid-master/src/data/grid-tasks.ts b/osrs-grid-master/src/data/grid-tasks.ts index a6cc2ad..4fc90fe 100644 --- a/osrs-grid-master/src/data/grid-tasks.ts +++ b/osrs-grid-master/src/data/grid-tasks.ts @@ -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' }, ]; \ No newline at end of file diff --git a/osrs-grid-master/src/types/osrs.ts b/osrs-grid-master/src/types/osrs.ts index cd18195..6623581 100644 --- a/osrs-grid-master/src/types/osrs.ts +++ b/osrs-grid-master/src/types/osrs.ts @@ -40,6 +40,7 @@ export interface GridTask { completed: boolean; row: number; col: number; + type?: 'main' | 'side' | 'bottom'; } export interface UserProgress {