-1
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TrueTone | Industrial Calibration</title>
    <style>
        /* CSS RESET & VARIABLES */
        :root {
            --bg-color: #0f0f0f; --text-main: #e0e0e0; --text-muted: #9e9e9e;
            --accent: #ffffff; --border: #333333; --success: #4CAF50;
            --fail: #F44336; --cyan: #00bcd4; --magenta: #e91e63; --yellow: #FFC107;
            --grid-color: #2a2a2c;
            --font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            --font-mono: "Courier New", Courier, monospace;
        }

        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { background-color: var(--bg-color); color: var(--text-main); font-family: var(--font-main); line-height: 1.6; padding-bottom: 5rem; overflow-x: hidden; }

        h2 { text-transform: uppercase; letter-spacing: 2px; margin-bottom: 1.5rem; font-weight: 900; }

        .container { max-width: 1050px; margin: 0 auto; padding: 0 2rem; position: relative; }
        header { padding: 2rem 0; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid var(--border); margin-bottom: 3rem;}
        
        .logo { font-family: var(--font-main); font-size: 1.8rem; font-weight: 900; letter-spacing: 4px; color: var(--accent); border: 3px solid var(--accent); padding: 8px 16px; border-radius: 4px; text-transform: uppercase;}

        .user-badge { display: flex; align-items: center; gap: 15px; flex-wrap: wrap;}
        .vdi-display { font-family: var(--font-mono); font-weight: bold; color: var(--cyan); border: 1px solid var(--cyan); padding: 4px 10px; border-radius: 2px; letter-spacing: 1px; transition: color 0.2s, border-color 0.2s; cursor: pointer; user-select: none;}
        .vdi-display:hover { background: rgba(0, 188, 212, 0.1); box-shadow: 0 0 10px rgba(0, 188, 212, 0.2); }
        .vdi-updating { color: var(--accent) !important; border-color: var(--accent) !important; }

        .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(5px); display: flex; justify-content: center; align-items: center; z-index: 1000; }
        .auth-box { background-color: #111; border: 4px solid #444; padding: 3rem; width: 100%; max-width: 450px; box-shadow: 0 20px 50px rgba(0,0,0,1); position: relative; }
        .auth-header { font-family: var(--font-mono); font-size: 1.2rem; text-transform: uppercase; letter-spacing: 2px; color: #888; margin-bottom: 2rem; border-bottom: 2px dashed #444; padding-bottom: 1rem; }
        .input-group { margin-bottom: 1.5rem; }
        .input-group label { display: block; font-family: var(--font-mono); font-size: 0.8rem; text-transform: uppercase; color: #888; margin-bottom: 0.5rem; }
        .industrial-input { width: 100%; background: #0a0a0a; border: 2px solid #333; color: var(--accent); padding: 12px; font-family: var(--font-mono); font-size: 1rem; outline: none; transition: border-color 0.2s; }
        .industrial-input:focus { border-color: var(--cyan); }
        .close-modal { position: absolute; top: 15px; right: 20px; color: #888; font-size: 1.5rem; cursor: pointer; font-family: var(--font-mono); }
        .close-modal:hover { color: var(--accent); }

        .stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1.5rem; }
        .stat-card { background: #1a1a1c; border: 1px solid #333; padding: 1.5rem 1rem; text-align: center; border-radius: 4px; }
        .stat-label { font-size: 0.65rem; color: #888; text-transform: uppercase; letter-spacing: 2px; }
        .stat-value { font-family: var(--font-mono); font-size: 1.8rem; color: var(--accent); font-weight: bold; margin-top: 0.5rem; }

        .module-box { display: grid; grid-template-columns: 80px 1fr; background-color: #161616; border: 2px solid #444; border-radius: 4px; margin: 3rem 0; box-shadow: 0 10px 30px rgba(0,0,0,0.8); min-height: 300px; overflow: hidden; }
        .module-sidebar { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 4px; background: #222; border-right: 1px solid #333; color: var(--cyan); display: flex; align-items: center; justify-content: center; font-weight: bold; }
        .module-content { padding: 3rem 2rem; display: flex; flex-direction: column; align-items: center; width: 100%; position: relative;}

        .comparison-wrapper { width: 100%; display: flex; flex-direction: column; align-items: center; margin: 1rem auto; }
        .labels { display: flex; width: 288px; justify-content: space-between; margin-bottom: 0.8rem; }
        .labels span { flex: 1; text-align: center; font-size: 0.8rem; letter-spacing: 2px; text-transform: uppercase; color: #888; font-weight: bold; }
        .comparison-board { display: flex; justify-content: center; border: 4px solid #222; border-radius: 4px; overflow: hidden; width: 288px; box-shadow: 0 8px 25px rgba(0,0,0,0.6); }
        .swatch { width: 140px; height: 140px; }
        .hidden { display: none !important; }
        
        .controls-wrapper { display: flex; align-items: center; justify-content: center; gap: 1.5rem; margin: 1rem 0; position: relative; z-index: 100; flex-wrap: wrap;}
        .slider-group { display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 400px; margin-bottom: 1.5rem; }
        .slider-label { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 2px; color: #888; margin-bottom: 0.8rem; font-weight: bold; width: 100%; text-align: left; }
        
        .icon-btn { background-color: #222; color: var(--accent); border: 2px solid #555; width: 45px; height: 45px; border-radius: 4px; font-size: 1.5rem; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; font-weight: bold;}
        .icon-btn:hover { background-color: #444; border-color: #888; }
        .icon-btn:active { transform: scale(0.95); }
        
        input[type="range"] { width: 100%; cursor: pointer; position: relative; z-index: 101; height: 12px; background: #444; outline: none; border-radius: 4px; -webkit-appearance: none; border: 1px solid #222;}
        input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 24px; height: 24px; background: var(--accent); border-radius: 4px; cursor: pointer; border: 2px solid #111; box-shadow: 0 2px 5px rgba(0,0,0,0.5);}

        .btn-container { display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
        .btn { background-color: #ddd; color: #111; border: 2px solid #ddd; padding: 1rem 2rem; font-size: 0.9rem; font-weight: bold; border-radius: 4px; cursor: pointer; transition: all 0.2s; text-transform: uppercase; letter-spacing: 2px; }
        .btn:hover { background-color: #fff; border-color: #fff; }
        .btn-outline { background-color: transparent; color: #aaa; border: 2px solid #555; }
        .btn-outline:hover { background-color: #333; color: #fff; border-color: #888;}
        .btn-active { background-color: var(--cyan) !important; color: #000 !important; border-color: var(--cyan) !important; box-shadow: 0 0 15px rgba(0, 188, 212, 0.4) !important;}
        
        .result-text { margin-top: 1.5rem; font-weight: bold; font-size: 1.3rem; min-height: 28px; text-align: center; }

        @keyframes slam { 0% { transform: scale(2.5); opacity: 0; } 50% { transform: scale(0.95); opacity: 1; } 100% { transform: scale(1); } }
        @keyframes mechanical-shake { 0% { transform: translate(0, 0); } 20% { transform: translate(-2px, 1px); } 40% { transform: translate(2px, -1px); } 60% { transform: translate(-1px, 2px); } 80% { transform: translate(1px, -1px); } 100% { transform: translate(0, 0); } }
        @keyframes slideInUp { 0% { transform: translateY(20px); opacity: 0; } 100% { transform: translateY(0); opacity: 1; } }

        .feedback-stamp-container { display: flex; justify-content: center; gap: 1rem; margin-top: 2rem; flex-wrap: wrap; }
        .stamp { border: 3px solid #555; border-radius: 4px; padding: 10px 15px; font-family: var(--font-main); font-weight: 900; font-size: 1.1rem; text-transform: uppercase; letter-spacing: 1px; color: #888; display: flex; align-items: center; gap: 8px; background: #111; min-width: 120px; justify-content: center;}
        .stamp.ok { border-color: var(--success); color: var(--success); }
        .stamp.fail { border-color: var(--fail); color: var(--fail); }
        .stamp-icon { font-size: 1.3rem; }
        .stamp-animated { animation: slam 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards, mechanical-shake 0.15s 0.25s ease-in-out; }

        .hint-text { text-align: left; font-family: var(--font-mono); font-size: 0.9rem; margin-top: 1.5rem; border: 1px dashed #FFEB3B; padding: 15px 20px; border-radius: 4px; background: rgba(255, 235, 59, 0.05); display: inline-block; animation: slideInUp 0.3s ease-out; width: 100%; max-width: 560px; }
        .diagnostic-header { color: #888; font-weight: bold; text-transform: uppercase; margin-bottom: 10px; letter-spacing: 1px; border-bottom: 1px solid #444; padding-bottom: 5px; }
        .diagnostic-list { list-style-type: square; padding-left: 20px; margin: 0; line-height: 1.8; color: #e0e0e0; }
        .diagnostic-list span { color: #FFEB3B; font-weight: bold; }

        .procreate-workspace { position: relative; width: 100%; max-width: 800px; aspect-ratio: 16/9; background-color: #1a1a1c; border-radius: 8px; overflow: hidden; box-shadow: 0 0 0 2px #333, 0 15px 30px rgba(0,0,0,0.8), inset 0 0 40px rgba(0,0,0,0.6); margin-top: 1rem; }
        .main-canvas { width: 100%; height: 100%; cursor: crosshair; touch-action: none; display: block; }
        
        .reference-cam-container { position: absolute; top: 20px; left: 20px; width: 25%; background: #111; border: 2px solid #555; border-radius: 6px; box-shadow: 0 10px 25px rgba(0,0,0,0.9); z-index: 10; overflow: hidden; pointer-events: none; }
        .reference-cam-label { background: #222; color: var(--cyan); font-family: var(--font-mono); font-size: 0.6rem; text-transform: uppercase; letter-spacing: 2px; padding: 4px 8px; border-bottom: 1px solid #444; font-weight: bold; }
        .reference-canvas { width: 100%; height: auto; display: block; background: #1a1a1c; }

        .floating-toolbar { position: absolute; top: 20px; right: 20px; display: flex; flex-direction: column; gap: 12px; z-index: 10; }
        .tool-btn { background: rgba(17, 17, 17, 0.85); backdrop-filter: blur(8px); border: 1px solid #555; color: #ccc; font-family: var(--font-mono); font-weight: bold; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 1px; padding: 12px 15px; border-radius: 6px; cursor: pointer; transition: all 0.2s; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
        .tool-btn:hover { background: #333; color: #fff; border-color: #888; }
        .verify-btn { background: var(--accent); color: #000; margin-top: 10px; border-color: var(--accent); }
        .verify-btn:hover { background: var(--success); border-color: var(--success); }

        .floating-result { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); padding: 10px 20px; border-radius: 4px; border: 1px solid #444; font-family: var(--font-mono); font-size: 1rem; font-weight: bold; z-index: 10; white-space: nowrap; }
        
        .loading-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #1a1a1c; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 15; font-family: var(--font-mono); color: var(--cyan); text-transform: uppercase; letter-spacing: 2px; text-align: center; padding: 20px; }
        .loading-sub { font-size: 0.7rem; color: #888; margin-top: 10px; }
        .metadata-tag { position: absolute; bottom: 10px; left: 20px; background: rgba(0,0,0,0.7); padding: 5px 10px; border-radius: 4px; border: 1px solid #444; font-family: var(--font-mono); font-size: 0.7rem; color: #888; pointer-events: none; z-index: 10; }
        
        .floating-reward { position: absolute; font-family: var(--font-mono); font-weight: bold; text-shadow: 0 2px 10px rgba(0,0,0,1); pointer-events: none; z-index: 2000; transition: all 1.2s cubic-bezier(0.25, 1, 0.5, 1); display: flex; flex-direction: column; align-items: center; }
        .reward-credits { font-size: 1.2rem; color: var(--success); }
        .reward-vdi { font-size: 0.9rem; color: var(--cyan); margin-top: -5px; }
    </style>
</head>
<body>

    <div id="authModal" class="modal-overlay hidden">
        <div class="auth-box">
            <span class="close-modal" id="closeModal">X</span>
            <div class="auth-header" id="authTitle">Secure Access Port</div>
            <div class="input-group"><label>Callsign / Username</label><input type="text" id="authUsername" class="industrial-input" placeholder="ENTER ID"></div>
            <div class="input-group"><label>Passcode</label><input type="password" id="authPassword" class="industrial-input" placeholder="••••••••"></div>
            <div id="authError" class="stamp fail hidden" style="margin-bottom: 1.5rem; font-size: 0.9rem; padding: 5px;"><span class="stamp-icon">🚫</span> AUTH FAILED</div>
            <button class="btn" id="submitAuthBtn" style="width: 100%; margin: 0;">Initialize</button>
            <p style="text-align: center; margin-top: 1rem; font-size: 0.8rem; font-family: var(--font-mono); cursor: pointer;" id="toggleAuthMode">New User? Register Here.</p>
        </div>
    </div>

    <div id="statsModal" class="modal-overlay hidden">
        <div class="auth-box" style="max-width: 650px;">
            <span class="close-modal" id="closeStatsModal">X</span>
            <div class="auth-header">Telemetric Profile Log</div>
            <div class="stats-grid">
                <div class="stat-card"><div class="stat-label">Value Drift</div><div class="stat-value" id="statValueVDI">--</div></div>
                <div class="stat-card"><div class="stat-label">Chroma Drift</div><div class="stat-value" id="statChromaVDI">--</div></div>
                <div class="stat-card"><div class="stat-label">Spatial Drift</div><div class="stat-value" id="statSpatialVDI">--</div></div>
                <div class="stat-card"><div class="stat-label">Anchor Extrema</div><div class="stat-value" id="statAnchorVDI" style="color: var(--cyan);">--</div></div>
            </div>
            <p style="text-align: center; font-size: 0.8rem; color: #666; font-family: var(--font-mono); text-transform: uppercase;">Total Shifts Logged: <span id="statTotalRounds" style="color: var(--success); font-weight: bold;">--</span></p>
            <div class="hint-text" style="width: 100%; max-width: 100%; margin-top: 1rem;">
                <div class="diagnostic-header">Perception Bias Insight</div>
                <div id="diagnosticInsight" style="color: #e0e0e0; line-height: 1.6;">Insufficient data. Clock in and complete more shifts.</div>
            </div>
        </div>
    </div>

    <div class="container">
        <header>
            <div class="logo">TRUETONE</div>
            <nav id="navArea"><button class="btn btn-outline" id="loginBtn" style="padding: 0.5rem 1.5rem; font-size: 0.8rem;">Log In</button></nav>
        </header>

        <section class="hero">
            <h1 style="font-family: var(--font-main); font-weight: 900; letter-spacing: -1px; text-transform: uppercase;">Calibrate Your Eye</h1>
            <p style="text-transform: uppercase; letter-spacing: 1px; font-size: 0.9rem; font-weight: bold; color: #666;">Master Value. Mix Chroma. Replicate Form.</p>
        </section>

        <section class="module-box" id="module1">
            <div class="module-sidebar">Module 01</div>
            <div class="module-content">
                <h2>Value Match</h2>
                <div class="comparison-wrapper">
                    <div class="labels"><span>Target</span><span>Match</span></div>
                    <div class="comparison-board"><div class="swatch" id="targetValue"></div><div class="swatch" id="userValue"></div></div>
                </div>
                <div class="controls-wrapper">
                    <button type="button" class="icon-btn" id="btnMinus">-</button>
                    <div class="slider-group" style="margin-bottom: 0;"><input type="range" id="valueSlider" min="0" max="255" value="255"></div>
                    <button type="button" class="icon-btn" id="btnPlus">+</button>
                </div>
                <div class="btn-container">
                    <button type="button" class="btn" id="checkBtn">Verify Match</button>
                    <button type="button" class="btn btn-outline hidden" id="nextBtn">Next Challenge</button>
                </div>
                <div id="result" class="result-text"></div>
            </div>
        </section>

        <section class="module-box" id="module3">
            <div class="module-sidebar">Module 02</div>
            <div class="module-content">
                <h2>Color Theory</h2>
                <div class="comparison-wrapper">
                    <div class="labels"><span>Target</span><span>Match</span></div>
                    <div class="comparison-board"><div class="swatch" id="targetChroma"></div><div class="swatch" id="userChroma"></div></div>
                </div>
                <div class="controls-wrapper" style="flex-direction: column; gap: 1rem; width: 100%;">
                    <div class="slider-group"><div class="slider-label">Hue</div><input type="range" id="hueSlider" min="0" max="360" value="0" style="background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);"></div>
                    <div class="slider-group"><div class="slider-label">Saturation</div><input type="range" id="satSlider" min="0" max="100" value="50"></div>
                    <div class="slider-group"><div class="slider-label">Lightness</div><input type="range" id="litSlider" min="0" max="100" value="50" style="background: linear-gradient(to right, #000, #fff);"></div>
                </div>
                <div class="btn-container">
                    <button type="button" class="btn" id="checkChromaBtn">Verify Mix</button>
                    <button type="button" class="btn btn-outline hidden" id="nextChromaBtn">Next Challenge</button>
                </div>
                <div id="chromaFeedback" class="feedback-stamp-container hidden">
                    <div class="stamp" id="stampHue">HUE <span class="stamp-icon" id="iconHue"></span></div>
                    <div class="stamp" id="stampSat">SAT <span class="stamp-icon" id="iconSat"></span></div>
                    <div class="stamp" id="stampLit">LGT <span class="stamp-icon" id="iconLit"></span></div>
                </div>
                <div id="chromaHint" class="hint-text hidden"></div>
                <div id="chromaResult" class="result-text" style="margin-top: 1rem;"></div>
            </div>
        </section>

        <section class="module-box" id="module2">
            <div class="module-sidebar">Module 03</div>
            <div class="module-content">
                <h2>Spatial Drafting</h2>
                <div class="procreate-workspace">
                    <div class="reference-cam-container">
                        <div class="reference-cam-label">Target Data</div>
                        <canvas id="targetPad" width="800" height="450" class="reference-canvas"></canvas>
                    </div>
                    <canvas id="drawPad" width="800" height="450" class="main-canvas"></canvas>
                    <div class="floating-toolbar">
                        <button class="tool-btn" id="toggleGridBtn" title="Toggle Grid Overlay">Grid</button>
                        <button class="tool-btn" id="toggleEraserBtn" title="Toggle Eraser">Eraser</button>
                        <button class="tool-btn" id="clearDrawBtn" title="Clear Canvas">Clear</button>

Hi all,

I am building a vanilla JS art calibration tool that requires users to identify the absolute lightest and darkest pixels in Old Master paintings.

The Problem: > I’m running a local server (python3 -m http.server), but I am constantly getting errors when calling ctx.getImageData().

What I’ve tried:

  1. Setting img.crossOrigin = "anonymous"; before the src.

  2. Using Wikimedia Commons direct URLs (which supposedly support CORS).

  3. Cache-busting with Date.now() (this actually caused the host to reject the request).

  4. Public CORS proxies (these get flagged by global firewalls/Jagex/Imperva).

It seems like browsers are inconsistently caching the images without the proper headers, leading to a permanent lock on the getImageData method.

The Question: Is there a way to force a 'Clean Pipe' for pixel-level access on CC0 images without building a dedicated Node.js backend proxy? Are there specific art-friendly CDNs that guarantee the Access-Control-Allow-Origin: *header for canvas manipulation?

New contributor
Rafail is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
5
  • You might want to give How to Ask a read. The most important part of your post is the question. The place of most significance in your post is the beginning. That's where your question should be. The method of reproducing the symptom is important, but less so than the question itself, hence it should come after the question. Least important is the goal of your real code; that could potentially be omitted, unless you feel it adds something beyond bragging about your project. Commented 8 hours ago
  • 3
    That looks like a lot of CSS for a JavaScript question. And a lot of divs and buttons. The question should contain a minimal reproducible example, something short that demonstrates your issue, preferably as a Stack Overflow code snippet. Demonstrate "getting errors" or "pixel-level access" (whichever you want to ask about), not "a vanilla JS art calibration tool". Commented 8 hours ago
  • I was able to fetch a Wikimedia image and use getImageData without any problem. So I would guess there is some mistake in your code that loads the image, but you have not included that code in the question. Commented 4 hours ago
  • @Yogi Some time ago Rafail posted a snarky reply to JaMiT's comments, and told us that he had already solved this problem without telling us how. Then deleted it again. So don't expect anything useful from Rafail. Commented 4 hours ago
  • You forgot to tell us what error you're getting, and in what code. We can't fix what we can't see. Commented 4 hours ago

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.