/* ==========================================================================
   Glass Select 1.1 — frosted-glass dropdown
   --------------------------------------------------------------------------
   Plain CSS. No framework, no build step, no external request.

   The component enhances a native <select>. Until the script has run, the
   browser control is what the visitor sees and uses, so the page never
   depends on JavaScript to be usable.

   PROPORTIONS
   Every metric is expressed in em and derives from --gs-font-size. The ratios
   below were measured on the reference design, pixel by pixel, and they are
   what makes the component look like itself at any size:

     text          1                 (the reference)
     trigger       1.77 em tall
     option        1.62 em tall      (options are shorter than the trigger)
     gap           0.063 em          (barely a hairline between panes)
     radius        0.21 em           (squarer than it looks at a glance)
     side padding  0.41 em
     chevron       0.70 em wide, in a 1 em box

   So changing --gs-font-size rescales the whole component, and nothing else
   needs to be touched.

   Order of this file:
     1. Design tokens
     2. Block and label
     3. Native control (fallback, then hidden once enhanced)
     4. Trigger
     5. List and options
     6. Open / close motion
     7. Variants
     8. Fallbacks: no backdrop-filter, reduced motion, forced colours

   Naming follows BEM: .glass-select, .glass-select__part, .glass-select--variant.
   ========================================================================== */

/* ==========================================================================
   1. Design tokens
   ========================================================================== */

.glass-select {
  /* Scale. The reference design is a large-type control: the label is as big
     as the value. Use .glass-select--compact for an ordinary form field. */
  --gs-font-size: 2rem;

  /* Glass. The tint is stored as raw RGB channels so the same value can be
     reused at several opacities below. */
  --gs-tint: 255 255 255;
  --gs-tint-idle: 0.1; /* measured on the reference: ~10% white */
  --gs-tint-current: 0.15; /* the option currently selected */
  --gs-tint-hover: 0.19; /* pointer over, or keyboard-highlighted */
  --gs-blur: 18px;
  /* The reference glass keeps the background *more* saturated than a plain
     white veil would: solving tint + saturation against the measured pixels
     lands around 180%. */
  --gs-saturate: 180%;
  --gs-border: rgba(255, 255, 255, 0.09);
  --gs-border-active: rgba(255, 255, 255, 0.32); /* keyboard / pointer row */
  --gs-highlight: rgba(255, 255, 255, 0.12); /* 1px light edge on top */
  --gs-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.18);

  /* Ink. The reference text is not pure white but ~72% white, which is what
     gives it its soft, photographic look. */
  --gs-color: rgba(255, 255, 255, 0.72);
  --gs-color-label: rgba(255, 255, 255, 0.85);
  --gs-color-muted: rgba(255, 255, 255, 0.5); /* placeholder, group titles */
  --gs-color-disabled: rgba(255, 255, 255, 0.3);
  --gs-focus: rgba(255, 255, 255, 0.8);

  /* Metrics, all relative to --gs-font-size (see the header). */
  --gs-height: 1.77em;
  --gs-option-height: 1.58em;
  --gs-gap: 0.045em;
  --gs-radius: 0.21em;
  --gs-padding-inline: 0.41em;
  --gs-icon: 1em; /* box of the chevron; its stroke spans 70% of it */
  --gs-group-size: 0.55em; /* <optgroup> title */
  --gs-list-max-height: 9em;
  --gs-font: inherit;

  /* Motion */
  --gs-duration: 260ms;
  --gs-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --gs-stagger: 34ms; /* delay added per option, top to bottom */

  position: relative;
  display: block;
  font-family: var(--gs-font);
  /* Set here, not on the parts: every em below then resolves against it. */
  font-size: var(--gs-font-size);
  color: var(--gs-color);
}

/* Declared by the component rather than assumed from the page: without it, a
   host page with the default content-box adds padding and borders on top of
   every height below, and the options come out taller than the trigger. */
.glass-select,
.glass-select *,
.glass-select *::before,
.glass-select *::after {
  box-sizing: border-box;
}

/* ==========================================================================
   2. Block and label
   ========================================================================== */

.glass-select__label {
  display: block;
  margin-bottom: 0.34em;
  color: var(--gs-color-label);
  font-size: 1em;
  line-height: 1.2;
}

/* The trigger and the list share this positioning context. Nothing else does,
   so a page can put the label anywhere without breaking the dropdown. */
.glass-select__field {
  position: relative;
}

/* ==========================================================================
   3. Native control

   Two states. Before the script runs (or if it never does) the browser select
   is styled to match the rest of the component. Once enhanced it stays in the
   DOM — it carries the name, the value and the native validation — but it is
   pushed out of sight.
   ========================================================================== */

.glass-select__native {
  width: 100%;
  height: var(--gs-height);
  padding: 0 var(--gs-padding-inline);
  border: 1px solid var(--gs-border);
  border-radius: var(--gs-radius);
  appearance: none;
  background-color: rgb(var(--gs-tint) / var(--gs-tint-idle));
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 0.72em center, right 0.53em center;
  background-repeat: no-repeat;
  background-size: 0.19em 0.19em, 0.19em 0.19em;
  color: inherit;
  font: inherit;
  -webkit-backdrop-filter: blur(var(--gs-blur)) saturate(var(--gs-saturate));
  backdrop-filter: blur(var(--gs-blur)) saturate(var(--gs-saturate));
}

