.admin-section-shadow {
    box-shadow: 0 2px 5px #00000029,0 2px 10px #0000001f;
}
.scroll-thin {
    scrollbar-width: thin;
}

/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* Firefox */
input[type=number] {
    -moz-appearance: textfield;
}

.pulse-once {
    animation: pulseOnce 1s;
}
@keyframes pulseOnce {
    0% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.05);
    }
    100% {
        transform: scale(1);
    }
}

/* Sim Cards */
.text-blended {
    mix-blend-mode: overlay;
    color: white;
    text-shadow: 0 1px 4px rgba(0,0,0,0.7);
}

.description-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}


/* Soundwave visualization for agent audio output */
.soundwave-bar {
    width: 1rem;
    min-height: 8px;
    background: linear-gradient(180deg, oklch(var(--p)) 0%, oklch(var(--s)) 100%);
    /*background: blue;*/
    border-radius: 3px;
    transition: height 0.1s ease-out;
    will-change: height;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
}

/* Optional: Add a glow effect when active */
.soundwave-bar.active {
    box-shadow: 0 0 12px oklch(var(--p) / 0.5);
}

/* Animated avatar border for active connection */
.avatar-border-active {
    border-color: oklch(var(--p));
    animation: avatarBorderPulse 2s ease-in-out infinite;
    box-shadow: 0 0 20px oklch(var(--p) / 0.6), 0 0 40px oklch(var(--p) / 0.4);
}

@keyframes avatarBorderPulse {
    0%, 100% {
        border-color: oklch(var(--p));
        box-shadow: 0 0 20px oklch(var(--p) / 0.6), 0 0 40px oklch(var(--p) / 0.4);
    }
    50% {
        border-color: oklch(var(--s));
        box-shadow: 0 0 30px oklch(var(--s) / 0.8), 0 0 60px oklch(var(--s) / 0.5), 0 0 80px oklch(var(--s) / 0.3);
    }
}

.loader {
  width: fit-content;
  /* font-size: 40px; */
  /* font-family: system-ui,sans-serif; */
  /* font-weight: bold; */
  /* text-transform: uppercase; */
  /* color: #0000; */
  /* The text is the gradient (clipped to it below): a green sweep over a label that stays readable (base-content at 70%)
     where the sweep is not. */
  color: transparent;
  /* -webkit-text-stroke: 1px var(--fallback-p,oklch(var(--p)/1)); */
  /* background:
    linear-gradient(90deg,#0000 33%,#000 0 67%,#0000 0)
    0/300% 100% no-repeat text; */
  background:
    linear-gradient(90deg,var(--fallback-bc,oklch(var(--bc)/0.7)) 33%,rgb(var(--brand-green-ink)) 0 67%,var(--fallback-bc,oklch(var(--bc)/0.7)) 0)
    0/300% 100% no-repeat;
    -webkit-background-clip: text; /* For older WebKit browsers */
    background-clip: text;         /* Standard compliant way */
  animation: l2 1.5s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .loader {
    animation: none;
  }
}
@keyframes loader-pulse {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 1; }
}

/* .loader:before {
  content: "Loading";
} */

@keyframes l2 {
  0%{background-position: 100%}
}


/* Base structural hiding - Tailwind handles the 'flex' when active */
#components-reconnect-modal {
    display: none;
}

/* Toggle visibility based on Blazor state */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex !important;
}

/* The Modal Card: the dialog surface, a green hairline and the overlay elevation (theme tokens). */
.reconnect-box {
    /*background: #ffffff;*/
    padding: 2.5rem 2rem;
    border-radius: var(--rounded-box, 12px);
    box-shadow: var(--shadow-overlay);
    text-align: center;
    max-width: 420px;
    width: 100%;
    border: 1px solid rgb(var(--brand-green) / 0.25);
}

.reconnect-box h5 {
    font-size: 1.25rem;
    font-weight: 600;
    /*color: #1f2937;*/
    margin-bottom: 0.5rem;
}

.reconnect-box p {
    font-size: 0.95rem;
    /*color: #4b5563;*/
    opacity: 0.7;
    margin-bottom: 1.5rem;
    line-height: 1.5;
}

/* Hide states by default */
.reconnect-state-show,
.reconnect-state-failed,
.reconnect-state-rejected {
    display: none;
}

/* Active State Display mapping */
#components-reconnect-modal.components-reconnect-show .reconnect-state-show { display: block; }
#components-reconnect-modal.components-reconnect-failed .reconnect-state-failed { display: block; }
#components-reconnect-modal.components-reconnect-rejected .reconnect-state-rejected { display: block; }

