/* ==========================================================================
   EVENITY COMPONENTS
   Requires tokens.css. Nothing here hardcodes a colour or a size.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

body.ev {
  margin: 0;
  background: var(--ev-void);
  color: var(--ev-white);
  font-family: var(--ev-font-ui);
  font-size: var(--ev-text-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.ev-mono { font-family: var(--ev-font-mono); }
.ev-muted { color: var(--ev-mist); }
.ev-dim { color: var(--ev-dusk); }
.ev-center { text-align: center; }
.ev-row { display: flex; align-items: center; gap: var(--ev-s3); }
.ev-col { display: flex; flex-direction: column; gap: var(--ev-s3); }
.ev-spacer { flex: 1; }
.ev-hide { display: none !important; }
/* ------------------------------------------------------------ scrollbars --
   The browser's own chrome is dark, and its scrollbars are ours.

   TWO PROBLEMS, ONE LINE EACH. Chromium paints scrollbars from the platform palette unless the
   page declares `color-scheme: dark` — which is why a dark modal had a WHITE bar with two arrow
   buttons down its side, shouting louder than the content. The second is that a scrollbar is
   chrome: it is drawn in white at low opacity, so it blends into whatever surface it sits on and
   only brightens under the pointer, instead of being a solid stripe of the page's own colour.
   ------------------------------------------------------------------------ */

html { color-scheme: dark; }

/* Firefox. Chromium ignores both of these and uses the rules below. */
* { scrollbar-width: thin; scrollbar-color: rgb(255 255 255 / 16%) transparent; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgb(255 255 255 / 9%);
  border-radius: var(--ev-r-full);
}
::-webkit-scrollbar-thumb:hover { background: rgb(255 255 255 / 22%); }
::-webkit-scrollbar-corner { background: transparent; }
/* The old Windows stepper arrows. Chromium still draws them on some configurations. */
::-webkit-scrollbar-button { display: none; }

.ev-scroll { overflow-y: auto; }

/* --------------------------------------------------------------- headings -- */

.ev-h1 { font-size: var(--ev-text-2xl); font-weight: 700; letter-spacing: var(--ev-tracking-tight); margin: 0; }
.ev-h2 { font-size: var(--ev-text-lg); font-weight: 650; letter-spacing: var(--ev-tracking-tight); margin: 0; }
.ev-h3 { font-size: var(--ev-text-md); font-weight: 650; margin: 0; }

.ev-section-title {
  font-size: var(--ev-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ev-tracking-caps);
  /* Mist, not dusk: a section title is wayfinding, and it is the smallest text on the
     panel. Dusk stays for decoration (badges, ids, an "off" tick). */
  color: var(--ev-mist);
  margin: 0 0 var(--ev-s3);
}

/* ---------------------------------------------------------------- buttons -- */

.ev-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ev-s2);
  padding: 9px var(--ev-s4);
  border-radius: var(--ev-r-sm);
  border: 1px solid transparent;
  background: var(--ev-graphite);
  color: var(--ev-white);
  font: inherit;
  font-size: var(--ev-text-md);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--ev-t-fast) var(--ev-ease), border-color var(--ev-t-fast) var(--ev-ease),
              color var(--ev-t-fast) var(--ev-ease), transform var(--ev-t-fast) var(--ev-ease);
}

.ev-btn:hover { background: var(--ev-ash); }
.ev-btn:active { transform: translateY(1px); }
.ev-btn:disabled { opacity: 0.42; cursor: not-allowed; transform: none; }

/* The one volt button on a screen. Text is near-black: volt is far too bright
   for white text to be legible on it. */
.ev-btn--primary { background: var(--ev-volt); color: var(--ev-void); border-color: var(--ev-volt); }
.ev-btn--primary:hover { background: var(--ev-volt-bright); border-color: var(--ev-volt-bright); }

/* White text, which is what this button has always carried. The measurement is
   worth knowing: #f2f2f7 on plasma is ~3.3:1, below AA for small text. It was
   left as it was rather than quietly changed. */
.ev-btn--danger { background: var(--ev-plasma); color: var(--ev-white); border-color: var(--ev-plasma); }
.ev-btn--danger:hover { background: var(--ev-plasma-bright); }

