/* Synfonos — shared component library ("Odak"), W-03.
 *
 * WHAT THIS FILE IS. Ten pieces the 2026-09-04 design-lane pass (nine lane reports,
 * synthesised in docs/design/odak-plan/BUILD-PLAN.md) found missing or partial against
 * the component/state-card canvases — see docs/design/odak-plan/states.md §2-3.
 * Citations below are `DUR:<line>` for `Synfonos - Durumlar ve bilesenler.dc.html` and
 * `AYAR:<line>` for `Synfonos - Ayarlar tam.dc.html`, both under
 * synfonos-desktop-audio-design/project/ (gitignored, read directly for this file rather
 * than trusted from the report's summary). Waves 1-5 draw their markup from the classes
 * below instead of re-deriving the same geometry per screen (BUILD-PLAN W-11/W-14/W-15/
 * W-17). Nothing here is a new design decision except where a comment says so and cites
 * what forced it — the same discipline every other stylesheet in this directory holds
 * itself to.
 *
 * SELF-CONTAINED ON PURPOSE. `app.css` sets `* { box-sizing: content-box }`;
 * `firstrun.css`/`send.css` set `* { box-sizing: border-box }`. This file does not yet
 * know which pages will link it, so every sized box below carries its own explicit
 * `box-sizing` rather than trusting an ambient default. Same reasoning for
 * `@keyframes synfonosSpin` and the reduced-motion block below: `evoSpin` and a blanket
 * `* { animation: none; transition: none }` already exist in app.css/send.css/
 * firstrun.css, each written locally rather than shared — this file repeats the pattern
 * under its own keyframe name instead of assuming one of the other three is also on the
 * page (two same-named `@keyframes` on one page is harmless; a spinner with no keyframe
 * at all because the one page that had it wasn't linked is not).
 *
 * TOKENS FIRST, HEXES ONLY WHERE THERE IS NO TOKEN — the rule app.css's panel section
 * already states for itself (app.css:888). Every fill below that has no named token
 * (three of the notification card's four severity tints) is a literal `rgba()` with the
 * DUR line it was read off, exactly as `.outbanner.is-crit` (app.css:322) already
 * carries a literal because no `--critical-fill` token exists either. `--serious-fill`
 * DOES exist (tokens.css, restored alongside `--serious` in this same round) because
 * this file is its first consumer and the alpha is worth naming once.
 *
 * FOCUS RING: 2px solid var(--focus), 2px offset (var(--focus-offset)), on every
 * interactive rule below, no exceptions. DUR's own component sheet draws a 3px offset
 * throughout Artboard 2 in isolation; tokens.css:129-132 already calls that "the
 * out-of-context value" against the 2px the shipped screens use everywhere else, and
 * this file's brief asked for 2px uniformly ("like the 20 existing rules") — so the
 * sheet's 3px is not reproduced anywhere here.
 *
 * KEYBOARD: every interactive piece below is a native `<button>` — Enter/Space activate
 * for free, exactly how `.switch`/`.segment` (app.css:1067/1115) already work, and no
 * custom keydown code exists in this file for basic activation. Where DUR/AYAR draw
 * several peers as one set (a radio group, the swatch grid) `components.js` adds the
 * same arrow-key roving-focus enhancement `settings.js`'s `.rail-list` handler already
 * carries (ArrowUp/Down/Home/End move AND select; every item stays individually
 * Tab-reachable — settings.js:148-168's own reasoning, reused rather than reinvented).
 *
 * COLOUR IS NEVER THE ONLY CARRIER. Every state below that DUR distinguishes by colour
 * also changes shape, icon or text: the checkbox's check glyph appears/disappears (not
 * fades), the reconnecting channel card gets a spinning icon and a retry badge, the
 * disabled channel card gets its own badge text, the selected swatch gets a check glyph
 * AND a two-ring halo (see §10) rather than a border-colour change alone.
 */

