Untitled

 avatar
unknown
plain_text
10 months ago
32 kB
14
Indexable
<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Production Line Timer - Sprues & Males</title>
    <style>
        /* CSS Variables for theming */
        :root {
            --sprues-color: #2E8B57;
            --males-color: #DC143C;
            --background: #1a1a1a;
            --surface: rgba(40, 40, 40, 0.95);
            --text-primary: #ffffff;
            --text-secondary: #cccccc;
            --success: #27ae60;
            --warning: #f39c12;
            --danger: #e74c3c;
            --shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            --shadow-light: 0 4px 16px rgba(0, 0, 0, 0.2);
        }

```
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }

    body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
        background: var(--background);
        color: var(--text-primary);
        overflow: hidden;
        height: 100vh;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: none;
    }

    body.show-cursor {
        cursor: default;
    }

    .container {
        width: 100%;
        height: 100vh;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        position: relative;
        padding: 2rem;
    }

    .main-display {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        text-align: center;
        transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
    }

    .main-display.shift-transition {
        transform: scale(1.02);
    }

    .status-bar {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 8px;
        z-index: 100;
        transition: background-color 0.5s ease;
    }

    .status-bar.sprues {
        background: var(--sprues-color);
    }

    .status-bar.males {
        background: var(--males-color);
    }

    .current-shift {
        font-size: clamp(8rem, 20vw, 24rem);
        font-weight: 900;
        margin-bottom: 2rem;
        transition: all 0.8s ease;
        text-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
        letter-spacing: -0.02em;
        line-height: 0.9;
    }

    .current-shift.sprues {
        color: var(--sprues-color);
    }

    .current-shift.males {
        color: var(--males-color);
    }

    .time-display {
        font-size: clamp(6rem, 15vw, 18rem);
        font-weight: 800;
        font-variant-numeric: tabular-nums;
        margin-bottom: 3rem;
        text-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
        letter-spacing: -0.02em;
    }

    .progress-container {
        position: relative;
        width: clamp(300px, 40vw, 600px);
        height: clamp(300px, 40vw, 600px);
        margin: 2rem auto;
    }

    .progress-ring {
        width: 100%;
        height: 100%;
        transform: rotate(-90deg);
    }

    .progress-ring-bg {
        fill: none;
        stroke: rgba(255, 255, 255, 0.1);
        stroke-width: 12;
    }

    .progress-ring-progress {
        fill: none;
        stroke: var(--sprues-color);
        stroke-width: 12;
        stroke-linecap: round;
        transition: stroke 0.5s ease, stroke-dasharray 0.3s ease;
        filter: drop-shadow(0 0 10px currentColor);
    }

    .next-indicator {
        position: fixed;
        bottom: 4rem;
        left: 50%;
        transform: translateX(-50%);
        font-size: clamp(1.5rem, 4vw, 3rem);
        color: var(--text-secondary);
        font-weight: 600;
        opacity: 0.8;
    }

    .settings-trigger {
        position: fixed;
        top: 2rem;
        right: 2rem;
        width: 60px;
        height: 60px;
        background: transparent;
        border: none;
        cursor: pointer;
        opacity: 0;
        transition: opacity 0.3s ease;
        z-index: 200;
    }

    .settings-trigger:hover,
    .settings-trigger:focus {
        opacity: 0.7;
    }

    .settings-trigger::before {
        content: '⚙️';
        font-size: 2rem;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

    .modal {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.9);
        backdrop-filter: blur(10px);
        z-index: 1000;
        align-items: center;
        justify-content: center;
    }

    .modal.show {
        display: flex;
    }

    .modal-content {
        background: var(--surface);
        border-radius: 16px;
        padding: 3rem;
        width: 90%;
        max-width: 600px;
        box-shadow: var(--shadow);
        border: 1px solid rgba(255, 255, 255, 0.1);
    }

    .modal-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 3rem;
    }

    .modal-title {
        font-size: 2rem;
        font-weight: 700;
        color: var(--text-primary);
    }

    .close-btn {
        background: none;
        border: none;
        font-size: 2rem;
        cursor: pointer;
        padding: 0.5rem;
        border-radius: 50%;
        width: 50px;
        height: 50px;
        display: flex;
        align-items: center;
        justify-content: center;
        color: var(--text-secondary);
        transition: all 0.2s ease;
    }

    .close-btn:hover {
        background: rgba(255, 255, 255, 0.1);
        color: var(--text-primary);
    }

    .controls-section {
        margin-bottom: 3rem;
        padding-bottom: 2rem;
        border-bottom: 1px solid rgba(255, 255, 255, 0.1);
    }

    .controls-title {
        font-size: 1.5rem;
        font-weight: 600;
        margin-bottom: 2rem;
        color: var(--text-primary);
    }

    .controls {
        display: flex;
        gap: 1rem;
        flex-wrap: wrap;
        justify-content: center;
        margin-top: 2rem;
    }

    .btn {
        padding: 1rem 2rem;
        border: none;
        border-radius: 12px;
        font-size: 1.1rem;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s ease;
        min-width: 120px;
        position: relative;
        overflow: hidden;
        backdrop-filter: blur(10px);
    }

    .btn:focus {
        outline: 3px solid var(--primary-color);
        outline-offset: 2px;
    }

    .btn:disabled {
        opacity: 0.6;
        cursor: not-allowed;
    }

    .btn-primary {
        background: var(--primary-color);
        color: white;
    }

    .btn-primary:hover:not(:disabled) {
        background: #357abd;
        transform: translateY(-2px);
        box-shadow: var(--shadow);
    }

    .btn-secondary {
        background: var(--surface);
        color: var(--text-primary);
        border: 2px solid var(--primary-color);
    }

    .btn-secondary:hover:not(:disabled) {
        background: var(--primary-color);
        color: white;
        transform: translateY(-2px);
    }

    .form-group {
        margin-bottom: 2rem;
    }

    .form-label {
        display: block;
        margin-bottom: 1rem;
        font-weight: 600;
        font-size: 1.2rem;
        color: var(--text-primary);
    }

    .time-inputs {
        display: flex;
        gap: 1rem;
        align-items: center;
        font-size: 1.1rem;
    }

    .form-input {
        padding: 1rem;
        border: 2px solid rgba(255, 255, 255, 0.2);
        border-radius: 8px;
        font-size: 1.2rem;
        width: 100px;
        text-align: center;
        background: rgba(255, 255, 255, 0.1);
        color: var(--text-primary);
    }

    .form-input:focus {
        outline: none;
        border-color: var(--sprues-color);
        background: rgba(255, 255, 255, 0.15);
    }

    .toggle {
        display: flex;
        align-items: center;
        gap: 1rem;
    }

    .toggle-switch {
        position: relative;
        width: 60px;
        height: 30px;
        background: rgba(255, 255, 255, 0.2);
        border-radius: 15px;
        cursor: pointer;
        transition: background 0.3s;
    }

    .toggle-switch.active {
        background: var(--sprues-color);
    }

    .toggle-slider {
        position: absolute;
        top: 3px;
        left: 3px;
        width: 24px;
        height: 24px;
        background: white;
        border-radius: 50%;
        transition: transform 0.3s;
    }

    .toggle-switch.active .toggle-slider {
        transform: translateX(30px);
    }

    .flash-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(255, 255, 255, 0.3);
        display: none;
        z-index: 500;
        animation: flash 1s ease-out;
    }

    @keyframes flash {
        0% { opacity: 0; }
        25% { opacity: 1; }
        75% { opacity: 1; }
        100% { opacity: 0; }
    }

    .sr-only {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    /* Hide cursor after inactivity */
    .cursor-timer {
        position: fixed;
        top: -100px;
        opacity: 0;
        pointer-events: none;
    }

    /* Mobile optimizations */
    @media (max-width: 768px) {
        .container {
            padding: 1rem;
        }

        .current-shift {
            font-size: clamp(4rem, 18vw, 12rem);
        }

        .time-display {
            font-size: clamp(3rem, 12vw, 8rem);
        }

        .next-indicator {
            font-size: clamp(1rem, 3vw, 2rem);
            bottom: 2rem;
        }

        .modal-content {
            padding: 2rem;
            margin: 1rem;
        }

        .controls {
            grid-template-columns: 1fr;
        }
    }

    @media (max-width: 480px) {
        .progress-container {
            width: 250px;
            height: 250px;
        }
    }

    /* Large screen optimizations */
    @media (min-width: 1920px) {
        .current-shift {
            font-size: clamp(12rem, 15vw, 20rem);
        }

        .time-display {
            font-size: clamp(8rem, 10vw, 14rem);
        }
    }
</style>
```

