/* ============================================================================
   APPEARANCE — orbs, colour wash, live glass overrides, and the settings page.
   Loaded after app.css so these win where they need to.
   ============================================================================ */

/* ---------------------------------------------------------------- ORBS ----
   Geometry comes from ebotservers.com verbatim (four blurred blobs, 50/60/40/30vw,
   offsets -10%/40%/-20%/20%, staggered delays via nth-child). Animated with the site's
   float/pulse keyframes. Held on a fixed layer with pointer-events:none so they can
   never intercept a click or affect layout.
   -------------------------------------------------------------------------- */
#ops-orbs {
  position: fixed; inset: 0;
  /* NEGATIVE, deliberately. The chrome (.wrap / .top / .side) is raised to z-index 1 so it
     paints above the orb layer; anything at z-index 0 would sit level with the page and
     `mix-blend-mode` would have nothing behind it to blend against across the whole panel,
     which is exactly why blend looked like it did nothing. Under it, every blend mode has the
     body background to act on. */
  z-index: -1;
  overflow: hidden; pointer-events: none;
  display: none;
}
html[data-orbs="on"] #ops-orbs { display: block; }

/* The orbs blend against the BODY, so the body must actually paint a colour. A transparent
   body (no background set) makes every blend mode a no-op against nothing — the other reason
   blend appeared broken. A solid --bg base guarantees there is something to blend with. */
html[data-orbs="on"] body { background-color: var(--bg); }


#ops-orbs .orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(var(--orb-blur, 80px));
  opacity: var(--orb-opacity, .45);
  mix-blend-mode: var(--orb-blend, multiply);
  will-change: transform;
  animation: var(--orb-anim, orb-float) var(--orb-speed, 2s) infinite ease-in-out;
}
/* size scale multiplies each orb's own vw box */
#ops-orbs .orb-1 { top: -10%; left: -10%; width: calc(50vw * var(--orb-size, 1)); height: calc(50vw * var(--orb-size, 1)); background: #e0f2fe; animation-delay: 0s; }
#ops-orbs .orb-2 { top: 40%; right: -20%; width: calc(60vw * var(--orb-size, 1)); height: calc(60vw * var(--orb-size, 1)); background: #f0fdf4; animation-delay: -5s; }
#ops-orbs .orb-3 { bottom: -20%; left: 20%; width: calc(40vw * var(--orb-size, 1)); height: calc(40vw * var(--orb-size, 1)); background: #eef2ff; animation-delay: -10s; }
#ops-orbs .orb-4 { top: 20%; left: 30%; width: calc(30vw * var(--orb-size, 1)); height: calc(30vw * var(--orb-size, 1)); background: #f5f3ff; animation-delay: -15s; }

/* Dark palettes: the site's orbs are LIGHT tints on a light page. On a near-black base
   they need screen blending or they read as grey smudges. */
html[data-orbs="on"] #ops-orbs .orb {
  background: color-mix(in srgb, var(--acc) 55%, #ffffff);
}
/* content must sit above the orb layer */
/* content must sit above the orb layer. ONLY the z-index — re-declaring `position` here
   would override `position: sticky` on .side (same specificity, later file), which is
   exactly how the sidebar came unstuck and stopped reaching the bottom. A sticky element
   already forms a stacking context, so z-index takes effect without touching position. */
html[data-orbs="on"] .wrap,
html[data-orbs="on"] .top { position: relative; z-index: 1; }
html[data-orbs="on"] .side { z-index: 1; }

/* Reduce-motion: hold the orbs still rather than animating behind the text. */
@media (prefers-reduced-motion: reduce) {
  #ops-orbs .orb { animation: none !important; }
}

/* ---------------------------------------------------------- COLOUR WASH ----
   A tinted layer between the theme background and the content. Sits BELOW the orb
   layer's sibling order but above body paint, and never captures pointer events.
   -------------------------------------------------------------------------- */
#ops-wash {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none; display: none;
}

/* --------------------------------------------------- LIVE GLASS OVERRIDES ---
   When the operator moves the glass dials we set data-glass="custom" and the
   --ap-glass-* vars. These rules translate those dials onto the real surfaces.
   Without a custom setting the theme's own glass values apply untouched.
   -------------------------------------------------------------------------- */