/* Custom Refresh Button */
.btn-refresh {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background-color: var(--fallback-p,oklch(var(--p)/1));
    color: var(--fallback-pc,oklch(var(--pc)/1));
    font-weight: 500;
    padding: 0.625rem 1.25rem;
    border-radius: 6px;
    transition: all 0.2s ease-in-out;
    border: none;
    cursor: pointer;
}

.btn-refresh:hover {
    filter: brightness(1.08);
    box-shadow: 0 4px 6px -1px var(--fallback-p,oklch(var(--p)/0.25));
}

/* State Graphic elements */
.icon-wrapper {
    font-size: 2.5rem;
    margin-bottom: 1rem;
}

/* Concentric plug spinner setup */
.spinner-container {
    position: relative;
    width: 60px;
    height: 60px;
    margin: 0 auto 1.25rem auto;
}

.spinner {
    border: 3px solid #f3f3f3;
    border-top: 3px solid var(--fallback-p,oklch(var(--p)/1));
    border-radius: 50%;
    width: 100%;
    height: 100%;
    animation: spin 1s linear infinite;
}

.spinner-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 1.25rem;
    color: var(--fallback-p,oklch(var(--p)/1));
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* Carousel fade effect for sides */
.sides-fade {
    -webkit-mask-image: linear-gradient(
            to right,
            transparent 0%,
            black 10%,
            black 90%,
            transparent 100%
    );
    mask-image: linear-gradient(
            to right,
            transparent 0%,
            black 10%,
            black 90%,
            transparent 100%
    );
}

/* carousel styles */
.carousel-viewport {
    overflow: hidden;
    position: relative;
}

.carousel-count-marker{
    /*color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));*/
    /*background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));*/
    opacity: 0.25;
}

.carousel-count-marker:hover{
    cursor: pointer;
    opacity: 0.75;
}

.carousel-count-marker.selected{
    opacity: 0.75;
}

.carousel-item-custom {
    width: 100%;
}

.radial-size-auto{
    --size: 4rem;
}

@media (min-width: 1024px) {
    .radial-size-auto{
        --size: 7rem;
    }
}

/* Mic-test calibration phrase words.
   Visual state is driven by the data-state attribute; JS only flips the
   attribute, never adds/removes utility classes. Palette mirrors .loader so
   the active DaisyUI theme flows through. */
.config-word {
    position: relative;
    display: inline-block;
    color: oklch(var(--bc) / 0.8);
    font-weight: 500;
    font-style: italic;
    opacity: 0.8;
    /* anchor the ::after wipe to the word's own box */
    isolation: isolate;
}

.config-word[data-state="pending"] {
    font-weight: 400;
    /* base styles only — dim, italic, no decoration */
}

.config-word[data-state="current"] {
    color: oklch(var(--bc));
    font-weight: 400;
    opacity: 1;
    /*text-decoration: underline;*/
    /*text-underline-offset: 4px;*/
}

/* 1. Base "Said" State (Instant Final State)
   Applied to all words with data-state="said". No animation by default. */
.config-word[data-state="said"] {
    color: transparent;
    -webkit-text-fill-color: transparent;
    font-weight: 600;
    opacity: 1;
    background-image: linear-gradient(90deg,
    var(--fallback-p, oklch(var(--p) / 1)) 50%,
    transparent 50%);
    background-size: 200% 100%;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
    background-clip: text;

    /* Set the background position directly to the final state (0% 0) */
    background-position: 0% 0;
}