@keyframes synfonosSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ===== 1 · warning button — DUR:252-258 ===================================
 *
 * The one button variant `.btn` (app.css:591) doesn't have. Same 44px pill box model as
 * every other button on the sheet, 12.5/600 (the sheet's own value — app.css's `.btn` is
 * WB's in-context 13/500 delta and stays that way), amber escalating through the three
 * --warn-fill tokens srcchip/outbanner already use — this is their fourth consumer, not
 * a new colour.
 *
 * A STRUCTURAL TWIN OF `.btn`, NOT A MODIFIER. `.btn` is written for the Ana ekran's
 * `.btn-row` (`flex:1`) and is not guaranteed to share a page with this file —
 * firstrun.html/send.html load neither app.css nor `.btn`. `.btn-warning` therefore
 * carries its own complete box model and stands alone as `<button class="btn-warning">`;
 * on a page that also has `.btn`, `class="btn btn-warning"` still resolves correctly
 * because every property below is set explicitly, not inherited.
 *
 * Markup: <button type="button" class="btn-warning">Bluetooth ile bağlan</button>
 *         <button type="button" class="btn-warning" disabled>…</button>
 */
.btn-warning {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: var(--touch);
  padding: 0 20px;
  border-radius: var(--r-pill);
  border: 1px solid var(--warning);
  background: var(--warn-fill);
  color: var(--ink-1);
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 600;
  transition: background var(--dur-enter) var(--ease);
}
.btn-warning:hover { background: var(--warn-fill-hover); }
.btn-warning:active { background: var(--warn-fill-active); }
.btn-warning:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }
.btn-warning[disabled] {
  border-color: var(--line);
  /* DUR:257 — 6%. No named --warn-fill-disabled token (only rest/hover/active exist),
   * so this one step is a literal, on the same precedent .outbanner.is-crit already
   * set for a fill with no token behind it. */
  background: rgba(250, 178, 25, 0.06);
  color: var(--ink-3);
  cursor: not-allowed;
}

/* ===== 2 · checkbox — DUR:288-291 ==========================================
 *
 * 22x22, r8 (var(--r-sm)). A native <button role="checkbox"> — aria-checked IS the
 * paint, exactly how `.switch` (app.css:1067) already works, and components.js's
 * renderCheckbox(el, checked) is the one place that writes it (same shape as
 * settings.js:41's renderSwitch — a pure setter, never the click handler; the click
 * handler is always the consumer's, because what a checkbox click actually DOES is
 * feature-specific — see settings.js:335-349 for the pattern this follows).
 *
 * DISABLED KEEPS THE OFF GEOMETRY AND FADES THE INK — --line border, --sunken fill,
 * cursor:not-allowed, no opacity anywhere. The same rule DUR:285/329/349-353 already
 * set for switch/segment/slider and this file repeats rather than re-derives.
 *
 * The check glyph is a required inline <svg> child, not a background-image: its stroke
 * has to be var(--ink-on-fill), which flips in dark theme (tokens.css:148) — a baked
 * data-URI hex (the pattern app.css:566/751 use for icons that never flip) would go
 * invisible the moment the checkbox's own ON fill (--ink-1) flips too.
 *
 * Markup: <button type="button" class="checkbox" role="checkbox" aria-checked="false"
 *                  aria-label="Bildirimleri göster">
 *           <svg width="15" height="15" viewBox="0 0 24 24" fill="none"
 *                stroke="currentColor" stroke-width="2.8" stroke-linecap="round"
 *                stroke-linejoin="round" aria-hidden="true" focusable="false">
 *             <path d="M4.8 12.6 9.6 17.4 19.2 6.8"></path>
 *           </svg>
 *         </button>
 * aria-label repeats the row's own visible label text — settings.html:234's `#guardsw`
 * precedent, not aria-labelledby: this codebase's one working convention for a labelled
 * non-native control.
 */
.checkbox {
  box-sizing: border-box;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: var(--r-sm);
  border: 1.5px solid var(--ink-2);
  background: transparent;
  color: var(--ink-on-fill);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--dur-enter) var(--ease),
              border-color var(--dur-enter) var(--ease);
}
.checkbox svg { display: none; flex: none; }
.checkbox[aria-checked='true'] { border-color: transparent; background: var(--ink-1); }
.checkbox[aria-checked='true'] svg { display: block; }
.checkbox:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }
.checkbox[disabled] {
  border-color: var(--line);
  background: var(--sunken);
  cursor: not-allowed;
}
.checkbox[disabled] svg { display: none; }