html[data-glass="custom"] .card,
html[data-glass="custom"] .ctl-menu,
html[data-glass="custom"] .chat-pop,
html[data-glass="custom"] .login,
html[data-glass="custom"] .panel,
html[data-glass="custom"] .side,
html[data-glass="custom"] .top,
html[data-glass="custom"] .ap-card {
  backdrop-filter: saturate(var(--ap-glass-sat, 140%)) blur(var(--ap-glass-blur, 14px));
  -webkit-backdrop-filter: saturate(var(--ap-glass-sat, 140%)) blur(var(--ap-glass-blur, 14px));
}
html[data-glass="custom"] .card,
html[data-glass="custom"] .ctl-menu,
html[data-glass="custom"] .chat-pop,
html[data-glass="custom"] .panel,
html[data-glass="custom"] .ap-card {
  background-color: color-mix(in srgb, var(--card) calc(var(--ap-glass-a, .6) * 100%), transparent);
  border-color: color-mix(in srgb, var(--glass-line, var(--line)) calc(var(--ap-glass-line, .4) * 100%), transparent);
  box-shadow: 0 calc(10px * var(--ap-glass-sh, .45)) calc(30px * var(--ap-glass-sh, .45))
              rgba(0, 0, 0, calc(.45 * var(--ap-glass-sh, .45)));
}
html[data-glass="custom"] .theme-btn,
html[data-glass="custom"] .nav-chip,
html[data-glass="custom"] .bigbtn,
html[data-glass="custom"] .refresh button {
  backdrop-filter: saturate(var(--ap-glass-sat, 140%)) blur(var(--ap-glass-blur, 14px));
  -webkit-backdrop-filter: saturate(var(--ap-glass-sat, 140%)) blur(var(--ap-glass-blur, 14px));
}

/* ================================================== THE APPEARANCE PAGE ==== */
.appear { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); }
.ap-card {
  border: 1px solid var(--glass-line, var(--line));
  background: var(--glass, var(--card));
  padding: 12px 14px 14px;
  min-width: 0;
}
.ap-card h2 {
  font-size: 12px; letter-spacing: 0; text-transform: uppercase;
  margin: 0 0 4px; color: var(--fg); display: flex; gap: 8px; align-items: baseline;
  flex-wrap: wrap;
}
.ap-card h2 .meta { font-size: 10px; letter-spacing: 0; text-transform: none; }
.ap-hint { font-size: 11px; color: var(--dim, var(--fg)); margin: 0 0 10px; line-height: 1.4; }

/* pill groups (theme / font / size) */
.ap-themes, .ap-fonts, .ap-sizes { display: flex; flex-wrap: wrap; gap: 6px; }
.ap-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; cursor: pointer; font: inherit; font-size: 12px;
  color: var(--fg); background: var(--card);
  border: 1px solid var(--glass-line, var(--line)); border-radius: 0;
  white-space: nowrap;
}
.ap-pill:hover { border-color: var(--acc); color: var(--acc); }
.ap-pill.on { border-color: var(--acc); color: var(--acc); font-weight: 700;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--acc) 40%, transparent); }
.ap-sw { width: 10px; height: 10px; border-radius: 50%; display: inline-block;
  border: 1px solid rgba(0,0,0,.25); flex: 0 0 auto; }

/* background image grid */
.ap-bg-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(74px, 1fr));
  gap: 6px; max-height: 250px; overflow-y: auto; margin-bottom: 10px;
}
.ap-bg-tile {
  position: relative; height: 50px; padding: 0; cursor: pointer;
  border: 1px solid var(--glass-line, var(--line));
  background-size: cover; background-position: center; background-repeat: no-repeat;
  overflow: hidden;
}
.ap-bg-tile:hover { border-color: var(--acc); }
.ap-bg-tile.on { border-color: var(--acc); box-shadow: 0 0 0 2px color-mix(in srgb, var(--acc) 45%, transparent); }
.ap-bg-tile.ap-bg-none { display: flex; align-items: center; justify-content: center;
  background: var(--panel); color: var(--dim); font-size: 10px; }
.ap-bg-n {
  position: absolute; inset: auto 0 0 0; font-size: 8.5px; padding: 1px 3px;
  background: rgba(0,0,0,.6); color: #fff; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}

/* sliders */
.ap-slider {
  display: flex; align-items: center; gap: 9px; margin: 7px 0;
  font-size: 11px; color: var(--dim, var(--fg));
}
.ap-slider > span { flex: 0 0 92px; }
.ap-slider input[type="range"] { flex: 1 1 auto; min-width: 0; accent-color: var(--acc); cursor: pointer; }
.ap-slider input[type="color"] {
  width: 44px; height: 24px; padding: 0; border: 1px solid var(--glass-line, var(--line));
  background: none; cursor: pointer;
}
.ap-slider output {
  flex: 0 0 46px; text-align: right; font-variant-numeric: tabular-nums;
  color: var(--fg); font-size: 11px;
}
.ap-select {
  flex: 1 1 auto; font: inherit; font-size: 11px; padding: 3px 6px;
  color: var(--fg); background: var(--card);
  border: 1px solid var(--glass-line, var(--line));
}
.ap-check { display: flex; align-items: center; gap: 8px; font-size: 12px; margin: 6px 0 2px; cursor: pointer; }
.ap-check input { accent-color: var(--acc); }
.ap-reset {
  font: inherit; font-size: 11.5px; padding: 6px 11px; cursor: pointer;
  color: var(--fg); background: var(--card);
  border: 1px solid var(--glass-line, var(--line));
}
.ap-reset:hover { border-color: var(--acc); color: var(--acc); }


/* ---- PER-SURFACE TRANSPARENCY (LOADED LATE — see note) -------------------------
   Set --surf-*-eff on <html> from appearance.js; this file only consumes it.

   WHY THIS LIVES IN appearance.css AND NOT app.css: the themes paint .top/.side/.card
   with `background: var(--glass)` at the same specificity as any rule here, so SOURCE
   ORDER decides the winner. themes.css loads AFTER app.css, so the same rules placed in
   app.css silently lost. appearance.css loads after themes.css, which is the only place
   this can win.

   The `-eff` suffix means "effective": appearance.js folds in the Glass morphism surface
   opacity when the operator ticks "apply glass to the surfaces", so ONE number drives each
   surface no matter which control the operator reached for. The CSS never has to know
   whether glass is involved.

   Mirrors of a theme can never be "lost": at 0 the mix resolves to exactly what the theme
   declared, which is why these ship on by default and are inert until moved. */
:root { --surf-top-eff: 1; --surf-body-eff: 1; --surf-card-eff: 1; --surf-side-eff: 1; }

/* header — the real element is .top, which sits inside .main */
html[data-surfaces] .top {
  background-color: color-mix(in srgb,
      var(--panel, var(--bg)) calc(var(--surf-top-eff) * 100%), transparent);
}
/* page body — EVERY module page renders through div.modbody, so this one dial covers the
   whole content area without hunting for each module's own wrapper. */
html[data-surfaces] .modbody {
  background-color: color-mix(in srgb,
      var(--panel, var(--bg)) calc(var(--surf-body-eff) * 100%), transparent);
}
/* cards — section.ap-card is the Appearance page's card; .card and the table cells are the
   same idea on data pages, so the dial covers all of them. Mixed from --card (a SOLID hex in
   every theme) rather than --glass, which is already partly transparent: mixing an alpha
   colour toward transparent gives no visible change at low values. */
html[data-surfaces] .ap-card,
html[data-surfaces] .card,
html[data-surfaces] .livedata > div,
html[data-surfaces] .b td,
html[data-surfaces] .b th {
  background-color: color-mix(in srgb,
      var(--card, var(--bg)) calc(var(--surf-card-eff) * 100%), transparent);
}
/* Dashboard containers, as their OWN rule.
   NOTE (this bit us once): a comment must never be placed inside a selector list. The previous
   edit put this block between a trailing comma and the next selector, which silently swallowed
   .dash-panel / .dw / .dw-wrap into the comment's neighbourhood and left the declarations
   applying to the wrong element — the CSS read correctly while the browser used the old value.
   The dashboard renders DIRECTLY into .modbody: no .livedata wrapper, no .ap-card around the
   widget grid, so the real containers are .dash-panel, .dw (the tiles are <a>) and .dw-wrap. */
html[data-surfaces] .dash-panel {
  background-color: color-mix(in srgb,
      var(--card, var(--bg)) calc(var(--surf-card-eff) * 100%), transparent);
}
/* FLAT TRANSPARENT, by operator instruction: the summary tile grid and the tiles themselves
   carry NO background at all, both the colour and any theme background-image. The dial is
   deliberately NOT applied here — the tiles sit directly on the page surface, so the
   background image and the orbs show through them. Declared together because `background`
   resets the image property too, and setting only `background-color` would leave a theme
   gradient painting an opaque layer over the transparency. */
html[data-surfaces] .dw-wrap,
/* .dw (the seven summary TILES) is deliberately NOT in this list: the wrappers must be
   see-through, but the tiles carry "a lil background like the other widgets". Listing .dw here
   made the wrappers transparent at the cost of flattening the tiles to rgba(0,0,0,0). */
/* Specificity matters here: the dashboard renders as
   .modbody > section.livedata > div.dw-wrap, so the `.livedata > div` selector in the card
   rule above is (0,2,2) and this one was only (0,2,1) — the card rule won and painted an
   opaque --card over the transparency. Naming the direct-child form matches that weight
   (0,2,2) and, being later in the file, takes it. */
html[data-surfaces] .livedata > .dw-wrap,
html[data-surfaces] .livedata > .wgrid,
html[data-surfaces] .livedata > .dash-cols,
html[data-surfaces] .livedata > .qa-wrap,
html[data-surfaces] .wgrid,
html[data-surfaces] .dash-cols,
html[data-surfaces] .qa-wrap {
  background: transparent;
  background-color: transparent;
  background-image: none;
}
/* sidebar */
html[data-surfaces] .side {
  background-color: color-mix(in srgb,
      var(--panel, var(--bg)) calc(var(--surf-side-eff) * 100%), transparent);
}
/* the page dial also has to stand the theme's decorative gradients DOWN, or the body keeps
   painting an opaque gradient over the transparency we just asked for. Only above 0. */
html[data-surfaces][data-surf-body="1"] body { background-image: none; }
html[data-surfaces][data-surf-card="1"] .card,
html[data-surfaces][data-surf-card="1"] .ap-card,
html[data-surfaces][data-surf-card="1"] .dw,
html[data-surfaces][data-surf-card="1"] .dw-wrap,
html[data-surfaces][data-surf-card="1"] .dash-panel { background-image: none; }

/* Text shadow — a real setting, because a background image behind text is exactly when
   legibility breaks. Off by default; data-text-shadow is only present when enabled.
   Applied to body copy only, never to headings that carry their own treatment, and never
   to icon glyphs (a shadow on a glyph looks broken). */
html[data-text-shadow] body,
html[data-text-shadow] .main p,
html[data-text-shadow] .main td,
html[data-text-shadow] .main li,
html[data-text-shadow] .livedata,
html[data-text-shadow] .nav-item .lbl,
html[data-text-shadow] .side-logo a {
  text-shadow: var(--ops-text-shadow, none);
}


/* Text shadow — a real setting, because a background image behind text is exactly when
   legibility breaks. Off by default; data-text-shadow is only present when enabled.
   Applied to body copy only, never to headings that carry their own treatment, and never
   to icon glyphs (a shadow on a glyph looks broken). */
html[data-text-shadow] body,
html[data-text-shadow] .main p,
html[data-text-shadow] .main td,
html[data-text-shadow] .main li,
html[data-text-shadow] .livedata,
html[data-text-shadow] .nav-item .lbl,
html[data-text-shadow] .side-logo a {
  text-shadow: var(--ops-text-shadow, none);
}

/* TEXT COLOUR OVERRIDE — independent of the shadow, and separate from it because
   "make the text readable on this background" and "give the text a drop shadow" are two
   different wishes. Order matters below: colour is set first, then every element that
   needs its OWN colour re-asserts it, so the override cannot flatten the palette into one
   tone. Scoped to CONTENT, never to icon glyphs: a FontAwesome glyph inherits colour, so
   overriding it is how icons end up the wrong colour inside their own button. */
html[data-text-color] body,
html[data-text-color] .main,
html[data-text-color] .main p,
html[data-text-color] .main td,
html[data-text-color] .main li,
html[data-text-color] .livedata,
html[data-text-color] .nav-item,
html[data-text-color] .nav-item .lbl {
  color: var(--ops-text-color);
}
/* put the meaningful exceptions back */
html[data-text-color] .main .meta,
html[data-text-color] .side .meta,
html[data-text-color] .ap-hint { color: var(--dim, inherit); }
html[data-text-color] .nav-item.active,
html[data-text-color] .nav-item.active .lbl { color: var(--acc); }
html[data-text-color] .main a:not(.nav-item) { color: var(--acc); }
html[data-text-color] .err { color: var(--red, #ff5f9e); }
/* icons are never recoloured by the override — they carry their own meaning */
html[data-text-color] i.fa-solid,
html[data-text-color] i.fa-regular,
html[data-text-color] i.fa-brands { color: inherit; }

/* ============================================================================
   ORB ANIMATION PRESETS
   The keyframes and the preset->animation mappings now live in ONE file,
   /static/orb_presets.css (11 presets + flurry). They used to be duplicated here AND in
   appearance.css, which is how the menu ended up naming an animation that did not exist.
   Do not re-declare @keyframes orb-* here — extend orb_presets.css instead.
   ============================================================================ */


/* ============================================================================
   ORB BUTTON PANEL — presets + dials. Mirrors the BG picker's glass menu.
   ============================================================================ */
.orb-menu { min-width: 320px; max-width: 360px; }
.orb-on-row {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; font-size: 12px; cursor: pointer;
  border-bottom: 1px solid var(--line);
}
.orb-on-row input { accent-color: var(--acc); }
.orb-presets { display: flex; flex-direction: column; padding: 4px 0; }
.orb-preset {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; cursor: pointer; text-align: left;
  font: inherit; color: var(--fg); background: none; border: 0;
  border-left: 2px solid transparent;
}
.orb-preset:hover { background: color-mix(in srgb, var(--acc) 10%, transparent); }
.orb-preset.on {
  border-left-color: var(--acc);
  background: color-mix(in srgb, var(--acc) 14%, transparent);
}
.orb-preset-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.orb-preset-txt b { font-size: 12px; font-weight: 600; }
.orb-preset.on .orb-preset-txt b { color: var(--acc); }
.orb-preset-txt i {
  font-size: 10px; font-style: normal; line-height: 1.3;
  color: var(--dim, var(--fg)); opacity: .85;
}

/* A live dot per preset, animated with that preset's own keyframes — so the menu shows
   the motion rather than only naming it. The dot is a blurred blob, like a mini orb. */
.orb-preset-dot {
  width: 20px; height: 20px; border-radius: 50%; flex: 0 0 auto;
  background: radial-gradient(circle at 35% 30%, var(--acc), color-mix(in srgb, var(--acc) 40%, transparent));
  filter: blur(3px);
  animation-duration: 6s; animation-iteration-count: infinite; animation-timing-function: ease-in-out;
}
.orb-preset-dot.p-float   { animation-name: orb-float-site; }
.orb-preset-dot.p-drift   { animation-name: orb-drift; }
.orb-preset-dot.p-orbit   { animation-name: orb-orbit; }
.orb-preset-dot.p-breathe { animation-name: orb-breathe; }
.orb-preset-dot.p-wander  { animation-name: orb-wander; }
.orb-preset-dot.p-pulse   { animation-name: orb-pulse; }
.orb-preset-dot.p-sway    { animation-name: orb-sway; }
.orb-preset-dot.p-tumble  { animation-name: orb-tumble; }
.orb-preset-dot.p-ripple  { animation-name: orb-ripple; }
.orb-preset-dot.p-spiral  { animation-name: orb-spiral; }
.orb-preset-dot.p-cascade { animation-name: orb-cascade; }
.orb-preset-dot.p-flurry  { animation-name: orb-flurry; }

.orb-dial {
  display: flex; align-items: center; gap: 9px;
  padding: 5px 10px; font-size: 11px; color: var(--dim, var(--fg));
}
.orb-dial > span { flex: 0 0 74px; }
.orb-dial input[type="range"] { flex: 1 1 auto; min-width: 0; accent-color: var(--acc); cursor: pointer; }
.orb-dial output {
  flex: 0 0 44px; text-align: right; color: var(--fg);
  font-variant-numeric: tabular-nums; font-size: 11px;
}
.orb-select {
  flex: 1 1 auto; font: inherit; font-size: 11px; padding: 3px 6px;
  color: var(--fg); background: var(--card);
  border: 1px solid var(--glass-line, var(--line));
}

/* Preset class applied by the picker: the wrapper carries p-<name> and the CSS maps it
   onto the real orbs. Declared here next to the keyframes they reference. */

/* ---------------------------------------------------------------- RADIUS ---
   One dial for the corner geometry of every glass surface.
   WHY a single var and not per-element rules: the surfaces already declare radius
   individually (10px cards, 30px FAB pill, 16px chat pop, 0px in the square themes).
   Re-declaring each one here would mean maintaining that list in two places and any
   surface added later would silently ignore the dial. Instead the var is applied at the
   SAME specificity as the existing rules but scoped to [data-glass="custom"], so the
   operator's choice wins only when they have actually moved a glass dial — an untouched
   theme keeps its own geometry exactly as before.
   The round chat FAB and the avatar are deliberately EXCLUDED: they are circles
   (`border-radius: 50%`), and a radius slider that squares off a circle reads as broken. */
html[data-glass="custom"] .card,
html[data-glass="custom"] .ctl-menu,
html[data-glass="custom"] .chat-pop,
html[data-glass="custom"] .login,
html[data-glass="custom"] .panel,
html[data-glass="custom"] .ap-card,
html[data-glass="custom"] .bigbtn,
html[data-glass="custom"] .theme-btn,
html[data-glass="custom"] .nav-chip,
html[data-glass="custom"] .refresh button,
html[data-glass="custom"] .logout-btn,
html[data-glass="custom"] .send-btn,
html[data-glass="custom"] .chat-input,
html[data-glass="custom"] pre,
html[data-glass="custom"] .pill {
  border-radius: var(--ap-glass-r, 12px);
}
/* the sidebar rail and the header bar keep square outer edges — they are full-bleed
   chrome, not floating surfaces; rounding them detaches them from the viewport edge. */
html[data-glass="custom"] .side,
html[data-glass="custom"] .top { border-radius: 0; }

/* --------------------------------------------------------- DROPDOWN MENUS ---
   The menus get their OWN transparency dial. They were folded into the card surface,
   which is wrong conceptually: a card is IN the page flow, a dropdown floats OVER the
   content, so a transparent page would drag the menus transparent with it and text
   behind them would bleed through the panel.
   Mixed from --card like the other surfaces (a SOLID hex in every theme) rather than
   --glass, which is already partly transparent — mixing an alpha colour toward
   transparent produces no visible change at low values.
   Applied to .ctl-menu only. Sub-panels (.bg-menu, .em-modal-box) are children of it
   and inherit the resulting backdrop, so one dial covers every dropdown. */
html[data-surfaces] .ctl-menu {
  background-color: color-mix(in srgb,
      var(--card, var(--bg)) calc(var(--surf-menu-eff, 1) * 100%), transparent);
}

/* ---- orb animation dropdown ------------------------------------------------------------
   The preset list is a native <select> now: a real dropdown, keyboard navigable, and it
   cannot desync from the value the way a custom popup can. */
.orb-sel-row { display: flex; align-items: center; gap: 9px; padding: 7px 9px; }
.orb-sel-row > span { font-size: 11.5px; color: var(--dim, var(--fg)); min-width: 62px; }
.orb-select {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: 12.5px;
  padding: 6px 28px 6px 9px; border-radius: 1px; color: var(--fg);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  border: 1px solid var(--line);
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 14px) 52%, calc(100% - 9px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.orb-select:focus { outline: none; border-color: var(--acc); }
.orb-select optgroup { color: var(--dim, var(--fg)); font-weight: 700; font-size: 11px; }
.orb-select option { color: var(--fg); background: var(--card, var(--bg)); }
.orb-desc {
  margin: 2px 9px 8px; font-size: 11px; line-height: 1.45; min-height: 2.9em;
  color: var(--dim, var(--fg));
}
/* the swatch column, capped so 22 presets cannot make the menu endless */
.orb-presets { max-height: 240px; overflow-y: auto; }