.config-word[data-state="said"]:not(:has(~ .config-word[data-state="said"])) {
    background-position: 100% 0;
    animation: configWordReveal 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

.gain-phrase[data-capture-tone="success"] .config-word[data-state="pending"],
.gain-phrase[data-capture-tone="success"] .config-word[data-state="current"],
.gain-phrase[data-capture-tone="success"] .config-word[data-state="said"] {
    color: oklch(var(--su));
    -webkit-text-fill-color: currentColor;
    background-image: none;
    opacity: 1;
}

.gain-phrase[data-capture-tone="warning"] .config-word[data-state="pending"],
.gain-phrase[data-capture-tone="warning"] .config-word[data-state="current"],
.gain-phrase[data-capture-tone="warning"] .config-word[data-state="said"] {
    color: oklch(var(--wa));
    -webkit-text-fill-color: currentColor;
    background-image: none;
    opacity: 1;
}

.gain-phrase[data-capture-tone="error"] .config-word[data-state="pending"],
.gain-phrase[data-capture-tone="error"] .config-word[data-state="current"],
.gain-phrase[data-capture-tone="error"] .config-word[data-state="said"] {
    color: oklch(var(--er));
    -webkit-text-fill-color: currentColor;
    background-image: none;
    opacity: 1;
}

@keyframes configWordReveal {
    from {
        background-position: 100% 0;
    }
    to {
        background-position: 0% 0;
    }
}


.vid-options-gradient {
    --bg-gradient-color: var(--fallback-n,oklch(var(--n)/0.5));

    background: linear-gradient(
            to bottom,
            rgb(from var(--bg-gradient-color) r g b / 0) 0%,
            rgb(from var(--bg-gradient-color) r g b / 0.5) 30%
    );
}

/* ATTENTION ANIMS */
/* Required so the CSS engine knows how to smoothly transition these raw numbers */
@property --pulse-scale { syntax: '<number>'; initial-value: 1; inherits: false; }
@property --pulse-overlay { syntax: '<number>'; initial-value: 0; inherits: false; }
@property --hover-scale { syntax: '<number>'; initial-value: 1; inherits: false; }
@property --hover-overlay { syntax: '<number>'; initial-value: 1; inherits: false; }

.attention-pulse {
    display: inline-block;
    position: relative;

    /* Base hover states */
    --hover-scale: 1;
    --hover-overlay: 1; /* Acts as a multiplier */

    /*
       Scale uses whichever value is bigger: the running pulse, or the hover transition.
    */
    transform: scale(max(var(--pulse-scale), var(--hover-scale))) translateZ(0);

    backface-visibility: hidden;
    will-change: transform;
    -webkit-font-smoothing: antialiased;

    transition: --hover-scale 0.75s ease-out, --hover-overlay 0.75s ease-out;

    animation: pulse-anim 5s infinite;
}

.attention-pulse::after {
    content: "";
    position: absolute;
    inset: 0;
    background-color: #ffffff;
    border-radius: inherit;
    pointer-events: none;

    /* Multiply the pulse opacity by the hover multiplier */
    opacity: calc(var(--pulse-overlay) * var(--hover-overlay));
    will-change: opacity;
}

.attention-pulse:hover {
    /* We DO NOT kill or pause the animation. We just trigger the transition. */
    --hover-scale: 1.25;

    /* Drops the overlay multiplier to 0, killing the white flash */
    --hover-overlay: 0;
}

@keyframes pulse-anim {
    0%, 80% {
        --pulse-scale: 1;
        --pulse-overlay: 0;
    }
    90% {
        --pulse-scale: 1.25;
        --pulse-overlay: 0.3;
    }
    100% {
        --pulse-scale: 1;
        --pulse-overlay: 0;
    }
}


.attention-shine {
    position: relative;
    overflow: hidden;   /* Hides the shine while it waits off-screen */
    isolation: isolate; /* Magic trick: keeps pseudo-element behind text but above base background */

    --bg-gradient-opacity: 0.5;
    --bg-gradient-color:var(--fallback-nc,oklch(var(--nc)/var(--bg-gradient-opacity)));
}

.attention-shine::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1; /* Stays under text thanks to the parent's 'isolation' property */

    /* Your exact opacity mapping blueprint */
    background: linear-gradient(
            to right,
            rgb(from var(--bg-gradient-color) r g b / 0) 0%,
            rgb(from var(--bg-gradient-color) r g b / 0) 25%,
            rgb(from var(--bg-gradient-color) r g b / 0.7) 50%,
            rgb(from var(--bg-gradient-color) r g b / 0) 75%,
            rgb(from var(--bg-gradient-color) r g b / 0) 100%
    );

    /* Hardware acceleration for a stutter-free sweep */
    will-change: transform;

    /* 5-second loop running continuously on the background layer */
    animation: shine-sweep 5s infinite linear;
    /* transition --bg-gradient-color */
    transition: --bg-gradient-opacity 0.75s ease-out;
}
.attention-shine:hover {
    --bg-gradient-opacity: 0;
}

/* --- Keyframes --- */
@keyframes shine-sweep {
    /* 0% to 70%: The shine sits completely asleep, staged off-screen to the left */
    0%, 90% {
        transform: translateX(-100%);
    }
    /* 70% to 100%: In the last 30% of the time, it dashes completely to the right */
    100% {
        transform: translateX(100%);
    }
}


/* library extra styles */