/* ===== 3 · radio — DUR:292-294 =============================================
 *
 * 22x22, r999. Same native-<button role="radio"> idiom as the checkbox above; the ON
 * dot is a plain div (a background-colour circle needs no theme-aware stroke trick).
 * A11Y-APG: buttons that form one choice belong in `role="radiogroup"` with its own
 * aria-label — components.js's initRovingGroup() adds the arrow-key layer described in
 * the file header; every radio keeps its own tabindex (DUR/AYAR draw every swatch and
 * every radio individually focusable, never a stripped roving-tabindex-only set).
 *
 * Markup: <div role="radiogroup" aria-label="Gecikme profili">
 *           <button type="button" class="radio" role="radio" aria-checked="true"
 *                   aria-label="Dengeli"><span class="radio-dot"></span></button>
 *           …
 *         </div>
 */
.radio {
  box-sizing: border-box;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--ink-2);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color var(--dur-enter) var(--ease),
              background var(--dur-enter) var(--ease);
}
.radio-dot {
  width: 11px;
  height: 11px;
  border-radius: var(--r-pill);
  background: var(--ink-1);
  display: none;
}
.radio[aria-checked='true'] { border-color: var(--ink-1); }
.radio[aria-checked='true'] .radio-dot { display: block; }
.radio:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }
.radio[disabled] {
  border-color: var(--line);
  background: var(--sunken);
  cursor: not-allowed;
}
.radio[disabled] .radio-dot { background: var(--ink-3); }

/* ===== 4 · transport badge — DUR:358-370 ===================================
 *
 * Two rests (Yerel/Wi-Fi, sunken pill) and one elevated variant (>150ms, DUR:366-369's
 * amber pill — the same threshold and the same --warn-fill/--warning pair the srcchip
 * degraded-latency pill (app.css:738-759) already carries; that pill is scoped inside
 * `.srcchip`, this is the free-standing version for anywhere else a transport needs
 * naming). Not a control — no focus-visible rule, because DUR never draws it as one
 * (no tabindex, no role, on any of the three swatches) and this file adds no
 * interactivity nothing in the canvas gives it.
 *
 * Markup: <span class="transport-badge">Yerel<span class="transport-badge-ms">4 ms</span></span>
 *         <span class="transport-badge is-elevated">
 *           <svg class="transport-badge-icon" …>…</svg>Bluetooth
 *           <span class="transport-badge-ms">248 ms</span>
 *         </span>
 */
.transport-badge {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 28px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  background: var(--sunken);
  color: var(--ink-1);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
}
.transport-badge.is-elevated {
  gap: 6px;
  background: var(--warn-fill);
  border: 1px solid var(--warning);
}
.transport-badge-icon { flex: none; color: currentColor; }
.transport-badge-ms {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--ink-1);
}

/* ===== 5 · tooltip — DUR:410 ================================================
 *
 * Ink-1 bubble, r-md, 12.5/500, ink-on-fill text — DUR draws exactly one example
 * ("Ölçülen gecikme · son 30 sn") with no trigger encoded, so the show/hide mechanics
 * below are this file's own, built to the accessible pattern DUR's own rule (DUR:600,
 * "renk hiçbir yerde tek başına anlam taşımaz") implies but doesn't draw: a real
 * `aria-describedby` link, not a CSS-only `content: attr()` bubble a screen reader may
 * never announce. components.js's initTooltip(trigger, text) builds the bubble, wires
 * aria-describedby, and shows it on hover/focus/Escape — see that file's header.
 *
 * Markup — `.tooltip-trigger` is a plain WRAPPER, never the focusable element itself: a
 * <div> tooltip bubble is flow content and a <button>'s content model (phrasing content
 * only, HTML §4.10.6) does not permit a flow-content descendant, so the bubble cannot
 * be a child of the button it describes. initTooltip(triggerEl, text) wraps whatever
 * focusable element it is given in a `.tooltip-trigger` span and appends the bubble as
 * that span's second child, a sibling of the real trigger, not inside it:
 *   before: <button type="button" id="latinfo" aria-label="Gecikme bilgisi">…</button>
 *   after:  <span class="tooltip-trigger">
 *             <button type="button" id="latinfo" aria-label="Gecikme bilgisi"
 *                     aria-describedby="…">…</button>
 *             <div class="tooltip" role="tooltip" id="…" hidden>Ölçülen gecikme · son 30 sn</div>
 *           </span>
 */
.tooltip-trigger { position: relative; display: inline-flex; }
.tooltip {
  position: absolute;
  z-index: 6;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  max-width: 240px;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--ink-1);
  color: var(--ink-on-fill);
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.4;
  white-space: nowrap;
  box-shadow: var(--elev-pop);
  pointer-events: none;
}
.tooltip[hidden] { display: none; }

/* ===== 6 · shortcut badge — DUR:412-413 ====================================
 *
 * A key-cap pill (raised fill, 1px line, mono) plus the plain-text action it performs
 * beside it — DUR never draws the key alone, always paired with its own description
 * ("Boşluk" + "sustur"), so `.shortcut-row` carries both rather than leaving the pairing
 * to whoever assembles it per screen.
 *
 * Markup: <span class="shortcut-row">
 *           <kbd class="shortcut-badge">Boşluk</kbd>
 *           <span class="shortcut-desc">sustur</span>
 *         </span>
 */
.shortcut-row { display: inline-flex; align-items: center; gap: 6px; }
.shortcut-badge {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 28px;
  padding: 0 8px;
  border-radius: var(--r-sm);
  background: var(--raised);
  border: 1px solid var(--line);
  color: var(--ink-1);
  font-family: var(--font-mono);
  font-style: normal;
  font-size: 12px;
  font-weight: 500;
}
.shortcut-desc { font-family: var(--font-ui); font-size: 12px; font-weight: 400; color: var(--ink-2); }

/* ===== 7 · notification card — DUR:480-497, "BİLDİRİM / UYARI KARTI · DÖRT DURUM" ====
 *
 * Four severity fills, icon + one line of text, reproduced exactly as drawn — DUR gives
 * this card no exit action (unlike the ten-card state/error family in states.md §2,
 * which W-14 builds and which DOES carry exactly one exit action each); this component
 * is the smaller, message-only sibling, not a shrunk version of that one. Three of the
 * four fills have no named token and are literals cited to their line, same as
 * `.outbanner.is-crit`; the third fill IS --serious-fill, restored in tokens.css
 * alongside --serious specifically because this card is its first real consumer.
 *
 * Markup: <div class="notif-card notif-card--warning" role="status">
 *           <svg class="notif-card-icon" …>…</svg>
 *           <p class="notif-card-text">Bluetooth kanalı 248 ms gecikiyor</p>
 *         </div>
 * Modifiers: notif-card--good | notif-card--warning | notif-card--serious | notif-card--critical
 * `role="status"` (not alert) — none of the four DUR examples describes an interruption
 * severe enough for assertive announcement; a caller drawing the critical tier for an
 * actual feedback-detected event should override to role="alert" itself.
 */
.notif-card {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: var(--r-md);
  padding: 12px 14px;
}
.notif-card-icon { flex: none; color: var(--ink-1); }
.notif-card-text { margin: 0; font-size: 12.5px; font-weight: 500; color: var(--ink-1); }
.notif-card--good {
  background: rgba(12, 163, 12, 0.1); /* DUR:482 — no --good-fill token */
  border: 1px solid var(--good);
}
.notif-card--warning { background: var(--warn-fill); border: 1px solid var(--warning); }
.notif-card--serious { background: var(--serious-fill); border: 1px solid var(--serious); }
.notif-card--critical {
  background: rgba(208, 59, 59, 0.14); /* DUR:494 — same literal .outbanner.is-crit already carries */
  border: 1px solid var(--critical);
}

/* ===== 8 · empty state — DUR:299-305 =======================================
 *
 * THIS IS THE GENERAL-PURPOSE LIBRARY VERSION, DELIBERATELY NAMED `.empty-state`, NOT
 * `.empty`. `.empty` (app.css:495-522/860-872) already exists, is byte-faithful to this
 * same DUR geometry, and is wired live on index.html's `#emptycard` (D51/A2-4/A7-5/
 * FP-05) — reusing that name here would shadow a component another lane owns and that
 * already ships. `.empty-state` is the same box for every OTHER screen this one has not
 * reached yet, self-contained the way §1's `.btn-warning` is self-contained against
 * `.btn`. Icon, title and body are the canvas's own generic placeholder copy/glyph —
 * every real instance supplies its own.
 *
 * Markup: <div class="empty-state">
 *           <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
 *                stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
 *                aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.4"/></svg>
 *           <h2>Henüz kaynak yok</h2>
 *           <p>Bir cihaz bağladığında halka canlanır.</p>
 *           <button type="button" class="empty-state-action">Cihaz ekle</button>
 *         </div>
 */
