Skip to content

Commit 165f26c

Browse files
committed
Update hideout tracker to match quest tracker style
- Replace slider with tick-based progress UI - Add hover popover showing level requirements with item icons - Match quest tracker styling (rounded ends, hover effects) - Click tick to set/unset level progress
1 parent 09c28cd commit 165f26c

2 files changed

Lines changed: 162 additions & 19 deletions

File tree

‎src/main.ts‎

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,9 @@ class App {
482482
const moduleName = module.name;
483483

484484
const getLevelText = (level: number) => {
485-
return level === 0 ? 'Not Unlocked' : `Level ${level} / ${module.maxLevel}`;
485+
if (level === 0) return 'Not Unlocked';
486+
if (level === module.maxLevel) return `Level ${level} (Max)`;
487+
return `Level ${level} / ${module.maxLevel}`;
486488
};
487489

488490
const card = document.createElement('div');
@@ -491,34 +493,136 @@ class App {
491493
<h3>${moduleName}</h3>
492494
<div class="workshop-card__level">
493495
<span class="workshop-card__level-text">${getLevelText(currentLevel)}</span>
494-
<input
495-
type="range"
496-
min="0"
497-
max="${module.maxLevel}"
498-
value="${currentLevel}"
499-
data-module-id="${module.id}"
500-
class="workshop-card__slider"
501-
/>
496+
<div class="workshop-card__ticks"></div>
502497
</div>
503498
`;
504499

505-
const slider = card.querySelector('.workshop-card__slider') as HTMLInputElement;
506500
const levelText = card.querySelector('.workshop-card__level-text') as HTMLSpanElement;
501+
const ticksContainer = card.querySelector('.workshop-card__ticks') as HTMLDivElement;
507502

508-
slider.addEventListener('input', (e) => {
509-
const newLevel = parseInt((e.target as HTMLInputElement).value);
503+
// Helper to update UI and save progress
504+
const updateProgress = (newLevel: number) => {
510505
levelText.textContent = getLevelText(newLevel);
511-
});
512506

513-
slider.addEventListener('change', (e) => {
514-
const newLevel = parseInt((e.target as HTMLInputElement).value);
507+
// Update tick visual states
508+
ticksContainer.querySelectorAll('.workshop-card__tick').forEach((tick, idx) => {
509+
tick.classList.toggle('completed', idx < newLevel);
510+
});
511+
512+
// Save progress
515513
this.updateWorkshopLevel(module.id, newLevel);
516-
});
514+
};
515+
516+
// Create tick marks for each level (1 to maxLevel)
517+
for (let level = 1; level <= module.maxLevel; level++) {
518+
const tick = document.createElement('div');
519+
tick.className = 'workshop-card__tick';
520+
if (level <= currentLevel) {
521+
tick.classList.add('completed');
522+
}
523+
tick.dataset.moduleId = module.id;
524+
tick.dataset.level = String(level);
525+
526+
// Find level data for requirements
527+
const levelData = module.levels.find(l => l.level === level);
528+
529+
// Add hover for popover
530+
tick.addEventListener('mouseenter', (e) => this.showHideoutPopover(module, level, levelData, e));
531+
tick.addEventListener('mouseleave', () => this.hideHideoutPopover());
532+
533+
// Click to set progress
534+
tick.addEventListener('click', () => {
535+
const currentlyCompleted = tick.classList.contains('completed');
536+
// If clicking on a completed level, set to previous level
537+
// If clicking on incomplete level, complete up to and including this one
538+
const newLevel = currentlyCompleted ? level - 1 : level;
539+
updateProgress(newLevel);
540+
});
541+
542+
ticksContainer.appendChild(tick);
543+
}
517544

518545
workshopGrid.appendChild(card);
519546
});
520547
}
521548

549+
private showHideoutPopover(module: any, level: number, levelData: any, event: MouseEvent) {
550+
// Remove existing popover if any
551+
this.hideHideoutPopover();
552+
553+
const popover = document.createElement('div');
554+
popover.className = 'quest-popover'; // Reuse quest popover styling
555+
popover.id = 'hideout-popover';
556+
557+
// Build requirements HTML
558+
let requirementsHtml = '<span class="quest-popover__none">None</span>';
559+
if (levelData?.requirementItemIds && levelData.requirementItemIds.length > 0) {
560+
requirementsHtml = levelData.requirementItemIds.map((req: any) => {
561+
const itemId = req.item_id || req.itemId;
562+
const quantity = req.quantity || '?';
563+
const item = this.gameData.items.find(i => i.id === itemId);
564+
const itemName = item?.name || itemId || 'Unknown';
565+
const iconUrl = item ? dataLoader.getIconUrl(item) : '';
566+
567+
return `
568+
<div class="quest-popover__item">
569+
${iconUrl ? `<img src="${iconUrl}" alt="" class="quest-popover__item-icon" />` : ''}
570+
<span>${quantity}x ${itemName}</span>
571+
</div>
572+
`;
573+
}).join('');
574+
}
575+
576+
// Check for other requirements (like coins for stash)
577+
let otherReqsHtml = '';
578+
if (levelData?.otherRequirements && levelData.otherRequirements.length > 0) {
579+
otherReqsHtml = levelData.otherRequirements.map((req: string) => `
580+
<div class="quest-popover__item">
581+
<span>${req}</span>
582+
</div>
583+
`).join('');
584+
}
585+
586+
const allRequirementsHtml = requirementsHtml + otherReqsHtml || '<span class="quest-popover__none">None</span>';
587+
588+
popover.innerHTML = `
589+
<div class="quest-popover__title">${module.name} - Level ${level}</div>
590+
${levelData?.description ? `<div class="quest-popover__desc">${levelData.description}</div>` : ''}
591+
<div class="quest-popover__section">
592+
<div class="quest-popover__section-title">Requirements</div>
593+
<div class="quest-popover__items">${allRequirementsHtml}</div>
594+
</div>
595+
`;
596+
597+
document.body.appendChild(popover);
598+
599+
// Position the popover near the tick (above it)
600+
const rect = (event.target as HTMLElement).getBoundingClientRect();
601+
const popoverRect = popover.getBoundingClientRect();
602+
603+
let left = rect.left + rect.width / 2 - popoverRect.width / 2;
604+
let top = rect.top - popoverRect.height - 8;
605+
606+
// Keep popover within viewport
607+
if (left < 8) left = 8;
608+
if (left + popoverRect.width > window.innerWidth - 8) {
609+
left = window.innerWidth - popoverRect.width - 8;
610+
}
611+
if (top < 8) {
612+
top = rect.bottom + 8;
613+
}
614+
615+
popover.style.left = `${left}px`;
616+
popover.style.top = `${top}px`;
617+
}
618+
619+
private hideHideoutPopover() {
620+
const existing = document.getElementById('hideout-popover');
621+
if (existing) {
622+
existing.remove();
623+
}
624+
}
625+
522626
private updateWorkshopLevel(moduleId: string, level: number) {
523627
this.userProgress.hideoutLevels[moduleId] = level;
524628
StorageManager.saveUserProgress(this.userProgress);

‎src/styles/main.css‎

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -802,10 +802,49 @@ body {
802802
transition: all var(--transition-fast);
803803
}
804804

805-
.workshop-card__slider::-moz-range-thumb:hover {
806-
width: 14px;
805+
/* Workshop card ticks (matching quest tracker style) */
806+
.workshop-card__ticks {
807+
display: flex;
808+
gap: 2px;
809+
width: 100%;
810+
margin-top: var(--spacing-sm);
811+
}
812+
813+
.workshop-card__tick {
814+
flex: 1;
807815
height: 14px;
808-
box-shadow: 0 0 12px rgba(0, 188, 212, 0.8);
816+
min-width: 0;
817+
border-radius: 0;
818+
background: var(--color-bg-elevated);
819+
border: 1px solid var(--color-text-muted);
820+
cursor: pointer;
821+
transition: all var(--transition-fast);
822+
}
823+
824+
/* Rounded left end on first tick */
825+
.workshop-card__tick:first-child {
826+
border-radius: 6px 0 0 6px;
827+
}
828+
829+
/* Rounded right end on last tick */
830+
.workshop-card__tick:last-child {
831+
border-radius: 0 6px 6px 0;
832+
}
833+
834+
/* Single tick gets both rounded ends */
835+
.workshop-card__tick:only-child {
836+
border-radius: 6px;
837+
}
838+
839+
.workshop-card__tick:hover {
840+
transform: scaleY(1.15);
841+
border-color: var(--color-accent-cyan);
842+
background: rgba(0, 188, 212, 0.2);
843+
}
844+
845+
.workshop-card__tick.completed {
846+
background: var(--color-accent-cyan);
847+
border-color: var(--color-accent-cyan);
809848
}
810849

811850
/* ========================================

0 commit comments

Comments
 (0)