@keyframes simRowFadeIn {
     from { opacity: 0; transform: translateY(4px); }
     to { opacity: 1; transform: translateY(0); }
}
.sim-row-enter {
    animation: simRowFadeIn 0.15s ease-out forwards;
}

/* The actions column fits its buttons; the minimum only keeps a lone Run from squeezing the "Actions" header. */
@media (min-width: 1024px) {
    .library-grid-cols-2 {
        grid-template-columns: minmax(7rem, auto) minmax(0, 1fr);
    }

    .library-grid-cols-3 {
        grid-template-columns: minmax(7rem, auto) minmax(0, 2fr) minmax(0, 1fr);
    }

    .library-grid-cols-4 {
        grid-template-columns: minmax(7rem, auto) minmax(0, 1.8fr) minmax(0, 1fr) minmax(0, 0.9fr);
    }
}

.blink-loop{
    animation: blink linear 1s infinite;
}

.blink-twice {
    animation: blink linear 1s 2;
}

@keyframes blink {
    0% { opacity: 1; }
    50% { opacity: 0; }
    100% { opacity: 1; }
}

/* new shine effect for text */

.shine-bg {
  /* 1. Your base properties */
  --tw-bg-opacity: 0.3;
  --tw-text-opacity: 0.65;
  color: var(--fallback-nc, oklch(var(--nc)/var(--tw-text-opacity)));
  border-width: 0px;
  
  /* Change 'background' to 'background-color' so the gradient can sit on top */
  background-color: var(--fallback-n, oklch(var(--n)/var(--tw-bg-opacity)));

  /* 2. The shine gradient */
  background-image: linear-gradient(
    -40deg, 
    transparent 0%, 
    transparent 40%, 
    rgba(255, 255, 255, 0.5) 50%, /* Swapped pure #fff for rgba to blend better with your 0.3 opacity background */
    transparent 60%, 
    transparent 100%
  );
  
  /* 3. Sizing and Animation */
  background-size: 200px 100%;
  background-repeat: no-repeat;
  animation: shine-bg 5s
   infinite;
}

.shine-bg-safe {
  /* Your exact base properties untouched */
  --tw-bg-opacity: 0.3;
  --tw-text-opacity: 0.65;
  background: var(--fallback-n, oklch(var(--n)/var(--tw-bg-opacity)));
  color: var(--fallback-nc, oklch(var(--nc)/var(--tw-text-opacity)));
  border-width: 0px;
  
  /* Required to contain the shine */
  position: relative;
  overflow: hidden; 
}

.shine-bg-safe::before {
  content: "";
  position: absolute;
  inset: 0; /* Shorthand for top/right/bottom/left 0 */
  pointer-events: none; /* Prevents the shine from blocking clicks */
  
  background-image: linear-gradient(-40deg, transparent 0%, transparent 40%, rgba(255, 255, 255, 0.5) 50%, transparent 60%, transparent 100%);
  background-size: 200px 100%;
  background-repeat: no-repeat;
  animation: shine-bg 5s infinite;
}

@keyframes shine-bg {
  0%, 10% {
    background-position: -200px top; /* Start completely off-screen left */
  }
  20% {
    background-position: left top;
  }
  90% {
    background-position: right top;
  }
  100% {
    background-position: calc(100% + 200px) top; /* End completely off-screen right */
  }
}
/* ---- Speech-analysis timeline: gutter name marquee ----
   The context speaker's name (an AI persona, e.g. "Alex Sterling") is frequently wider than the
   gutter that labels its rail. Rather than leave it truncated, walk it: hold the start, crawl to
   the end, hold there, then slide back fast.

   The shift is `min(0px, 5rem - 100%)`, where 100% is the label's OWN width — so a name that
   already fits resolves to 0 and never moves, and no JS measurement is needed anywhere. The 5rem
   MUST stay in step with the `w-20` gutter in SpeechAnalysisTimeline.razor; if that width changes
   and this does not, the name stops short of its end or overshoots it. */
@keyframes speechGutterMarquee {
    0%, 25% {
        transform: translateX(0);
        animation-timing-function: linear;
    }
    60%, 92% {
        transform: translateX(min(0px, calc(5rem - 100%)));
        animation-timing-function: ease-in-out;
    }
    100% {
        transform: translateX(0);
    }
}

.speech-gutter-marquee-track {
    animation: speechGutterMarquee 10s infinite;
}

/* Hovering already surfaces the full name in a tooltip; two things moving at once is just noise. */
.speech-gutter-marquee:hover .speech-gutter-marquee-track {
    animation-play-state: paused;
}

