<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Leibovitz</title> <meta name="description" content="Leibovitz is a web-based camera that lets you make fun photos."> <link rel="apple-touch-icon" sizes="57x57" href="apple-icon-57x57.png"> <link rel="apple-touch-icon" sizes="60x60" href="apple-icon-60x60.png"> <link rel="apple-touch-icon" sizes="72x72" href="apple-icon-72x72.png"> <link rel="apple-touch-icon" sizes="76x76" href="apple-icon-76x76.png"> <link rel="apple-touch-icon" sizes="114x114" href="apple-icon-114x114.png"> <link rel="apple-touch-icon" sizes="120x120" href="apple-icon-120x120.png"> <link rel="apple-touch-icon" sizes="144x144" href="apple-icon-144x144.png"> <link rel="apple-touch-icon" sizes="152x152" href="apple-icon-152x152.png"> <link rel="apple-touch-icon" sizes="180x180" href="apple-icon-180x180.png"> <link rel="icon" type="image/png" sizes="192x192" href="android-icon-192x192.png"> <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"> <link rel="icon" type="image/png" sizes="96x96" href="favicon-96x96.png"> <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"> <link rel="manifest" href="manifest.json"> <meta name="msapplication-TileColor" content="#ffffff"> <meta name="msapplication-TileImage" content="ms-icon-144x144.png"> <meta name="theme-color" content="#ffffff"> <style> @font-face { font-family: 'ChicagoFLF'; src: url('./ChicagoFLF.ttf') format('truetype'); font-weight: normal; font-style: normal; } body, html { margin: 0; padding: 0; width: 100%; height: 100%; display: flex; flex-direction: column; background-color: beige; overflow: hidden; font-family: 'ChicagoFLF', sans-serif; font-size: 16px; } .preview-container { flex: 1; position: relative; margin: 0; min-height: 0; } #canvas { width: 100%; height: 100%; object-fit: contain; display: none; background-color: transparent; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .slide-controls { position: absolute; bottom: 20px; left: 0; right: 0; display: none; justify-content: space-around; align-items: center; background-color: rgba(255, 255, 255, 0.8); padding: 10px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 10; margin: 0 20px; } .slide-controls.visible { display: flex; } .slider-group { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1; max-width: 200px; } .slider-group label { font-size: 12px; color: #666; font-family: 'ChicagoFLF', sans-serif; } .slider-group input[type="range"] { width: 100%; height: 4px; -webkit-appearance: none; background: rgba(0, 0, 0, 0.2); border-radius: 2px; outline: none; } .slider-group input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: teal; border-radius: 0; cursor: pointer; } .slider-group input[type="range"]::-moz-range-thumb { width: 16px; height: 16px; background: teal; border-radius: 0; cursor: pointer; border: none; } .slider-group .value { font-size: 12px; color: #666; font-family: 'ChicagoFLF', sans-serif; } .side-control { position: absolute; top: 50%; height: 100%; width: auto; transform: translateY(-50%); display: flex; flex-direction: column; align-items: center; gap: 1em; padding: 1em 0; background-color: rgba(255, 255, 255, 0.8); z-index: 10; } .side-control.left { left: 0; } .side-control.right { right: 0; } .vertical-slider { transform: rotate(-90deg); width: 200px; margin: 90px -80px; cursor: pointer; } input[type="range"]::-webkit-slider-thumb, .vertical-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; background: teal; border-radius: 0; cursor: pointer; } input[type="range"]::-webkit-slider-runnable-track, .vertical-slider::-webkit-slider-runnable-track { width: 100%; height: 4px; background: rgba(255, 255, 255, 0.8); border-radius: 2px; } input[type="range"]::-moz-range-thumb, .vertical-slider::-moz-range-thumb { width: 20px; height: 20px; background: teal; border-radius: 0; cursor: pointer; border: none; } input[type="range"]::-moz-range-track, .vertical-slider::-moz-range-track { width: 100%; height: 4px; background: rgba(255, 255, 255, 0.8); border-radius: 2px; } .vertical-label { transform: rotate(90deg); font-size: 12px; color: #666; user-select: none; white-space: nowrap; margin: 20px 0; font-family: 'ChicagoFLF', sans-serif; padding: 0 5px; } #blur-value { font-size: 12px; color: #666; user-select: none; white-space: nowrap; font-family: 'ChicagoFLF', sans-serif; padding: 0 5px; } #contrast-value { font-size: 12px; color: #666; user-select: none; white-space: nowrap; font-family: 'ChicagoFLF', sans-serif; padding: 0 5px; } #controls { width: 100%; display: flex; justify-content: space-between; align-items: center; gap: 0; padding: 0; background-color: rgba(255, 255, 255, 0.8); box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); flex-shrink: 0; } #settings-container { width: 100%; display: flex; flex-direction: column; gap: 10px; align-items: center; background-color: rgba(255, 255, 255, 0.8); padding: 10px; box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); flex-shrink: 0; } .top-controls { display: flex; gap: 10px; align-items: center; width: 100%; justify-content: center; flex-wrap: wrap; } .color-controls { display: flex; gap: 5px; align-items: center; } #reset-color { padding: 8px 12px; font-size: 16px; background: none; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; font-family: 'ChicagoFLF', sans-serif; } #reset-color:hover { background: #f0f0f0; } button, select, input[type="color"] { padding: 10px; font-size: 18px; cursor: pointer; font-family: 'ChicagoFLF', sans-serif; } select { padding: 10px 15px; font-family: 'ChicagoFLF', sans-serif; } #dither-select { min-width: 200px; } button.capture { background-color: teal; color: #FFFFFF; padding: 10px 20px; font-family: 'ChicagoFLF', sans-serif; } #toggle-camera { padding: 10px 20px; font-family: 'ChicagoFLF', sans-serif; } button, select { border: none; cursor: pointer; transition: background-color 0.3s ease; } button:hover, button:focus { outline: none; } button.capture:disabled { background-color: #ccc; color: #5c5c5c; } .contrast-control { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%; max-width: 300px; margin: 0 auto; padding: 0 10px; } .contrast-control label { font-size: 12px; color: #666; width: 100%; text-align: center; font-family: 'ChicagoFLF', sans-serif; } .contrast-control input[type="range"] { width: 100%; } .contrast-control .slider-container { display: flex; align-items: center; gap: 10px; width: 100%; } .contrast-control .slider-container input[type="range"] { flex: 1; } #block-size-value { font-size: 12px; color: #666; min-width: 40px; text-align: right; font-family: 'ChicagoFLF', sans-serif; } #focus-container { display: none; } #toggle-camera, button.capture { flex: 1; padding: 20px; font-family: 'ChicagoFLF', sans-serif; border-radius: 0; text-align: center; } #toggle-camera { border-right: 1px solid rgba(0, 0, 0, 0.1); } button.capture:disabled { background-color: #ccc; color: #5c5c5c; } @media (max-width: 600px) { #controls { flex-direction: column; } #toggle-camera, button.capture, #edit-image { width: 100%; border-right: none; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } #toggle-camera:last-child, button.capture:last-child, #edit-image:last-child { border-bottom: none; } .slide-controls { flex-direction: column; gap: 12px; padding: 15px; } .slider-group { width: 100%; max-width: none; flex-direction: row; align-items: center; gap: 12px; } .slider-group label { min-width: 60px; text-align: right; } .slider-group input[type="range"] { flex: 1; } .slider-group .value { min-width: 40px; text-align: left; } } #edit-image { display: block; padding: 20px; font-family: 'ChicagoFLF', sans-serif; border-radius: 0; text-align: center; background-color: #f0f0f0; } #edit-image.hidden { display: none; } </style> </head> <body> <div class="preview-container"> <canvas id="canvas"></canvas> <div class="slide-controls"> <div class="slider-group"> <label for="blur-slider">Blur</label> <input type="range" id="blur-slider" min="0" max="20" value="0" step="1"> <span class="value" id="blur-value">0%</span> </div> <div class="slider-group"> <label for="contrast-slider">Contrast</label> <input type="range" id="contrast-slider" min="-255" max="255" value="0" step="1"> <span class="value" id="contrast-value">0</span> </div> <div class="slider-group"> <label for="balance-slider">White Balance</label> <input type="range" id="balance-slider" min="2000" max="10000" value="6500" step="100"> <span class="value" id="balance-value">6500K</span> </div> <div class="slider-group" id="focus-control" style="display: none;"> <label for="focus-slider">Focus</label> <input type="range" id="focus-slider" min="0" max="100" step="1" value="50" disabled> <span class="value" id="focus-value">50%</span> </div> <div class="slider-group" id="pixel-size-control" style="display: none;"> <label for="block-size-slider">Pixel Size</label> <input type="range" id="block-size-slider" min="1" max="12" value="4" step="1"> <span class="value" id="block-size-value">4px</span> </div> </div> </div> <div id="settings-container"> <div class="top-controls"> <select id="dither-select"> <option value="none">No Dithering</option> <option value="floyd-steinberg">Floyd-Steinberg</option> <option value="ordered">Ordered</option> <option value="atkinson">Atkinson</option> <option value="bayer">Bayer</option> </select> <div class="color-controls"> <input type="color" id="color-tint" title="Color Tint"> <button id="reset-color" title="Reset Color Tint">↺</button> </div> </div> </div> <div id="controls"> <button id="toggle-camera">Camera On</button> <button id="edit-image" class="edit-image">Edit Image</button> <button id="capture" disabled class="capture">Capture Image</button> <input type="file" id="image-input" accept="image/*" style="display: none;"> </div> <script src="dither.js"></script> <script src="contrast.js"></script> <script src="color.js"></script> <script src="blur.js"></script> <script src="balance.js"></script> <script src="leibovitz.js"></script> </body> </html>