.ev-btn--ghost { background: transparent; border-color: var(--ev-border-strong); color: var(--ev-fog); }
.ev-btn--ghost:hover { background: var(--ev-slate); color: var(--ev-white); border-color: var(--ev-iron); }

.ev-btn--sm { padding: 5px var(--ev-s3); font-size: var(--ev-text-sm); }
.ev-btn--icon { padding: 8px; width: 34px; height: 34px; }

/* Focus is always volt and always visible: this is a keyboard tool as much as
   a mouse one, and streamers run it while watching OBS on another monitor. */
.ev-btn:focus-visible,
.ev-input:focus-visible,
.ev-select:focus-visible,
.ev-toggle input:focus-visible + .ev-toggle__track {
  outline: 2px solid var(--ev-volt);
  outline-offset: 2px;
}

/* ----------------------------------------------------------------- inputs -- */

.ev-input,
.ev-select,
.ev-textarea {
  width: 100%;
  padding: 9px var(--ev-s3);
  border-radius: var(--ev-r-sm);
  border: 1px solid var(--ev-ash);
  /* A FIELD IS RECESSED, SO IT IS NOT THE CARD'S OWN COLOUR. `--ev-obsidian` is
     what a card and a panel use; using it here too meant a field was visible
     only by its border — the same "berkabut" problem one layer in. `--ev-well`
     is one step BELOW the card, which is what makes an input read as something
     you type into. */
  background: var(--ev-well);
  color: var(--ev-white);
  font: inherit;
  font-size: var(--ev-text-md);
  transition: border-color var(--ev-t-fast) var(--ev-ease), background var(--ev-t-fast) var(--ev-ease);
}

.ev-input::placeholder { color: var(--ev-dusk); }
.ev-input:hover, .ev-select:hover { border-color: var(--ev-iron); }
/* Focus no longer repaints the field: the volt border and the outline are the
   signal, and jumping the surface under the caret was extra motion for nothing. */
.ev-input:focus, .ev-select:focus, .ev-textarea:focus {
  border-color: var(--ev-volt);
  outline: none;
}

.ev-input--mono { font-family: var(--ev-font-mono); font-size: var(--ev-text-sm); }

/* The browser's own colour swatch, sized to sit in a row with the other inputs.
   Padding is squeezed rather than removed so the swatch keeps a visible border. */
.ev-color { height: 38px; padding: 2px; cursor: pointer; }

/* ---------------------------------------------------------- volume slider --
   DRAGGED, NOT TYPED. Volume is the one setting a streamer decides by ear, so
   it is a slider, with the number beside it for the cases where they want the
   value to be exact and repeatable. Lives here rather than on one page because
   both the action editor and a sound alert ask the same question.
   ------------------------------------------------------------------------ */

.ev-slider {
  display: flex;
  align-items: center;
  gap: var(--ev-s4);
}

.ev-slider__input {
  flex: 1;
  min-width: 0;
  height: 6px;
  margin: 0;
  border-radius: var(--ev-r-full);
  background: var(--ev-graphite);
  box-shadow: inset 0 0 0 1px var(--ev-border);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}

/* The thumb is the only volt element here: it is the thing being moved, and it
   reads against the dark track the same way a fader does in the app. */
.ev-slider__input::-webkit-slider-thumb {
  width: 18px;
  height: 18px;
  border-radius: var(--ev-r-full);
  background: var(--ev-volt);
  border: 2px solid var(--ev-obsidian);
  box-shadow: var(--ev-glow-volt);
  appearance: none;
  -webkit-appearance: none;
  cursor: grab;
}

.ev-slider__input::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: var(--ev-r-full);
  background: var(--ev-volt);
  border: 2px solid var(--ev-obsidian);
  box-shadow: var(--ev-glow-volt);
  cursor: grab;
}

.ev-slider__input:focus-visible { outline: 2px solid var(--ev-volt-a40); outline-offset: 3px; }

.ev-slider__value {
  min-width: 46px;
  text-align: right;
  font-family: var(--ev-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--ev-volt);
}

.ev-field { display: flex; flex-direction: column; gap: var(--ev-s2); }
.ev-field > label { font-size: var(--ev-text-sm); font-weight: 600; color: var(--ev-fog); }
/* THE HINT IS THE TEXT THAT TEACHES THE FORM, so it is read in mist, not dusk, and at the
   same size as a label. It used to be the smallest AND the dimmest text in the app, which
   is exactly backwards: the streamer has to read this sentence to know what the field does. */
