/*
 * noVNC general input element CSS
 * Copyright (C) 2025 The noVNC authors
 * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
 * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
 */

/* ------- SHARED BETWEEN INPUT ELEMENTS -------- */

input,
textarea,
button,
select,
input::file-selector-button {
    padding: 0.5em var(--input-xpadding);
    border-radius: 6px;
    appearance: none;
    text-overflow: ellipsis;

    /* Respect standard font settings */
    font: inherit;
    line-height: 1.6;
}
input:disabled,
textarea:disabled,
button:disabled,
select:disabled,
label[disabled] {
    opacity: 0.4;
}

input:focus-visible,
textarea:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible::file-selector-button {
    outline: 2px solid var(--novnc-lightblue);
    outline-offset: 1px;
}

/* ------- TEXT INPUT -------- */

input:not([type]),
input[type=date],
input[type=datetime-local],
input[type=email],
input[type=month],
input[type=number],
input[type=password],
input[type=search],
input[type=tel],
input[type=text],
input[type=time],
input[type=url],
input[type=week],
textarea {
    border: 1px solid var(--novnc-lightgrey);
    /* Account for borders on text inputs, buttons dont have borders */
    padding: calc(0.5em - 1px) var(--input-xpadding);
}
input:not([type]):focus-visible,
input[type=date]:focus-visible,
input[type=datetime-local]:focus-visible,
input[type=email]:focus-visible,
input[type=month]:focus-visible,
input[type=number]:focus-visible,
input[type=password]:focus-visible,
input[type=search]:focus-visible,
input[type=tel]:focus-visible,
input[type=text]:focus-visible,
input[type=time]:focus-visible,
input[type=url]:focus-visible,
input[type=week]:focus-visible,
textarea:focus-visible {
    outline-offset: -1px;
}

textarea {
    margin: unset; /* Remove Firefox's built in margin */
    /* Prevent layout from shifting when scrollbars show */
    scrollbar-gutter: stable;
    /* Make textareas show at minimum one line. This does not work when
       using box-sizing border-box, in which case, vertical padding and
       border width needs to be taken into account. */
    min-height: 1lh;
    vertical-align: baseline; /* Firefox gives "text-bottom" by default */
}

/* ------- NUMBER PICKERS ------- */

/* We can't style the number spinner buttons:
   https://github.com/w3c/csswg-drafts/issues/8777 */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
    /* Get rid of increase/decrease buttons in WebKit */
    appearance: none;
}
input[type=number] {
    /* Get rid of increase/decrease buttons in Firefox */
    appearance: textfield;
}

/* ------- BUTTON ACTIVATIONS -------- */

/* A color overlay that depends on the activation level. The level can then be
   set for different states on an element, for example hover and click on a
   <button>. */
input, button, select, option,
input::file-selector-button,
.button-activations {
    --button-activation-level: 0;
    /* Note that CSS variables aren't functions, beware when inheriting */
    --button-activation-alpha: calc(0.08 * var(--button-activation-level));
    /* FIXME: We want the image() function instead of the linear-gradient()
              function below. But it's not supported in the browsers yet. */
    --button-activation-overlay:
        linear-gradient(rgba(0, 0, 0, var(--button-activation-alpha))
        100%, transparent);
    --button-activation-overlay-light:
        linear-gradient(rgba(255, 255, 255, calc(0.23 * var(--button-activation-level)))
        100%, transparent);
}
.button-activations {
    background-image: var(--button-activation-overlay);

    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
    -webkit-tap-highlight-color: transparent;
}
/* When we want the light overlay on activations instead.
   This is best used on elements with darker backgrounds. */
.button-activations.light-overlay {
    background-image: var(--button-activation-overlay-light);
    /* Can't use the normal blend mode since that gives washed out colors. */
    /* FIXME: For elements with these activation overlays we'd like only
              the luminosity to change. The proprty "background-blend-mode" set
              to "luminosity" sounds good, but it doesn't work as intended,
              see: https://bugzilla.mozilla.org/show_bug.cgi?id=1806417 */
    background-blend-mode: overlay;
}

input:hover, button:hover, select:hover, option:hover,
input::file-selector-button:hover,
.button-activations:hover {
    --button-activation-level: 1;
}
/* Unfortunately we have to disable the :hover effect on touch devices,
   otherwise the style lingers after tapping the button. */
@media (any-pointer: coarse) {
    input:hover, button:hover, select:hover, option:hover,
    input::file-selector-button:hover,
    .button-activations:hover {
        --button-activation-level: 0;
    }
}
input:active, button:active, select:active, option:active,
input::file-selector-button:active,
.button-activations:active {
    --button-activation-level: 2;
}
input:disabled, button:disabled, select:disabled, select:disabled option,
input:disabled::file-selector-button,
.button-activations:disabled {
    --button-activation-level: 0;
}

/* ------- BUTTONS -------- */

input[type=button],
input[type=color],
input[type=image],
input[type=reset],
input[type=submit],
input::file-selector-button,
button,
select {
    min-width: 8em;
    border: none;
    color: black;
    font-weight: bold;
    background-color: var(--novnc-buttongrey);
    background-image: var(--button-activation-overlay);
    cursor: pointer;
    /* Disable Chrome's touch tap highlight */
    -webkit-tap-highlight-color: transparent;
}
input[type=button]:disabled,
input[type=color]:disabled,
input[type=image]:disabled,
input[type=reset]:disabled,
input[type=submit]:disabled,
input:disabled::file-selector-button,
button:disabled,
select:disabled {
    /* See Firefox bug:
       https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */
    cursor: default;
}

input[type=button],
input[type=color],
input[type=reset],
input[type=submit] {
    /* Workaround for text-overflow bugs in Firefox and Chromium:
        https://bugzilla.mozilla.org/show_bug.cgi?id=1800077
        https://bugs.chromium.org/p/chromium/issues/detail?id=1383144 */
    overflow: clip;
}

/* ------- COLOR PICKERS ------- */

input[type=color] {
    min-width: unset;
    box-sizing: content-box;
    width: 1.4em;
    height: 1.4em;
}
input[type=color]::-webkit-color-swatch-wrapper {
    padding: 0;
}
/* -webkit-color-swatch & -moz-color-swatch cant be in a selector list:
   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
input[type=color]::-webkit-color-swatch {
    border: none;
    border-radius: 6px;
}
input[type=color]::-moz-color-swatch {
    border: none;
    border-radius: 6px;
}

/* -- SHARED BETWEEN CHECKBOXES, RADIOBUTTONS AND THE TOGGLE CLASS -- */

input[type=radio],
input[type=checkbox] {
    display: inline-flex;
    justify-content: center;
    align-items: center;
    background-color: var(--novnc-buttongrey);
    background-image: var(--button-activation-overlay);
    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
    -webkit-tap-highlight-color: transparent;
    width: 16px;
    --checkradio-height: 16px;
    height: var(--checkradio-height);
    padding: 0;
    margin: 0 6px 0 0;
    /* Don't have transitions for outline in order to be consistent
       with other elements */
    transition: all 0.2s, outline-color 0s, outline-offset 0s;

    /* A transparent outline in order to work around a graphical clipping issue
       in WebKit. See bug: https://bugs.webkit.org/show_bug.cgi?id=256003 */
    outline: 1px solid transparent;
    position: relative; /* Since ::before & ::after are absolute positioned */

    /* We want to align with the middle of capital letters, this requires
       a workaround. The default behavior is to align the bottom of the element
       on top of the text baseline, this is too far up.
       We want to push the element down half the difference in height between
       it and a capital X. In our font, the height of a capital "X" is 0.698em.
     */
    vertical-align: calc(0px - (var(--checkradio-height) - 0.698em) / 2);
    /* FIXME: Could write 1cap instead of 0.698em, but it's only supported in
              Firefox as of 2023 */
    /* FIXME: We probably want to use round() here, see bug 8148 */
}
input[type=radio]:focus-visible,
input[type=checkbox]:focus-visible {
    outline-color: var(--novnc-lightblue);
}
input[type=checkbox]::before,
input[type=checkbox]:not(.toggle)::after,
input[type=radio]::before,
input[type=radio]::after {
    content: "";
    display: block; /* width & height doesn't work on inline elements */
    transition: inherit;
    /* Let's prevent the pseudo-elements from taking up layout space so that
       the ::before and ::after pseudo-elements can be in the same place. This
       is also required for vertical-align: baseline to work like we want it to
       on radio/checkboxes. If the pseudo-elements take up layout space, the
       baseline of text inside them will be used instead. */
    position: absolute;
}
input[type=checkbox]:not(.toggle)::after,
input[type=radio]::after {
    width: 10px;
    height: 2px;
    background-color: transparent;
    border-radius: 2px;
}

