add quests page
This commit is contained in:
parent
9a556bacdd
commit
91257a09f4
7 changed files with 2711 additions and 1 deletions
2308
data/Areas.csv
Normal file
2308
data/Areas.csv
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -155,6 +155,7 @@ export class Armory {
|
||||||
app.get("/character/:realm/:character/achievements/data", this.wrapRoute(charsController.achievementsData.bind(charsController)));
|
app.get("/character/:realm/:character/achievements/data", this.wrapRoute(charsController.achievementsData.bind(charsController)));
|
||||||
app.get("/character/:realm/:name/pvp", this.wrapRoute(charsController.pvp.bind(charsController)));
|
app.get("/character/:realm/:name/pvp", this.wrapRoute(charsController.pvp.bind(charsController)));
|
||||||
app.get("/character/:realm/:name/reputation", this.wrapRoute(charsController.reputation.bind(charsController)));
|
app.get("/character/:realm/:name/reputation", this.wrapRoute(charsController.reputation.bind(charsController)));
|
||||||
|
app.get("/character/:realm/:name/quests", this.wrapRoute(charsController.quests.bind(charsController)));
|
||||||
|
|
||||||
const guildsController = new GuildController(this);
|
const guildsController = new GuildController(this);
|
||||||
app.get("/guild/:realm/:name", this.wrapRoute(guildsController.guild.bind(guildsController)));
|
app.get("/guild/:realm/:name", this.wrapRoute(guildsController.guild.bind(guildsController)));
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { RowDataPacket } from "mysql2/promise";
|
||||||
import { Armory } from "../Armory";
|
import { Armory } from "../Armory";
|
||||||
import { IRealmConfig } from "../Config";
|
import { IRealmConfig } from "../Config";
|
||||||
import { IEmblem, Utils } from "../Utils";
|
import { IEmblem, Utils } from "../Utils";
|
||||||
import { IAchievement as IAchievementDbc, ISkillDbc } from "../data/DbcReader";
|
import { IAchievement as IAchievementDbc, ISkillDbc, IAreas } from "../data/DbcReader";
|
||||||
|
|
||||||
interface ICharacterData {
|
interface ICharacterData {
|
||||||
guid: number;
|
guid: number;
|
||||||
|
|
@ -90,6 +90,15 @@ interface IReputation {
|
||||||
expansionId: number;
|
expansionId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IQuest {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
status: 'Completed' | 'In Progress';
|
||||||
|
level: number;
|
||||||
|
questLevel: number;
|
||||||
|
questSortID: number;
|
||||||
|
}
|
||||||
|
|
||||||
const ItemClassGem = 3;
|
const ItemClassGem = 3;
|
||||||
const SpellMechanicMounted = 21;
|
const SpellMechanicMounted = 21;
|
||||||
const RaceDisplayName = {
|
const RaceDisplayName = {
|
||||||
|
|
@ -119,6 +128,7 @@ const ClassDisplayName = {
|
||||||
|
|
||||||
export class CharacterController {
|
export class CharacterController {
|
||||||
private armory: Armory;
|
private armory: Armory;
|
||||||
|
private areaById: { [key: number]: IAreas };
|
||||||
private itemInventoryTypes: { [key: number]: number };
|
private itemInventoryTypes: { [key: number]: number };
|
||||||
private itemIcons: { [key: number]: number };
|
private itemIcons: { [key: number]: number };
|
||||||
private gemItems: { [key: number]: boolean };
|
private gemItems: { [key: number]: boolean };
|
||||||
|
|
@ -204,6 +214,10 @@ export class CharacterController {
|
||||||
for await (const skill of this.armory.dbc.skill()) {
|
for await (const skill of this.armory.dbc.skill()) {
|
||||||
this.skillById[skill.id] = skill;
|
this.skillById[skill.id] = skill;
|
||||||
}
|
}
|
||||||
|
this.areaById = {};
|
||||||
|
for await (const area of this.armory.dbc.areas()) {
|
||||||
|
this.areaById[area.id] = area;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async character(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
public async character(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||||
|
|
@ -349,6 +363,44 @@ export class CharacterController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async quests(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||||
|
const realmName = req.params.realm;
|
||||||
|
const charName = req.params.name;
|
||||||
|
|
||||||
|
const realm = this.armory.getRealm(realmName);
|
||||||
|
if (realm === undefined) {
|
||||||
|
return next(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const charData = await this.getCharacterData(realm, charName);
|
||||||
|
if (charData === null) {
|
||||||
|
return next(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const quests = await this.getQuests(realm.name, charData.guid);
|
||||||
|
|
||||||
|
// Group quests by zone/profession
|
||||||
|
const questsByCategory = quests.reduce((acc, quest) => {
|
||||||
|
const category = quest.questSortID > 0 ?
|
||||||
|
this.getZoneName(quest.questSortID):
|
||||||
|
this.getProfessionName(quest.questSortID);
|
||||||
|
|
||||||
|
if (!acc[category]) {
|
||||||
|
acc[category] = [];
|
||||||
|
}
|
||||||
|
acc[category].push(quest);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
res.render("character-quests.hbs", {
|
||||||
|
title: `Armory - ${charData.name} - Quests`,
|
||||||
|
...this.makeSharedDataObject(realm, charData),
|
||||||
|
data: {
|
||||||
|
categories: questsByCategory
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async getReputations(realm: string, character: number): Promise<IReputation[]> {
|
private async getReputations(realm: string, character: number): Promise<IReputation[]> {
|
||||||
const [rows] = await this.armory.getCharactersDb(realm).query({
|
const [rows] = await this.armory.getCharactersDb(realm).query({
|
||||||
sql: `
|
sql: `
|
||||||
|
|
@ -423,6 +475,145 @@ export class CharacterController {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getQuests(realm: string, character: number): Promise<IQuest[]> {
|
||||||
|
// Get completed and rewarded quests
|
||||||
|
const [completedRows] = await this.armory.getCharactersDb(realm).query({
|
||||||
|
sql: `
|
||||||
|
SELECT quest FROM (
|
||||||
|
SELECT quest FROM character_queststatus
|
||||||
|
WHERE guid = ? AND status = 1
|
||||||
|
UNION
|
||||||
|
SELECT quest FROM character_queststatus_rewarded
|
||||||
|
WHERE guid = ?
|
||||||
|
) AS completed_quests
|
||||||
|
`,
|
||||||
|
values: [character, character],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get in progress quests
|
||||||
|
const [inProgressRows] = await this.armory.getCharactersDb(realm).query({
|
||||||
|
sql: `
|
||||||
|
SELECT quest
|
||||||
|
FROM character_queststatus
|
||||||
|
WHERE guid = ? AND status = 3
|
||||||
|
`,
|
||||||
|
values: [character],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const quests: IQuest[] = [];
|
||||||
|
|
||||||
|
// Process completed quests
|
||||||
|
for (const row of completedRows as RowDataPacket[]) {
|
||||||
|
const questInfo = await this.getQuestInfo(row.quest);
|
||||||
|
if (questInfo) {
|
||||||
|
quests.push({
|
||||||
|
id: row.quest,
|
||||||
|
title: questInfo.title,
|
||||||
|
status: 'Completed',
|
||||||
|
level: questInfo.minLevel,
|
||||||
|
questLevel: questInfo.questLevel,
|
||||||
|
questSortID: questInfo.questSortID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process in progress quests
|
||||||
|
for (const row of inProgressRows as RowDataPacket[]) {
|
||||||
|
const questInfo = await this.getQuestInfo(row.quest);
|
||||||
|
if (questInfo) {
|
||||||
|
quests.push({
|
||||||
|
id: row.quest,
|
||||||
|
title: questInfo.title,
|
||||||
|
status: 'In Progress',
|
||||||
|
level: questInfo.minLevel,
|
||||||
|
questLevel: questInfo.questLevel,
|
||||||
|
questSortID: questInfo.questSortID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return quests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getQuestInfo(questId: number): Promise<any> {
|
||||||
|
const [rows] = await this.armory.worldDb.query({
|
||||||
|
sql: `
|
||||||
|
SELECT ID, LogTitle as title, MinLevel as minLevel, QuestLevel as questLevel, QuestSortID as questSortID
|
||||||
|
FROM quest_template
|
||||||
|
WHERE ID = ?
|
||||||
|
`,
|
||||||
|
values: [questId],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
return rows[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getZoneName(zoneId: number): string {
|
||||||
|
this.areaById[zoneId]?.zoneName
|
||||||
|
return this.areaById[zoneId]?.zoneName || `Zone ${zoneId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProfessionName(professionId: number): string {
|
||||||
|
const questTypes = {
|
||||||
|
// Negative IDs (Classes and Professions)
|
||||||
|
// Classes
|
||||||
|
"-61": "Warlock",
|
||||||
|
"-81": "Warrior",
|
||||||
|
"-82": "Shaman",
|
||||||
|
"-141": "Paladin",
|
||||||
|
"-161": "Mage",
|
||||||
|
"-162": "Rogue",
|
||||||
|
"-261": "Hunter",
|
||||||
|
"-262": "Priest",
|
||||||
|
"-263": "Druid",
|
||||||
|
"-372": "Death Knight",
|
||||||
|
// Professions
|
||||||
|
"-24": "Herbalism",
|
||||||
|
"-101": "Fishing",
|
||||||
|
"-121": "Blacksmithing",
|
||||||
|
"-181": "Alchemy",
|
||||||
|
"-182": "Leatherworking",
|
||||||
|
"-201": "Engineering",
|
||||||
|
"-264": "Tailoring",
|
||||||
|
"-304": "Cooking",
|
||||||
|
"-324": "First Aid",
|
||||||
|
"-371": "Inscription",
|
||||||
|
"-373": "Jewelcrafting",
|
||||||
|
"-762": "Riding",
|
||||||
|
// Misc
|
||||||
|
"-1": "Epic",
|
||||||
|
"-21": "Wailing Caverns",
|
||||||
|
"-22": "Seasonal",
|
||||||
|
"-23": "Undercity",
|
||||||
|
"-25": "Battlegrounds",
|
||||||
|
"-41": "Uldaman",
|
||||||
|
"-221": "Treasure Map",
|
||||||
|
"-241": "Tournament",
|
||||||
|
"-284": "Special",
|
||||||
|
"-344": "Legendary",
|
||||||
|
"-364": "Darkmoon Faire",
|
||||||
|
"-365": "Ahn'Qiraj War",
|
||||||
|
"-366": "Lunar Festival",
|
||||||
|
"-367": "Reputation",
|
||||||
|
"-368": "Invasion",
|
||||||
|
"-369": "Midsummer",
|
||||||
|
"-370": "Brewfest",
|
||||||
|
"-374": "Noblegarden",
|
||||||
|
"-375": "Pilgrim's Bounty",
|
||||||
|
"-376": "Love is in the Air"
|
||||||
|
};
|
||||||
|
return questTypes[professionId] || `Category ${professionId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getQuestExpansionId(questLevel: number): number {
|
||||||
|
if (questLevel <= 60) return 0; // Classic
|
||||||
|
if (questLevel <= 70) return 1; // TBC
|
||||||
|
return 2; // WotLK
|
||||||
|
}
|
||||||
|
|
||||||
public async achievements(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
public async achievements(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||||
const realmName = req.params.realm;
|
const realmName = req.params.realm;
|
||||||
const charName = req.params.name;
|
const charName = req.params.name;
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,13 @@ export interface IFactionDbc {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAreas {
|
||||||
|
id: number;
|
||||||
|
zoneName: string;
|
||||||
|
mapId: number;
|
||||||
|
areaId: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface IAsyncGeneratorWithArrayMethods<T> {
|
interface IAsyncGeneratorWithArrayMethods<T> {
|
||||||
[Symbol.asyncIterator](): AsyncGenerator<T>;
|
[Symbol.asyncIterator](): AsyncGenerator<T>;
|
||||||
toArray(): Promise<T[]>;
|
toArray(): Promise<T[]>;
|
||||||
|
|
@ -321,6 +328,7 @@ const dir = path.join(process.cwd(), "data");
|
||||||
export const DbcFiles = {
|
export const DbcFiles = {
|
||||||
achievement: path.join(dir, "Achievement_3.3.5_12340.csv"),
|
achievement: path.join(dir, "Achievement_3.3.5_12340.csv"),
|
||||||
achievementCategory: path.join(dir, "AchievementCategory_3.3.5_12340.csv"),
|
achievementCategory: path.join(dir, "AchievementCategory_3.3.5_12340.csv"),
|
||||||
|
areas: path.join(dir, "Areas.csv"),
|
||||||
faction: path.join(dir, "Factions.csv"),
|
faction: path.join(dir, "Factions.csv"),
|
||||||
glyphProperties: path.join(dir, "GlyphProperties_3.3.5_12340.csv"),
|
glyphProperties: path.join(dir, "GlyphProperties_3.3.5_12340.csv"),
|
||||||
item: path.join(dir, "Item_3.3.5_12340.csv"),
|
item: path.join(dir, "Item_3.3.5_12340.csv"),
|
||||||
|
|
@ -341,6 +349,7 @@ export const DbcFiles = {
|
||||||
const dbcFields = {
|
const dbcFields = {
|
||||||
achievement: ["id", "faction", "titleLang0", "descriptionLang0", "category", "points", "flags", "iconId"],
|
achievement: ["id", "faction", "titleLang0", "descriptionLang0", "category", "points", "flags", "iconId"],
|
||||||
achievementCategory: ["id", "parent", "nameLang0"],
|
achievementCategory: ["id", "parent", "nameLang0"],
|
||||||
|
areas: ["id", "zoneName", "mapId", "areaId"],
|
||||||
glyphProperties: ["id", "spellId"],
|
glyphProperties: ["id", "spellId"],
|
||||||
item: ["id", "classId", "subclassId", "displayInfoId", "inventoryType"],
|
item: ["id", "classId", "subclassId", "displayInfoId", "inventoryType"],
|
||||||
itemRetail: ["id", "inventoryType"],
|
itemRetail: ["id", "inventoryType"],
|
||||||
|
|
@ -373,6 +382,7 @@ const dbcFields = {
|
||||||
export class DbcManager {
|
export class DbcManager {
|
||||||
private _achievement: IAchievement[];
|
private _achievement: IAchievement[];
|
||||||
private _achievementCategory: IAchievementCategory[];
|
private _achievementCategory: IAchievementCategory[];
|
||||||
|
private _areas: IAreas[];
|
||||||
private _glyphProperties: IGlyphProperties[];
|
private _glyphProperties: IGlyphProperties[];
|
||||||
private _faction: IFactionDbc[];
|
private _faction: IFactionDbc[];
|
||||||
private _item: IItemDbc[];
|
private _item: IItemDbc[];
|
||||||
|
|
@ -395,6 +405,7 @@ export class DbcManager {
|
||||||
DbcFiles.achievementCategory,
|
DbcFiles.achievementCategory,
|
||||||
dbcFields.achievementCategory,
|
dbcFields.achievementCategory,
|
||||||
).toArray();
|
).toArray();
|
||||||
|
this._areas = await this.read<IAreas>(DbcFiles.areas).toArray();
|
||||||
this._glyphProperties = await this.read<IGlyphProperties>(DbcFiles.glyphProperties, dbcFields.glyphProperties).toArray();
|
this._glyphProperties = await this.read<IGlyphProperties>(DbcFiles.glyphProperties, dbcFields.glyphProperties).toArray();
|
||||||
this._item = await this.read<IItemDbc>(DbcFiles.item, dbcFields.item).toArray();
|
this._item = await this.read<IItemDbc>(DbcFiles.item, dbcFields.item).toArray();
|
||||||
this._itemRetail = await this.read<IItemRetailDbc>(DbcFiles.itemRetail, dbcFields.itemRetail).toArray();
|
this._itemRetail = await this.read<IItemRetailDbc>(DbcFiles.itemRetail, dbcFields.itemRetail).toArray();
|
||||||
|
|
@ -425,6 +436,9 @@ export class DbcManager {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.achievementCategory, this._achievementCategory, dbcFields.achievementCategory);
|
return this.getLoadedDataOrRead(DbcFiles.achievementCategory, this._achievementCategory, dbcFields.achievementCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public areas() {
|
||||||
|
return this.getLoadedDataOrRead(DbcFiles.areas, this._areas);
|
||||||
|
}
|
||||||
public faction() {
|
public faction() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.faction, this._faction, dbcFields.faction);
|
return this.getLoadedDataOrRead(DbcFiles.faction, this._faction, dbcFields.faction);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
91
static/character-quests.hbs
Normal file
91
static/character-quests.hbs
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{websiteRoot}}/css/character-quests.css">
|
||||||
|
{{> icons }}
|
||||||
|
<script type="application/javascript">
|
||||||
|
const aowow_tooltips = { "renamelinks": true, };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="{{aowow}}/static/widgets/power.js"></script>
|
||||||
|
|
||||||
|
{{> character-header }}
|
||||||
|
|
||||||
|
{{#each data.categories as |quests category|}}
|
||||||
|
<div class="quests-table" style="margin-bottom: 20px;">
|
||||||
|
<h2 class="is-size-4 category-header collapsible">
|
||||||
|
{{category}}
|
||||||
|
<span class="collapse-icon">▼</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="category-content expanded">
|
||||||
|
<!-- In Progress Quests -->
|
||||||
|
<h3 class="is-size-5 collapsible">
|
||||||
|
In Progress
|
||||||
|
<span class="collapse-icon">▼</span>
|
||||||
|
</h3>
|
||||||
|
<div class="collapsible-content expanded">
|
||||||
|
<table class="table is-striped is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Quest</th>
|
||||||
|
<th>Level</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each quests}}
|
||||||
|
{{#if (eq this.status "In Progress")}}
|
||||||
|
<tr>
|
||||||
|
<td class="quest-name">{{this.title}}</td>
|
||||||
|
<td class="quest-level">{{this.questLevel}}</td>
|
||||||
|
<td class="quest-status {{this.status}}">{{this.status}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Completed Quests -->
|
||||||
|
<h3 class="is-size-5 collapsible">
|
||||||
|
Completed
|
||||||
|
<span class="collapse-icon">▼</span>
|
||||||
|
</h3>
|
||||||
|
<div class="collapsible-content">
|
||||||
|
<table class="table is-striped is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Quest</th>
|
||||||
|
<th>Level</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each quests}}
|
||||||
|
{{#if (eq this.status "Completed")}}
|
||||||
|
<tr>
|
||||||
|
<td class="quest-name">{{this.title}}</td>
|
||||||
|
<td class="quest-level">{{this.questLevel}}</td>
|
||||||
|
<td class="quest-status {{this.status}}">{{this.status}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.querySelectorAll('.collapsible').forEach(header => {
|
||||||
|
header.addEventListener('click', function(e) {
|
||||||
|
// Stop event from bubbling to parent collapsible elements
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const content = this.nextElementSibling;
|
||||||
|
content.classList.toggle('expanded');
|
||||||
|
this.classList.toggle('expanded');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
104
static/css/character-quests.css
Normal file
104
static/css/character-quests.css
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
.quests-table {
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quests-table h2 {
|
||||||
|
color: #FFD700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quest-name {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quest-level {
|
||||||
|
text-align: center;
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quest-status {
|
||||||
|
text-align: center;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quest-status.completed {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quest-status.in-progress {
|
||||||
|
color: #FFD100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-icon {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible.expanded .collapse-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-content {
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-content.expanded {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed-quests.expanded .collapsible-content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed-quests.expanded .collapse-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quest-category {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quest-category h3 {
|
||||||
|
color: #ccc;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-size-5 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content {
|
||||||
|
display: none;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content.expanded {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header .collapse-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header.expanded .collapse-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/talents">Talents</a> 
|
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/talents">Talents</a> 
|
||||||
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/skills">Skills</a> 
|
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/skills">Skills</a> 
|
||||||
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/reputation">Reputation</a> 
|
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/reputation">Reputation</a> 
|
||||||
|
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/quests">Quests</a> 
|
||||||
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/achievements">Achievements</a> 
|
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/achievements">Achievements</a> 
|
||||||
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/pvp">PvP</a> 
|
<a href="{{websiteRoot}}/character/{{realm}}/{{name}}/pvp">PvP</a> 
|
||||||
<br>
|
<br>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue