@layer components {
  .fieldset {
    display: flex;
    flex-direction: column;
    gap: var(--block-space-half);
  }

  /* Hint text under an input ("Comma-separated…"). Pulls itself back against
     the input by countering the fieldset's flex gap, so notes hug their field
     with no extra vertical space. */
  .fieldset__note {
    color: var(--color-ink-dark);
    font-size: var(--text-small);
    margin-block-start: calc(var(--block-space-half) * -0.75);
  }

  /* Direct-upload gallery: existing + staged thumbnails + an "add" tile */
  .upload {
    border: 2px dashed var(--color-ink-light);
    border-radius: 0.75em;
    padding: var(--block-space);
    transition: border-color 150ms ease, background-color 150ms ease;
  }

  .upload--dragover {
    background-color: var(--color-selected);
    border-color: var(--color-link);
  }

  .upload--error {
    border-color: var(--color-negative);
  }

  .upload__input {
    display: none;
  }

  .upload__grid {
    display: flex;
    flex-wrap: wrap;
    gap: var(--block-space-half);
  }

  .upload__add {
    align-items: center;
    background: none;
    block-size: 5rem;
    border: 2px dashed var(--color-ink-light);
    border-radius: 0.5em;
    color: var(--color-ink-medium);
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 0.1em;
    inline-size: 5rem;
    justify-content: center;

    @media (any-hover: hover) {
      &:hover {
        border-color: var(--color-link);
        color: var(--color-link);
      }
    }
  }

  .upload__add-icon {
    font-size: 1.5rem;
    line-height: 1;
  }

  /* Staged selections become direct children of the grid, uniform with existing */
  .upload__preview {
    display: contents;
  }

  /* Staged (selected, not-yet-uploaded) files, each removable before submit */
  .upload__staged {
    block-size: 5rem;
    border-radius: 0.5em;
    inline-size: 5rem;
    overflow: hidden;
    position: relative;

    img {
      block-size: 100%;
      inline-size: 100%;
      object-fit: cover;
    }
  }

  .upload__staged-remove {
    align-items: center;
    background-color: oklch(var(--lch-black) / 55%);
    border: 0;
    border-radius: 50%;
    block-size: 1.4em;
    color: white;
    cursor: pointer;
    display: flex;
    font-size: 1rem;
    inline-size: 1.4em;
    inset-block-start: 0.25rem;
    inset-inline-end: 0.25rem;
    justify-content: center;
    line-height: 1;
    padding: 0;
    position: absolute;
  }

  .upload__progress {
    background-color: var(--color-ink-lightest);
    block-size: 0.4rem;
    border-radius: 99rem;
    margin-block-start: var(--block-space-half);
    overflow: hidden;
  }

  .upload__bar {
    background-color: var(--color-link);
    block-size: 100%;
    inline-size: 0;
    transition: inline-size 150ms ease;
  }

  /* Existing attachment tiles, with a mark-to-remove toggle (applied on save) */
  .upload__file {
    align-items: center;
    block-size: 5rem;
    border-radius: 0.5em;
    cursor: pointer;
    display: flex;
    inline-size: 5rem;
    justify-content: center;
    overflow: hidden;
    position: relative;

    img {
      block-size: 100%;
      inline-size: 100%;
      object-fit: cover;
    }
  }

  .upload__filename {
    padding: var(--block-space-half);
    text-align: center;
    word-break: break-word;
  }

  .upload__remove-check {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .upload__remove {
    align-items: center;
    background-color: oklch(var(--lch-black) / 55%);
    block-size: 1.4em;
    border-radius: 50%;
    color: white;
    display: flex;
    font-size: 1rem;
    inline-size: 1.4em;
    inset-block-start: 0.25rem;
    inset-inline-end: 0.25rem;
    justify-content: center;
    line-height: 1;
    position: absolute;
  }

  /* Marked for removal → dim + strike so it's clear it'll go on save */
  .upload__file:has(.upload__remove-check:checked) {
    outline: 2px solid var(--color-negative);

    img,
    .upload__filename {
      filter: grayscale(1);
      opacity: 0.45;
    }

    .upload__remove {
      background-color: var(--color-negative);
    }
  }

  .input {
    accent-color: var(--input-accent-color, var(--color-ink));
    background-color: var(--input-background, transparent);
    border-radius: var(--input-border-radius, 0.5em);
    border: var(--input-border-size, 1px) solid var(--input-border-color, var(--color-ink-medium));
    color: var(--input-color, var(--color-ink));
    font-size: max(16px, 1em);
    inline-size: 100%;
    line-height: inherit;
    max-inline-size: var(--input-max-width, 30em);
    padding: var(--input-padding, 0.5em 0.8em);
    resize: none;

    &:autofill,
    &:-webkit-autofill,
    &:-webkit-autofill:hover,
    &:-webkit-autofill:focus {
      -webkit-text-fill-color: var(--color-ink);
      -webkit-box-shadow: 0 0 0px 1000px var(--color-selected) inset;
    }

    &:where(:focus) {
      --focus-ring-offset: -1px;
    }

    &[readonly] {
      --focus-ring-size: 0;
    }

    &[type='number'] {
      &::-webkit-outer-spin-button,
      &::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
      }
    }

    @supports (hanging-punctuation: first) and (font: -apple-system-body) and (-webkit-appearance: none) {
      @media (hover: none) {
        font-size: max(16px, 1em) !important;
      }
    }

    &.input--invalid {
      --input-border-color: var(--color-negative);
    }
  }

  .field-error {
    color: var(--color-negative);
    font-size: var(--text-small);
    margin: 0;
  }

  /* Applies to every <select class="input"> on its own rather than through a
     modifier class. Unlike .input--invalid, which encodes state the markup has
     to declare, "this is a select" is derivable from the tag — so a modifier
     could only ever be forgotten, and was, at 7 of 11 call sites.
     [multiple] is excluded: a list box renders as rows, so the pill radius and
     caret would be wrong for it. */
  select.input:not([multiple]) {
    --input-border-radius: 2em;
    --input-padding: 0.5em 1.8em 0.5em 1.2em;
    --caret-icon: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23000'/%3E%3C/svg%3E");
    --caret-icon-dark: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23fff'/%3E%3C/svg%3E");

    -webkit-appearance: none;
    appearance: none;
    background-image: var(--caret-icon);
    background-size: 0.5em;
    background-position: center right 0.9em;
    background-repeat: no-repeat;
    text-align: start;

    html[data-theme="dark"] & {
      --caret-icon: var(--caret-icon-dark);
    }

    @media (prefers-color-scheme: dark) {
      html:not([data-theme]) & {
        --caret-icon: var(--caret-icon-dark);
      }
    }

    option {
      background-color: var(--color-canvas);
      color: var(--color-ink);
    }
  }

  /* An ordinary .input holding a fixed count of digits, sized to them rather
     than to the form. `ch` is the width of a "0", so `--digits * 1ch` is exactly
     the value; the rest is padding, which has to be counted here because
     box-sizing is border-box.

     The side padding is trimmed from .input's 0.8em: a two-digit value under
     full-width padding is mostly padding. The placeholder is set below 1em
     because "mm" is half again as wide as "00" in this font, and sizing the box
     to fit the placeholder would make every field wider than its content needs.
     tabular-nums keeps 1 and 8 the same width so nothing twitches as you type. */
  .input--digits {
    --input-padding: 0.5em 0.5em;

    font-variant-numeric: tabular-nums;
    inline-size: calc(var(--digits, 2) * 1ch + 1.6em);
    min-inline-size: 0;
    text-align: center;

    &::placeholder {
      font-size: 0.8em;
    }
  }

  .input--digits-4 {
    --digits: 4;
  }

  .input--textarea {
    --input-padding: 0.5em 0.8em;
    /* Free text gets more room than single-line inputs, but still caps out
       near a comfortable prose measure. */
    --input-max-width: 45em;

    line-height: inherit;
    min-block-size: 6em;
    min-inline-size: 100%;

    @supports (field-sizing: content) {
      field-sizing: content;
      max-block-size: 20em;
      min-block-size: 3em;
    }
  }

  /* Toggle switch */
  .switch {
    --switch-color: var(--color-ink-medium);

    block-size: 1.75em;
    border-radius: 2em;
    display: inline-flex;
    inline-size: 3em;
    position: relative;

    &:has(:focus-visible) {
      .switch__btn {
        outline: var(--focus-ring-size) solid var(--focus-ring-color);
      }
    }
  }

  .switch__input {
    block-size: 0;
    inline-size: 0;
    opacity: 0.1;
  }

  .switch__btn {
    background-color: var(--switch-color);
    border-radius: 2em;
    cursor: pointer;
    inset: 0;
    outline-offset: var(--focus-ring-offset);
    position: absolute;
    transition: 150ms ease;

    &::before {
      background-color: var(--color-ink-inverted);
      block-size: 1.35em;
      border-radius: 50%;
      content: "";
      inline-size: 1.35em;
      inset-block-end: 0.2em;
      inset-inline-start: 0.2em;
      position: absolute;
      transition: 150ms ease;
    }

    @media (any-hover: hover) {
      &:hover {
        background-color: color-mix(in srgb, var(--switch-color) 80%, var(--color-ink));
      }
    }

    .switch__input:checked + & {
      --switch-color: var(--color-link);

      &::before {
        transform: translateX(1.2em);
      }
    }

    .switch__input:disabled + & {
      cursor: not-allowed;
      opacity: 0.5;
    }
  }

  /* Checkbox */
  .checkbox {
    appearance: none;
    background-color: var(--color-canvas);
    block-size: 1.1em;
    border: 1px solid currentColor;
    border-radius: 0.15em;
    color: currentColor;
    display: grid;
    font: inherit;
    inline-size: 1.1em;
    margin: 0;
    place-content: center;
    transform: translateY(0.1em);

    &::before {
      background-color: CanvasText;
      block-size: 0.65em;
      box-shadow: inset 1em 1em currentColor;
      clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
      content: "";
      inline-size: 0.65em;
      transform: scale(0);
      transform-origin: center;
      transition: 150ms transform ease-in-out;
    }

    &:checked::before {
      transform: scale(1) rotate(10deg);
    }

    &:where([disabled]):not(:hover):not(:active) {
      filter: none;
      opacity: 0.5;
    }
  }
}