/* ------- CHECKBOXES ------- */

input[type=checkbox]:not(.toggle) {
    border-radius: 4px;
}
input[type=checkbox]:not(.toggle):checked,
input[type=checkbox]:not(.toggle):indeterminate {
    background-color: var(--novnc-blue);
    background-image: var(--button-activation-overlay-light);
    background-blend-mode: overlay;
}
input[type=checkbox]:not(.toggle)::before {
    width: 25%;
    height: 55%;
    border-style: solid;
    border-color: transparent;
    border-width: 0 2px 2px 0;
    border-radius: 1px;
    transform: translateY(-1px) rotate(35deg);
}
input[type=checkbox]:not(.toggle):checked::before {
    border-color: white;
}
input[type=checkbox]:not(.toggle):indeterminate::after {
    background-color: white;
}

/* ------- RADIO BUTTONS ------- */

input[type=radio] {
    border-radius: 50%;
    border: 1px solid transparent; /* To ensure a smooth transition */
}
input[type=radio]:checked {
    border: 4px solid var(--novnc-blue);
    background-color: white;
    /* button-activation-overlay should be removed from the radio
       element to not interfere with button-activation-overlay-light
       that is set on the ::before element. */
    background-image: none;
}
input[type=radio]::before {
    width: inherit;
    height: inherit;
    border-radius: inherit;
    /* We can achieve the highlight overlay effect on border colors by
       setting button-activation-overlay-light on an element that stays
       on top (z-axis) of the element with a border. */
    background-image: var(--button-activation-overlay-light);
    mix-blend-mode: overlay;
    opacity: 0;
}
input[type=radio]:checked::before {
    opacity: 1;
}
input[type=radio]:indeterminate::after {
    background-color: black;
}

/* ------- TOGGLE SWITCHES ------- */

/* These are meant to be used instead of checkboxes in some cases. If all of
   the following critera are true you should use a toggle switch:

    * The choice is a simple ON/OFF or ENABLE/DISABLE
    * The choice doesn't give the feeling of "I agree" or "I confirm"
    * There are not multiple related & grouped options
 */

input[type=checkbox].toggle {
    display: inline-block;
    --checkradio-height: 18px; /* Height value used in calc, see above */
    width: 31px;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    border-radius: 9px;
}
input[type=checkbox].toggle:disabled {
    cursor: default;
}
input[type=checkbox].toggle:indeterminate {
    background-color: var(--novnc-buttongrey);
    background-image: var(--button-activation-overlay);
}
input[type=checkbox].toggle:checked {
    background-color: var(--novnc-blue);
    background-image: var(--button-activation-overlay-light);
    background-blend-mode: overlay;
}
input[type=checkbox].toggle::before {
    --circle-diameter: 10px;
    --circle-offset: 4px;
    width: var(--circle-diameter);
    height: var(--circle-diameter);
    top: var(--circle-offset);
    left: var(--circle-offset);
    background: white;
    border-radius: 6px;
}
input[type=checkbox].toggle:checked::before {
    left: calc(100% - var(--circle-offset) - var(--circle-diameter));
}
input[type=checkbox].toggle:indeterminate::before {
    left: calc(50% - var(--circle-diameter) / 2);
}