.ev-field__hint { font-size: var(--ev-text-sm); color: var(--ev-mist); line-height: 1.5; margin: 0; }

/* ----------------------------------------------------------------- toggle -- */

.ev-toggle { display: inline-flex; align-items: flex-start; gap: var(--ev-s3); cursor: pointer; user-select: none; }
.ev-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }

.ev-toggle__track {
  width: 40px;
  height: 22px;
  border-radius: var(--ev-r-full);
  background: var(--ev-graphite);
  border: 1px solid var(--ev-ash);
  position: relative;
  flex: 0 0 auto;
  margin-top: 1px;
  transition: background var(--ev-t-base) var(--ev-ease), border-color var(--ev-t-base) var(--ev-ease);
}

.ev-toggle__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: var(--ev-r-full);
  background: var(--ev-dusk);
  transition: transform var(--ev-t-base) var(--ev-ease), background var(--ev-t-base) var(--ev-ease);
}

.ev-toggle input:checked + .ev-toggle__track { background: var(--ev-volt-a24); border-color: var(--ev-volt-dim); }
.ev-toggle input:checked + .ev-toggle__track .ev-toggle__thumb { transform: translateX(18px); background: var(--ev-volt); }

.ev-toggle__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ev-toggle__label { font-size: var(--ev-text-md); font-weight: 600; }
.ev-toggle__desc { font-size: var(--ev-text-sm); color: var(--ev-mist); }

/* ------------------------------------------------------------------ cards -- */

.ev-card {
  background: var(--ev-obsidian);
  border: 1px solid var(--ev-border);
  border-radius: var(--ev-r-lg);
  padding: var(--ev-s5);
}

.ev-card--flush {
  padding: 0;
  overflow: hidden;
  /* A COLUMN, so a body can fill the height the card was given by whatever stretched it — the leaderboard's
     empty state needs that to centre itself, and a block card gave it nothing to fill. */
  display: flex;
  flex-direction: column;
}
.ev-card__head { display: flex; align-items: center; gap: var(--ev-s3); padding: var(--ev-s4) var(--ev-s5); border-bottom: 1px solid var(--ev-border); }
.ev-card__body { padding: var(--ev-s5); }

/* THE PANEL VARIANT: the card a surface is "about" — a stronger edge than a plain card, and nothing
   else.
   IT USED TO BE A NOTCH. The corner was cut, not rounded, on the theory that a machined corner reads
   as hardware rather than as a generic app card. The founder retired that shape on 21 SEP 2026 and
   asked for the rounded corner, so the class was renamed with it: `--notch` on a rounded card is a
   lie the next person has to read twice to notice. Use it for the panel a surface is about, never for
   every card — that part was always the point. */
.ev-card--panel {
  border-radius: var(--ev-r-lg);
  border-color: var(--ev-border-strong);
}

/* A QUIET CARD: the OBS steps, which are instructions rather than a surface to look at.
   THE CLASS WAS IN THE MARKUP WITH NO RULE ANYWHERE — `ev-card--quiet` was written on the setup card
   and never defined, so the card it was meant to calm down looked exactly like the widget cards above
   it. It is defined here as a card that sits ON the page rather than above it: the page's own
   background, so only the border says it is a card. */
.ev-card--quiet {
  background: var(--ev-void);
  border-color: var(--ev-border);
}

/* ----------------------------------------------------------------- tables --

   EVERY LIST IN THE PRODUCT IS ONE GRID, AND THE COLUMNS COME IN AS AN ARGUMENT.

   A table here is a CSS grid, not a <table>. `table-layout: auto` sizes a column by
   its CONTENT, which is two bugs wearing one coat: the longest sound name in the list
   decides how wide that column is for every row, and on a wide window the leftover
   space is handed out by proportion rather than by need — which is how the Trigger
   column ended up 397px wide to describe a 20px thumbnail while the Sound column,
   holding the longest string in the row, was squeezed to 305px.

   A grid takes the column list from `--ev-table-cols`, so the header and the rows
   agree on the geometry BY CONSTRUCTION: they read one variable instead of being
   kept in sync by hand. A page declares its columns once, on the table, and the
   header and body are the same grid from then on.

   THE FOUR RULES BELOW ARE THE CONTRACT. Every one of them is a bug this file was
   written to end, and each is marked at the rule that enforces it:

     1. ONE RECORD, ONE LINE.       A long value is shortened. It never wraps.
     2. ONE ROW HEIGHT, EVERY ROW.  `--ev-row-h`, a token, not padding.
     3. THE TABLE SCROLLS SIDEWAYS. A narrow window never costs you a column.
     4. THE ACTION CELL NEVER USES `.ev-row`. It is nowrap by definition.

   The fourth is not hypothetical. `.ev-row` is a shared class, and the Tools page
   gives it `flex-wrap: wrap` for its phone layout — which is scoped, but the
   Sound Alerts row was relying on the same class for its Edit/Delete pair, so a
   fix on one page reached into the other. A table column needs its own container
   whose behaviour cannot be changed from somewhere else.
   ------------------------------------------------------------------------- */