.empty-state {
  box-sizing: border-box;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  color: var(--ink-2);
}
.empty-state svg { width: 24px; height: 24px; color: var(--ink-2); }
.empty-state h2 { margin: 10px 0 0; font-size: var(--size-body); font-weight: 600; color: var(--ink-1); }
.empty-state p { margin: 4px 0 0; font-size: var(--size-sub); font-weight: 400; color: var(--ink-2); }
.empty-state-action {
  box-sizing: border-box;
  margin-top: 12px;
  height: var(--touch);
  padding: 0 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--ink-1);
  background: transparent;
  color: var(--ink-1);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: background var(--dur-enter) var(--ease);
}
.empty-state-action:hover { background: var(--sunken); }
.empty-state-action:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }

/* ===== 9 · channel card — SHELL, DUR:422-451 — live wiring is W-15 =========
 *
 * SHELL AND STATES ONLY. This section draws the five states DUR's "KANAL KARTI" row
 * shows side by side; it does not read a live `StreamDto`, does not run the 5-second
 * retry timer card #2's brief describes, and does not decide when a device is actually
 * disabled versus reconnecting — none of that data exists on the wire yet outside
 * `Dormitory`/`Dormant` (states.md §2, root-cause paragraph; W-02 exposes it). What this
 * gives W-15 is the five paint states and the one JS helper that switches between them
 * (components.js's setChancardState).
 *
 * No `.chancard`/`.streamcard` class family existed anywhere before this file
 * (states.md §3 confirms the targeted grep came back empty) — this is new, not a
 * rename.
 *
 * Base = normal (1px line border). Height 64, r-lg, flex row, 8px colour dot,
 * name+meta stack, then EITHER a level% reading OR a status row, then a badge pill.
 * The whole card is a native <button> — Enter/Space select it for free, and "selected"
 * reuses `.btn[aria-pressed='true']`'s own citation (app.css:614: "the filled variant
 * of the button it already is") rather than inventing a second toggle idiom.
 *
 * Markup (normal):
 *   <button type="button" class="chancard">
 *     <span class="chancard-dot" style="background:var(--dev-1)"></span>
 *     <span class="chancard-body">
 *       <span class="chancard-name">Bu bilgisayar</span>
 *       <span class="chancard-meta">Yerel · 4 ms</span>
 *     </span>
 *     <span class="chancard-level">%72</span>
 *     <span class="chancard-badge">normal</span>
 *   </button>
 * `setChancardState(el, 'selected' | 'muted' | 'reconnecting' | 'disabled' | 'normal', meta)`
 * (components.js) swaps the modifier class, the badge text, the trailing slot (level% /
 * mute icon / spinner+retry / disabled badge) and — for 'disabled' — aria-disabled plus
 * tabindex="-1", the same "truly inert, not just grey" pairing settings.js:142-143
 * already uses for a rail item nothing routes to.
 */
