Pārlūkot izejas kodu

upgrade color translator css styles

zxlie 9 mēneši atpakaļ
vecāks
revīzija
4bdbcdb0c1
3 mainītis faili ar 946 papildinājumiem un 83 dzēšanām
  1. 407 18
      apps/trans-color/index.css
  2. 130 23
      apps/trans-color/index.html
  3. 409 42
      apps/trans-color/index.js

+ 407 - 18
apps/trans-color/index.css

@@ -1,30 +1,419 @@
 @import url("../static/css/bootstrap.min.css");
 
+.wrapper {
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+}
+
+.panel-heading h3 {
+    font-weight: 600;
+}
+
+.panel-body {
+    padding-top: 20px;
+}
+
+.row {
+    margin-bottom: 15px;
+}
+
+.input-group {
+    display: flex;
+    align-items: center;
+    margin-top: 10px;
+}
+
+.row.x-tips {
+    font-size: 13px;
+    color: #6c757d;
+    font-style: normal;
+    background-color: #f8f9fa;
+    padding: 10px 15px;
+    border-radius: 4px;
+    line-height: 1.6;
+    margin-bottom: 20px;
+    max-width: 800px;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.row h4 {
+    font-size: 18px;
+    border-bottom: 1px solid #e9ecef;
+    padding-bottom: 8px;
+    margin: 25px 0 0;
+    font-weight: 500;
+    width: 100%;
+}
+
 .col-input {
-    display: inline-block;
-    width: 220px;
+    flex: 1;
+    max-width: 250px;
+    margin-right: 10px;
+    border-radius: 4px;
+    border: 1px solid #ced4da;
+    padding: 8px 12px;
+    transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+
+.col-input:focus {
+    border-color: #80bdff;
+    outline: 0;
+    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.input-group label {
+    margin-right: 8px;
+    font-weight: 500;
+    width: 40px;
+    text-align: right;
+    color: #495057;
+    flex-shrink: 0;
+}
+
+.input-group label[for^="from"] {
+    /* Style for primary input labels if needed */
+}
+
+.input-group label:not([for]) {
+    width: auto;
+    margin-left: 5px;
+    margin-right: 5px;
+    text-align: left;
+    font-weight: normal;
+}
+
+.input-group label[for$="_placeholder"],
+.input-group span.col-input,
+.input-group .transparent-bg {
+    visibility: hidden;
 }
-label {
-    margin-right: 0;
+
+.input-group .transparent-bg {
+     background: repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%) 50% / 10px 10px;
+     visibility: visible;
+     opacity: 0.5;
 }
+
 .demo-color {
     display: inline-block;
-    width: 60px;
-    height: 24px;
-    border: 1px solid #000;
+    width: 30px;
+    height: 30px;
+    border: 1px solid #adb5bd;
+    border-radius: 4px;
+    vertical-align: middle;
+    flex-shrink: 0;
     margin-left: 5px;
 }
+
 .x-xlabel {
-    margin-left: 80px;
+    margin-left: 10px;
+    width: auto;
+    text-align: left;
 }
-.row.x-tips {
-    font-size: 14px;
-    color: #aaa;
-    font-style: italic;
-}
-.panel-body h4 {
-    font-size: 16px;
-    border-bottom: 1px solid #dfdfdf;
-    padding-bottom: 5px;
-    margin: 30px 0 20px;
+
+.spacer {
+    width: 20px;
+    flex-shrink: 0;
+}
+
+.btn-copy {
+    padding: 4px 8px;
+    font-size: 12px;
+    margin-left: 5px;
+    cursor: pointer;
+    background-color: #f8f9fa;
+    border: 1px solid #ced4da;
+    border-radius: 4px;
+    color: #495057;
+    line-height: 1;
+    height: 30px;
+    flex-shrink: 0;
+}
+
+.btn-copy:hover {
+    background-color: #e2e6ea;
+    border-color: #adb5bd;
+}
+
+.btn-copy:active {
+    background-color: #d6dade;
+}
+
+.alpha-slider-row {
+    display: flex;
+    align-items: center;
+    margin-top: 25px;
+}
+
+.alpha-slider-row label {
+    width: auto;
+    margin-right: 15px;
+    flex-shrink: 0;
+    text-align: left;
+}
+
+.alpha-slider {
+    flex-grow: 1;
+    cursor: pointer;
+    height: 8px;
+    background: linear-gradient(to right, transparent, #6c757d);
+    border-radius: 4px;
+    outline: none;
+    -webkit-appearance: none;
+    appearance: none;
+    border: 1px solid #ced4da;
+}
+
+.alpha-slider::-webkit-slider-thumb {
+    -webkit-appearance: none;
+    appearance: none;
+    width: 16px;
+    height: 16px;
+    background: #007bff;
+    border-radius: 50%;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 0 2px rgba(0,0,0,0.5);
+}
+
+.alpha-slider::-moz-range-thumb {
+    width: 14px;
+    height: 14px;
+    background: #007bff;
+    border-radius: 50%;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 0 2px rgba(0,0,0,0.5);
+}
+
+.alpha-value {
+    margin-left: 15px;
+    font-weight: 500;
+    color: #495057;
+    min-width: 40px;
+    text-align: right;
+}
+
+.ui-mt-20 h4 {
+    margin-top: 40px;
+}
+
+input[readonly] {
+    background-color: #e9ecef;
+    cursor: default;
+}
+
+::placeholder {
+  color: #adb5bd;
+  opacity: 1;
+}
+
+:-ms-input-placeholder {
+ color: #adb5bd;
+}
+
+::-ms-input-placeholder {
+ color: #adb5bd;
+}
+
+/* Global Alpha Slider Styles (at the top) */
+.global-alpha-slider {
+    display: flex;
+    align-items: center;
+    margin-bottom: 25px;
+    padding: 10px 15px;
+    background-color: #f8f9fa;
+    border-radius: 4px;
+    max-width: 800px;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.global-alpha-slider label {
+    width: auto;
+    margin-right: 15px;
+    flex-shrink: 0;
+    text-align: left;
+}
+
+.global-alpha-slider input {
+    flex-grow: 1;
+    cursor: pointer;
+    height: 8px;
+    background: linear-gradient(to right, transparent, #6c757d);
+    border-radius: 4px;
+    outline: none;
+    -webkit-appearance: none;
+    appearance: none;
+    border: 1px solid #ced4da;
+}
+
+.global-alpha-slider input::-webkit-slider-thumb {
+    -webkit-appearance: none;
+    appearance: none;
+    width: 16px;
+    height: 16px;
+    background: #007bff;
+    border-radius: 50%;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 0 2px rgba(0,0,0,0.5);
+}
+
+.global-alpha-slider input::-moz-range-thumb {
+    width: 14px;
+    height: 14px;
+    background: #007bff;
+    border-radius: 50%;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 0 2px rgba(0,0,0,0.5);
+}
+
+.global-alpha-slider .alpha-value {
+    margin-left: 15px;
+    font-weight: 500;
+    color: #495057;
+    min-width: 40px;
+    text-align: right;
+}
+
+/* Modules Grid Layout */
+.modules-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); /* Responsive: 1 or 2 columns */
+    gap: 25px; /* Increased gap */
+}
+
+.color-module {
+    border: 1px solid #dee2e6; /* Slightly darker border */
+    border-radius: 8px; /* More rounded corners */
+    padding: 20px; /* Increased padding */
+    background-color: #ffffff; /* Ensure white background */
+    box-shadow: 0 4px 8px rgba(0,0,0,0.06); /* Enhanced shadow */
+    transition: box-shadow 0.2s ease-in-out;
+}
+
+.color-module:hover {
+    box-shadow: 0 6px 12px rgba(0,0,0,0.08);
+}
+
+.color-module h4 {
+    font-size: 15px; /* Slightly smaller */
+    font-weight: 600;
+    margin-top: 0;
+    margin-bottom: 18px; /* Increased margin */
+    padding-bottom: 10px; /* Increased padding */
+    border-bottom: 1px solid #e9ecef;
+    color: #343a40;
+}
+
+.color-module h5 {
+    font-size: 13px; /* Slightly smaller */
+    font-weight: 500;
+    margin-top: 25px; /* Increased margin */
+    margin-bottom: 12px; /* Increased margin */
+    color: #495057;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+}
+
+/* Input/Output group styling within modules */
+.color-module .input-group,
+.color-module .output-group {
+    display: flex;
+    align-items: center;
+    margin-bottom: 12px; /* Increased margin */
+    margin-top: 0;
+}
+
+.color-module .primary-input {
+    margin-bottom: 15px; /* More space after primary input */
+}
+
+.color-module .output-group:last-child {
+    margin-bottom: 0;
+}
+
+.color-module label {
+    width: 40px; /* Adjusted width */
+    margin-right: 8px; /* Adjusted margin */
+    font-size: 13px;
+    color: #6c757d;
+    text-align: right;
+    flex-shrink: 0;
+}
+
+.color-module .primary-input label[for] {
+    font-weight: 600;
+    color: #343a40;
+}
+
+/* Special label for preview in primary input */
+.color-module .primary-input label:not([for]) {
+     width: auto;
+     margin-left: 8px; /* Adjusted margin */
+     margin-right: 8px; /* Adjusted margin */
+     text-align: left;
+     font-weight: normal;
+     color: #6c757d;
+}
+
+.color-module .col-input, /* Primary input */
+.color-module .col-output /* Output inputs */
+{
+    flex-grow: 1;
+    margin-right: 8px; /* Adjusted margin */
+    font-size: 13px;
+    padding: 8px 12px; /* Adjusted padding */
+    border-radius: 4px;
+    border: 1px solid #ced4da;
+}
+
+/* Style Primary Input */
+.color-module .col-input {
+     transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+.color-module .col-input:focus {
+    border-color: #80bdff;
+    outline: 0;
+    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+/* Style Output Inputs */
+.color-module .col-output {
+    background-color: #f1f3f5; /* Lighter gray for output */
+    cursor: text; /* Allow text selection */
+    font-family: Consolas, Monaco, monospace;
+}
+
+.color-module .demo-color {
+     width: 30px;
+     height: 30px;
+     margin-left: 0; /* Remove margin, handled by label */
+     flex-shrink: 0;
+     border-radius: 4px;
+     border: 1px solid #adb5bd;
+     display: inline-block;
+}
+
+.color-module .btn-copy {
+    height: 30px; /* Match input height */
+    padding: 6px 10px;
+    margin-left: 0;
+    flex-shrink: 0;
+    font-size: 12px;
+    cursor: pointer;
+    background-color: #e9ecef; /* Button color */
+    border: 1px solid #ced4da;
+    border-radius: 4px;
+    color: #495057;
+    line-height: 1;
+}
+.color-module .btn-copy:hover {
+    background-color: #dee2e6;
+    border-color: #adb5bd;
+}
+.color-module .btn-copy:active {
+    background-color: #ced4da;
 }

+ 130 - 23
apps/trans-color/index.html

@@ -4,6 +4,8 @@
     <title>颜色转换工具</title>
     <meta charset="UTF-8">
     <link rel="shortcut icon" href="../static/img/favicon.ico">
+    <!-- Remove Color Picker CSS -->
+    <!-- <link rel="stylesheet" href="../static/vendor/a-color-picker/acolorpicker.min.css"> -->
     <link rel="stylesheet" href="index.css" />
     <script type="text/javascript" src="../static/vendor/evalCore.min.js"></script>
     <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
@@ -18,39 +20,144 @@
                     <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:颜色转换工具</h3>
         </div>
     </div>
-    <div class="panel-body">
+    <div class="panel-body"> <!-- Remove main-content-area -->
+        <!-- Remove colorPickerContainer -->
+
         <div class="row x-tips ui-mt-10">
-            // 支持颜色值的RGB(RGBA)与十六进制(HEX)色值之间的互转,eg:<br>
-            1、#43ad7f7f  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  =>  &nbsp;&nbsp;&nbsp;  rgba(67, 173, 127, 0.50) <br>
-            2、rgb(190,20,128)  &nbsp;&nbsp;  =>  &nbsp;&nbsp;&nbsp;  #be1480
+             // 支持颜色值的RGB/RGBA、HEX、HSL/HSLA、HSV/HSVA互转。
         </div>
 
-        <div class="row">
-            <h4>HEX转RGB:</h4>
-            <label for="fromHEX">HEX</label>
-            <input type="text" class="form-control col-input ui-mr-10" id="fromHEX" placeholder="如:#F2C685"
-                   maxlength="9" v-model="fromHEX" @input="colorHexToRgb">
-            <label>预览</label><div class="demo-color" :style="{background:fromHEX}">&nbsp;</div>
+        <!-- Move Alpha Slider Row to the top -->
+        <div class="row ui-mt-20 alpha-slider-row global-alpha-slider">
+            <label for="alphaSlider">全局透明度 (Alpha):</label>
+            <input type="range" id="alphaSlider" class="alpha-slider" min="0" max="100" step="1" v-model.number="alphaPercent" @input="updateAlphaFromSlider">
+            <span class="alpha-value">{{ (alphaPercent / 100).toFixed(2).replace(/\.0+$/,'') }}</span>
+       </div>
 
-            <label for="toRGB" class="x-xlabel">RGB</label>
-            <input type="text" class="form-control col-input" id="toRGB" readonly v-model="toRGB">
-            <label>预览</label><div class="demo-color" :style="{background:toRGB}">&nbsp;</div>
-        </div>
+        <!-- Reintroduce modules grid -->
+        <div class="modules-grid ui-mt-20">
+            <!-- HEX Module -->
+            <div class="color-module module-hex">
+                <h4>HEX 输入</h4>
+                <div class="input-group primary-input">
+                    <label for="fromHEX">HEX</label>
+                    <input type="text" class="form-control col-input" id="fromHEX" placeholder="如: #F2C68580"
+                           maxlength="9" v-model="fromHEX" @input="updateFromHEX">
+                    <label>预览</label><div class="demo-color" :style="{background: safeBgColor(fromHEX)}">&nbsp;</div>
+                </div>
+                <h5>输出:</h5>
+                <div class="output-group">
+                    <label>RGB</label>
+                    <input type="text" class="form-control col-output" readonly :value="toRGB">
+                    <button class="btn-copy" title="复制" @click="copyToClipboard(toRGB)"> C </button>
+                </div>
+                <div class="output-group">
+                    <label>HSL</label>
+                    <input type="text" class="form-control col-output" readonly :value="toHSL">
+                    <button class="btn-copy" title="复制" @click="copyToClipboard(toHSL)"> C </button>
+                </div>
+                <div class="output-group">
+                    <label>HSV</label>
+                    <input type="text" class="form-control col-output" readonly :value="toHSV">
+                    <button class="btn-copy" title="复制" @click="copyToClipboard(toHSV)"> C </button>
+                </div>
+            </div>
+
+            <!-- RGB Module -->
+            <div class="color-module module-rgb">
+                <h4>RGB 输入</h4>
+                <div class="input-group primary-input">
+                    <label for="fromRGB">RGB</label>
+                    <input type="text" class="form-control col-input" id="fromRGB" placeholder="如: rgba(67,173,127,0.5)"
+                           maxlength="35" v-model="fromRGB" @input="updateFromRGB">
+                     <label>预览</label><div class="demo-color" :style="{background: safeBgColor(fromRGB)}">&nbsp;</div>
+                </div>
+                 <h5>输出:</h5>
+                 <div class="output-group">
+                     <label>HEX</label>
+                     <input type="text" class="form-control col-output" readonly :value="toHEX">
+                     <button class="btn-copy" title="复制" @click="copyToClipboard(toHEX)"> C </button>
+                 </div>
+                 <div class="output-group">
+                     <label>HSL</label>
+                     <input type="text" class="form-control col-output" readonly :value="toHSL">
+                     <button class="btn-copy" title="复制" @click="copyToClipboard(toHSL)"> C </button>
+                 </div>
+                 <div class="output-group">
+                     <label>HSV</label>
+                     <input type="text" class="form-control col-output" readonly :value="toHSV">
+                     <button class="btn-copy" title="复制" @click="copyToClipboard(toHSV)"> C </button>
+                 </div>
+            </div>
+
+            <!-- HSL Module -->
+            <div class="color-module module-hsl">
+                 <h4>HSL 输入</h4>
+                 <div class="input-group primary-input">
+                     <label for="fromHSL">HSL</label>
+                     <input type="text" class="form-control col-input" id="fromHSL" placeholder="如: hsla(154, 44%, 47%, 0.5)"
+                            maxlength="45" v-model="fromHSL" @input="updateFromHSL">
+                     <label>预览</label><div class="demo-color" :style="{background: safeBgColor(fromHSL)}">&nbsp;</div>
+                 </div>
+                 <h5>输出:</h5>
+                  <div class="output-group">
+                      <label>HEX</label>
+                      <input type="text" class="form-control col-output" readonly :value="toHEX">
+                      <button class="btn-copy" title="复制" @click="copyToClipboard(toHEX)"> C </button>
+                  </div>
+                  <div class="output-group">
+                      <label>RGB</label>
+                      <input type="text" class="form-control col-output" readonly :value="toRGB">
+                      <button class="btn-copy" title="复制" @click="copyToClipboard(toRGB)"> C </button>
+                  </div>
+                  <div class="output-group">
+                      <label>HSV</label>
+                      <input type="text" class="form-control col-output" readonly :value="toHSV">
+                      <button class="btn-copy" title="复制" @click="copyToClipboard(toHSV)"> C </button>
+                  </div>
+            </div>
 
-        <div class="row ui-mt-20">
-            <h4>RGB转HEX:</h4>
-            <label for="fromRGB">RGB</label>
-            <input type="text" class="form-control col-input ui-mr-10" id="fromRGB" placeholder="如:rgb(200,100,255)"
-                   maxlength="25" v-model="fromRGB" @input="colorRgbToHex">
-            <label>预览</label><div class="demo-color" :style="{background:fromRGB}">&nbsp;</div>
+            <!-- HSV Module -->
+             <div class="color-module module-hsv">
+                 <h4>HSV 输入</h4>
+                 <div class="input-group primary-input">
+                     <label for="fromHSV">HSV</label>
+                     <input type="text" class="form-control col-input" id="fromHSV" placeholder="如: hsva(154, 61%, 68%, 0.5)"
+                            maxlength="45" v-model="fromHSV" @input="updateFromHSV">
+                     <label>预览</label><div class="demo-color" :style="{background: safeBgColor(fromHSV)}">&nbsp;</div>
+                 </div>
+                  <h5>输出:</h5>
+                  <div class="output-group">
+                      <label>HEX</label>
+                      <input type="text" class="form-control col-output" readonly :value="toHEX">
+                      <button class="btn-copy" title="复制" @click="copyToClipboard(toHEX)"> C </button>
+                  </div>
+                  <div class="output-group">
+                      <label>RGB</label>
+                      <input type="text" class="form-control col-output" readonly :value="toRGB">
+                      <button class="btn-copy" title="复制" @click="copyToClipboard(toRGB)"> C </button>
+                  </div>
+                  <div class="output-group">
+                      <label>HSL</label>
+                      <input type="text" class="form-control col-output" readonly :value="toHSL">
+                      <button class="btn-copy" title="复制" @click="copyToClipboard(toHSL)"> C </button>
+                  </div>
+             </div>
+        </div> <!-- Close .modules-grid -->
 
-            <label for="toHEX" class="x-xlabel">HEX</label>
-            <input type="text" class="form-control col-input" id="toHEX" readonly v-model="toHEX">
-            <label>预览</label><div class="demo-color" :style="{background:toHEX}">&nbsp;</div>
+        <!-- Remove Alpha slider from here -->
+        <!--
+        <div class="row ui-mt-20 alpha-slider-row">
+             <label for="alphaSlider">透明度 (Alpha):</label>
+             <input type="range" id="alphaSlider" class="alpha-slider" min="0" max="100" step="1" v-model.number="alphaPercent" @input="updateAlphaFromSlider">
+             <span class="alpha-value">{{ (alphaPercent / 100).toFixed(2).replace(/\.0+$/,'') }}</span>
         </div>
+        -->
     </div>
 </div>
 <script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
+<!-- Remove Color Picker JS -->
+<!-- <script src="../static/vendor/a-color-picker/acolorpicker.js"></script> -->
 <script type="text/javascript" src="index.js"></script>
 </body>
 </html>

+ 409 - 42
apps/trans-color/index.js

@@ -5,62 +5,429 @@ new Vue({
     el: '#pageContainer',
     data: {
         fromHEX: '#43ad7f7f',
+        fromRGB: '', // Will be calculated
+        fromHSL: '', // Will be calculated
+        fromHSV: '', // Will be calculated
+
+        toHEX: '',
         toRGB: '',
-        fromRGB: 'rgba(190,20,128,0.5)',
-        toHEX: ''
+        toHSL: '',
+        toHSV: '',
+
+        // Outputs specifically for HSL/HSV rows to avoid confusion
+        // These are no longer needed with the module layout
+        /*
+        toRGB_fromHSL: '',
+        toHEX_fromHSL: '',
+        toRGB_fromHSV: '',
+        toHEX_fromHSV: '',
+        */
+
+        // Internal representation (RGBA object)
+        color: { r: 0, g: 0, b: 0, a: 1 },
+        alphaPercent: 100 // For slider binding (0-100)
     },
 
     mounted: function () {
-        // 初始化颜色转换
-        this.colorHexToRgb();
-        this.colorRgbToHex();
+        this.updateFromHEX(); // Initial calculation
+    },
+
+    beforeDestroy: function() {
+        // Remove picker cleanup logic
     },
 
     methods: {
+        // Remove initColorPicker method
+        /*
+        initColorPicker: function() { ... },
+        */
+
+        // --- Input Update Handlers ---
+        updateFromHEX: function() {
+            // Remove picker update logic
+            /*
+            if (!this.isUpdatingFromPicker && this.pickerInstance) {
+                this.pickerInstance.setColor(this.fromHEX, true);
+            }
+            */
+            const result = this.parseHEX(this.fromHEX);
+            if (result) {
+                this.color = result;
+                this.updateAllOutputs(); // Removed parameter
+            } else {
+                this.clearOutputs();
+            }
+        },
+
+        updateFromRGB: function() {
+             // Remove picker update logic
+            /*
+             if (this.pickerInstance) {
+                const parsed = this.parseRGB(this.fromRGB);
+                if(parsed) {
+                    const hexObj = this.rgbToHex(parsed.r, parsed.g, parsed.b, parsed.a);
+                     this.pickerInstance.setColor(this.hexToString(hexObj), true);
+                }
+            }
+            */
+            const result = this.parseRGB(this.fromRGB);
+            if (result) {
+                this.color = result;
+                this.updateAllOutputs();
+            } else {
+                this.clearOutputs();
+            }
+        },
+
+        updateFromHSL: function() {
+            // Remove picker update logic
+            /*
+            if (this.pickerInstance) {
+                const parsed = this.parseHSL(this.fromHSL);
+                 if(parsed) {
+                    const rgb = this.hslToRgb(parsed.h, parsed.s, parsed.l, parsed.a);
+                    const hexObj = this.rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);
+                     this.pickerInstance.setColor(this.hexToString(hexObj), true);
+                 }
+            }
+            */
+            const result = this.parseHSL(this.fromHSL);
+            if (result) {
+                this.color = this.hslToRgb(result.h, result.s, result.l, result.a);
+                this.updateAllOutputs();
+            } else {
+                this.clearOutputs();
+            }
+        },
+
+        updateFromHSV: function() {
+            // Remove picker update logic
+            /*
+            if (this.pickerInstance) {
+                 const parsed = this.parseHSV(this.fromHSV);
+                 if(parsed) {
+                     const rgb = this.hsvToRgb(parsed.h, parsed.s, parsed.v, parsed.a);
+                     const hexObj = this.rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);
+                      this.pickerInstance.setColor(this.hexToString(hexObj), true);
+                 }
+            }
+            */
+            const result = this.parseHSV(this.fromHSV);
+            if (result) {
+                this.color = this.hsvToRgb(result.h, result.s, result.v, result.a);
+                this.updateAllOutputs();
+            } else {
+                this.clearOutputs();
+            }
+        },
+
+        updateAlphaFromSlider: function() {
+            if (this.color) {
+                this.color.a = this.alphaPercent / 100;
+                 // Remove picker update logic
+                 /*
+                if (this.pickerInstance) {
+                     const hexObj = this.rgbToHex(this.color.r, this.color.g, this.color.b, this.color.a);
+                     this.pickerInstance.setColor(this.hexToString(hexObj), true);
+                }
+                */
+                this.updateAllOutputs();
+            }
+        },
+
+        // --- Update All Outputs ---
+        updateAllOutputs: function() {
+            if (!this.color) return;
+            const { r, g, b, a } = this.color;
+
+            this.alphaPercent = Math.round(a * 100);
+
+            const hexObj = this.rgbToHex(r, g, b, a);
+            const rgbString = this.rgbaToString(r, g, b, a);
+            const hslObj = this.rgbToHsl(r, g, b, a);
+            const hsvObj = this.rgbToHsv(r, g, b, a);
+
+            const newHEX = this.hexToString(hexObj);
+            const newRGB = rgbString;
+            const newHSL = this.hslToString(hslObj);
+            const newHSV = this.hsvToString(hsvObj);
+
+            // Update output models (directly use these in template)
+            this.toHEX = newHEX;
+            this.toRGB = newRGB;
+            this.toHSL = newHSL;
+            this.toHSV = newHSV;
+
+             // Update inputs (only if they differ to avoid cursor jumps)
+             if (this.fromHEX !== newHEX) this.fromHEX = newHEX;
+             if (this.fromRGB !== newRGB) this.fromRGB = newRGB;
+             if (this.fromHSL !== newHSL) this.fromHSL = newHSL;
+             if (this.fromHSV !== newHSV) this.fromHSV = newHSV;
+
+            // Update the color picker IF the change didn't originate from it
+            // Remove picker update logic
+            /*
+            if (!this.isUpdatingFromPicker && this.pickerInstance) {
+                 // Use HEX8 format which a-color-picker likely prefers
+                this.pickerInstance.setColor(newHEX, true);
+            }
+            */
+        },
+
+        clearOutputs: function() {
+            this.toHEX = '';
+            this.toRGB = '';
+            this.toHSL = '';
+            this.toHSV = '';
+             // Remove clears for specific row outputs
+            /*
+            this.toRGB_fromHSL = '';
+            this.toHEX_fromHSL = '';
+            this.toRGB_fromHSV = '';
+            this.toHEX_fromHSV = '';
+            */
+            this.alphaPercent = 100;
+        },
+
+        // --- Parsers ---
+        parseHEX: function(hex) {
+            hex = hex.trim().replace(/^#/, '');
+            if (!/^[0-9a-fA-F]+$/.test(hex) || ![3, 4, 6, 8].includes(hex.length)) {
+                return null;
+            }
+
+            let r, g, b, a = 1;
+            if (hex.length === 3 || hex.length === 4) {
+                r = parseInt(hex[0] + hex[0], 16);
+                g = parseInt(hex[1] + hex[1], 16);
+                b = parseInt(hex[2] + hex[2], 16);
+                if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
+            } else { // 6 or 8
+                r = parseInt(hex.substring(0, 2), 16);
+                g = parseInt(hex.substring(2, 4), 16);
+                b = parseInt(hex.substring(4, 6), 16);
+                if (hex.length === 8) a = parseInt(hex.substring(6, 8), 16) / 255;
+            }
+
+            if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a)) return null;
+            return { r, g, b, a };
+        },
+
+        parseRGB: function(rgbStr) {
+            rgbStr = rgbStr.trim().toLowerCase();
+            const match = rgbStr.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([0-9\.]+))?\s*\)$/);
+            if (!match) return null;
+
+            const r = parseInt(match[1], 10);
+            const g = parseInt(match[2], 10);
+            const b = parseInt(match[3], 10);
+            let a = 1;
+            if (match[4] !== undefined) {
+                a = parseFloat(match[4]);
+            }
+
+            if (r > 255 || g > 255 || b > 255 || a < 0 || a > 1 || isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a)) {
+                return null;
+            }
+            return { r, g, b, a };
+        },
 
-        colorHexToRgb: function () {
-            let hex = this.fromHEX.trim().replace(/^#/, '');
-            let rgb = [];
-            switch (hex.length) {
-                case 3:
-                case 4:
-                    rgb.push(parseInt(hex[0] + '' + hex[0], 16).toString(10));                       // r
-                    rgb.push(parseInt(hex[1] + '' + hex[1], 16).toString(10));                       // g
-                    rgb.push(parseInt(hex[2] + '' + hex[2], 16).toString(10));                       // b
-                    hex.length === 4 && rgb.push((parseInt(parseInt(hex[3] + '' + hex[3], 16).toString(10)) / 256).toFixed(2));   // a
-                    break;
-                case 6:
-                case 8:
-                    rgb.push(parseInt(hex[0] + '' + hex[1], 16).toString(10));                       // r
-                    rgb.push(parseInt(hex[2] + '' + hex[3], 16).toString(10));                       // g
-                    rgb.push(parseInt(hex[4] + '' + hex[5], 16).toString(10));                       // b
-                    hex.length === 8 && rgb.push((parseInt(parseInt(hex[6] + '' + hex[7], 16).toString(10)) / 256).toFixed(2));   // a
-                    break;
-            }
-            if (rgb.length === 3) {
-                this.toRGB = 'rgb(' + rgb.join(', ') + ')';
-            } else if (rgb.length === 4) {
-                this.toRGB = 'rgba(' + rgb.join(', ') + ')';
+        parseHSL: function(hslStr) {
+            hslStr = hslStr.trim().toLowerCase();
+            const match = hslStr.match(/^hsla?\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)%?\s*,\s*([0-9\.]+)%?\s*(?:,\s*([0-9\.]+))?\s*\)$/);
+            if (!match) return null;
+
+            const h = parseFloat(match[1]);
+            const s = parseFloat(match[2]);
+            const l = parseFloat(match[3]);
+            let a = 1;
+            if (match[4] !== undefined) {
+                a = parseFloat(match[4]);
+            }
+
+             if (h < 0 || h > 360 || s < 0 || s > 100 || l < 0 || l > 100 || a < 0 || a > 1 || isNaN(h) || isNaN(s) || isNaN(l) || isNaN(a)) {
+                return null;
+            }
+            return { h, s, l, a };
+        },
+
+         parseHSV: function(hsvStr) {
+            hsvStr = hsvStr.trim().toLowerCase();
+             // Assuming HSV format like hsv(H, S%, V%) or hsva(H, S%, V%, A)
+            const match = hsvStr.match(/^hsva?\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)%?\s*,\s*([0-9\.]+)%?\s*(?:,\s*([0-9\.]+))?\s*\)$/);
+            if (!match) return null;
+
+            const h = parseFloat(match[1]);
+            const s = parseFloat(match[2]);
+            const v = parseFloat(match[3]);
+            let a = 1;
+            if (match[4] !== undefined) {
+                a = parseFloat(match[4]);
+            }
+
+             if (h < 0 || h > 360 || s < 0 || s > 100 || v < 0 || v > 100 || a < 0 || a > 1 || isNaN(h) || isNaN(s) || isNaN(v) || isNaN(a)) {
+                return null;
+            }
+            return { h, s, v, a };
+        },
+
+        // --- Conversion Functions (RGB as Base) ---
+        // RGB -> HEX
+        rgbToHex: function(r, g, b, a) {
+            const toHex = (c) => parseInt(c, 10).toString(16).padStart(2, '0');
+            const hex = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+            if (a < 1) {
+                const alphaHex = Math.round(a * 255).toString(16).padStart(2, '0');
+                return { hex: hex + alphaHex, hasAlpha: true };
+            }
+            return { hex: hex, hasAlpha: false };
+        },
+
+        // RGB -> HSL
+        rgbToHsl: function(r, g, b, a) {
+            r /= 255; g /= 255; b /= 255;
+            const max = Math.max(r, g, b), min = Math.min(r, g, b);
+            let h, s, l = (max + min) / 2;
+
+            if (max === min) {
+                h = s = 0; // achromatic
             } else {
-                this.toRGB = '';
+                const d = max - min;
+                s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+                switch (max) {
+                    case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+                    case g: h = (b - r) / d + 2; break;
+                    case b: h = (r - g) / d + 4; break;
+                }
+                h /= 6;
             }
+            return { h: h * 360, s: s * 100, l: l * 100, a: a };
         },
 
-        colorRgbToHex: function () {
-            let rgb = this.fromRGB.trim().replace(/\s+/g, '').replace(/[^\d,\.]/g, '').split(',').filter(n => {
-                return n.length && parseInt(n, 10) <= 255;
-            });
-            let hex = [];
-            if (rgb.length === 3 || rgb.length === 4) {
-                hex.push(parseInt(rgb[0], 10).toString(16).padStart(2, '0'));                       // r
-                hex.push(parseInt(rgb[1], 10).toString(16).padStart(2, '0'));                       // g
-                hex.push(parseInt(rgb[2], 10).toString(16).padStart(2, '0'));                       // b
-                rgb.length === 4 && hex.push(Math.floor(parseFloat(rgb[3], 10) * 255).toString(16).padStart(2, '0'));   // a
+        // RGB -> HSV
+        rgbToHsv: function(r, g, b, a) {
+            r /= 255; g /= 255; b /= 255;
+            const max = Math.max(r, g, b), min = Math.min(r, g, b);
+            let h, s, v = max;
+
+            const d = max - min;
+            s = max === 0 ? 0 : d / max;
+
+            if (max === min) {
+                h = 0; // achromatic
+            } else {
+                switch (max) {
+                    case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+                    case g: h = (b - r) / d + 2; break;
+                    case b: h = (r - g) / d + 4; break;
+                }
+                h /= 6;
             }
-            if (hex.length) {
-                this.toHEX = '#' + hex.join('');
+            return { h: h * 360, s: s * 100, v: v * 100, a: a };
+        },
+
+        // HSL -> RGB
+        hslToRgb: function(h, s, l, a) {
+            h /= 360; s /= 100; l /= 100;
+            let r, g, b;
+
+            if (s === 0) {
+                r = g = b = l; // achromatic
+            } else {
+                const hue2rgb = (p, q, t) => {
+                    if (t < 0) t += 1;
+                    if (t > 1) t -= 1;
+                    if (t < 1/6) return p + (q - p) * 6 * t;
+                    if (t < 1/2) return q;
+                    if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+                    return p;
+                };
+                const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+                const p = 2 * l - q;
+                r = hue2rgb(p, q, h + 1/3);
+                g = hue2rgb(p, q, h);
+                b = hue2rgb(p, q, h - 1/3);
+            }
+            return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a: a };
+        },
+
+        // HSV -> RGB
+        hsvToRgb: function(h, s, v, a) {
+             h /= 360; s /= 100; v /= 100;
+            let r, g, b;
+            const i = Math.floor(h * 6);
+            const f = h * 6 - i;
+            const p = v * (1 - s);
+            const q = v * (1 - f * s);
+            const t = v * (1 - (1 - f) * s);
+
+            switch (i % 6) {
+                case 0: r = v; g = t; b = p; break;
+                case 1: r = q; g = v; b = p; break;
+                case 2: r = p; g = v; b = t; break;
+                case 3: r = p; g = q; b = v; break;
+                case 4: r = t; g = p; b = v; break;
+                case 5: r = v; g = p; b = q; break;
+            }
+            return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a: a };
+        },
+
+        // --- Formatters ---
+        rgbaToString: function(r, g, b, a) {
+            if (a < 1) {
+                return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2).replace(/\.0+$/,'')})`;
+            } else {
+                return `rgb(${r}, ${g}, ${b})`;
+            }
+        },
+
+        hexToString: function(hexObj) {
+            return hexObj.hex;
+        },
+
+        hslToString: function(hslObj) {
+            const h = hslObj.h.toFixed(0);
+            const s = hslObj.s.toFixed(1).replace(/\.0$/,'');
+            const l = hslObj.l.toFixed(1).replace(/\.0$/,'');
+            if (hslObj.a < 1) {
+                return `hsla(${h}, ${s}%, ${l}%, ${hslObj.a.toFixed(2).replace(/\.0+$/,'')})`;
+            } else {
+                return `hsl(${h}, ${s}%, ${l}%)`;
+            }
+        },
+
+        hsvToString: function(hsvObj) {
+            const h = hsvObj.h.toFixed(0);
+            const s = hsvObj.s.toFixed(1).replace(/\.0$/,'');
+            const v = hsvObj.v.toFixed(1).replace(/\.0$/,'');
+            if (hsvObj.a < 1) {
+                return `hsva(${h}, ${s}%, ${v}%, ${hsvObj.a.toFixed(2).replace(/\.0+$/,'')})`;
+            } else {
+                return `hsv(${h}, ${s}%, ${v}%)`;
+            }
+        },
+
+        // --- Utilities ---
+        copyToClipboard: function(text) {
+            if (text && navigator.clipboard) {
+                navigator.clipboard.writeText(text).then(() => {
+                   // Optional: Show feedback to user
+                   // console.log('Copied:', text);
+                }).catch(err => {
+                    console.error('Failed to copy text: ', err);
+                });
+            }
+        },
+
+        safeBgColor: function() {
+            // Ignore the input colorStr, always use the internal RGBA color object
+            if (this.color && typeof this.color.r === 'number') {
+                // Generate an RGBA string which is universally supported by browsers
+                const { r, g, b, a } = this.color;
+                return `rgba(${r}, ${g}, ${b}, ${a})`;
             } else {
-                this.toHEX = '';
+                 // Return transparent or a default if the internal color is invalid
+                return 'transparent';
             }
         }
     }