/* An indefinitely looping animation is precisely what this setting exists to switch off. Fall back
   to the static ellipsis: `max-width` collapses the max-content track back onto the gutter. */
@media (prefers-reduced-motion: reduce) {
    .speech-gutter-marquee-track {
        animation: none;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

/* ── Simulation microphone control (Google Meet style) ────────────────────── */

/* Handle and mute read as one control, so they share a single rounded housing and
   the buttons inside it are transparent. */
.mic-control-group {
    --tw-bg-opacity: 0.35;
    background: var(--fallback-n, oklch(var(--n) / var(--tw-bg-opacity)));
    border-radius: 0.9rem;
    padding: 0.125rem;
    gap: 0.125rem;
    transition: background 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
}

.mic-control-group:hover {
    --tw-bg-opacity: 0.6;
}

.mic-control-group.is-muted {
    background: oklch(var(--er) / 0.25);
}

.mic-control-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 0.75rem;
    background: transparent;
    color: oklch(var(--nc) / 0.75);
    transition: background 0.15s ease-out, color 0.15s ease-out;
    cursor: pointer;
}

.mic-control-btn:hover:not(:disabled) {
    background: oklch(var(--nc) / 0.15);
    color: oklch(var(--nc));
}

.mic-control-btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.mic-control-handle {
    /* Wide enough to hold the three-bar meter comfortably. */
    width: 2.5rem;
    height: 2rem;
}

.mic-control-mute {
    width: 2.25rem;
    height: 2rem;
}

/* Transport (play/pause + stop) uses the same housing as the microphone cluster so the
   two read as one control strip. */
.sim-transport-btn {
    width: 2.25rem;
    height: 2rem;
}

/* Stop collapses out of the row when the pointer is away, rather than merely fading:
   an invisible-but-still-laid-out button left a hole between the two groups. */
.sim-stop-btn {
    max-width: 2.25rem;
    overflow: hidden;
    transition: max-width 0.25s ease-in-out, opacity 0.2s ease-in-out;
}

.sim-controls {
    gap: 0.5rem;
    transition: gap 0.25s ease-in-out;
}

/* Collapsed: stop is gone and the two housings sit flush against each other. */
.sim-controls-autohide {
    gap: 0.125rem;
}

.sim-controls-autohide .sim-stop-btn {
    max-width: 0;
    opacity: 0;
    pointer-events: none;
}

.group:hover .sim-controls-autohide,
.group:focus-within .sim-controls-autohide {
    gap: 0.5rem;
}

.group:hover .sim-controls-autohide .sim-stop-btn,
.group:focus-within .sim-controls-autohide .sim-stop-btn {
    max-width: 2.25rem;
    opacity: 1;
    pointer-events: auto;
}

/* Below Tailwind's lg breakpoint, hover is not a thing (touch devices) and the
   :focus-within escape hatch can't be reached on a pointer-events:none button,
   so the autohidden controls stay permanently expanded. Matches the lg:-scoped
   opacity autohide the sibling buttons in RunSimulationComponent already use. */
@media (max-width: 1023.98px) {
    .sim-controls-autohide {
        gap: 0.5rem;
    }

    .sim-controls-autohide .sim-stop-btn {
        max-width: 2.25rem;
        opacity: 1;
        pointer-events: auto;
    }
}

/* The handle is the input meter: three dots that rise into a wave as the microphone hears
   the user (mic-control.js sets the heights, so the level never crosses the Blazor circuit).
   The caret to the options menu takes their place while the pointer is on it or the menu is open. */
.mic-control .mic-eq {
    display: flex;
}

.mic-control .mic-caret-icon {
    display: none;
}

.mic-control-handle:hover .mic-eq,
.mic-control-handle:focus-visible .mic-eq,
.mic-control-handle[aria-expanded="true"] .mic-eq {
    display: none;
}

.mic-control-handle:hover .mic-caret-icon,
.mic-control-handle:focus-visible .mic-caret-icon,
.mic-control-handle[aria-expanded="true"] .mic-caret-icon {
    display: block;
}

.mic-eq-bar {
    width: 4px;
    height: 4px;
    align-self: center;
    border-radius: 2px;
    background: linear-gradient(180deg, oklch(var(--p)) 0%, oklch(var(--s)) 100%);
    transition: height 0.08s ease-out;
    will-change: height;
}

/* ── Quick settings menu ──────────────────────────────────────────────────── */

/* Dark, to match the simulation chrome it floats over - the app's default surface
   colours would drop a bright card on top of the video. */
.mic-menu-panel {
    /* Semi-transparent, matching the control bar it belongs to rather than sitting on
       the video as a solid slab. The blur keeps the labels readable over busy frames. */
    background: oklch(var(--n) / 0.55);
    color: oklch(var(--nc));
    border: 1px solid oklch(var(--nc) / 0.15);
    backdrop-filter: blur(12px) saturate(140%);
    -webkit-backdrop-filter: blur(12px) saturate(140%);
}

.mic-menu-label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: oklch(var(--nc) / 0.6);
    white-space: nowrap;
}