/* The option list of a native select is drawn by the operating system, which
   ignores most of the styling above — hence these two lines, so the fallback
   stays readable on a dark page. */
.glass-select__native option {
  background-color: #16181a;
  color: #fff;
}

/* Enhanced: 1px tall, transparent, no pointer events. Not `display: none`,
   because a hidden control cannot receive focus and the browser then refuses
   to report a failed `required` check on it. */
.glass-select.is-enhanced .glass-select__native {
  position: absolute;
  bottom: 0;
  left: var(--gs-padding-inline);
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  background: none;
  pointer-events: none;
}

/* ==========================================================================
   4. Trigger

   A real <button>, so Enter and Space work without a line of JavaScript.
   ========================================================================== */

.glass-select__trigger {
  display: flex;
  align-items: center;
  gap: 0.4em;
  width: 100%;
  height: var(--gs-height);
  padding: 0 var(--gs-padding-inline);
  border: 1px solid var(--gs-border);
  border-radius: var(--gs-radius);
  box-shadow: inset 0 1px 0 var(--gs-highlight), var(--gs-shadow);
  background-color: rgb(var(--gs-tint) / var(--gs-tint-idle));
  color: inherit;
  font: inherit;
  line-height: 1;
  text-align: left;
  cursor: pointer;
  transition: background-color 160ms linear, border-color 160ms linear;
  -webkit-backdrop-filter: blur(var(--gs-blur)) saturate(var(--gs-saturate));
  backdrop-filter: blur(var(--gs-blur)) saturate(var(--gs-saturate));
}

.glass-select__trigger:hover {
  background-color: rgb(var(--gs-tint) / var(--gs-tint-hover));
}

.glass-select__trigger:focus-visible {
  outline: 2px solid var(--gs-focus);
  outline-offset: 2px;
}

.glass-select__trigger:disabled {
  color: var(--gs-color-disabled);
  cursor: not-allowed;
  opacity: 0.6;
}

/* The value takes the free space and truncates rather than wrapping: the
   trigger keeps a fixed height whatever the label length. */
.glass-select__value {
  flex: 1;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.glass-select__value.is-placeholder {
  color: var(--gs-color-muted);
}

.glass-select__chevron {
  flex-shrink: 0;
  width: var(--gs-icon);
  height: var(--gs-icon);
  transition: transform var(--gs-duration) var(--gs-ease);
}

.glass-select.is-open .glass-select__chevron {
  transform: rotate(180deg);
}

/* ==========================================================================
   5. List and options

   The list itself is invisible: each option is its own pane of glass, which
   is what gives the component its look. The blur is applied per option, not
   once on a shared panel.
   ========================================================================== */

.glass-select__list {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--gs-gap));
  right: 0;
  left: 0;
  max-height: var(--gs-list-max-height);
  margin: 0;
  padding: 0;
  overflow-y: auto;
  visibility: hidden;
  list-style: none;
  /* Delayed so the options finish fading out before the list is taken away.
     Reset to 0s when open, otherwise opening would wait too. */
  transition: visibility 0s linear var(--gs-duration);
  overscroll-behavior: contain;
}

.glass-select.is-open .glass-select__list {
  visibility: visible;
  transition-delay: 0s;
}

/* Opening upwards, decided by the script when the viewport is too short. */
.glass-select.is-up .glass-select__list {
  top: auto;
  bottom: calc(100% + var(--gs-gap));
}

.glass-select__option {
  display: flex;
  align-items: center;
  min-height: var(--gs-option-height);
  padding: 0.1em var(--gs-padding-inline);
  border: 1px solid var(--gs-border);
  border-radius: var(--gs-radius);
  box-shadow: inset 0 1px 0 var(--gs-highlight), var(--gs-shadow);
  background-color: rgb(var(--gs-tint) / var(--gs-tint-idle));
  line-height: 1.25;
  cursor: pointer;
  -webkit-backdrop-filter: blur(var(--gs-blur)) saturate(var(--gs-saturate));
  backdrop-filter: blur(var(--gs-blur)) saturate(var(--gs-saturate));
}

.glass-select__option + .glass-select__option,
.glass-select__group > .glass-select__option:first-child {
  margin-top: var(--gs-gap);
}

/* Selected value. Kept discreet, as in the reference design, where the current
   option barely stands out from the others. */
.glass-select__option[aria-selected="true"] {
  background-color: rgb(var(--gs-tint) / var(--gs-tint-current));
}

/* Pointer hover and keyboard highlight are one state on purpose: the mouse
   and the arrow keys must never highlight two different rows. */
/* Discreet on purpose: the reference design has no bright ring on the current
   row. The lighter surface carries the state, the border only confirms it. */
.glass-select__option.is-active {
  background-color: rgb(var(--gs-tint) / var(--gs-tint-hover));
  border-color: var(--gs-border-active);
}

