Skip to content

Instantly share code, notes, and snippets.

@williamjayjay
Created July 15, 2025 18:17
Show Gist options
  • Save williamjayjay/c87964ba2e98aebaa3ba84bccce10e94 to your computer and use it in GitHub Desktop.
Save williamjayjay/c87964ba2e98aebaa3ba84bccce10e94 to your computer and use it in GitHub Desktop.
RPG GAME - Focus Pomodoro D3
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Diário do Aventureiro - Registro de Missões</title>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=MedievalSharp&display=swap"
rel="stylesheet">
<style>
:root {
/* Cores e Elementos Inspirados em Diablo III */
--primary-color: #8c0a0a;
/* Vermelho escuro de Diablo */
--secondary-color: #5e0000;
/* Vermelho ainda mais escuro */
--background-color: #0d0d0d;
/* Fundo quase preto */
--card-background: #1c1c1c;
/* Placas de metal escuro */
--text-color: #e0d8c0;
/* Papel antigo / pergaminho */
--xp-color: #ffd700;
/* Ouro */
--border-color: #4a3b2e;
/* Borda de metal enferrujado */
--progress-bar-bg: #2d2d2d;
/* Fundo da barra de vida/recurso */
--progress-bar-fill: #0a700a;
/* Verde de vida */
--button-bg: #8c0a0a;
/* Botão vermelho */
--button-hover-bg: #a32d2d;
/* Hover mais claro */
--border-radius: 4px;
/* Cantos menos arredondados */
--padding-size: 20px;
}
body {
font-family: 'MedievalSharp', cursive;
/* Fonte de RPG */
margin: 0;
padding: 0;
background-color: var(--background-color);
color: var(--text-color);
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
overflow-x: hidden;
background-image: url('https://blz-contentstack-images.akamaized.net/v3/assets/bltfad370659b122b0c/blt3f5a287a32d665a3/62e08e68400037064d1f2113/01_D2R_Launch_Wallpaper_Desktop_1920x1080.jpg');
/* Exemplo de BG de Diablo */
background-size: cover;
background-position: center;
background-attachment: fixed;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
/* Overlay escuro para melhorar legibilidade */
z-index: 1;
}
.container {
position: relative;
z-index: 2;
background-color: var(--card-background);
border-radius: var(--border-radius);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.8);
padding: var(--padding-size);
margin: var(--padding-size);
width: 95%;
max-width: 1000px;
display: flex;
flex-wrap: wrap;
gap: var(--padding-size);
box-sizing: border-box;
border: 2px solid var(--border-color);
/* Borda mais pronunciada */
background-image: url('https://www.diablowiki.net/images/thumb/8/87/UI_Inventory_Background.png/250px-UI_Inventory_Background.png');
/* Textura de fundo do painel */
background-size: cover;
background-blend-mode: multiply;
background-color: var(--card-background);
}
h1,
h2 {
font-family: 'Cinzel', serif;
/* Fonte de título de Diablo */
color: var(--primary-color);
text-align: center;
margin-bottom: var(--padding-size);
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
text-transform: uppercase;
letter-spacing: 1.5px;
}
/* Seções de Registro e Atributos */
.log-mission-section,
.skills-section,
.history-section {
flex: 1;
min-width: 350px;
background-color: rgba(30, 30, 30, 0.9);
/* Fundo semitransparente para card */
padding: var(--padding-size);
border-radius: var(--border-radius);
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.6);
box-sizing: border-box;
border: 1px solid var(--border-color);
/* Borda do card interno */
}
.mission-controls label {
display: block;
margin-bottom: 10px;
font-weight: bold;
color: var(--text-color);
font-size: 1.1em;
}
.mission-controls input[type="text"],
.mission-controls select {
width: calc(100% - 24px);
padding: 12px;
margin-bottom: 20px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
background-color: #3d3d3d;
color: var(--text-color);
box-sizing: border-box;
font-size: 1em;
font-family: 'MedievalSharp', cursive;
appearance: none;
/* Remove estilo padrão do select */
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23e0d8c0%22%20d%3D%22M287%2069.9a14.6%2014.6%200%2000-20.6%200l-123.8%20123.8L19.8%2069.9a14.6%2014.6%200%2000-20.6%2020.6L145.7%20247.7c6%206%2014.6%206%2020.6%200L287%2090.5a14.6%2014.6%200%20000-20.6z%22%2F%3E%3C%2Fsvg%3E');
background-repeat: no-repeat;
background-position: right 10px top 50%;
background-size: 12px auto;
}
.mission-controls button {
background-color: var(--button-bg);
color: #fff;
padding: 15px 25px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
cursor: pointer;
font-size: 1.1em;
transition: background-color 0.3s ease, transform 0.2s ease;
text-transform: uppercase;
font-weight: bold;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);
width: 100%;
font-family: 'Cinzel', serif;
}
.mission-controls button:hover {
background-color: var(--button-hover-bg);
transform: translateY(-2px);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.7);
}
.mission-controls button:disabled {
background-color: #555;
cursor: not-allowed;
opacity: 0.7;
transform: none;
box-shadow: none;
}
/* Seção de Skills */
.xp-display {
text-align: center;
font-size: 2.2em;
margin-bottom: 30px;
color: var(--xp-color);
font-weight: bold;
text-shadow: 0 0 12px rgba(255, 215, 0, 0.6);
letter-spacing: 1px;
}
.skills-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.skill-item {
background-color: rgba(40, 40, 40, 0.9);
padding: 15px;
border-radius: var(--border-radius);
display: flex;
flex-direction: column;
gap: 12px;
border: 1px solid var(--border-color);
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4);
}
.skill-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.skill-item span {
font-size: 1.3em;
font-weight: bold;
color: var(--primary-color);
font-family: 'Cinzel', serif;
}
.skill-item .level {
background-color: var(--secondary-color);
padding: 6px 14px;
border-radius: 4px;
font-size: 0.95em;
color: #fff;
text-transform: uppercase;
font-family: 'Cinzel', serif;
border: 1px solid var(--primary-color);
}
/* Barra de Progresso (Vida/Mana estilo Diablo) */
.progress-bar-container {
width: 100%;
background-color: var(--progress-bar-bg);
border-radius: 4px;
height: 16px;
/* Mais robusta */
overflow: hidden;
position: relative;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.5);
border: 1px solid #1a1a1a;
}
.progress-bar-fill {
height: 100%;
background-color: var(--progress-bar-fill);
width: 0%;
border-radius: 4px;
transition: width 0.5s ease-in-out;
box-shadow: inset 0 0 5px rgba(0, 255, 0, 0.3);
/* Brilho verde */
}
.progress-text {
position: absolute;
width: 100%;
text-align: center;
line-height: 16px;
font-size: 0.85em;
color: #fff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
top: 0;
font-family: 'Cinzel', serif;
}
.skill-item button {
background-color: var(--xp-color);
color: #333;
padding: 12px 20px;
border: 1px solid #a89400;
border-radius: var(--border-radius);
cursor: pointer;
font-size: 1em;
font-weight: bold;
transition: background-color 0.3s ease, transform 0.2s ease;
text-transform: uppercase;
align-self: flex-end;
margin-top: 10px;
font-family: 'Cinzel', serif;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}
.skill-item button:hover {
background-color: #e6b100;
transform: translateY(-1px);
}
.skill-item button:disabled {
background-color: #7f8c8d;
cursor: not-allowed;
opacity: 0.6;
transform: none;
box-shadow: none;
}
/* Seção de Histórico */
.history-section {
width: 100%;
margin-top: var(--padding-size);
}
.history-list {
list-style-type: none;
padding: 0;
max-height: 350px;
overflow-y: auto;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
background-color: rgba(30, 30, 30, 0.9);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
}
.history-list li {
padding: 12px 18px;
border-bottom: 1px dashed #3a3a3a;
font-size: 0.95em;
color: var(--text-color);
display: flex;
flex-direction: column;
align-items: flex-start;
}
.history-list li:last-child {
border-bottom: none;
}
.history-list li .task-name {
font-weight: bold;
color: var(--xp-color);
margin-bottom: 6px;
font-size: 1.1em;
font-family: 'Cinzel', serif;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
}
.history-list li .task-details {
display: flex;
flex-direction: column;
width: 100%;
font-size: 0.85em;
color: #ccc;
font-family: 'MedievalSharp', cursive;
}
.history-list li .task-details span {
margin-bottom: 4px;
}
/* Scrollbar Personalizada (Webkit) */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #2b2b2b;
border-radius: 5px;
}
::-webkit-scrollbar-thumb {
background: #555;
border-radius: 5px;
border: 1px solid #4a4a4a;
}
::-webkit-scrollbar-thumb:hover {
background: #777;
}
/* Responsividade */
@media (max-width: 768px) {
.container {
flex-direction: column;
margin: 10px;
padding: 15px;
}
.log-mission-section,
.skills-section,
.history-section {
min-width: unset;
width: 100%;
padding: 15px;
}
h1,
h2 {
font-size: 1.8em;
}
.xp-display {
font-size: 1.8em;
}
.skill-item span {
font-size: 1.1em;
}
.skill-item button {
padding: 10px 15px;
font-size: 0.9em;
}
}
</style>
</head>
<body>
<div class="container">
<div class="log-mission-section">
<h2>Registro de Missões Concluídas</h2>
<div class="mission-controls">
<label for="taskName">Nome da Missão (Obrigatório):</label>
<input type="text" id="taskName" placeholder="Ex: Treino de Espada, Estudo de Magia" required>
<label for="pomodoroDuration">Duração da Missão (Minutos):</label>
<select id="pomodoroDuration">
<option value="10">Treino Leve (10 min)</option>
<option value="18" selected>Patrulha Padrão (18 min)</option>
<option value="25">Expedição Longa (25 min)</option>
<option value="50">Jornada Épica (50 min)</option>
</select>
<button id="registerMission">Registrar Conclusão</button>
</div>
</div>
<div class="skills-section">
<h2>Atributos do Aventureiro</h2>
<div class="xp-display">
XP Disponível: <span id="currentXP">0</span>
</div>
<div class="skills-list">
<div class="skill-item">
<div class="skill-header">
<span>Força</span>
<span class="level" id="strengthLevel">Nível 1</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="strengthProgress"></div>
<div class="progress-text" id="strengthProgressText">0/100</div>
</div>
<button data-skill="strength" disabled>Aprimorar Força</button>
</div>
<div class="skill-item">
<div class="skill-header">
<span>Inteligência</span>
<span class="level" id="intelligenceLevel">Nível 1</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="intelligenceProgress"></div>
<div class="progress-text" id="intelligenceProgressText">0/100</div>
</div>
<button data-skill="intelligence" disabled>Aprimorar Inteligência</button>
</div>
<div class="skill-item">
<div class="skill-header">
<span>Agilidade</span>
<span class="level" id="agilityLevel">Nível 1</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="agilityProgress"></div>
<div class="progress-text" id="agilityProgressText">0/100</div>
</div>
<button data-skill="agility" disabled>Aprimorar Agilidade</button>
</div>
<div class="skill-item">
<div class="skill-header">
<span>Resistência</span>
<span class="level" id="resistanceLevel">Nível 1</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="resistanceProgress"></div>
<div class="progress-text" id="resistanceProgressText">0/100</div>
</div>
<button data-skill="resistance" disabled>Aprimorar Resistência</button>
</div>
</div>
</div>
</div>
<div class="container history-section">
<h2>Pergaminhos de Conquistas Passadas</h2>
<ul id="pomodoroHistoryList" class="history-list">
<li style="text-align: center; color: #777;">Nenhuma missão registrada ainda.</li>
</ul>
</div>
<script>
const registerMissionButton = document.getElementById('registerMission');
const taskNameInput = document.getElementById('taskName');
const pomodoroDurationSelect = document.getElementById('pomodoroDuration');
const xpDisplay = document.getElementById('currentXP');
const skillButtons = document.querySelectorAll('.skill-item button');
const pomodoroHistoryList = document.getElementById('pomodoroHistoryList');
let xpPoints = 0;
let pomodoroHistory = [];
// Valores de XP para cada duração de "pomodoro"
const xpValues = {
'10': 1,
'18': 2,
'25': 3,
'50': 5, // Nova duração
};
const skillsData = {
strength: { level: 1, progress: 0, maxProgress: 100 },
intelligence: { level: 1, progress: 0, maxProgress: 100 },
agility: { level: 1, progress: 0, maxProgress: 100 },
resistance: { level: 1, progress: 0, maxProgress: 100 },
};
// --- Utility Function for Formatting Dates ---
function formatDateTime(date) {
if (!date) return 'N/A';
const options = {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
};
return new Date(date).toLocaleString('pt-BR', options);
}
// --- Save/Load Game Data (via localStorage) ---
function saveGameData() {
const gameData = {
xpPoints: xpPoints,
skills: skillsData,
pomodoroHistory: pomodoroHistory.map(item => ({
...item,
timestamp: item.timestamp ? item.timestamp.toISOString() : null,
})),
lastSave: new Date().toISOString()
};
localStorage.setItem('rpgMissionLogData', JSON.stringify(gameData));
console.log('Game data saved:', gameData);
}
function loadGameData() {
const savedData = localStorage.getItem('rpgMissionLogData');
if (savedData) {
const gameData = JSON.parse(savedData);
xpPoints = gameData.xpPoints || 0;
xpDisplay.textContent = xpPoints;
pomodoroHistory = (gameData.pomodoroHistory || []).map(item => ({
...item,
timestamp: item.timestamp ? new Date(item.timestamp) : null,
}));
if (gameData.skills) {
for (const skillName in gameData.skills) {
if (skillsData[skillName]) {
skillsData[skillName].level = gameData.skills[skillName].level;
skillsData[skillName].progress = gameData.skills[skillName].progress;
updateSkillDisplay(skillName);
}
}
}
console.log('Game data loaded:', gameData);
}
updateUpgradeButtons();
renderHistory();
}
// --- Register Mission Function ---
function registerMission() {
// AJUSTE: Verifica se o nome da missão está vazio
const taskName = taskNameInput.value.trim();
if (taskName === '') {
alert('Atenção, Aventureiro! O nome da missão é obrigatório para registrar sua conquista.');
taskNameInput.focus();
return; // Impede que o restante da função seja executado
}
const selectedDuration = pomodoroDurationSelect.value;
const xpAwarded = xpValues[selectedDuration] || 0; // Se não encontrar, dá 0 XP
if (xpAwarded === 0) {
alert('Duração de missão inválida. Por favor, selecione uma opção válida.');
return;
}
xpPoints += xpAwarded;
xpDisplay.textContent = xpPoints;
const missionRecord = {
name: taskName,
duration: `${selectedDuration} min`, // Armazena a duração em minutos
xp: xpAwarded,
timestamp: new Date(), // Data e hora da conclusão
};
pomodoroHistory.unshift(missionRecord); // Adiciona ao início da lista
// Limita o histórico a 20 itens
if (pomodoroHistory.length > 20) {
pomodoroHistory.pop();
}
renderHistory();
saveGameData();
updateUpgradeButtons();
alert(`Missão "${taskName}" (${selectedDuration} min) concluída! Você ganhou ${xpAwarded} XP!`);
// Limpa o campo da missão após o registro
taskNameInput.value = '';
pomodoroDurationSelect.value = '18'; // Volta para a opção padrão
}
// --- Skill Functions ---
function updateSkillDisplay(skillName) {
const skill = skillsData[skillName];
const levelSpan = document.getElementById(`${skillName}Level`);
const progressBarFill = document.getElementById(`${skillName}Progress`);
const progressBarText = document.getElementById(`${skillName}ProgressText`);
levelSpan.textContent = `Nível ${skill.level}`;
const percentage = (skill.progress / skill.maxProgress) * 100;
progressBarFill.style.width = `${percentage}%`;
progressBarText.textContent = `${skill.progress}/${skill.maxProgress}`;
}
function updateUpgradeButtons() {
skillButtons.forEach(button => {
button.disabled = xpPoints < 1;
});
}
function upgradeSkill(event) {
if (xpPoints >= 1) {
const skillName = event.target.dataset.skill;
const skill = skillsData[skillName];
skill.progress += 25; // Cada aprimoramento custa 1 XP e dá 25 de progresso
if (skill.progress >= skill.maxProgress) {
skill.level++;
skill.progress = 0;
alert(`Parabéns! Sua skill de ${skillName.charAt(0).toUpperCase() + skillName.slice(1)} alcançou o Nível ${skill.level}!`);
}
xpPoints--; // Gasta 1 XP
xpDisplay.textContent = xpPoints;
updateSkillDisplay(skillName);
saveGameData();
updateUpgradeButtons();
} else {
alert('Você não tem XP suficiente para aprimorar!');
}
}
// --- History Functions ---
function renderHistory() {
pomodoroHistoryList.innerHTML = ''; // Limpa a lista atual
if (pomodoroHistory.length === 0) {
const li = document.createElement('li');
li.style.textAlign = 'center';
li.style.color = '#777';
li.textContent = 'Nenhuma missão registrada ainda.';
pomodoroHistoryList.appendChild(li);
return;
}
pomodoroHistory.forEach(item => {
const li = document.createElement('li');
li.innerHTML = `
<span class="task-name">${item.name}</span>
<div class="task-details">
<span>Duração: ${item.duration} (Ganhou ${item.xp} XP)</span>
<span>Registrado em: ${formatDateTime(item.timestamp)}</span>
</div>
`;
pomodoroHistoryList.appendChild(li);
});
}
// --- Event Listeners ---
registerMissionButton.addEventListener('click', registerMission);
skillButtons.forEach(button => {
button.addEventListener('click', upgradeSkill);
});
// --- Initialization ---
document.addEventListener('DOMContentLoaded', () => {
loadGameData();
for (const skillName in skillsData) {
updateSkillDisplay(skillName);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment