/* ===========================================================================
   Menus — one class family, one component (client/menu.jsx).

   The launcher tray, the right-click window menu and the Chat tools pulldown are
   the same control with a different anchor, so they share `.menu*` and a change
   to how a menu looks lands once. Callers add their own class only to say WHERE
   (see .launcher-menu in /css/dashboard.css), and `menu--desktop` to say the
   menu belongs to the desktop surface.

   `.menu-item--button` is a command, `.menu-item--check` is a decision; the panel
   closes for the first and stays open for the second.

   The tray's own control (.desktop-tray, .desktop-tray-toggle) is styled in
   /css/dashboard.css: it is a launcher button, not a menu.
   =========================================================================== */

/* The wrapper a trigger-anchored menu hangs its panel off. */
.menu-wrap {
  position: relative;
  display: inline-flex;
}

/* The button that opens one. */
.menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  font-size: 12px;
  background: var(--bg-input, var(--text-muted, #999));
  /* Paired ground/ink, not --text-primary: see --text-on-input above. The count
     and the caret inherit it, so the whole trigger reads as one control. */
  color: var(--text-on-input, var(--text-primary, #000));
  border: 2px solid var(--border-default, #777);
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}

.menu-trigger:hover:not(:disabled) {
  border-color: var(--accent, #4a9eff);
}

.menu-trigger:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.menu-trigger.is-open {
  border-color: var(--accent, #4a9eff);
}

.menu-trigger-count {
  font-family: var(--font-code);
  font-size: 10px;
  font-weight: 600;
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--bg-button-highlight, #4a4a4a);
  color: #fff;
}

.menu-trigger-caret {
  font-size: 9px;
  opacity: 0.7;
}

.menu {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: max-content;
  min-width: 184px;
  max-width: min(320px, calc(100vw - 24px));
  /* The list scrolls inside .menu-list so a filter box stays put above it. The
     component caps this further to the space the screen actually has. */
  max-height: 320px;
  overflow: hidden;
  padding: 6px;
  background: var(--bg-window, #fff);
  border: 1px solid var(--border-default, #777);
  border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
  transform-origin: top left;
  animation: menu-in 0.16s ease-out;
  /* Every caller positions the panel absolutely inside a layer it owns. An
     anchored menu hangs straight below its trigger unless it flips; a
     caller-positioned one overrides this inline or with its own class. */
  position: absolute;
  top: calc(100% + 6px);
  z-index: 100001;
  /* On a phone a long press is how the menu is opened in the first place, so the
     Safari text/element callout and selection handles must not answer it. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

@keyframes menu-in {
  /* Opacity only. A menu centred on its trigger hangs there with a transform,
     and an animated transform would fight the placement for 160ms. */
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Which edge of the trigger the panel hangs from — the caller says, the menu
   obeys (trigger.align). */
.menu--left { left: 0; }
.menu--right { right: 0; }
.menu--center { left: 50%; transform: translateX(-50%); }

/* Flipped. The Tools trigger sits in the toolbar at the foot of the chat, so a
   panel that always hangs below is clipped by the bottom of the screen and the
   last rows cannot be scrolled to — they are not off-screen in a scrollable box,
   they are off the page. The component adds this when the list would not fit
   below AND there is more room above, and caps the height inline to whichever
   side it chose. top/bottom only: the align rules above still place it
   horizontally, including the center transform. */
.menu.is-open-up {
  top: auto;
  bottom: calc(100% + 6px);
}

/* The scroll area. `.menu` clips and holds the cap; this takes what is left
   after the filter row and scrolls, so the box does not slide away as you read
   a long list. */
.menu-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 0;
  overflow-y: auto;
}

/* The filter, shown when the list is taller than its cap (filterable: 'auto')
   or always (filterable: true). An input, so it takes the paired ink. */
.menu-filter {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  margin-bottom: 2px;
  border: 1px solid var(--border-default, #777);
  border-radius: 6px;
  background: var(--bg-input, var(--text-muted, #999));
  color: var(--text-on-input, var(--text-primary, #000));
}

.menu-filter-icon {
  font-size: 10px;
  opacity: 0.7;
}

.menu-filter-input {
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: 12px;
  outline: none;
}

.menu-filter-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text-primary, #000);
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}

.menu-item:hover {
  background: var(--bg-button-hover, #eee);
}

/* A command is a touch target; a checkbox row is a dense list row. */
.menu-item--button {
  min-height: 40px;
}

/* Greyed rather than hidden: a locked desktop still explains which actions it is
   refusing (window chrome does the same by ignoring the click). */
.menu-item:disabled {
  opacity: 0.4;
  cursor: default;
}

.menu-item:disabled:hover {
  background: transparent;
}

/* A decision, not a command: the panel stays open across choices, because a
   checklist is a set of them and closing after the first makes a two-item change
   cost two openings. */
.menu-item--check {
  align-items: flex-start;
  gap: 8px;
  padding: 6px 8px;
  opacity: 0.55;
}

.menu-item--check:hover {
  opacity: 1;
}

.menu-item--check.is-on {
  opacity: 1;
}

.menu-icon {
  flex-shrink: 0;
  width: 18px;
  text-align: center;
  font-size: 12px;
  opacity: 0.75;
}

/* `is-loaded` is a caller's marker, not a system state: the model picker marks the
   aliases whose weights are already resident on their connection. There the icon IS
   the signal, so the icon carries the colour (same green as the function chips). */
.menu-item.is-loaded .menu-icon {
  color: #34c759;
  opacity: 1;
}

/* Drawn rather than a native input: the checkbox is the one form control that
   cannot be styled to match a dark theme across browsers. */
.menu-box {
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  margin-top: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  color: #fff;
  border: 1px solid var(--border-default, #777);
  border-radius: 3px;
}

.menu-item--check.is-on .menu-box {
  background: var(--accent, #4a9eff);
  border-color: var(--accent, #4a9eff);
}

.menu-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.menu-label {
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: 6px;
}

/* The small word after a label — `app`, `widget`, `loaded`, whatever the caller
   says. Chip, not sentence: it must not compete with the label it follows. */
.menu-meta {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 0.55;
}

/* The line UNDER a label — which connection a model lives on, and anything else
   a caller needs to say in small print. Ellipsised: it is context, not content,
   and a long connection name must not widen the panel. */
.menu-sub {
  font-size: 10px;
  line-height: 1.25;
  color: var(--text-secondary, #696969);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.menu-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
}

.menu-tags code {
  font-size: 10px;
  padding: 1px 4px;
  border-radius: 3px;
  background: rgba(52, 199, 89, 0.15);
  color: var(--text-secondary, #3c7a4a);
}

.menu-item--check:not(.is-on) .menu-tags code {
  opacity: 0.6;
}

.menu-section {
  padding: 8px 10px 2px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-secondary, #696969);
}

.menu-divider {
  height: 1px;
  margin: 4px 8px;
  background: var(--border-default, #777);
}

.menu-empty {
  padding: 10px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-secondary, #696969);
}

/* Click-away catcher for a caller-positioned menu — invisible, but it does
   swallow the click so the desktop or window underneath is not dragged or
   focused instead. A trigger-anchored menu uses a document listener instead. */
.menu-backdrop {
  position: absolute;
  inset: 0;
  z-index: 100000;
  background: transparent;
}

/* A long-press trigger — marked by the global engine for every element that
   declares `onContextMenu` (and by the `window.longPress` GUI helper). Two
   native behaviours fight a custom hold, and both answers live here rather
   than in any one App's sheet because they are the system's contract, not
   the caller's taste: the iOS callout (the copy/lookup bubble) and text
   selection — a hold that starts selecting marks the text and paints no menu. */
[data-longpress] {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