.glass-select__option[aria-disabled="true"] {
  color: var(--gs-color-disabled);
  cursor: not-allowed;
}

.glass-select__option[aria-disabled="true"].is-active {
  border-color: var(--gs-border);
  background-color: rgb(var(--gs-tint) / var(--gs-tint-idle));
}

/* <optgroup> becomes a titled group. The title is not selectable. */
.glass-select__group {
  margin: 0;
  padding: 0;
  list-style: none;
}

.glass-select__group-label {
  display: block;
  padding: 0.5em var(--gs-padding-inline) 0.25em;
  color: var(--gs-color-muted);
  font-size: var(--gs-group-size);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ==========================================================================
   6. Open / close motion

   Options fall one after the other, top to bottom (bottom to top when the
   list opens upwards). Closing plays at once: a staggered exit feels slow.
   ========================================================================== */

.glass-select__option {
  transform: translateY(-0.25em) scale(0.97);
  transform-origin: top center;
  opacity: 0;
  transition: opacity var(--gs-duration) var(--gs-ease),
    transform var(--gs-duration) var(--gs-ease), background-color 160ms linear,
    border-color 160ms linear;
}

.glass-select.is-up .glass-select__option {
  transform: translateY(0.25em) scale(0.97);
  transform-origin: bottom center;
}

.glass-select.is-open .glass-select__option {
  transform: none;
  opacity: 1;
  /* --gs-index is written once per option by the script, --gs-count once per
     component. Opening upwards runs the same cascade in reverse. */
  transition-delay: calc(var(--gs-index, 0) * var(--gs-stagger));
}

.glass-select.is-open.is-up .glass-select__option {
  transition-delay: calc(
    (var(--gs-count, 1) - 1 - var(--gs-index, 0)) * var(--gs-stagger)
  );
}

/* ==========================================================================
   7. Variants
   ========================================================================== */

/* Ordinary form-field scale. Everything else follows, since all metrics are
   in em. */
.glass-select--compact {
  --gs-font-size: 1.0625rem;
  --gs-height: 3.3em; /* 56px: a comfortable target at this text size */
  --gs-option-height: 3em;
  --gs-gap: 0.35em;
  --gs-radius: 0.7em;
  --gs-padding-inline: 1.05em;
  --gs-icon: 1em;
  --gs-group-size: 0.72em;
  --gs-list-max-height: 16em;
  --gs-color: rgba(255, 255, 255, 0.92);
  --gs-color-label: rgba(255, 255, 255, 0.92);
}

/* On a light background, where a white tint would disappear. */
.glass-select--on-light {
  --gs-tint: 14 16 18;
  --gs-tint-idle: 0.06;
  --gs-tint-current: 0.1;
  --gs-tint-hover: 0.13;
  --gs-border: rgba(14, 16, 18, 0.1);
  --gs-border-active: rgba(14, 16, 18, 0.3);
  --gs-highlight: rgba(255, 255, 255, 0.55);
  --gs-shadow: 0 0.25em 0.7em rgba(14, 16, 18, 0.1);
  --gs-color: rgba(22, 24, 26, 0.82);
  --gs-color-label: rgba(22, 24, 26, 0.9);
  --gs-color-muted: rgba(22, 24, 26, 0.5);
  --gs-color-disabled: rgba(22, 24, 26, 0.32);
  --gs-focus: rgba(22, 24, 26, 0.7);
}

.glass-select--on-light .glass-select__native option {
  background-color: #fff;
  color: #16181a;
}

/* ==========================================================================
   8. Fallbacks
   ========================================================================== */

/* Without backdrop-filter (older Firefox, blur disabled by the user) a 10%
   white tint is almost invisible. Fall back to an opaque surface. */
@supports not (
  (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))
) {
  .glass-select {
    --gs-tint-idle: 0.82;
    --gs-tint-current: 0.88;
    --gs-tint-hover: 0.92;
    --gs-color: rgba(22, 24, 26, 0.88);
    --gs-color-muted: rgba(22, 24, 26, 0.55);
    --gs-color-disabled: rgba(22, 24, 26, 0.35);
  }

  .glass-select--on-light {
    --gs-tint: 255 255 255;
    --gs-tint-idle: 1;
    --gs-tint-current: 0.94;
    --gs-tint-hover: 0.9;
  }
}

@media (prefers-reduced-motion: reduce) {
  .glass-select {
    --gs-duration: 1ms;
    --gs-stagger: 0ms;
  }

  .glass-select__option {
    transform: none;
  }
}

/* Windows high-contrast mode strips backgrounds and shadows: the states have
   to be redrawn with system colours or every row looks identical. */
@media (forced-colors: active) {
  .glass-select__trigger,
  .glass-select__option {
    border: 1px solid ButtonBorder;
    background: ButtonFace;
    color: ButtonText;
    forced-color-adjust: none;
  }

  .glass-select__option.is-active,
  .glass-select__option[aria-selected="true"] {
    background: Highlight;
    color: HighlightText;
  }

  .glass-select__option[aria-disabled="true"] {
    color: GrayText;
  }
}
