Spaces:
Running
Running
analyze fix upgrade, prioritize mvp top 3 e2e tested n functional simple but valuable human struggle time minimizers
3e519ec verified | class ToolFocus extends HTMLElement { | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| .container { | |
| background: #1e293b; | |
| border-radius: 1rem; | |
| padding: 2.5rem; | |
| text-align: center; | |
| border: 1px solid #334155; | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3); | |
| } | |
| .timer-display { | |
| font-size: 6rem; | |
| font-weight: 700; | |
| font-variant-numeric: tabular-nums; | |
| color: #fff; | |
| margin: 1rem 0; | |
| letter-spacing: -2px; | |
| text-shadow: 0 0 30px rgba(99, 102, 241, 0.3); | |
| } | |
| .controls { | |
| display: flex; | |
| justify-content: center; | |
| gap: 1rem; | |
| margin-top: 2rem; | |
| } | |
| button { | |
| padding: 0.75rem 2rem; | |
| border-radius: 9999px; | |
| border: none; | |
| font-weight: 600; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn-primary { | |
| background: #6366f1; | |
| color: white; | |
| box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4); | |
| } | |
| .btn-primary:hover { | |
| background: #4f46e5; | |
| transform: translateY(-2px); | |
| } | |
| .btn-secondary { | |
| background: #334155; | |
| color: #e2e8f0; | |
| } | |
| .btn-secondary:hover { | |
| background: #475569; | |
| } | |
| .status-badge { | |
| display: inline-block; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 999px; | |
| font-size: 0.875rem; | |
| background: #334155; | |
| color: #94a3b8; | |
| margin-bottom: 1rem; | |
| } | |
| .status-badge.active { | |
| background: rgba(99, 102, 241, 0.2); | |
| color: #818cf8; | |
| border: 1px solid rgba(99, 102, 241, 0.3); | |
| } | |
| .presets { | |
| display: flex; | |
| justify-content: center; | |
| gap: 0.5rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .preset-btn { | |
| background: transparent; | |
| border: 1px solid #334155; | |
| color: #94a3b8; | |
| padding: 0.25rem 0.75rem; | |
| font-size: 0.8rem; | |
| border-radius: 6px; | |
| } | |
| .preset-btn:hover { | |
| border-color: #6366f1; | |
| color: #6366f1; | |
| } | |
| </style> | |
| <div class="container"> | |
| <div class="status-badge" id="status">Ready to Focus</div> | |
| <div class="presets"> | |
| <button class="preset-btn" onclick="this.getRootNode().host.setTimer(25)">25m</button> | |
| <button class="preset-btn" onclick="this.getRootNode().host.setTimer(15)">15m</button> | |
| <button class="preset-btn" onclick="this.getRootNode().host.setTimer(5)">5m</button> | |
| </div> | |
| <div class="timer-display" id="display">25:00</div> | |
| <div class="controls"> | |
| <button class="btn-primary" id="startBtn"> | |
| <i data-feather="play" width="18" height="18"></i> Start | |
| </button> | |
| <button class="btn-secondary" id="resetBtn"> | |
| <i data-feather="refresh-ccw" width="18" height="18"></i> Reset | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| this.timeLeft = 25 * 60; | |
| this.timerId = null; | |
| this.isRunning = false; | |
| this.displayEl = this.shadowRoot.getElementById('display'); | |
| this.startBtn = this.shadowRoot.getElementById('startBtn'); | |
| this.resetBtn = this.shadowRoot.getElementById('resetBtn'); | |
| this.statusEl = this.shadowRoot.getElementById('status'); | |
| // Bind events | |
| this.startBtn.addEventListener('click', () => this.toggleTimer()); | |
| this.resetBtn.addEventListener('click', () => this.resetTimer()); | |
| this.updateDisplay(); | |
| feather.replace(); | |
| } | |
| updateDisplay() { | |
| const m = Math.floor(this.timeLeft / 60).toString().padStart(2, '0'); | |
| const s = (this.timeLeft % 60).toString().padStart(2, '0'); | |
| this.displayEl.textContent = `${m}:${s}`; | |
| // Update favicon/title dynamically could be a nice touch | |
| document.title = this.isRunning ? `(${m}:${s}) Focus` : 'FlowState OS π'; | |
| } | |
| toggleTimer() { | |
| if (this.isRunning) { | |
| this.pauseTimer(); | |
| } else { | |
| this.startTimer(); | |
| } | |
| } | |
| startTimer() { | |
| if (this.timeLeft === 0) return; | |
| this.isRunning = true; | |
| this.statusEl.textContent = 'Focusing...'; | |
| this.statusEl.classList.add('active'); | |
| this.startBtn.innerHTML = '<i data-feather="pause" width="18" height="18"></i> Pause'; | |
| feather.replace(); | |
| this.timerId = setInterval(() => { | |
| this.timeLeft--; | |
| this.updateDisplay(); | |
| if (this.timeLeft <= 0) { | |
| this.completeTimer(); | |
| } | |
| }, 1000); | |
| } | |
| pauseTimer() { | |
| this.isRunning = false; | |
| clearInterval(this.timerId); | |
| this.statusEl.textContent = 'Paused'; | |
| this.statusEl.classList.remove('active'); | |
| this.startBtn.innerHTML = '<i data-feather="play" width="18" height="18"></i> Resume'; | |
| feather.replace(); | |
| } | |
| resetTimer() { | |
| this.pauseTimer(); | |
| this.timeLeft = 25 * 60; | |
| this.statusEl.textContent = 'Ready to Focus'; | |
| this.startBtn.innerHTML = '<i data-feather="play" width="18" height="18"></i> Start'; | |
| this.updateDisplay(); | |
| feather.replace(); | |
| } | |
| setTimer(minutes) { | |
| this.resetTimer(); | |
| this.timeLeft = minutes * 60; | |
| this.updateDisplay(); | |
| } | |
| completeTimer() { | |
| this.pauseTimer(); | |
| this.timeLeft = 0; | |
| this.updateDisplay(); | |
| this.statusEl.textContent = 'Session Complete! π'; | |
| // Simple sound effect simulation via Visual Flash | |
| this.shadowRoot.querySelector('.container').style.borderColor = '#10b981'; | |
| setTimeout(() => { | |
| this.shadowRoot.querySelector('.container').style.borderColor = '#334155'; | |
| }, 2000); | |
| } | |
| } | |
| customElements.define('tool-focus', ToolFocus); |