.ev-table {
  width: 100%;
  background: var(--ev-obsidian);
  border: 1px solid var(--ev-border);
  border-radius: var(--ev-r-md);
  /* The radius has to clip the header band's own background, and the columns have to be
     cut off by the card rather than drawn past it. */
  overflow: hidden;
  font-size: var(--ev-text-md);
}

/* RULE 3. The scroll lives HERE, one level inside the card, so a table that is wider
   than its panel scrolls while the card keeps its own edges. The alternative — what
   the Sound Alerts table used to do — was a `max-width: 1100px` block that dropped the
   Key Shortcut column entirely, and addressed it as `:nth-child(5)`: at 1099px a
   feature silently disappeared, and inserting a cell anywhere before it would have
   hidden a different one instead. */
.ev-table__scroll { overflow-x: auto; }

.ev-table__head,
.ev-table__row {
  display: grid;
  grid-template-columns: var(--ev-table-cols);
  align-items: center;
  /* Separators come from the CELLS, not from a grid gap: a border on a grid item inside a
     gapped grid draws a short line with air above and below it, so the spacing moves into
     the cells and the divider spans the full height of the row. */
  gap: 0;
  padding: 0;
  /* The floor: the sum of the column floors, so the grid box is as wide as its own tracks
     and the scroller above has something real to scroll. Set by the page. */
  min-width: var(--ev-table-min, 0);
  /* RULE 1. Inherited by every cell, so the only way to wrap is to opt out on purpose. */
  white-space: nowrap;
}

.ev-table__head > *,
.ev-table__row > * {
  /* `min-width: 0`, or a cell that ellipsises cannot shrink inside its track. */
  min-width: 0;
  padding: 0 var(--ev-s3);
  border-right: 1px solid var(--ev-border);
  /* A cell clips its own overflow, so nothing ever spills across a divider. */
  overflow: hidden;
}

.ev-table__head > *:first-child,
.ev-table__row > *:first-child { padding-left: var(--ev-s4); }

.ev-table__head > *:last-child,
.ev-table__row > *:last-child { padding-right: var(--ev-s4); border-right: none; }

.ev-table__head {
  /* RULE 2, for the band: it has its own height, so a two-word column label cannot make
     the header taller than the rows below it. */
  height: var(--ev-row-head-h);
  /* A LABEL MAY WRAP; THE BAND DOES NOT GROW.

     The 40px above is the lock, and this line is the reason it is enough. The columns are sized
     for the labels the product actually ships — the longest one is "Shortcut tombol" in
     Indonesian against "Key Shortcut" in English — but a language that has not been written yet
     will be longer, and a cell that cannot wrap would CUT the word instead. With the height
     locked, wrapping changes nothing about the geometry: the band stays 40px, the rows stay
     --ev-row-h, and the columns do not move. */
  white-space: normal;
  background: var(--ev-slate);
  border-bottom: 1px solid var(--ev-border);
  font-size: var(--ev-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ev-tracking-caps);
  color: var(--ev-dusk);
}

.ev-table__row {
  height: var(--ev-row-h);
  border-bottom: 1px solid var(--ev-border);
}

.ev-table__row:last-child { border-bottom: none; }
.ev-table__row:hover { background: rgb(255 255 255 / 2.5%); }
.ev-table__row--off { opacity: 0.45; }

/* RULE 1, the other half: the shortened cell has to stay readable somewhere. Whatever a
   cell cuts off belongs in its `title`, or the streamer cannot check the value they saved. */
