/* Tools site - shared styling.
   No external fonts or CDNs: the CSP forbids them, and nothing should leak
   a referrer to a third party. */

:root {
    --bg:     #12141a;
    --card:   #1b1e26;
    --line:   #2a2f3a;
    --text:   #e6e8ee;
    --muted:  #949bab;
    --accent: #5b8cff;
    --err:    #ff6b6b;
    --ok:     #4ccf7f;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.5;
    min-height: 100vh;
}

body.centre {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

/* --- login / setup card --- */

.card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 32px;
    width: 100%;
    max-width: 380px;
}

.card h1 {
    font-size: 22px;
    font-weight: 600;
    margin-bottom: 18px;
}

label {
    display: block;
    font-size: 13px;
    color: var(--muted);
    margin: 14px 0 6px;
}

input[type="password"],
input[type="text"] {
    width: 100%;
    padding: 11px 13px;
    background: #10131a;
    border: 1px solid var(--line);
    border-radius: 9px;
    color: var(--text);
    font-size: 15px;
    font-family: inherit;
}

input:focus {
    outline: none;
    border-color: var(--accent);
}

input:disabled {
    opacity: .5;
    cursor: not-allowed;
}

button {
    width: 100%;
    margin-top: 20px;
    padding: 11px;
    background: var(--accent);
    color: #fff;
    border: 0;
    border-radius: 9px;
    font-size: 15px;
    font-weight: 500;
    font-family: inherit;
    cursor: pointer;
}

button:hover:not(:disabled) { background: #4a7bef; }
button:disabled { opacity: .5; cursor: not-allowed; }

.btn {
    display: inline-block;
    margin-top: 16px;
    padding: 9px 16px;
    background: var(--accent);
    color: #fff;
    border-radius: 9px;
    text-decoration: none;
    font-size: 14px;
}

/* --- messages --- */

.err, .ok, .note {
    font-size: 14px;
    border-radius: 9px;
}

.err {
    background: rgba(255,107,107,.12);
    border: 1px solid rgba(255,107,107,.3);
    color: var(--err);
    padding: 10px 13px;
    margin-bottom: 4px;
}

.ok {
    background: rgba(76,207,127,.12);
    border: 1px solid rgba(76,207,127,.3);
    color: var(--ok);
    padding: 10px 13px;
}

.note {
    color: var(--muted);
    font-size: 13px;
    margin-top: 14px;
}

code {
    background: #10131a;
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 12px;
}

/* --- dashboard --- */

.bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 24px;
    border-bottom: 1px solid var(--line);
    background: var(--card);
}

.brand { font-weight: 600; }

.inline { display: inline; width: auto; }

button.link {
    width: auto;
    margin: 0;
    padding: 0;
    background: none;
    color: var(--muted);
    font-size: 14px;
}

button.link:hover { color: var(--text); background: none; }

.wrap {
    max-width: 900px;
    margin: 0 auto;
    padding: 32px 24px;
}

.wrap.narrow { max-width: 560px; }

.brand a { color: inherit; text-decoration: none; }
.brand a:hover { color: var(--accent); }

/* --- settings panel --- */

.panel {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 24px;
}

.panel h2 {
    font-size: 16px;
    font-weight: 600;
    margin-bottom: 4px;
}

.tag {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 7px;
    border-radius: 20px;
    font-size: 11px;
    background: rgba(76,207,127,.15);
    color: var(--ok);
    text-transform: uppercase;
    letter-spacing: .4px;
}

.tag.warn {
    background: rgba(255,180,84,.15);
    color: #ffb454;
}

.row-actions {
    display: flex;
    gap: 8px;
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid var(--line);
}

button.sm {
    width: auto;
    margin: 0;
    padding: 7px 13px;
    font-size: 13px;
    background: var(--line);
    color: var(--text);
}