.chancard {
  box-sizing: border-box;
  width: 100%;
  height: 64px;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  text-align: left;
  color: var(--ink-1);
}
.chancard:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }
.chancard-dot { width: 8px; height: 8px; border-radius: var(--r-pill); flex: none; }
.chancard-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.chancard-name { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chancard-meta {
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 400;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chancard-level {
  flex: none;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 12.5px;
  color: var(--ink-2);
}
.chancard-badge {
  flex: none;
  height: 22px;
  padding: 0 8px;
  border-radius: var(--r-pill);
  background: var(--sunken);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-2);
}
.chancard-icon { flex: none; color: var(--ink-1); }
.chancard-spin { flex: none; color: var(--ink-2); animation: synfonosSpin 1.1s linear infinite; }
.chancard-status { display: flex; align-items: center; gap: 5px; margin-top: 2px; color: var(--critical); }
.chancard-status svg { flex: none; }
.chancard-status span { font-size: 11.5px; font-weight: 500; }

/* seçili — DUR:429-434: 2px ink-1 border (1px less horizontal padding, box-border
 * absorbs the extra 1px each side exactly as DUR:429 draws it), black "seçili" badge. */
.chancard.is-selected { border: 2px solid var(--ink-1); padding: 0 13px; }
.chancard.is-selected .chancard-badge { background: var(--ink-1); color: var(--ink-on-fill); }

/* susturulmuş — DUR:435-440: dimmed name+meta, mic-off icon replaces the level reading,
 * grey "sessiz" badge. Name dims to ink-2 (not opacity) — the disabled-state rule. */
.chancard.is-muted .chancard-name,
.chancard.is-muted .chancard-meta { color: var(--ink-2); }

/* yeniden bağlanıyor — DUR:441-446: red 1px border, red status row with a warning icon
 * ("bağlantı koptu"), a spinning retry icon, and a badge. DUR's badge just says "yeniden";
 * W-02 puts the actual attempt count on the wire, so the badge here carries a mono slot
 * for it ("deneme N/M") rather than the static word — setChancardState fills the digits,
 * this file only reserves where they render. */
.chancard.is-reconnecting { border-color: var(--critical); }
.chancard-badge--retry {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}

/* devre dışı — DUR:447-451: aria-disabled, sunken fill, ink-3 name/meta, raised badge
 * with a line border reading "devre dışı"/"eşleşmemiş". Not native [disabled]: the same
 * `.tab-locked` reasoning (app.css:210-216) — a truly-inert control is aria-disabled +
 * tabindex="-1" (settings.js:142-143's pairing), not the browser's own disabled attribute
 * for a control that stays legible to a screen reader as "here, currently unusable". */
.chancard[aria-disabled='true'] {
  background: var(--sunken);
  border-color: var(--line);
  cursor: not-allowed;
}
.chancard[aria-disabled='true'] .chancard-name,
.chancard[aria-disabled='true'] .chancard-meta { color: var(--ink-3); }
/* No CSS rule dims `.chancard-dot` here on purpose: its colour is per-device, set as an
 * inline `style="background:var(--dev-N)"` (the same pattern `#chanlevel`'s --fill-color
 * custom property already uses, app.css:589), and an inline style outranks any class
 * selector this file could write without reaching for `!important`. setChancardState
 * (components.js) sets `dot.style.background = 'var(--ink-3)'` directly when it renders
 * the disabled state instead — DUR:448 draws that swatch grey too, matching the name. */
.chancard[aria-disabled='true'] .chancard-badge {
  background: var(--raised);
  border: 1px solid var(--line);
  color: var(--ink-3);
}

/* ===== 10 · device-colour swatch grid — DUR:374-387 geometry + AYAR:834-843 a11y ====
 *
 * TWO CANVASES, ONE COMPONENT, BOTH CITED BECAUSE NEITHER ALONE IS ENOUGH. DUR's
 * component-sheet swatch (374-387) is presentational only — plain <div>s, no role, no
 * aria-checked, no aria-label — but states the artboard's own touch-target rule at its
 * header ("dokunma hedefi ≥44 px", DUR:216) and draws the 44px hit-target/30px fill-
 * circle geometry with a check glyph for "selected". AYAR:834-843 is the in-context,
 * already-shipped-shaped version on the real device panel: role="radio", aria-checked,
 * a real aria-label per swatch (Mavi/Turuncu/Yeşil/Camgöbeği/Gök mavisi/Menekşe/Zeytin/
 * Kum — used here VERBATIM, not the shorter names in tokens.css's own comments, because
 * AYAR's are the ones actually marked as an accessible name), and a selection halo
 * (`box-shadow: 0 0 0 2px paper, 0 0 0 4px ink-1`) instead of a check glyph. The two
 * disagree on selection treatment, and DUR's alone has a real contrast problem AYAR's
 * doesn't: a white check glyph over --dev-8 (Kum, #CFCBC2, DUR's own swatch never draws
 * a check on the light half of the set) would not read. This file draws BOTH — the check
 * glyph DUR specifies AND the ink-1/paper halo AYAR specifies — because the halo's
 * contrast never depends on which of the eight hues is selected and the check glyph is
 * still what states.md's report names as the component's own answer to "selected".
 *
 * Colours are var(--dev-1)..var(--dev-8), never the AYAR/DUR literals directly, so a
 * theme change and any future D-7-style palette decision reach this grid automatically
 * — see tokens.css:37-66 for why the token file, not either canvas, owns the eight hexes.
 *
 * THE "+ ADD" SWATCH IS NOT ON EITHER CANVAS. Both draw exactly eight, fixed order,
 * no ninth (tokens.css:58, "There is no ninth"). Recorded, not silently invented: this
 * file's own brief asked for "8 swatches + add", and the nearest real precedent for an
 * "add" affordance already in this codebase is `#addsrc`/`#empty-addsrc` — a bordered
 * circle with the same plus glyph. `.swatch-add` below is that glyph, sized to match
 * the other eight hit targets, dashed rather than solid-bordered so it never reads as a
 * ninth colour choice. It has no destination until a later wave gives it one.
 *
 * Markup: <div class="swatch-grid" role="radiogroup" aria-label="Cihaz rengi">
 *           <button type="button" class="swatch" role="radio" aria-checked="true"
 *                   aria-label="Mavi" style="--swatch-color:var(--dev-1)">
 *             <svg class="swatch-check" …>…</svg>
 *           </button>
 *           … (8 total, aria-label per AYAR:836-843) …
 *           <button type="button" class="swatch-add" aria-label="Renk ekle">
 *             <svg width="18" height="18" viewBox="0 0 24 24" fill="none"
 *                  stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
 *                  aria-hidden="true" focusable="false"><path d="M12 6.4v11.2M6.4 12h11.2"/></svg>
 *           </button>
 *         </div>
 * initRovingGroup (components.js) adds the arrow-key layer; renderSwatchSelection sets
 * aria-checked + the halo class from a selected colour token.
 */
.swatch-grid { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Both the 44px hit target and the 30px painted circle are centred with an identical
 * absolute-centre transform rather than flex, because the fill circle (::before), the
 * check glyph (a real child) and, when selected, the halo all have to stack on the SAME
 * point rather than lay out as flex siblings. */
.swatch {
  position: relative;
  box-sizing: border-box;
  flex: none;
  width: var(--touch);
  height: var(--touch);
  border-radius: var(--r-pill);
  background: transparent;
}
.swatch::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 30px;
  height: 30px;
  border-radius: var(--r-pill);
  background: var(--swatch-color, var(--ink-3));
}
.swatch-check {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 16px;
  height: 16px;
  color: var(--ink-on-fill);
}
.swatch[aria-checked='true']::before {
  /* AYAR:837 — 2px paper, 4px ink-1, centred on the 30px fill circle. */
  box-shadow: 0 0 0 2px var(--raised), 0 0 0 4px var(--ink-1);
}
.swatch:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }
.swatch-add {
  position: relative;
  box-sizing: border-box;
  flex: none;
  width: var(--touch);
  height: var(--touch);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-3);
  transition: color var(--dur-enter) var(--ease);
}
.swatch-add::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 30px;
  height: 30px;
  border-radius: var(--r-pill);
  border: 1.5px dashed var(--ink-3);
  box-sizing: border-box;
}
.swatch-add svg {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.swatch-add:hover { color: var(--ink-1); }
.swatch-add:hover::before { border-color: var(--ink-1); }
.swatch-add:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }

/* ===== K-65 · the shared (i) popup — `infotip.js` =============================
 * A 24px round button beside a title, and a small raised card under it. `[hidden]` is
 * restated because `.infopop` sets `display`. */
.info { position: relative; display: inline-flex; vertical-align: middle; margin-left: 6px; }
.infobtn {
  width: 24px; height: 24px; padding: 0;
  display: inline-grid; place-items: center;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.infobtn:hover { color: var(--ink-1); border-color: var(--ink-2); }
.infobtn[aria-expanded="true"] { color: var(--ink-1); background: var(--sunken); }
.infobtn:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--focus-offset); }
.infopop {
  position: absolute; z-index: 40; top: calc(100% + 8px); left: -12px;
  display: block;
  width: max-content; max-width: 300px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--raised, var(--ground));
  box-shadow: 0 10px 30px rgba(20, 22, 26, 0.14);
  color: var(--ink-1);
  font-size: var(--size-sub);
  line-height: 1.5;
  text-align: left;
  font-weight: 400;
}
.infopop[hidden] { display: none; }
.infopop-title { margin: 0 0 4px; font-weight: 600; }
.infopop-body { margin: 0; color: var(--ink-2); }
.infopop-value { margin: 6px 0 0; font-family: var(--font-num); color: var(--ink-1); }
.info[hidden] { display: none; }
.infopop { text-transform: none; letter-spacing: normal; white-space: normal; }
.infopop-title, .infopop-body, .infopop-value { display: block; }