/* ------- RANGE SLIDERS ------- */

input[type=range] {
    border: unset;
    border-radius: 8px;
    height: 15px;
    padding: 0;
    background: transparent;
    /* Needed to get properly rounded corners on -moz-range-progress
       when the thumb is all the way to the right. Without overflow
       hidden, the pointy edges of the progress track shows to the
       right of the thumb. */
    overflow: hidden;
}
@supports selector(::-webkit-slider-thumb) {
    input[type=range] {
        /* Needs a fixed width to match clip-path */
        width: 125px;
        /* overflow: hidden is not ideal for hiding the left part of the box
           shadow of -webkit-slider-thumb since it doesn't match the smaller
           border-radius of the progress track. The below clip-path has two
           circular sides to make the ends of the track have correctly rounded
           corners. The clip path shape looks something like this:

                  +-------------------------------+
              /---|                               |---\
             |                                         |
              \---|                               |---/
                  +-------------------------------+

           The larger middle part of the clip path is made to have room for the
           thumb. By using margins on the track, we prevent the thumb from
           touching the ends of the track.
         */
        clip-path: path(' \
         M 4.5 3 \
         L 4.5 0 \
         L 120.5 0 \
         L 120.5 3 \
         A 1 1 0 0 1 120.5 12 \
         L 120.5 15 \
         L 4.5 15 \
         L 4.5 12 \
         A 1 1 0 0 1 4.5 3 \
        ');
    }
}
input[type=range]:hover {
    cursor: grab;
}
input[type=range]:active {
    cursor: grabbing;
}
input[type=range]:disabled {
    cursor: default;
}
input[type=range]:focus-visible {
    clip-path: none; /* Otherwise it hides the outline */
}
/* -webkit-slider.. & -moz-range.. cant be in selector lists:
   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
input[type=range]::-webkit-slider-runnable-track {
    background-color: var(--novnc-buttongrey);
    height: 7px;
    border-radius: 4px;
    margin: 0 3px;
}
input[type=range]::-moz-range-track {
    background-color: var(--novnc-buttongrey);
    height: 7px;
    border-radius: 4px;
}
input[type=range]::-moz-range-progress {
    background-color: var(--novnc-blue);
    height: 9px;
    /* Needs rounded corners only on the left side. Otherwise the rounding of
       the progress track starts before the thumb, when the thumb is close to
       the left edge. */
    border-radius: 5px 0 0 5px;
}
input[type=range]::-webkit-slider-thumb {
    appearance: none;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background-color: white;
    background-image: var(--button-activation-overlay);
    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
    -webkit-tap-highlight-color: transparent;
    border: 3px solid var(--novnc-blue);
    margin-top: -4px; /* (track height / 2) - (thumb height /2) */

    /* Since there is no way to style the left part of the range track in
       webkit, we add a large shadow (1000px wide) to the left of the thumb and
       then crop it with a clip-path shaped like this:
                              ___
        +-------------------/     \
        |      progress     |Thumb|
        +-------------------\ ___ /

        The large left part of the shadow is clipped by another clip-path on on
        the main range input element. */
    /* FIXME: We can remove the box shadow workaround when this is standardized:
              https://github.com/w3c/csswg-drafts/issues/4410 */

    box-shadow: calc(-100vw - 8px) 0 0 100vw var(--novnc-blue);
    clip-path: path(' \
     M -1000 3 \
     L 3 3 \
     L 15 7.5 \
     A 1 1 0 0 1 0 7.5 \
     A 1 1 0 0 1 15 7.5 \
     L 3 12 \
     L -1000 12 Z \
    ');
}
input[type=range]::-moz-range-thumb {
    appearance: none;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    box-sizing: border-box;
    background-color: white;
    background-image: var(--button-activation-overlay);
    border: 3px solid var(--novnc-blue);
    margin-top: -7px;
}

