/* ============================================================
   ASK jameshansen.ai — inference dashboard
   Extends the overlay in enhancements.css (#ask-overlay, .ask-card).
   Loaded after it, so the cascade here wins on shared selectors.

   Design note: the palette is inherited, not invented. Cyan (#22d3ee) already
   means "the .ai part" on this site, so here it means "free": tokens restored
   from cache, work not done. Violet (#8b5cf6) is the brand's working colour, so
   it means CPU that actually burned. The prefill bar is the one loud element.
   Numbers use the monospace stack already present in .ask-answer code, so no
   extra webfont is loaded for the sake of a readout.
   ============================================================ */

:root {
    --ask-cache:   #22d3ee;   /* restored: instant */
    --ask-compute: #8b5cf6;   /* evaluated: 8 tok/s of someone's afternoon */
    --ask-dim:     rgba(199, 197, 240, 0.55);
    --ask-rule:    rgba(255, 255, 255, 0.1);
    --ask-mono:    ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}

/* ============================================================
   ASK jameshansen.ai — docked Q&A panel (see script.js / ask.php)
   Bottom-right, like any other chat widget. The launcher bubble opens it.
   ============================================================ */
/* Right edge tracks #content's inner edge, not the viewport's: #content is
   max-width 1100px, centred, with 10px of padding, so its inner right edge sits
   540px from centre. Below 1100px wide the max() floor takes over and the panel
   sits 10px in, matching the container's padding. Percentages resolve against
   the client width, so unlike 100vw this does not drift by the scrollbar. */
#ask-overlay {
    position: fixed;
    right: max(10px, calc(50% - 540px));
    bottom: 0;
    z-index: 100000; /* above DHTMLX windows */
    display: none;
    width: min(400px, calc(100% - 20px));
}
#ask-overlay.open { display: block; }

/* Launcher bubble. Hidden while the panel is open. */
#ask-launch {
    position: fixed;
    right: max(10px, calc(50% - 540px));
    bottom: 16px;
    z-index: 99999;
    width: 56px;
    height: 56px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 50%;
    background: linear-gradient(135deg, #6366f1, #8b5cf6);
    color: #fff;
    font-size: 21px;
    cursor: pointer;
    box-shadow: 0 10px 30px rgba(20, 12, 60, 0.55);
    animation: ask-pop 0.28s ease;
}
#ask-launch:hover { filter: brightness(1.12); }
#ask-launch.hide { display: none; }

.ask-card {
    position: relative;
    width: 100%;
    box-sizing: border-box;   /* no global reset here: padding must stay inside */
    max-height: calc(100vh - 20px);
    overflow-y: auto;
    padding: 30px 24px 22px;
    border-radius: 18px 18px 0 0;   /* flush with the bottom of the window */
    background: rgba(23, 20, 48, 0.88);
    border: 1px solid rgba(255, 255, 255, 0.14);
    box-shadow: 0 24px 60px rgba(20, 12, 60, 0.55);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    color: #f4f3ff;
    font-family: 'Inter', system-ui, sans-serif;
    animation: ask-pop 0.28s ease;
}
@keyframes ask-pop {
    from { opacity: 0; transform: translateY(14px) scale(0.97); }
    to   { opacity: 1; transform: none; }
}

.ask-close {
    position: absolute;
    top: 12px; right: 14px;
    width: 34px; height: 34px;
    border: 0; border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    color: #d9d7ff;
    font-size: 22px; line-height: 1;
    cursor: pointer;
}
.ask-close:hover { background: rgba(255, 255, 255, 0.18); }