button.sm:hover:not(:disabled) { background: #333a49; }

button.sm.danger {
    background: rgba(255,107,107,.12);
    color: var(--err);
}

button.sm.danger:hover:not(:disabled) { background: rgba(255,107,107,.2); }

/* --- upscaling --- */

.drop {
    border: 2px dashed var(--line);
    border-radius: 10px;
    padding: 34px 20px;
    text-align: center;
    cursor: pointer;
    transition: .15s;
}

.drop:hover, .drop.over {
    border-color: var(--accent);
    background: #1d2230;
}

.drop strong { display: block; margin-bottom: 3px; }
.drop span { color: var(--muted); font-size: 13px; }

/* Children must not swallow drag events aimed at the drop zone. Clicks are
   unaffected: the parent still receives them via normal event bubbling. */
.drop strong, .drop span { pointer-events: none; }

.filepick { display: none; }

/* Checkbox rows: the whole label is clickable, so the hit area is not just
   the 16px box. */
label.check {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 16px 0 0;
    color: var(--text);
    font-size: 14px;
    cursor: pointer;
    user-select: none;
}

label.check input[type="checkbox"] {
    width: 16px;
    height: 16px;
    margin: 0;
    flex: none;
    accent-color: var(--accent);
    cursor: pointer;
}

label.check span { color: var(--muted); }
label.check:hover span { color: var(--text); }

.chosen {
    margin-top: 14px;
    padding: 9px 12px;
    background: #10131a;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 13.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.chosen.bad { color: var(--err); border-color: rgba(255,107,107,.35); }

select {
    width: 100%;
    padding: 10px 12px;
    background: #10131a;
    border: 1px solid var(--line);
    border-radius: 9px;
    color: var(--text);
    font-size: 15px;
    font-family: inherit;
}

select:focus { outline: none; border-color: var(--accent); }

.jobline {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 10px;
    font-size: 14px;
}

.jobline span:first-child {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pbar {
    height: 5px;
    background: var(--line);
    border-radius: 3px;
    overflow: hidden;
}

.pbar i {
    display: block;
    height: 100%;
    width: 0;
    background: var(--accent);
    transition: width .6s linear;
}

.result { margin-top: 18px; }

.result img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 10px;
    background: #0d1016;
}

a.btn.sm {
    margin: 0;
    padding: 7px 13px;
    font-size: 13px;
}

.dim { color: var(--muted); }

table.jobs { width: 100%; border-collapse: collapse; font-size: 13.5px; }

table.jobs td {
    padding: 9px 8px 9px 0;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}

table.jobs tr:last-child td { border-bottom: 0; }

table.jobs .nm {
    max-width: 240px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

table.jobs a { color: var(--accent); }

.panel h2 + table.jobs { margin-top: 10px; }

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 16px;
}

.tool {
    display: flex;
    flex-direction: column;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 22px;
    text-decoration: none;
    color: var(--text);
}

a.tool:hover {
    border-color: var(--accent);
    transform: translateY(-1px);
    transition: .12s;
}

.tool.off { opacity: .45; }

.icon { font-size: 26px; margin-bottom: 10px; }
.tname { font-weight: 600; margin-bottom: 3px; }
.tdesc { font-size: 13px; color: var(--muted); }

.soon {
    margin-top: 10px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--muted);
}

/* --- transparency cropping --- */

.crop-card { margin-top: 18px; }

.crop-card .jobline { margin-bottom: 8px; }

/* A checkerboard behind the preview: against the flat card colour a
   transparent edge and a dark edge look identical, which defeats the point
   of showing the crop at all. */
.crop-preview {
    display: block;
    width: 100%;
    height: auto;
    max-height: 420px;
    object-fit: contain;
    border-radius: 10px;
    border: 1px solid var(--line);
    background-color: #0d1016;
    background-image:
        linear-gradient(45deg, #23283400 75%, #232834 75%),
        linear-gradient(45deg, #232834 25%, #23283400 25%),
        linear-gradient(45deg, #23283400 75%, #232834 75%),
        linear-gradient(45deg, #232834 25%, #23283400 25%);
    background-size: 20px 20px;
    background-position: 0 0, 0 0, 10px 10px, 10px 10px;
}

.crop-dims { margin-top: 12px; }

.crop-dims td:first-child { width: 90px; }

.crop-card .err { margin-bottom: 0; }

.crop-clear { margin-top: 18px; }

.drop.working { opacity: .6; cursor: progress; }

/* --- scratchpad --- */

.pad {
    display: block;
    width: 100%;
    min-height: 56vh;
    margin-top: 2px;
    padding: 13px 14px;
    resize: vertical;
    background: #10131a;
    border: 1px solid var(--line);
    border-radius: 9px;
    color: var(--text);
    /* 16px and no text-size-adjust: anything smaller makes iOS Safari zoom
       the page the moment the field is focused, which on a typing surface
       is far more disruptive than on a one-line input. */
    font-size: 16px;
    line-height: 1.55;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    -webkit-text-size-adjust: 100%;
}

.pad:focus { outline: none; border-color: var(--accent); }

.padbar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-top: 10px;
    font-size: 13px;
}

.stat { color: var(--muted); }
.stat.ok { color: var(--ok); }
.stat.bad { color: var(--err); }

.padactions {
    display: flex;
    gap: 8px;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid var(--line);
}

/* --- Lernkarten --- */

/* The horizontal swipe is ours; the browser keeps vertical panning so a long
   answer still scrolls. overscroll-behavior-x stops a horizontal overscroll
   from becoming a back-navigation where the browser honours it - iOS Safari's
   screen-edge back gesture cannot be cancelled from a page, which is why the
   stack keeps a margin away from the edges. */
.lk-body { overscroll-behavior-x: contain; }

.lk-wrap { padding-bottom: 40px; }

.lk-fields label.check { margin-top: 12px; }

.lk-avail { font-size: 13px; margin-top: 14px; }

.lk-decknote { font-size: 13px; line-height: 1.45; margin: 6px 0 2px; }

.lk-setup h2 { margin-bottom: 10px; }

button.sm.lk-primary { background: var(--accent); color: #fff; }
button.sm.lk-primary:hover:not(:disabled) { background: #4a7bef; }

/* --- progress --- */

.lk-progress { margin-bottom: 14px; }

.lk-counts {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 8px;
    font-size: 13px;
    color: var(--muted);
}

.lk-c-ok    { color: var(--ok); }
.lk-c-bad   { color: var(--err); }
.lk-c-later { color: var(--accent); }
.lk-c-open { color: var(--muted); }
.lk-counts .dim { margin-left: auto; }

/* --- card stack --- */

.lk-stack {
    position: relative;
    /* Room for the fanned card behind, and a gap from the screen edges so a
       swipe rarely starts in the browser's edge-gesture zone. */
    margin: 0 4px;
    min-height: 60vh;
}

.lk-ghost {
    position: absolute;
    inset: 10px 0 0;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 16px;
    transform: scale(.97);
    opacity: .5;
}

.lk-card {
    position: relative;
    display: flex;
    flex-direction: column;
    /* Fixed height, not just a floor. .lk-scroll below is flex:1 with
       overflow-y:auto, but a flex child only scrolls once its parent is
       bounded - without a ceiling the card simply grew and the PAGE scrolled
       instead. That was survivable while answers were short plain text; the
       rich back_html answers are long enough to push "Gewusst"/"Nicht
       gewusst" several screens down and to turn the whole tall card into a
       swipe surface. Same value as the floor, so short and long cards are the
       same size and the stack never jumps. */
    min-height: 60vh;
    max-height: 60vh;
    padding: 18px 18px 14px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: 0 10px 30px rgba(0,0,0,.35);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    touch-action: pan-y;          /* vertical scroll stays with the browser */
    will-change: transform;
}

.lk-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.lk-card.lk-fly { transition: transform .18s ease-out, opacity .18s ease-out; }
.lk-card.lk-dragging { transition: none; }

.lk-card.lk-tint-yes { border-color: rgba(76,207,127,.65); }
.lk-card.lk-tint-no  { border-color: rgba(255,107,107,.65); }

.lk-meta {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    font-size: 12px;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .5px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--line);
}

.lk-deckname {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lk-side { flex: none; }
.lk-card.lk-flipped .lk-side { color: var(--accent); }

/* The only scroller on the card. Momentum scrolling stays inside it, so a
   long answer is readable without the page moving underneath. */
.lk-scroll {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 16px 2px 8px;
}

.lk-text {
    /* 17px: comfortably above the 16px iOS zoom threshold and readable at
       arm's length on a phone. */
    font-size: 17px;
    line-height: 1.55;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.lk-card.lk-flipped .lk-text { font-size: 16.5px; }

.lk-tags {
    margin-top: 14px;
    font-size: 12px;
    color: var(--muted);
}

.lk-tags:empty { display: none; }

/* --- rich answer (back_html) ---
   Built by lkSafeHtml from an allowlist: ul, ol, li, b, br, code, pre, u and
   nothing else. The plain answer keeps pre-wrap for its own line breaks; the
   rich one must not, or the whitespace between tags becomes visible gaps. */

.lk-text.lk-rich { white-space: normal; }

.lk-text.lk-rich ul,
.lk-text.lk-rich ol {
    margin: 0 0 10px;
    /* Enough to hold a two-digit marker without the text stepping out of the
       card on a narrow phone. */
    padding-left: 1.35em;
}

.lk-text.lk-rich ul { list-style: disc; }
.lk-text.lk-rich ol { list-style: decimal; }

.lk-text.lk-rich li {
    margin: 0 0 6px;
    padding-left: 2px;
    line-height: 1.5;
}

.lk-text.lk-rich li:last-child { margin-bottom: 0; }

/* Nested lists sit closer, so a sub-point reads as a sub-point. */
.lk-text.lk-rich li > ul,
.lk-text.lk-rich li > ol { margin: 6px 0 2px; }

.lk-text.lk-rich b { color: var(--text); font-weight: 650; }
.lk-text.lk-rich u { text-underline-offset: 2px; }

.lk-text.lk-rich code {
    padding: 1px 5px;
    background: rgba(91,140,255,.13);
    border-radius: 5px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .88em;
    /* A long identifier must break rather than widen the card. */
    overflow-wrap: anywhere;
}

/* A code block is the one thing allowed to scroll sideways: wrapping a command
   in the middle makes it unreadable, and the card itself must not widen. */
.lk-text.lk-rich pre {
    margin: 0 0 10px;
    padding: 10px 12px;
    background: rgba(18,20,26,.55);
    border: 1px solid var(--line);
    border-radius: 8px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    white-space: pre;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    line-height: 1.45;
}

/* Inside a block the inline treatment would double the background. */
.lk-text.lk-rich pre code {
    padding: 0;
    background: none;
    border-radius: 0;
    font-size: inherit;
}

/* --- Tipp --- */

/* Same thumb target as the KI button it sits beside, and far enough from the
   card that a swipe never starts on it. */
button.sm.lk-tipbtn {
    min-height: 40px;
    padding: 9px 18px;
    font-size: 14px;
    border-color: rgba(240,190,90,.45);
    color: var(--text);
    touch-action: manipulation;          /* no 300ms tap delay on iOS */
}

button.sm.lk-tipbtn:hover:not(:disabled) { background: rgba(240,190,90,.16); }

/* hidden is an attribute, and the row is a flex container - display:flex on a
   child would otherwise win over the UA's display:none. */
button.sm.lk-tipbtn[hidden] { display: none; }

.lk-tippanel {
    margin-top: 10px;
    padding: 12px 14px 13px;
    background: var(--card);
    border: 1px solid rgba(240,190,90,.38);
    border-radius: 14px;
}

.lk-tippanel[hidden] { display: none; }

.lk-tipkicker {
    display: block;
    margin-bottom: 5px;
    font-size: 12px;
    font-weight: 600;
    color: rgba(240,190,90,.95);
}

.lk-tiptext {
    margin: 0;
    font-size: 15px;
    line-height: 1.55;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.lk-flaghint {
    padding-top: 10px;
    font-size: 12px;
    color: var(--muted);
    text-align: center;
    border-top: 1px solid var(--line);
}

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

.lk-badge {
    position: absolute;
    top: 16px;
    padding: 6px 12px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    opacity: 0;
    pointer-events: none;
    z-index: 2;
}

.lk-badge-yes {
    right: 14px;
    color: var(--ok);
    border: 2px solid var(--ok);
    background: rgba(76,207,127,.12);
    transform: rotate(8deg);
}

.lk-badge-no {
    left: 14px;
    color: var(--err);
    border: 2px solid var(--err);
    background: rgba(255,107,107,.12);
    transform: rotate(-8deg);
}

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

/* "Später" gets its own row. As a fourth button beside the ratings it would
   squeeze "✗ Nicht gewusst" onto three lines on a narrow iPhone. */
.lk-laterrow {
    display: flex;
    justify-content: center;
    margin-top: 12px;
}

button.sm.lk-laterbtn {
    min-width: 180px;
    min-height: 40px;
    padding: 9px 20px;
    font-size: 14px;
    border-color: rgba(91,140,255,.35);
    color: var(--text);
    touch-action: manipulation;          /* no 300ms tap delay on iOS */
}

button.sm.lk-laterbtn:hover:not(:disabled) { background: rgba(91,140,255,.14); }

/* Marks a postponed card in the end-of-round list. */
.lk-sumlater {
    color: var(--accent);
    font-size: 13px;
}

.lk-actions {
    display: flex;
    gap: 8px;
    margin-top: 14px;
}

button.lk-big {
    width: auto;
    flex: 1;
    margin: 0;
    /* 52px: comfortably above the 44px minimum touch target. */
    min-height: 52px;
    padding: 10px 6px;
    font-size: 14px;
    font-weight: 600;
    background: var(--line);
    color: var(--text);
}

button.lk-mid { flex: 0 0 96px; font-weight: 500; }

button.lk-no {
    background: rgba(255,107,107,.14);
    color: var(--err);
    border: 1px solid rgba(255,107,107,.3);
}

button.lk-no:hover:not(:disabled) { background: rgba(255,107,107,.24); }

button.lk-yes {
    background: rgba(76,207,127,.14);
    color: var(--ok);
    border: 1px solid rgba(76,207,127,.3);
}

button.lk-yes:hover:not(:disabled) { background: rgba(76,207,127,.24); }

.lk-subactions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 12px;
}

.lk-subactions .stat { margin-left: auto; font-size: 12.5px; text-align: right; }

/* --- summary --- */

.lk-sumline { font-size: 15px; margin-top: 6px; }

.lk-sumlist {
    margin: 14px 0 4px;
    padding-left: 20px;
    max-height: 46vh;
    overflow-y: auto;
    font-size: 14px;
    color: var(--muted);
}

.lk-sumlist li { margin-bottom: 7px; }
.lk-sumlist:empty { display: none; }

.lk-summary .padactions { flex-wrap: wrap; }

/* --- KI-Erklärung --- */

.lk-airow {
    display: flex;
    justify-content: center;
    align-items: center;
    /* Tipp and KI sit side by side here; they wrap rather than overflow on a
       very narrow screen. */
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 12px;
    /* Tall enough for the cat from the first paint, so it never shifts the
       layout once the script fills the slot. The button is 40px. */
    min-height: 56px;
    position: relative;
}

/* A comfortable thumb target on a phone, and far enough from the card that a
   swipe never starts on it. */
button.sm.lk-aibtn {
    min-height: 40px;
    padding: 9px 18px;
    font-size: 14px;
    border-color: rgba(91,140,255,.45);
    color: var(--text);
    touch-action: manipulation;          /* no 300ms tap delay on iOS */
}

button.sm.lk-aibtn:hover:not(:disabled) { background: rgba(91,140,255,.18); }

.lk-aipanel {
    margin-top: 12px;
    padding: 14px 16px 16px;
    background: var(--card);
    border: 1px solid rgba(91,140,255,.35);
    border-radius: 14px;
}

.lk-aipanel.lk-ai-bad { border-color: rgba(255,107,107,.5); }

.lk-aihead {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.lk-aititle {
    font-size: 13px;
    color: var(--muted);
    margin-right: auto;
}

.lk-aihead button.sm { touch-action: manipulation; }

.lk-aibody {
    font-size: 15.5px;
    line-height: 1.6;
    max-height: 52vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}

.lk-ai-h {
    margin-top: 12px;
    font-weight: 600;
    color: var(--accent);
}

.lk-ai-h:first-child { margin-top: 0; }

.lk-ai-p { margin-top: 6px; }
.lk-ai-p:first-child { margin-top: 0; }

.lk-ai-li {
    position: relative;
    margin-top: 5px;
    padding-left: 16px;
}

.lk-ai-li::before {
    content: "\2022";
    position: absolute;
    left: 2px;
    color: var(--muted);
}

.lk-ai-wait    { color: var(--muted); }
.lk-ai-errtext { color: var(--err); }

/* --- decorative pixel cat -------------------------------------------------
   Sits on the right of the KI row, clear of the centred button. Absolutely
   positioned inside a row whose height already allows for it, so it adds no
   layout and shifts nothing; pointer-events are off on both the slot and the
   canvas, so a tap, a swipe or a scroll passes straight through it. */

.lk-catslot,
.lk-catrow {
    pointer-events: none;
}

.lk-catslot {
    position: absolute;
    right: 0;
    bottom: 1px;
    width: 54px;
    height: 54px;
}

/* On the summary the cat gets a row of its own, above the heading. */
.lk-catrow {
    display: flex;
    justify-content: center;
    min-height: 54px;
    margin-bottom: 4px;
}

.lk-cat {
    display: block;
    /* The canvas is already drawn at device resolution, so this only guards
       against a browser deciding to smooth it anyway. */
    image-rendering: pixelated;
    image-rendering: -webkit-optimize-contrast;
}

/* Text as it streams in. The model's own line breaks are all the structure
   there is until the answer is complete and gets re-rendered into blocks, so
   preserve them rather than collapsing everything into one paragraph. */
.lk-ai-stream {
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

/* A caret while tokens are still arriving, so a pause reads as "thinking"
   rather than as "finished". Driven by a class on the panel, never inline. */
.lk-aipanel.lk-ai-live .lk-ai-stream::after {
    content: "\258D";
    margin-left: 1px;
    color: var(--accent);
    animation: lk-ai-blink 1s steps(2, start) infinite;
}

@keyframes lk-ai-blink {
    to { visibility: hidden; }
}

@media (prefers-reduced-motion: reduce) {
    .lk-aipanel.lk-ai-live .lk-ai-stream::after { animation: none; }
}

@media (max-width: 420px) {
    .lk-wrap { padding-left: 14px; padding-right: 14px; }
    button.lk-big { font-size: 13px; }
    button.lk-mid { flex: 0 0 84px; }
    .lk-aibody { font-size: 15px; }
}

/* --- Schach ---------------------------------------------------------------
   The board is 64 plain divs in a CSS grid with an SVG arrow layer on top.
   No board library: a chessboard is a grid and a piece is a glyph, and the
   heavyweight options all bring their own event model, their own theming and
   their own opinions about touch. */

/* The hidden attribute loses to any explicit display value, and several
   elements below are flex containers that spend most of their life hidden
   (the thinking veil, the verdict row). Without this the veil sits over the
   board permanently, dimming it. */
.chess-boardcol [hidden],
.chess-side [hidden] { display: none !important; }

.chess-wrap { max-width: 1040px; }

.chess-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 24px;
    align-items: start;
}

.chess-boardcol { min-width: 0; }

.chess-evalrow {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

.chess-status { min-width: 0; }
.chess-status strong { display: block; font-size: 15px; font-weight: 600; }
.chess-eval {
    font-size: 13px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* Eval bar. Black is the track, White grows from the bottom. */
.evalbar {
    width: 10px;
    height: 44px;
    border-radius: 5px;
    background: #0b0d12;
    border: 1px solid var(--line);
    position: relative;
    overflow: hidden;
    flex: none;
}

.evalbar-fill {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 50%;
    background: #e9ecf3;
    transition: height .35s ease;
}

.board-frame {
    position: relative;
    width: 100%;
    max-width: 560px;
    aspect-ratio: 1 / 1;
    border-radius: 12px;
    overflow: hidden;
    border: 1px solid var(--line);
    /* The single most important line for iPhone: without it Safari starts a
       scroll or a pinch-zoom from the same touch that begins a drag, and the
       piece is dropped the moment the page moves. */
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}

.board {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    grid-template-rows: repeat(8, 1fr);
    width: 100%;
    height: 100%;
}

.sq {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Warm and a step further apart than the old blue-greys, which read as flat
   grey on a phone. The blue move/last overlays and the red check wash are
   complementary to this pair, so they stay obvious against both squares. */
.sq.light { background: #b0987a; }
.sq.dark  { background: #5b4c3c; }

/* Coordinates, written into the border squares by the layout pass. */
.sq::after,
.sq::before {
    position: absolute;
    font-size: 9px;
    font-weight: 600;
    opacity: .55;
    pointer-events: none;
}
.sq::after  { content: attr(data-file); right: 3px; bottom: 1px; }
.sq::before { content: attr(data-rank); left: 3px; top: 1px; }
.sq.light::after, .sq.light::before { color: #3b3024; }
.sq.dark::after,  .sq.dark::before  { color: #d9ccb6; }

/* The piece symbols in chess.php carry their own fills and strokes - a white
   piece is white with black outlines and black interior lines, a black piece is
   black with white interior lines. Setting fill or stroke here would paint over
   that detail and leave twelve flat silhouettes, so .pc and its .w / .b
   variants deliberately style geometry only. */
.pc {
    width: 92%;
    height: 92%;
    pointer-events: none;
    display: block;
}

.sq.lifted .pc { opacity: .25; }

/* The dragged copy follows the pointer at the top of the stacking order.
   left and top are written once, at 0/0, and never again: the position is
   carried by a transform (see paintGhost in chess.js), so a drag frame costs
   the compositor a matrix change and nothing else - no layout, no repaint.
   will-change is what buys the element its own layer to make that true. */
.pc.ghost {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 60;
    pointer-events: none;
    transform-origin: 50% 50%;
    will-change: transform;
    /* Lifted off the board rather than lying on it: the scale comes from the
       transform, the shadow from here. */
    filter: drop-shadow(0 10px 14px rgba(0,0,0,.55));
}

/* Only while the dropped copy settles onto its square. The piece being dragged
   is never transitioned - it has to track the finger exactly, 1:1. */
.pc.ghost.snapping { transition: transform 130ms cubic-bezier(.2,.7,.3,1); }

/* A piece that arrives without having been dragged - an engine reply, or a
   move made by clicking - slides in from the square it came from. Set for one
   frame by playSlide in chess.js and removed again when it lands. */
.pc.sliding { transition: transform 140ms cubic-bezier(.2,.7,.3,1); }

/* Move affordances. Dots for quiet moves, a ring for captures, so a capture
   never hides the piece it would take. */
.sq.move::before,
.sq.take::before { content: ""; }

.sq.move::before {
    position: absolute;
    left: 50%; top: 50%;
    width: 26%; height: 26%;
    margin: -13% 0 0 -13%;
    border-radius: 50%;
    background: rgba(91,140,255,.85);
    z-index: 2;
}

.sq.take::before {
    position: absolute;
    left: 6%; top: 6%;
    width: 88%; height: 88%;
    margin: 0;
    border-radius: 50%;
    border: 4px solid rgba(91,140,255,.85);
    background: none;
    z-index: 2;
}

.sq.sel   { box-shadow: inset 0 0 0 3px var(--accent); }
/* Amber rather than the site's blue: a blue wash over these warm squares
   desaturates into a grey-lavender that reads as a rendering artefact instead
   of a highlight. Amber sits with the board and is the usual chess convention
   for "this was the last move". */
.sq.last  { background-image: linear-gradient(rgba(255,196,74,.34), rgba(255,196,74,.34)); }
.sq.check { background-image: radial-gradient(circle, rgba(255,107,107,.75), rgba(255,107,107,0) 72%); }

.board-arrows {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 5;
}

.arrow-line {
    stroke: rgba(76,207,127,.85);
    stroke-width: .13;
    stroke-linecap: round;
}
.arrow-head { fill: rgba(76,207,127,.85); }

.board-veil {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(18,20,26,.45);
    backdrop-filter: blur(1px);
    font-size: 13px;
    color: var(--text);
    z-index: 10;
}

.promo {
    position: absolute;
    inset: 0;
    display: flex;
    flex-wrap: wrap;
    align-content: center;
    justify-content: center;
    gap: 8px;
    background: rgba(18,20,26,.88);
    z-index: 20;
}

/* Board-square tones rather than card tones: the pieces bring their own black
   and white, and a black piece on the dark card would be near-invisible. */
.promo-pick {
    width: 68px;
    height: 68px;
    margin: 0;
    padding: 6px;
    background: #7d8498;
    border: 1px solid var(--line);
    border-radius: 10px;
}
.promo-pick:hover { background: #939ab0; }

.promo-cancel {
    width: auto;
    margin: 8px 0 0;
    padding: 7px 14px;
    flex-basis: 100%;
    max-width: 160px;
    background: var(--line);
    font-size: 13px;
}

/* --- verdict and commentary --- */

.chess-verdict {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 14px;
    flex-wrap: wrap;
}

.verdict-badge {
    display: inline-block;
    padding: 3px 11px;
    border-radius: 20px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .3px;
    cursor: help;
}

.verdict-badge.brilliant  { background: rgba(91,140,255,.18); color: #8fb0ff; }
.verdict-badge.good       { background: rgba(76,207,127,.15); color: var(--ok); }
.verdict-badge.inaccurate { background: rgba(255,180,84,.15); color: #ffb454; }
.verdict-badge.mistake    { background: rgba(255,140,84,.16); color: #ff9254; }
.verdict-badge.blunder    { background: rgba(255,107,107,.15); color: var(--err); }

.verdict-text {
    font-size: 13px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.chess-comment {
    margin-top: 10px;
    padding: 11px 13px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
    font-size: 14px;
}

.comment-kicker {
    display: inline-block;
    margin-right: 8px;
    padding: 1px 7px;
    border-radius: 20px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .4px;
    background: rgba(148,155,171,.15);
    color: var(--muted);
}

.comment-body { color: var(--text); }

/* The "ask the server instead" offer, shown only when the local model answered
   and the plausibility guard threw that answer away.

   Styled as a quiet inline action rather than a primary button: the rule
   sentence next to it is already a usable answer, so this is a way forward and
   not an alarm. The bare `button` rule near the top of this file is full-width
   with a 20px top margin and an accent fill, so each of those is overridden.

   display: block puts it on its own line under the sentence - which is also why
   the [hidden] guard below is required rather than decorative: [hidden] is only
   a UA-stylesheet rule, so our own display: value would outrank it and the
   button would never hide. Same reason as button.sm.lk-tipbtn[hidden]. */
button.comment-offer {
    display: block;
    width: auto;
    margin: 9px 0 0;
    padding: 5px 10px;
    background: rgba(91,140,255,.14);
    color: var(--accent);
    border: 1px solid rgba(91,140,255,.35);
    border-radius: 7px;
    font-size: 12px;
    font-weight: 500;
}

button.comment-offer:hover:not(:disabled) { background: rgba(91,140,255,.24); }

button.comment-offer[hidden] { display: none; }

/* Reading aloud. The speaker button sits in the top-right corner of the
   explanation panel and the text flows around it. It overrides the bare
   `button` rule at the top of this file (full width, accent fill, 20px top
   margin) like button.comment-offer does, and needs its own [hidden] rule for
   the same reason: it sets display. chess.js toggles .is-on (speaking or
   about to) and .is-wait (asked, nothing audible yet). */
button.comment-speak {
    float: right;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    width: auto;
    margin: -2px -4px 4px 10px;
    padding: 3px 10px 3px 8px;
    background: transparent;
    color: var(--muted);
    border: 1px solid var(--line);
    border-radius: 20px;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.4;
    touch-action: manipulation;
}

button.comment-speak[hidden] { display: none; }

button.comment-speak:hover:not(:disabled) { background: rgba(91,140,255,.12); color: var(--accent); }

button.comment-speak svg { width: 14px; height: 14px; flex: none; }
button.comment-speak .spk-body { fill: currentColor; }
button.comment-speak .spk-wave {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
}

button.comment-speak.is-on {
    color: var(--accent);
    border-color: rgba(91,140,255,.45);
    background: rgba(91,140,255,.12);
}

button.comment-speak.is-on .spk-wave { animation: chess-spk-wave 1s ease-in-out infinite; }
button.comment-speak.is-wait .spk-wave { animation: none; opacity: .3; }
button.comment-speak.is-wait .spk-body { animation: chess-spk-wave 1s ease-in-out infinite; }

@keyframes chess-spk-wave { 50% { opacity: .3; } }

.chess-panel .speak-note { margin-top: 6px; }
.chess-panel .speak-note.is-err { color: var(--err); }

@media (prefers-reduced-motion: reduce) {
    button.comment-speak.is-on .spk-wave,
    button.comment-speak.is-wait .spk-body { animation: none; }
}

/* AI loading look for the commentary panel. chess.js only toggles classes on
   #comment: .is-loading until the first word arrives, .is-streaming while words
   arrive. Everything else is drawn here (the CSP forbids inline styles).

   The dots and the sweep fade in after 150 ms, so a cached answer that comes
   back at once does not flash a loader. */
.chess-comment { position: relative; overflow: hidden; }

.comment-loader { display: none; }

.chess-comment.is-loading .comment-loader {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-right: 8px;
    vertical-align: middle;
    animation: chess-ai-in .2s .15s both;
}

.comment-loader i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    opacity: .25;
    animation: chess-ai-dot 1.1s ease-in-out infinite;
}
.comment-loader i:nth-child(2) { animation-delay: .15s; }
.comment-loader i:nth-child(3) { animation-delay: .3s; }

.chess-comment.is-loading .comment-kicker {
    background: rgba(91,140,255,.16);
    color: var(--accent);
}

.comment-body { transition: opacity .25s; }
.chess-comment.is-loading .comment-body { opacity: .5; }

/* An indeterminate sweep along the bottom edge. */
.chess-comment.is-loading::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 40%;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    animation: chess-ai-in .2s .15s both, chess-ai-sweep 1.3s ease-in-out infinite;
}

/* A caret while words are still arriving, as on the Lernkarten page. */
.chess-comment.is-streaming .comment-body::after {
    content: "\258D";
    margin-left: 1px;
    color: var(--accent);
    animation: chess-ai-blink 1s steps(2, start) infinite;
}

@keyframes chess-ai-in { from { opacity: 0; } to { opacity: 1; } }

@keyframes chess-ai-dot {
    0%, 80%, 100% { opacity: .25; transform: translateY(0); }
    40%           { opacity: 1;   transform: translateY(-3px); }
}

@keyframes chess-ai-sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(250%); }
}

@keyframes chess-ai-blink { to { visibility: hidden; } }

@media (prefers-reduced-motion: reduce) {
    .comment-loader i { animation: none; opacity: .7; }
    .chess-comment.is-loading::after { animation: none; width: 100%; opacity: .5; }
    .chess-comment.is-streaming .comment-body::after { animation: none; }
}

/* --- side panel --- */

.chess-side { display: flex; flex-direction: column; gap: 16px; }
.chess-panel { padding: 18px; }
.chess-panel label:first-of-type { margin-top: 0; }

.chess-btns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

.chess-btns button { width: 100%; margin: 0; }

.chess-progress { margin-top: 10px; }

.chess-progress-bar {
    height: 6px;
    border-radius: 3px;
    background: #10131a;
    border: 1px solid var(--line);
    overflow: hidden;
}

.chess-progress-bar > div {
    height: 100%;
    width: 0;
    background: var(--accent);
    transition: width .25s ease;
}

.movelist {
    list-style: none;
    max-height: 190px;
    overflow-y: auto;
    counter-reset: mv;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
}

.movelist li {
    display: grid;
    grid-template-columns: 28px 1fr 1fr;
    gap: 6px;
    padding: 2px 0;
}

.movelist li::before {
    counter-increment: mv;
    content: counter(mv) ".";
    color: var(--muted);
}

.piece-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

@media (max-width: 860px) {
    .chess-layout { grid-template-columns: minmax(0, 1fr); }
    .board-frame { max-width: none; }
    .chess-wrap { padding: 20px 14px; }
    .movelist { max-height: 140px; }
}

/* --- conversations (convo.php) --- every rule scoped to .cv-* --- */

.cv-wrap { padding-bottom: 48px; }
.cv-wrap .panel { margin-top: 16px; }

.cv-status {
    font-size: 13px;
    color: var(--muted);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
}
.cv-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--muted);
    margin-right: 4px;
    flex: none;
}
.cv-status.on .cv-dot  { background: var(--ok); }
.cv-status.off .cv-dot { background: var(--err); }
.cv-status.off { color: var(--err); }

.cv-tight { margin-top: 2px; margin-bottom: 12px; }

.cv-speakers { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }

.cv-spk {
    display: grid;
    grid-template-columns: 30px minmax(0, 1fr) minmax(0, 1.4fr) 38px 34px;
    gap: 8px;
    align-items: center;
}
.cv-letter {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: rgba(91,140,255,.16);
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 14px;
}
.cv-spk input[type="text"].cv-name { padding: 9px 11px; font-size: 14px; }
.cv-spk select.cv-voice { padding: 9px 10px; font-size: 14px; }
button.sm.cv-play { padding: 7px 0; width: 38px; text-align: center; }
button.sm.cv-play.on { background: var(--accent); color: #fff; }
button.sm.cv-rm { padding: 7px 0; width: 34px; }

.cv-script { min-height: 260px; font-size: 15px; }
.cv-small  { min-height: 76px; font-size: 15px; font-family: inherit; }

button.link.cv-linkbtn {
    width: auto;
    margin: 0;
    padding: 0;
    background: none;
    color: var(--accent);
    font-size: inherit;
}
button.link.cv-linkbtn:hover { text-decoration: underline; background: none; }
button.link.cv-del { color: var(--muted); }

.cv-opts {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.cv-actions {
    display: flex;
    gap: 10px;
    align-items: stretch;
    margin-top: 18px;
}
.cv-actions button.sm { flex: 0 0 auto; }
.cv-actions button.cv-go { flex: 1 1 auto; margin-top: 0; }

.cv-wrap .err { margin-top: 14px; }

.cv-player { width: 100%; margin-top: 12px; }
.cv-dl { margin-top: 10px; font-size: 14px; color: var(--muted); }
.cv-dl a { color: var(--accent); }

.cv-acts { white-space: nowrap; text-align: right; }

.cv-voices { margin-top: 10px; }
.cv-vrow {
    display: grid;
    grid-template-columns: 38px minmax(0, 1fr) auto;
    gap: 10px;
    align-items: center;
    padding: 9px 0;
    border-bottom: 1px solid var(--line);
}
.cv-vrow:last-child { border-bottom: 0; }
.cv-vname { font-size: 14px; font-weight: 500; }
.cv-vmeta { font-weight: 400; font-size: 12px; margin-left: 8px; }
.cv-vdesc {
    font-size: 12.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cv-new { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 14px; }
.cv-new summary { cursor: pointer; font-size: 14px; color: var(--accent); }
.cv-new[open] summary { margin-bottom: 4px; }
.cv-file { display: block; margin-top: 8px; font-size: 13px; color: var(--muted); }
.cv-new .note.cv-bad { color: var(--err); }

@media (max-width: 640px) {
    .cv-spk { grid-template-columns: 30px minmax(0, 1fr) 38px 34px; }
    .cv-spk select.cv-voice { grid-column: 2 / 5; grid-row: 2; }
    .cv-opts { grid-template-columns: 1fr; }
    .cv-actions { flex-direction: column; }
    .cv-acts { white-space: normal; }
}

/* --- GPU section (index) and GPU badges on tool cards ---
   Same tile language as the tool cards above: .tool box, .tname / .tdesc / .soon type. */
.tgpu { color: var(--accent); }

h2.sect {
    margin: 30px 0 12px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--muted);
}

.gputile { gap: 0; }
.gputile.wide { grid-column: span 2; }
@media (max-width: 560px) { .gputile.wide { grid-column: auto; } }

.gpuhead {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 12px;
}
.gpuhead .tname { margin-bottom: 0; }

.membar {
    display: flex;
    height: 6px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--line);
    margin-bottom: 14px;
}
.membar i { display: block; height: 100%; }
.membar i.always { background: #6b7385; }
.membar i.demand { background: var(--accent); }
/* Held by an app outside the tools (Unsloth Studio, Ollama): hatched, since the tools cannot free it. */
.membar i.other { background: repeating-linear-gradient(135deg, var(--accent) 0 3px, transparent 3px 6px); }

.gpulist { list-style: none; margin: 0 0 12px; padding: 0; }
.gpulist li {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 4px 0;
    font-size: 13px;
}
.gpulist .gname { flex: 0 0 9.5em; }
.gpulist .gstate { color: var(--muted); }

.dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: transparent; border: 1px solid #4a5163; }
.dot.always, .dot.on, .dot.busy { border: 0; }
.dot.always { background: #6b7385; }
.dot.on { background: var(--accent); }
.dot.busy { background: var(--ok); box-shadow: 0 0 0 3px rgba(76,207,127,.18); }

.gpufoot {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-top: auto;
}
.gpufoot .soon { margin-top: 0; }
.gpufoot button.link { font-size: 12px; color: var(--accent); }
.gpufoot button.link:hover { color: var(--text); }
.gputile > .tdesc { margin-bottom: 0; }
.gputile > .soon { margin-top: auto; padding-top: 10px; }
.gpumsg { margin-top: 10px; font-size: 12px; color: var(--ok); }
.gpumsg.err { color: var(--err); }
.vmtile .tdesc { margin-bottom: 0; }
.vmtile .gpufoot { margin-top: auto; padding-top: 10px; }
.vmtile .gpufoot button.link, .vmtile .gpuhead .tdesc { white-space: nowrap; }
.vmtile.vmon { border-color: var(--accent); box-shadow: inset 0 0 0 1px rgba(91,140,255,.35); }

/* --- Talk (talk.php) --- every rule scoped to .tk-* -------------------------
   One big "call" card: an orb that swells with the AI's voice (--ai) and a ring
   that answers your microphone (--mic), both set per frame by talk.js. */

.tk-wrap { padding-bottom: 48px; }
.tk-wrap .panel { margin-top: 16px; }
.tk-wrap select, .tk-wrap textarea { width: 100%; }

.tk-stage {
    margin-top: 16px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 18px;
    padding: 34px 20px 26px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.tk-orb {
    --ai: 0;
    --mic: 0;
    position: relative;
    width: 200px;
    height: 200px;
    margin: 4px 0 20px;
}
.tk-core, .tk-glow, .tk-ring { position: absolute; border-radius: 50%; }
.tk-core {
    inset: 40px;
    background: radial-gradient(circle at 35% 30%, #a9c1ff 0%, #5b8cff 45%, #2a45b0 100%);
    box-shadow: 0 0 40px rgba(91,140,255,.35);
    transform: scale(calc(1 + var(--ai) * .2));
    transition: filter .4s;
}
.tk-glow {
    inset: 26px;
    background: radial-gradient(circle, rgba(91,140,255,.5), rgba(91,140,255,0) 70%);
    transform: scale(calc(1 + var(--ai) * .85));
    opacity: calc(.3 + var(--ai) * .7);
}
.tk-ring {
    inset: 20px;
    border: 2px solid rgba(76,207,127, calc(var(--mic) * .9));
    transform: scale(calc(1.02 + var(--mic) * .3));
}

.tk-stage[data-state="idle"] .tk-core,
.tk-stage[data-state="ended"] .tk-core,
.tk-stage[data-state="offline"] .tk-core { filter: saturate(.5) brightness(.75); }
.tk-stage[data-state="ringing"] .tk-glow,
.tk-stage[data-state="connecting"] .tk-glow { animation: tk-pulse 1.6s ease-in-out infinite; }

@keyframes tk-pulse {
    0%, 100% { transform: scale(1);    opacity: .3; }
    50%      { transform: scale(1.35); opacity: .85; }
}

.tk-who   { font-size: 14px; color: var(--muted); min-height: 1.5em; }
.tk-state { font-size: 22px; font-weight: 600; margin-top: 2px; }
.tk-timer { font-size: 14px; color: var(--muted); min-height: 1.5em; font-variant-numeric: tabular-nums; }

.tk-pbar {
    width: 60%;
    max-width: 260px;
    height: 4px;
    margin-top: 10px;
    background: var(--line);
    border-radius: 2px;
    overflow: hidden;
}
.tk-pbar[hidden] { display: none; }
.tk-pbar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .25s; }
.tk-pbar.indet i { width: 35%; animation: tk-sweep 1.2s ease-in-out infinite; }
@keyframes tk-sweep { from { transform: translateX(-100%); } to { transform: translateX(300%); } }

.tk-caption {
    min-height: 3em;
    max-width: 440px;
    margin: 14px auto 0;
    font-size: 16px;
    line-height: 1.45;
    white-space: pre-line;
}
.tk-caption[hidden] { display: none; }

.tk-controls { display: flex; gap: 22px; margin-top: 20px; align-items: flex-start; justify-content: center; }

button.tk-btn {
    width: auto;
    margin: 0;
    padding: 0;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    background: none;
    color: var(--muted);
    font-size: 13px;
}
button.tk-btn:hover:not(:disabled) { background: none; color: var(--text); }
button.tk-btn[hidden] { display: none; }
.tk-btn svg {
    width: 64px;
    height: 64px;
    padding: 18px;
    border-radius: 50%;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: transform .2s, background .2s;
}
.tk-call svg { background: var(--ok); fill: #fff; stroke: none; }
.tk-stage.live .tk-call svg { background: var(--err); transform: rotate(135deg); }
.tk-mute svg { background: #262b36; color: var(--text); }
.tk-mute.on svg { background: var(--text); color: var(--bg); }
.tk-mute .tk-slash { display: none; }
.tk-mute.on .tk-slash { display: inline; }

.tk-err { margin-top: 16px; max-width: 440px; }
.tk-prompt { min-height: 96px; margin-top: 8px; font-size: 15px; font-family: inherit; }
.tk-tight { margin-top: 6px; }
label.tk-check { display: flex; gap: 8px; align-items: center; margin-top: 16px; color: var(--text); font-size: 14px; cursor: pointer; }
.tk-list { margin: 8px 0 0 18px; font-size: 14px; color: var(--muted); }
.tk-list li { margin: 4px 0; }
.tk-transcript p { margin: 8px 0; font-size: 15px; }
.tk-debug { font-variant-numeric: tabular-nums; min-height: 1.2em; }

@media (prefers-reduced-motion: reduce) {
    .tk-stage .tk-glow { animation: none !important; }
    .tk-core { transform: none; }
}

/* Loudspeaker switch (talk.js shows it only where the browser offers the routes). */
.tk-spk svg { background: #262b36; color: var(--text); }
.tk-spk.on svg { background: var(--text); color: var(--bg); }
.tk-spk .tk-wave { opacity: .35; }
.tk-spk.on .tk-wave { opacity: 1; }