</head>
<body>
    <div class="container">
        <!-- Status bar at top -->
        <div class="status-bar sprues" id="statusBar"></div>

```
    <!-- Flash overlay for visual feedback -->
    <div class="flash-overlay" id="flashOverlay"></div>

    <!-- Hidden settings trigger -->
    <button class="settings-trigger" id="settingsBtn" aria-label="Open settings"></button>

    <!-- Main timer interface -->
    <div class="timer-card" id="timerCard">
        <div class="status-indicator">NOW:</div>
        <h1 class="current-shift sprues" id="currentShift" aria-live="polite">Sprues</h1>
        
        <div class="progress-container">
            <svg class="progress-ring" viewBox="0 0 200 200">
                <circle class="progress-ring-bg" cx="100" cy="100" r="90"></circle>
                <circle class="progress-ring-progress" id="progressRing" cx="100" cy="100" r="90" 
                        stroke-dasharray="565.48" stroke-dashoffset="565.48"></circle>
            </svg>
            <div class="time-display" id="timeDisplay" aria-live="polite">5:00</div>
        </div>

        <div class="next-indicator" id="nextIndicator">Next: Males (in 5:00)</div>

        <!-- Screen reader descriptions -->
        <div id="startPauseDesc" class="sr-only">Start or pause the timer. Keyboard shortcut: Space</div>
        <div id="resetDesc" class="sr-only">Reset the timer to the beginning. Keyboard shortcut: R</div>
        <div id="skipDesc" class="sr-only">Skip to the next shift. Keyboard shortcut: S</div>
    </div>

    <!-- ARIA live region for announcements -->
    <div id="announcements" class="sr-only" aria-live="assertive" aria-atomic="true"></div>
</div>

<!-- Settings Modal -->
<div class="modal" id="settingsModal" role="dialog" aria-labelledby="modalTitle" aria-modal="true">
    <div class="modal-content">
        <div class="modal-header">
            <h2 class="modal-title" id="modalTitle">Production Line Settings</h2>
            <button class="close-btn" id="closeModalBtn" aria-label="Close settings">&times;</button>
        </div>

        <!-- Timer Controls Section -->
        <div class="controls-section">
            <h3 class="controls-title">Timer Controls</h3>
            <div class="controls">
                <button class="btn btn-primary" id="startPauseBtn" aria-describedby="startPauseDesc">Start</button>
                <button class="btn btn-secondary" id="resetBtn" aria-describedby="resetDesc">Reset</button>
                <button class="btn btn-secondary" id="skipBtn" aria-describedby="skipDesc">Skip</button>
            </div>
        </div>

        <!-- Duration Settings -->
        <form id="settingsForm">
            <div class="form-group">
                <label class="form-label">Sprues Duration</label>
                <div class="time-inputs">
                    <input type="number" class="form-input" id="spruesMinutes" min="0" max="59" value="5" aria-label="Sprues minutes">
                    <span>min</span>
                    <input type="number" class="form-input" id="spruesSeconds" min="0" max="59" value="0" aria-label="Sprues seconds">
                    <span>sec</span>
                </div>
            </div>

            <div class="form-group">
                <label class="form-label">Males Duration</label>
                <div class="time-inputs">
                    <input type="number" class="form-input" id="malesMinutes" min="0" max="59" value="2" aria-label="Males minutes">
                    <span>min</span>
                    <input type="number" class="form-input" id="malesSeconds" min="0" max="59" value="0" aria-label="Males seconds">
                    <span>sec</span>
                </div>
            </div>

            <div class="form-group">
                <div class="toggle">
                    <label class="form-label">Visual Flash Alert</label>
                    <div class="toggle-switch active" id="alertToggle" role="switch" aria-checked="true" aria-label="Toggle visual alerts" tabindex="0">
                        <div class="toggle-slider"></div>
                    </div>
                </div>
            </div>

            <button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 2rem;">Save Settings</button>
        </form>
    </div>
</div>

<script>
    /**
     * Production Line Timer - Traffic System for Plastic Conveyer
     * Full-screen display alternating between Sprues and Males shifts
     */
    class ProductionTimer {
        constructor() {
            // Timer state
            this.isRunning = false;
            this.currentShift = 'sprues'; // 'sprues' or 'males'
            this.startTime = null;
            this.pausedTime = 0;
            this.animationFrame = null;
            this.cursorTimeout = null;

            // Default durations (in seconds)
            this.durations = {
                sprues: 300, // 5 minutes
                males: 120   // 2 minutes
            };

            // Settings
            this.settings = {
                visualAlert: true
            };

            // DOM elements
            this.elements = {
                statusBar: document.getElementById('statusBar'),
                currentShift: document.getElementById('currentShift'),
                timeDisplay: document.getElementById('timeDisplay'),
                nextIndicator: document.getElementById('nextIndicator'),
                startPauseBtn: document.getElementById('startPauseBtn'),
                resetBtn: document.getElementById('resetBtn'),
                skipBtn: document.getElementById('skipBtn'),
                progressRing: document.getElementById('progressRing'),
                timerCard: document.getElementById('timerCard'),
                flashOverlay: document.getElementById('flashOverlay'),
                announcements: document.getElementById('announcements'),
                settingsModal: document.getElementById('settingsModal'),
                settingsBtn: document.getElementById('settingsBtn'),
                closeModalBtn: document.getElementById('closeModalBtn'),
                settingsForm: document.getElementById('settingsForm'),
                spruesMinutes: document.getElementById('spruesMinutes'),
                spruesSeconds: document.getElementById('spruesSeconds'),
                malesMinutes: document.getElementById('malesMinutes'),
                malesSeconds: document.getElementById('malesSeconds'),
                alertToggle: document.getElementById('alertToggle')
            };

            // Initialize
            this.loadSettings();
            this.updateDisplay();
            this.bindEvents();
            this.updateSettingsForm();
            this.initCursorBehavior();
        }

        /**
         * Initialize cursor hiding behavior
         */
        initCursorBehavior() {
            this.hideCursor();
            
            // Show cursor on mouse movement
            document.addEventListener('mousemove', () => {
                this.showCursor();
                clearTimeout(this.cursorTimeout);
                this.cursorTimeout = setTimeout(() => {
                    if (!this.elements.settingsModal.classList.contains('show')) {
                        this.hideCursor();
                    }
                }, 3000);
            });
        }

        showCursor() {
            document.body.classList.add('show-cursor');
        }

        hideCursor() {
            document.body.classList.remove('show-cursor');
        }

        /**
         * Load settings from localStorage
         */
        loadSettings() {
            try {
                const savedSettings = localStorage.getItem('productionTimerSettings');
                if (savedSettings) {
                    const parsed = JSON.parse(savedSettings);
                    Object.assign(this.durations, parsed.durations || {});
                    Object.assign(this.settings, parsed.settings || {});
                }
            } catch (e) {
                console.warn('Failed to load settings from localStorage:', e);
            }
        }

        /**
         * Save settings to localStorage
         */
        saveSettings() {
            try {
                const settingsData = {
                    durations: this.durations,
                    settings: this.settings
                };
                localStorage.setItem('productionTimerSettings', JSON.stringify(settingsData));
            } catch (e) {
                console.warn('Failed to save settings to localStorage:', e);
            }
        }

        /**
         * Bind event listeners
         */
        bindEvents() {
            // Main controls
            this.elements.startPauseBtn.addEventListener('click', () => this.toggleTimer());
            this.elements.resetBtn.addEventListener('click', () => this.resetTimer());
            this.elements.skipBtn.addEventListener('click', () => this.skipShift());

            // Settings modal
            this.elements.settingsBtn.addEventListener('click', () => this.openSettings());
            this.elements.closeModalBtn.addEventListener('click', () => this.closeSettings());
            this.elements.settingsForm.addEventListener('submit', (e) => this.saveSettingsForm(e));

            // Alert toggle
            this.elements.alertToggle.addEventListener('click', () => this.toggleAlert());
            this.elements.alertToggle.addEventListener('keydown', (e) => {
                if (e.key === 'Enter' || e.key === ' ') {
                    e.preventDefault();
                    this.toggleAlert();
                }
            });

            // Modal overlay click to close
            this.elements.settingsModal.addEventListener('click', (e) => {
                if (e.target === this.elements.settingsModal) {
                    this.closeSettings();
                }
            });

            // Keyboard shortcuts
            document.addEventListener('keydown', (e) => this.handleKeyboard(e));

            // Handle tab visibility changes for accurate timing
            document.addEventListener('visibilitychange', () => {
                if (!document.hidden && this.isRunning) {
                    this.updateTimer();
                }
            });

            // Double click to open settings (for kiosk mode)
            this.elements.currentShift.addEventListener('dblclick', () => {
                this.openSettings();
            });
        }

        /**
         * Handle keyboard shortcuts
         */
        handleKeyboard(e) {
            // Don't trigger shortcuts when modal is open or typing in inputs
            if (this.elements.settingsModal.classList.contains('show') || 
                e.target.tagName === 'INPUT') {
                return;
            }

            switch (e.key.toLowerCase()) {
                case ' ':
                    e.preventDefault();
                    this.toggleTimer();
                    break;
                case 'r':
                    e.preventDefault();
                    this.resetTimer();
                    break;
                case 's':
                    e.preventDefault();
                    this.skipShift();
                    break;
                case 'escape':
                    if (this.elements.settingsModal.classList.contains('show')) {
                        this.closeSettings();
                    } else {
                        this.stopTimer();
                    }
                    break;
                case 'enter':
                    e.preventDefault();
                    this.toggleTimer();
                    break;
            }
        }

        /**
         * Toggle timer start/pause
         */
        toggleTimer() {
            if (this.getDuration(this.currentShift) <= 0) {
                this.announce('Cannot start timer: duration must be greater than zero');
                return;
            }

            if (this.isRunning) {
                this.pauseTimer();
            } else {
                this.startTimer();
            }
        }

        /**
         * Start the timer
         */
        startTimer() {
            this.isRunning = true;
            this.startTime = performance.now() - this.pausedTime;
            this.elements.startPauseBtn.textContent = 'Pause';
            this.updateTimer();
            this.announce(`${this.currentShift.toUpperCase()} timer started`);
        }

        /**
         * Pause the timer
         */
        pauseTimer() {
            this.isRunning = false;
            if (this.animationFrame) {
                cancelAnimationFrame(this.animationFrame);
            }
            this.elements.startPauseBtn.textContent = 'Start';
            this.announce('Timer paused');
        }

        /**
         * Stop the timer completely
         */
        stopTimer() {
            this.isRunning = false;
            this.pausedTime = 0;
            this.startTime = null;
            if (this.animationFrame) {
                cancelAnimationFrame(this.animationFrame);
            }
            this.elements.startPauseBtn.textContent = 'Start';
            this.updateDisplay();
            this.announce('Timer stopped');
        }

        /**
         * Reset the timer
         */
        resetTimer() {
            this.stopTimer();
            this.announce('Timer reset');
        }

        /**
         * Skip to next shift
         */
        skipShift() {
            const wasRunning = this.isRunning;
            this.switchShift();
            
            if (wasRunning) {
                this.startTimer();
            } else {
                this.resetTimer();
            }
            
            this.announce(`Switched to ${this.currentShift.toUpperCase()}`);
        }

        /**
         * Switch to the next shift
         */
        switchShift() {
            this.currentShift = this.currentShift === 'sprues' ? 'males' : 'sprues';
            this.pausedTime = 0;
            this.startTime = performance.now();
            
            // Trigger transition animation
            this.elements.timerCard.classList.add('shift-transition');
            setTimeout(() => {
                this.elements.timerCard.classList.remove('shift-transition');
            }, 500);

            // Show visual alert if enabled
            if (this.settings.visualAlert) {
                this.showFlash();
            }

            this.updateDisplay();
            this.announce(`Now showing: ${this.currentShift.toUpperCase()}`);
        }

        /**
         * Show flash overlay for visual feedback
         */
        showFlash() {
            this.elements.flashOverlay.style.display = 'block';
            this.elements.flashOverlay.style.animation = 'none';
            // Force reflow
            this.elements.flashOverlay.offsetHeight;
            this.elements.flashOverlay.style.animation = 'flash 1s ease-out';
            
            setTimeout(() => {
                this.elements.flashOverlay.style.display = 'none';
            }, 1000);
        }

        /**
         * Update timer - called every frame when running
         */
        updateTimer() {
            if (!this.isRunning) return;

            const elapsed = performance.now() - this.startTime;
            this.pausedTime = elapsed;
            
            const duration = this.getDuration(this.currentShift) * 1000; // Convert to ms
            const remaining = Math.max(0, duration - elapsed);

            if (remaining <= 0) {
                // Time's up - switch shifts
                this.switchShift();
                if (this.isRunning) {
                    this.startTimer();
                }
            } else {
                this.updateDisplay();
                this.animationFrame = requestAnimationFrame(() => this.updateTimer());
            }
        }

        /**
         * Update all display elements
         */
        updateDisplay() {
            const duration = this.getDuration(this.currentShift) * 1000;
            const elapsed = this.pausedTime;
            const remaining = Math.max(0, duration - elapsed);

            // Update current shift display
            this.elements.currentShift.textContent = this.capitalize(this.currentShift);
            this.elements.currentShift.className = `current-shift ${this.currentShift}`;

            // Update time display
            const timeString = this.formatTime(Math.ceil(remaining / 1000));
            this.elements.timeDisplay.textContent = timeString;

            // Update progress ring
            this.updateProgressRing(elapsed, duration);

            // Update next shift indicator
            this.updateNextIndicator();

            // Update progress ring color based on current shift
            const progressColor = this.currentShift === 'sprues' ? 
                'var(--sprues-color)' : 'var(--males-color)';
            this.elements.progressRing.style.stroke = progressColor;
        }

        /**
         * Update circular progress indicator
         */
        updateProgressRing(elapsed, duration) {
            const circumference = 2 * Math.PI * 90; // radius = 90
            const progress = Math.min(elapsed / duration, 1);
            const offset = circumference * (1 - progress);
            
            this.elements.progressRing.style.strokeDasharray = circumference;
            this.elements.progressRing.style.strokeDashoffset = offset;
        }

        /**
         * Update next shift indicator
         */
        updateNextIndicator() {
            const nextShift = this.currentShift === 'sprues' ? 'males' : 'sprues';
            const nextDuration = this.getDuration(nextShift);
            const nextTimeString = this.formatTime(nextDuration);
            
            this.elements.nextIndicator.textContent = 
                `Next: ${this.capitalize(nextShift)} (${nextTimeString})`;
        }

        /**
         * Open settings modal
         */
        openSettings() {
            this.elements.settingsModal.classList.add('show');
            this.elements.settingsModal.focus();
            this.updateSettingsForm();
            this.showCursor();
        }

        /**
         * Close settings modal
         */
        closeSettings() {
            this.elements.settingsModal.classList.remove('show');
            this.elements.settingsBtn.focus();
            this.hideCursor();
        }

        /**
         * Update settings form with current values
         */
        updateSettingsForm() {
            const spruesTotal = this.durations.sprues;
            const malesTotal = this.durations.males;

            this.elements.spruesMinutes.value = Math.floor(spruesTotal / 60);
            this.elements.spruesSeconds.value = spruesTotal % 60;
            this.elements.malesMinutes.value = Math.floor(malesTotal / 60);
            this.elements.malesSeconds.value = malesTotal % 60;

            // Update toggle
            this.elements.alertToggle.classList.toggle('active', this.settings.visualAlert);
            this.elements.alertToggle.setAttribute('aria-checked', this.settings.visualAlert);

            // Update start/pause button text
            this.elements.startPauseBtn.textContent = this.isRunning ? 'Pause' : 'Start';
        }

        /**
         * Save settings form
         */
        saveSettingsForm(e) {
            e.preventDefault();

            // Validate and save durations
            const spruesMinutes = parseInt(this.elements.spruesMinutes.value) || 0;
            const spruesSeconds = parseInt(this.elements.spruesSeconds.value) || 0;
            const malesMinutes = parseInt(this.elements.malesMinutes.value) || 0;
            const malesSeconds = parseInt(this.elements.malesSeconds.value) || 0;

            const spruesTotal = spruesMinutes * 60 + spruesSeconds;
            const malesTotal = malesMinutes * 60 + malesSeconds;

            // Validation
            if (spruesTotal <= 0 || malesTotal <= 0) {
                this.announce('Error: Both shift durations must be greater than zero');
                return;
            }

            if (spruesTotal > 3600 || malesTotal > 3600) {
                this.announce('Error: Maximum duration is 60 minutes');
                return;
            }

            // Save new durations
            this.durations.sprues = spruesTotal;
            this.durations.males = malesTotal;

            this.saveSettings();
            this.closeSettings();
            this.resetTimer();
            this.announce('Settings saved successfully');
        }

        /**
         * Toggle visual alert setting
         */
        toggleAlert() {
            this.settings.visualAlert = !this.settings.visualAlert;
            this.elements.alertToggle.classList.toggle('active', this.settings.visualAlert);
            this.elements.alertToggle.setAttribute('aria-checked', this.settings.visualAlert);
            this.saveSettings();
        }

        /**
         * Get duration for a shift in seconds
         */
        getDuration(shift) {
            return this.durations[shift] || 0;
        }

        /**
         * Format time in MM:SS format
         */
        formatTime(seconds) {
            const mins = Math.floor(seconds / 60);
            const secs = seconds % 60;
            return `${mins}:${secs.toString().padStart(2, '0')}`;
        }

        /**
         * Announce message to screen readers
         */
        announce(message) {
            this.elements.announcements.textContent = message;
            // Clear after a short delay to allow for new announcements
            setTimeout(() => {
                this.elements.announcements.textContent = '';
            }, 1000);
        }
    }

    // Initialize the timer when DOM is loaded
    document.addEventListener('DOMContentLoaded', () => {
        new ProductionTimer();
    });
</script>
```

</body>
</html>
Editor is loading...
Leave a Comment