.mic-menu-select {
    min-width: 11rem;
    max-width: 16rem;
    background: oklch(var(--n) / 0.65);
    color: oklch(var(--nc));
    border: 1px solid oklch(var(--nc) / 0.2);
}

.mic-menu-select:focus {
    outline: 2px solid oklch(var(--p) / 0.6);
    outline-offset: 1px;
}

.mic-menu-select:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Options are painted by the OS, which ignores the control's colours - force a dark
   palette so the open list doesn't flash white. */
.mic-menu-select option {
    background: oklch(var(--n));
    color: oklch(var(--nc));
}

.mic-menu-settings-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border: 1px solid oklch(var(--nc) / 0.2);
    border-radius: 0.5rem;
    background: oklch(var(--n) / 0.65);
    color: oklch(var(--nc) / 0.85);
    transition: background 0.15s ease-out, color 0.15s ease-out;
    cursor: pointer;
}

.mic-menu-settings-btn:hover:not(:disabled) {
    background: oklch(var(--nc) / 0.2);
    color: oklch(var(--nc));
}

.mic-menu-settings-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Reserved user speech bubble: animated while we are still waiting on the
   provider's transcript, static once the turn resolved with nothing. */
.chat-line-placeholder {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 1.2em;
}

.chat-line-placeholder .placeholder-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.45;
}

.chat-line-placeholder.is-reserved .placeholder-dot {
    animation: placeholderDotPulse 1.2s ease-in-out infinite;
}

.chat-line-placeholder.is-reserved .placeholder-dot:nth-child(2) {
    animation-delay: 0.18s;
}

.chat-line-placeholder.is-reserved .placeholder-dot:nth-child(3) {
    animation-delay: 0.36s;
}

/* Bracketed emotion cues in transcript lines ("[Chuckles]"), rendered as small tinted chips.
   The family colour comes from the cue's category; the text colour follows the bubble. */
.chat-emotion-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    margin: 0 0.15em;
    padding: 0.05em 0.5em 0.05em 0.4em;
    border-radius: 999px;
    border: 1px solid var(--emotion-tint, rgba(120, 120, 120, 0.55));
    background: color-mix(in srgb, var(--emotion-tint, rgb(120, 120, 120)) 18%, transparent);
    font-size: 0.78em;
    font-style: italic;
    font-weight: 500;
    line-height: 1.3;
    vertical-align: baseline;
    white-space: nowrap;
    opacity: 0.9;
}

.chat-emotion-tag i {
    font-size: 0.95em;
    color: var(--emotion-tint, inherit);
}

.chat-emotion-amused    { --emotion-tint: rgb(217, 154, 20); }
.chat-emotion-sad       { --emotion-tint: rgb(66, 133, 244); }
.chat-emotion-weary     { --emotion-tint: rgb(126, 132, 148); }
.chat-emotion-angry     { --emotion-tint: rgb(220, 72, 65); }
.chat-emotion-surprised { --emotion-tint: rgb(156, 92, 226); }
.chat-emotion-soft      { --emotion-tint: rgb(72, 170, 160); }
.chat-emotion-pause     { --emotion-tint: rgb(140, 140, 140); }
.chat-emotion-throat    { --emotion-tint: rgb(160, 120, 80); }
.chat-emotion-neutral   { --emotion-tint: rgb(120, 120, 120); }

/* A reserved bubble the transcript never filled: a voice-activity trip on non-speech. */
.chat-line-noise {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    font-style: italic;
    font-size: 0.9em;
    opacity: 0.6;
    letter-spacing: 0.01em;
}

.chat-line-noise i {
    font-size: 0.9em;
}

@keyframes placeholderDotPulse {
    0%, 100% {
        opacity: 0.25;
        transform: translateY(0) scale(0.85);
    }
    50% {
        opacity: 1;
        transform: translateY(-2px) scale(1.1);
    }
}