.ev-table__cell { overflow: hidden; text-overflow: ellipsis; }

/* Numbers that get compared down a column must not wobble as they change. */
.ev-table__num { font-family: var(--ev-font-mono); font-variant-numeric: tabular-nums; }
.ev-table__name { font-weight: 600; }

/* RULE 4. NOWRAP ALWAYS, and against the right edge: these are the row's last words, and
   their column is one the eye returns to. */
.ev-table__actions {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ev-s2);
}

.ev-table__empty {
  padding: var(--ev-s12) var(--ev-s4);
  text-align: center;
  /* A sentence the streamer reads: mist, like every other piece of text that has to be
     read. Dusk is for ids, badges and an "off" marker. */
  color: var(--ev-mist);
}

/* ----------------------------------------------------------------------------
   RULE 3(a) ON A PHONE: THE TWO ENDS PIN, THE MIDDLE SCROLLS.

   RULE 3 above says a narrow window scrolls instead of squeezing, and that is
   still right — but scrolling alone put this product's most-used controls off
   the edge of the device. The Actions table's floor is 1195px (the audit
   measured it), so at 360px the Edit and Delete buttons sat ~1100px to the
   right of the row the streamer was looking at: present, correct, and
   unreachable without knowing to swipe a table nothing suggested was scrollable.

   THE FIX IS THE CHEAPEST ONE THAT WORKS: pin the FIRST cell (the enable
   switch) to the left edge and the LAST cell (Test / Edit / Delete) to the
   right, so both are on screen at every scroll position while the columns
   between them move under the finger. Nothing is hidden, no column is dropped,
   and no row height changes — the geometry the layout contract checks is
   untouched, which is why this can be a media query at all.

   480px AND NOT 900px: the sidebar becomes a drawer at 900px, and a pinned
   action column on a 700px-wide laptop panel would hide the column it is
   pinning beside. A phone is the case that was broken.

   THE BACKGROUNDS ARE OPAQUE AND EXPLICIT: a sticky cell moves over the cells
   it covers, so a transparent one would show two values in one place. They are
   the same tokens the bands already use — obsidian for rows, slate for the
   header — and the one-pixel shadow line is the divider the covered cell's own
   `border-right` can no longer provide.
   ---------------------------------------------------------------------------- */
@media (max-width: 480px) {
  .ev-table__head > *:first-child,
  .ev-table__row > *:first-child {
    position: sticky;
    left: 0;
    /* Above the scrolling cells, below a modal. */
    z-index: 2;
    background: var(--ev-obsidian);
    box-shadow: 1px 0 0 var(--ev-border);
  }

  .ev-table__head > *:first-child { background: var(--ev-slate); }

  .ev-table__head > *:last-child,
  .ev-table__row > *:last-child {
    position: sticky;
    right: 0;
    z-index: 2;
    background: var(--ev-obsidian);
    box-shadow: -1px 0 0 var(--ev-border);
  }

  .ev-table__head > *:last-child { background: var(--ev-slate); }
}

/* ------------------------------------------------------------------ badges -- */

.ev-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ev-s2);
  padding: 3px var(--ev-s3);
  border-radius: var(--ev-r-full);
  font-size: var(--ev-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ev-tracking-caps);
  border: 1px solid var(--ev-border);
  background: var(--ev-slate);
  color: var(--ev-mist);
}

.ev-badge--volt { background: var(--ev-volt-a12); border-color: var(--ev-volt-a24); color: var(--ev-volt); }
.ev-badge--plasma { background: var(--ev-plasma-a14); border-color: var(--ev-plasma-a32); color: var(--ev-plasma); }
.ev-badge--ion { background: var(--ev-ion-a14); border-color: var(--ev-ion-a32); color: var(--ev-ion); }
.ev-badge--amber { background: var(--ev-amber-a16); border-color: var(--ev-amber); color: var(--ev-amber); }

/* --------------------------------------------------------------- progress -- */

.ev-bar { height: 6px; border-radius: var(--ev-r-full); background: var(--ev-graphite); overflow: hidden; }
.ev-bar__fill { height: 100%; background: var(--ev-volt); border-radius: var(--ev-r-full); transition: width var(--ev-t-slow) var(--ev-ease); }


/* ------------------------------------------------------------- empty state -- */