.ask-brand {
    font-size: 26px; font-weight: 800; letter-spacing: -0.03em;
    background: linear-gradient(90deg, #e0e7ff, #c7d2fe);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
}
/* ".ai" gets its own brighter gradient + glow so it pops off the wordmark. */
.ask-brand-ai {
    background: linear-gradient(90deg, var(--brand-stops));
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 0 12px var(--brand-glow));
}
.ask-prompt { margin: 8px 0 18px; font-size: 17px; color: #d7d5f5; }

.ask-form { display: flex; gap: 8px; }
.ask-input {
    flex: 1;
    padding: 13px 16px;
    border-radius: 11px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: rgba(255, 255, 255, 0.06);
    color: #fff; font-size: 15px;
    outline: none;
}
.ask-input::placeholder { color: rgba(230, 228, 255, 0.5); }
.ask-input:focus { border-color: #a5b4fc; background: rgba(255, 255, 255, 0.1); }
.ask-send {
    width: 48px; border: 0; border-radius: 11px;
    background: linear-gradient(135deg, #6366f1, #8b5cf6);
    color: #fff; font-size: 20px; cursor: pointer;
}
.ask-send:hover { filter: brightness(1.1); }

.ask-samples { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.ask-sample {
    padding: 7px 13px;
    border-radius: 999px;
    border: 1px solid rgba(165, 180, 252, 0.4);
    background: rgba(255, 255, 255, 0.04);
    color: #d7d5f5; font-size: 13px; cursor: pointer;
    transition: opacity 0.3s ease, filter 0.3s ease, transform 0.3s ease;
}
.ask-sample:hover { background: rgba(165, 180, 252, 0.18); color: #fff; }
.ask-sample.swapping { opacity: 0; filter: blur(4px); transform: scale(0.92); }

/* Spinner + cycling "runs locally" fact, shown while the model thinks. */
.ask-loading { display: none; align-items: center; gap: 11px; margin-top: 18px; }
.ask-loading.show { display: flex; }
.ask-spinner {
    width: 18px; height: 18px; flex: none;
    border: 2px solid rgba(165, 180, 252, 0.25);
    border-top-color: #a5b4fc;
    border-radius: 50%;
    animation: ask-spin 0.8s linear infinite;
}
@keyframes ask-spin { to { transform: rotate(360deg); } }
.ask-fact {
    font-size: 12.5px; font-style: italic;
    color: rgba(199, 197, 240, 0.75);
    opacity: 0; transition: opacity 0.5s ease;
}
.ask-fact.show { opacity: 1; }

.ask-answer {
    margin-top: 18px;
    font-size: 15px; line-height: 1.55;
    color: #efeeff;
    max-height: 240px;
    overflow-y: auto;
    padding-right: 10px;
    scrollbar-width: thin;                                  /* Firefox */
    scrollbar-color: rgba(139, 92, 246, 0.6) transparent;
}
.ask-answer:empty { display: none; }
.ask-answer.error { color: #fca5a5; }
.ask-answer::-webkit-scrollbar { width: 8px; }
.ask-answer::-webkit-scrollbar-track { background: transparent; }
.ask-answer::-webkit-scrollbar-thumb {
    background: linear-gradient(#6366f1, #8b5cf6);
    border-radius: 999px;
}
.ask-answer::-webkit-scrollbar-thumb:hover { background: linear-gradient(#7c7ff5, #9d6cf0); }

/* Markdown formatting inside a streamed answer. */
.ask-answer > *:first-child { margin-top: 0; }
.ask-answer > *:last-child { margin-bottom: 0; }
.ask-answer p { margin: 0 0 10px; }
.ask-answer ul, .ask-answer ol { margin: 0 0 10px; padding-left: 20px; }
.ask-answer li { margin: 3px 0; }
.ask-answer a { color: #a5b4fc; text-decoration: underline; }
.ask-answer h1, .ask-answer h2, .ask-answer h3 {
    font-size: 15px; font-weight: 700; color: #fff; margin: 12px 0 6px;
}
.ask-answer code {
    font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 12.5px;
    background: rgba(255, 255, 255, 0.1);
    padding: 1px 5px; border-radius: 5px;
}
.ask-answer pre {
    margin: 0 0 10px;
    padding: 12px 14px;
    background: rgba(10, 8, 30, 0.6);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 10px;
    overflow-x: auto;
}
.ask-answer pre code { background: none; padding: 0; line-height: 1.5; }
.ask-answer blockquote {
    margin: 0 0 10px; padding-left: 12px;
    border-left: 3px solid rgba(165, 180, 252, 0.5);
    color: #cfceec;
}

/* Animation while it's writing */
.answer-active::after {
  content: '|'; /* The trailing character */
  color: #fff; /* Custom cursor color */
  margin-left: 4px;
  animation: answer-active-blink 0.8s steps(2, start) infinite; 
}
@keyframes answer-active-blink {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}

/* Streamed words: already-shown ones are static; only new ones fade/unblur in. */
.ask-word-new { animation: ask-word-in 0.4s ease backwards; }
@keyframes ask-word-in {
    from { opacity: 0; filter: blur(4px); }
    to   { opacity: 1; filter: blur(0); }
}

.ask-browse {
    display: block;
    margin: 20px auto 0;
    padding: 6px 4px;
    border: 0; background: none;
    color: rgba(215, 213, 245, 0.7);
    font-size: 13px; text-decoration: underline; cursor: pointer;
}
.ask-browse:hover { color: #fff; }

/* Disclaimer line above the wordmark. Absolute placement fitted the old wide
   modal; in a 400px panel it collides with the close button. */
.ask-model {
    display: block;
    margin: 0 34px 6px 0;
    font-size: 11px; font-style: italic;
    color: rgba(199, 197, 240, 0.6);
    pointer-events: none;
}

/* Phones: the right/width rules above already give a full-width sheet inside
   the container padding; just buy back some room. */
@media (max-width: 560px) {
    .ask-card { max-height: 80vh; padding: 26px 18px 20px; }
    .ask-brand { font-size: 23px; }
}


/* ---- Prefill bar: the signature ------------------------------------------ */
/* Two segments, drawn from prompt_progress. On a warm slot the cyan segment
   fills the whole track in about a tenth of a second and the violet one never
   appears, which is precisely what a working KV cache looks like. */

.ask-prefill {
    display: none;
    margin-top: 18px;
}
.ask-prefill.show { display: block; }

.ask-prefill-track {
    position: relative;
    display: flex;
    height: 6px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.07);
    overflow: hidden;
}
.ask-prefill-seg {
    height: 100%;
    width: 0;
    transition: width 0.12s linear;
}
.ask-prefill-seg.cache   { background: var(--ask-cache); box-shadow: 0 0 10px rgba(34, 211, 238, 0.5); }
.ask-prefill-seg.compute { background: var(--ask-compute); }

/* Sweeps while the model is decoding, since prefill is finished by then and a
   static full bar reads as "stuck". Stops the moment the answer completes. */
.ask-prefill.decoding .ask-prefill-track::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.35), transparent);
    animation: ask-sweep 1.4s ease-in-out infinite;
}
@keyframes ask-sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
}

.ask-prefill-label {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    font-family: var(--ask-mono);
    font-size: 11px;
    letter-spacing: 0.02em;
    color: var(--ask-dim);
}
.ask-pre-text { min-width: 0; }   /* lets the readout wrap rather than crush the spinner */
.ask-prefill-label b { color: #e6e4ff; font-weight: 600; }
.ask-prefill-label .k-cache   { color: var(--ask-cache); }
.ask-prefill-label .k-compute { color: #c4b5fd; }

/* Spinner while the corpus is being read into the cache. It belongs to prefill
   only: the moment the first token lands, the bar's sweep takes over as the
   sign of life and a second spinner would be noise. Reuses .ask-spinner's box
   from enhancements.css, shrunk to sit on the label's baseline. */
.ask-pre-spin {
    width: 12px; height: 12px; flex: none;
    border: 2px solid rgba(165, 180, 252, 0.25);
    border-top-color: var(--ask-cache);
    border-radius: 50%;
    animation: ask-spin 0.8s linear infinite;
}
.ask-prefill.decoding .ask-pre-spin,
.ask-prefill.done .ask-pre-spin { display: none; }

/* ---- Answer: one span per model token ------------------------------------ */
/* Plain text, not Markdown. The system prompt forbids Markdown, so marked and
   DOMPurify are no longer needed on this page and can be dropped from index.php.
   pre-wrap preserves the model's own spacing without a parse step. */

.ask-answer { white-space: pre-wrap; }

.ask-tok { display: inline; }

/* Its own keyframes rather than borrowing ask-word-in from enhancements.css.
   That one was tuned for whole words arriving at 8 tok/s. llama-server streams
   subword tokens several times faster, so each fragment used to finish its blur
   before the eye could catch it and the whole effect read as instant. The cure
   is a stagger, applied per token in script.js as an animation-delay, not a
   longer duration: a slower fade on every token would just look sluggish.
   `both` keeps the from-state applied during the delay, so a token waiting its
   turn is invisible rather than popping in and then fading. */
.ask-tok-new {
    animation: ask-tok-in 0.4s cubic-bezier(0.22, 0.8, 0.3, 1) both;
    will-change: opacity, filter;
}
@keyframes ask-tok-in {
    from { opacity: 0; filter: blur(6px); }
    to   { opacity: 1; filter: blur(0); }
}

/* Confidence. Underline rather than colour: coloured text on this card would be
   unreadable, and the point is to annotate the answer, not repaint it. A token
   the model was sure of looks completely ordinary. Doubt is what gets marked. */
.ask-tok[data-conf="low"] {
    border-bottom: 1px dotted rgba(252, 165, 165, 0.55);
    cursor: help;
}
.ask-tok[data-conf="vlow"] {
    border-bottom: 1px dotted rgba(252, 165, 165, 0.9);
    background: rgba(252, 165, 165, 0.08);
    border-radius: 2px;
    cursor: help;
}

/* ---- Stats ---------------------------------------------------------------- */

.ask-stats {
    display: none;
    grid-template-columns: repeat(2, 1fr);
    gap: 1px;
    margin-top: 18px;
    border-top: 1px solid var(--ask-rule);
    border-left: 1px solid var(--ask-rule);
    background: var(--ask-rule);
    border-radius: 8px;
    overflow: hidden;
}
.ask-stats.show { display: grid; }

.ask-stat {
    padding: 9px 11px;
    background: rgba(23, 20, 48, 0.9);
}

/* The cells resolve left to right, in the order the work happened: first token,
   context, cache, prefill, decode, answer. Same blur as the tokens, so the
   readout feels like the last thing the answer produced rather than a panel
   that was always there. 60ms apart is enough to read as a sequence and short
   enough that the whole row settles in a third of a second. */
.ask-stats.show .ask-stat,
.ask-rig.show {
    animation: ask-tok-in 0.45s cubic-bezier(0.22, 0.8, 0.3, 1) both;
}
.ask-stats.show .ask-stat:nth-child(1) { animation-delay: 0ms; }
.ask-stats.show .ask-stat:nth-child(2) { animation-delay: 60ms; }
.ask-stats.show .ask-stat:nth-child(3) { animation-delay: 120ms; }
.ask-stats.show .ask-stat:nth-child(4) { animation-delay: 180ms; }
.ask-stats.show .ask-stat:nth-child(5) { animation-delay: 240ms; }
.ask-stats.show .ask-stat:nth-child(6) { animation-delay: 300ms; }
.ask-rig.show { animation-delay: 380ms; }
.ask-stat-k {
    font-size: 9.5px;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--ask-dim);
}
.ask-stat-v {
    margin-top: 2px;
    font-family: var(--ask-mono);
    font-size: 14px;
    font-variant-numeric: tabular-nums;   /* digits stop jittering as they tick */
    color: #f4f3ff;
}
.ask-stat-v small { font-size: 10px; color: var(--ask-dim); margin-left: 2px; }
.ask-stat.is-cache .ask-stat-v { color: var(--ask-cache); }

/* ---- Rig line ------------------------------------------------------------- */
/* Replaces the old cycling "sorry it's slow" facts. It does not apologise. */

.ask-rig {
    display: none;
    margin-top: 10px;
    font-family: var(--ask-mono);
    font-size: 10.5px;
    color: var(--ask-dim);
    text-align: right;
}
.ask-rig.show { display: block; }
.ask-rig .dot { opacity: 0.4; margin: 0 5px; }

/* ---- Quiet floor ---------------------------------------------------------- */

.ask-tok:focus-visible,
.ask-sample:focus-visible,
.ask-send:focus-visible,
.ask-close:focus-visible {
    outline: 2px solid var(--ask-cache);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .ask-tok-new,
    .ask-stats.show .ask-stat,
    .ask-rig.show { animation: none; }
    .ask-prefill.decoding .ask-prefill-track::after { animation: none; opacity: 0; }
    .ask-prefill-seg { transition: none; }
    .ask-pre-spin { animation-duration: 2.4s; }  /* still turns: it means "working" */
}

@media (max-width: 560px) {
    .ask-rig { text-align: left; }
}