/* ------- FILE CHOOSERS ------- */

input[type=file] {
    background-image: none;
    border: none;
}
input::file-selector-button {
    margin-right: 6px;
}
input[type=file]:focus-visible {
    outline: none; /* We outline the button instead of the entire element */
}

/* ------- SELECT BUTTONS ------- */

select {
    --select-arrow: url('data:image/svg+xml;utf8, \
        <svg width="11" height="6" version="1.1" viewBox="0 0 11 6" \
             xmlns="http://www.w3.org/2000/svg"> \
            <path d="m10.5.5-5 5-5-5" fill="none" \
                  stroke="black" stroke-width="1.5" \
                  stroke-linecap="round" stroke-linejoin="round"/> \
        </svg>');

    /* FIXME: A bug in Firefox, requires a workaround for the background:
              https://bugzilla.mozilla.org/show_bug.cgi?id=1810958 */
    /* The dropdown list will show the select element's background above and
       below the options in Firefox. We want the entire dropdown to be white. */
    background-color: white;
    /* However, we don't want the select element to actually show a white
       background, so let's place a gradient above it with the color we want. */
    --grey-background: linear-gradient(var(--novnc-buttongrey) 100%,
                                       transparent);
    background-image:
        var(--select-arrow),
        var(--button-activation-overlay),
        var(--grey-background);
    background-position: calc(100% - var(--input-xpadding)), left top, left top;
    background-repeat: no-repeat;
    padding-right: calc(2*var(--input-xpadding) + 11px);
    overflow: auto;
}
/* FIXME: :active isn't set when the <select> is opened in Firefox:
          https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */
select:active {
    /* Rotated arrow */
    background-image: url('data:image/svg+xml;utf8, \
        <svg width="11" height="6" version="1.1" viewBox="0 0 11 6" \
             xmlns="http://www.w3.org/2000/svg" transform="rotate(180)"> \
            <path d="m10.5.5-5 5-5-5" fill="none" \
                  stroke="black" stroke-width="1.5" \
                  stroke-linecap="round" stroke-linejoin="round"/> \
        </svg>'),
        var(--button-activation-overlay),
        var(--grey-background);
}
select:disabled {
    background-image:
        var(--select-arrow),
        var(--grey-background);
}
/* Note that styling for <option> doesn't work in all browsers
   since its often drawn directly by the OS. We are generally very
   limited in what we can change here. */
option {
    /* Prevent Chrome from inheriting background-color from the <select> */
    background-color: white;
    color: black;
    font-weight: normal;
    background-image: var(--button-activation-overlay);
}
option:checked {
    background-color: var(--novnc-lightgrey);
}
/* Change the look when the <select> isn't used as a dropdown. When "size"
   or "multiple" are set, these elements behaves more like lists. */
select[size]:not([size="1"]), select[multiple] {
    background-color: white;
    background-image: unset; /* Don't show the arrow and other gradients */
    border: 1px solid var(--novnc-lightgrey);
    padding: 0;
    font-weight: normal; /* Without this, options get bold font in WebKit. */

    /* As an exception to the "list"-look, multi-selects in Chrome on Android,
       and Safari on iOS, are unfortunately designed to be shown as a single
       line. We can mitigate this inconsistency by at least fixing the height
       here. By setting a min-height that matches other input elements, it
       doesn't look too much out of place:
         (1px border * 2) + (6.5px padding * 2) + 24px line-height = 39px */
    min-height: 39px;
}
select[size]:not([size="1"]):focus-visible,
select[multiple]:focus-visible {
    /* Text input style focus-visible highlight */
    outline-offset: -1px;
}
select[size]:not([size="1"]) option, select[multiple] option {
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 4px var(--input-xpadding);
}