.ev-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--ev-s4);
  padding: var(--ev-s16) var(--ev-s6);
  text-align: center;
  border-radius: var(--ev-r-lg);
  border: 1px dashed var(--ev-border-strong);
  color: var(--ev-mist);
}

.ev-empty__icon { font-size: 34px; opacity: 0.5; }
.ev-empty__title { font-size: var(--ev-text-lg); font-weight: 650; color: var(--ev-fog); }

/* ----------------------------------------------------------------- toasts -- */

.ev-toasts {
  position: fixed;
  right: var(--ev-s5);
  bottom: var(--ev-s5);
  display: flex;
  flex-direction: column;
  gap: var(--ev-s2);
  z-index: var(--ev-z-toast);
  pointer-events: none;
}

.ev-toast {
  display: flex;
  align-items: center;
  gap: var(--ev-s3);
  min-width: 260px;
  max-width: 420px;
  padding: var(--ev-s3) var(--ev-s4);
  border-radius: var(--ev-r-md);
  background: var(--ev-slate);
  border: 1px solid var(--ev-border-strong);
  box-shadow: var(--ev-shadow-lg);
  font-size: var(--ev-text-md);
  animation: ev-toast-in var(--ev-t-slow) var(--ev-ease);
  pointer-events: auto;
}

.ev-toast--ok { border-left: 3px solid var(--ev-volt); }
.ev-toast--err { border-left: 3px solid var(--ev-plasma); }
.ev-toast--info { border-left: 3px solid var(--ev-ion); }
.ev-toast--leaving { animation: ev-toast-out var(--ev-t-base) var(--ev-ease) forwards; }

@keyframes ev-toast-in { from { opacity: 0; transform: translateX(16px) scale(0.98); } to { opacity: 1; transform: none; } }
@keyframes ev-toast-out { to { opacity: 0; transform: translateX(16px); } }

/* ------------------------------------------------------------------ modal -- */

.ev-modal-backdrop {
  position: fixed;
  inset: 0;
  background: var(--ev-scrim);
  backdrop-filter: blur(6px);
  display: grid;
  place-items: center;
  padding: var(--ev-s6);
  z-index: var(--ev-z-modal);
  animation: ev-fade var(--ev-t-base) var(--ev-ease);
}

.ev-modal {
  /* 620px, not 560: the type scale went up one notch (see tokens.css), and the panels that
     live in here — the action editor, the keystroke script box — would otherwise wrap every
     line one word earlier for no reason. */
  width: min(620px, 100%);
  max-height: 84vh;
  overflow: auto;
  background: var(--ev-obsidian);
  border: 1px solid var(--ev-border-strong);
  border-radius: var(--ev-r-lg);
  box-shadow: var(--ev-shadow-lg);
  animation: ev-modal-in var(--ev-t-slow) var(--ev-ease);
}

@keyframes ev-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes ev-modal-in { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: none; } }

/* ------------------------------------------------------------------- tabs -- */

.ev-tabs { display: flex; gap: var(--ev-s1); border-bottom: 1px solid var(--ev-border); }

.ev-tab {
  padding: var(--ev-s3) var(--ev-s4);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--ev-mist);
  font: inherit;
  font-size: var(--ev-text-md);
  font-weight: 600;
  cursor: pointer;
  transition: color var(--ev-t-fast) var(--ev-ease), border-color var(--ev-t-fast) var(--ev-ease);
}

.ev-tab:hover { color: var(--ev-fog); }
.ev-tab--active { color: var(--ev-volt); border-bottom-color: var(--ev-volt); }

/* ---------------------------------------------------------------- skeleton -- */

.ev-skeleton {
  background: linear-gradient(90deg, var(--ev-graphite) 25%, var(--ev-ash) 50%, var(--ev-graphite) 75%);
  background-size: 200% 100%;
  border-radius: var(--ev-r-sm);
  animation: ev-shimmer 1.4s linear infinite;
}

@keyframes ev-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* ------------------------------------------------------------ inline code -- */

.ev-code {
  font-family: var(--ev-font-mono);
  font-size: var(--ev-text-sm);
  background: var(--ev-slate);
  border: 1px solid var(--ev-border);
  border-radius: var(--ev-r-xs);
  padding: 1px 5px;
  color: var(--ev-fog);
}

@media (prefers-reduced-motion: reduce) {
  .ev-skeleton { animation: none; }
}

