/* KarateShowCase tenant — dashboards
 *
 * Generated by tools/consolidate-tenant-css.mjs from 9 source files,
 * concatenated in their original cascade order. Edit the sources, re-run the
 * tool; do not hand-edit this file.
 */

/* ══════════════════════════════════════════════════════════════════════
   dojo-global-admin-menu-hide.v1.css
   ══════════════════════════════════════════════════════════════════════ */
    /* ── Admin menu hide ──────────────────────────────────────────────────── */
    #li-admin.admin-hidden,
    #li-tournament-manager.admin-hidden,
    #li-platform-owner.admin-hidden,
    #drawer-manage-label.admin-hidden {
      display: none !important;
    }


/* ══════════════════════════════════════════════════════════════════════
   dojo-platform-owner-dashboard.v1.css
   ══════════════════════════════════════════════════════════════════════ */
    /* ── PLATFORM OWNER DASHBOARD ──────────────────────────────────────────────
     * Self-contained. Deliberately shares no class names with the admin
     * dashboard so the two tab strips cannot fight over .active.
     *
     * Token-clean as of 2026-08-16: every colour here is a --ks-* semantic
     * token (or a documented component-local custom property), so the view is
     * light by construction — the pin in dojo-global-theme-tokens.v1.css and
     * this file now agree instead of the pin covering for 75 literals.
     * ───────────────────────────────────────────────────────────────────────── */
    #view-platform-owner {
      background: var(--ks-canvas);
      color: var(--ks-ink);
      min-height: 100vh
    }

    #view-platform-owner .po-header {
      position: relative;
      overflow: hidden;
      padding: 1.75rem 2rem 1.25rem;
      background: linear-gradient(180deg, var(--ks-band-raised), var(--ks-band));
      border-bottom: 1px solid var(--ks-band-edge)
    }

    /* ── The platform mark ────────────────────────────────────────────────────
       A watermark, which means it has exactly one job and a long list of things
       it must never do. It must not be readable as content (aria-hidden, empty
       alt), must not take a click (pointer-events), must not raise the cost of
       any text over it (it sits under everything, at an opacity measured against
       the band rather than guessed), and must not move the layout (absolute, so
       the header's height comes from its words).

       Sized in vw with a ceiling so it scales with the band instead of being a
       fixed image that dominates a laptop and disappears on a wide monitor. It
       is clipped by the header's overflow rather than by a hand-tuned height. */
    #view-platform-owner .po-watermark {
      position: absolute;
      /* INSIDE THE VIEWPORT, not bled past it. This sat at right:-2.5rem so the
         mark would run off the edge, and the responsive suite was right to call
         it: a decorative element still paints past the right edge, and "it is
         only decoration" is the same excuse every horizontal overflow makes.
         The header's own overflow:hidden crops it vertically, which is where a
         crop costs nothing. */
      right: 0;
      top: 50%;
      transform: translateY(-50%);
      width: clamp(150px, 20vw, 300px);
      height: auto;
      opacity: .07;
      pointer-events: none;
      user-select: none;
      z-index: 0
    }

    #view-platform-owner .po-header-inner {
      position: relative;
      z-index: 1
    }

    /* The word that separates administering the SaaS from administering a dojo.
       Text, not an image: the mark beside it is decorative and says nothing to a
       screen reader, so this is what actually carries the distinction. */
    #view-platform-owner .po-eyebrow {
      margin: 0 0 .3rem;
      font-size: .62rem;
      font-weight: 700;
      letter-spacing: 2.5px;
      text-transform: uppercase;
      color: var(--ks-band-ink-3)
    }

    #view-platform-owner .po-header h2 {
      font-family: var(--ks-display);
      font-size: 1.1rem;
      letter-spacing: 3px;
      text-transform: uppercase;
      /* The platform header is a dark band: --ks-accent-strong is the
         dark-on-light form and measured 2.30:1 here. */
      color: var(--ks-band-accent);
      margin: 0 0 .35rem
    }

    #view-platform-owner .po-header p {
      margin: 0;
      font-size: .74rem;
      color: var(--ks-band-ink-2);
      line-height: 1.55
    }

    /* ── Tabs ───────────────────────────────────────────────────────────────── */
    #view-platform-owner .po-tabs {
      display: flex;
      flex-wrap: wrap;
      padding: 0 2rem;
      background: var(--ks-surface);
      border-bottom: 2px solid var(--ks-line)
    }

    #view-platform-owner .po-tab {
      appearance: none;
      background: none;
      border: none;
      border-bottom: 2px solid transparent;
      margin-bottom: -2px;
      padding: 13px 22px;
      cursor: pointer;
      font: inherit;
      font-size: .7rem;
      font-weight: 700;
      letter-spacing: 2px;
      text-transform: uppercase;
      color: var(--ks-ink-3);
      transition: color .2s ease, border-color .2s ease
    }

    #view-platform-owner .po-tab:hover {
      color: var(--ks-ink);
      background: var(--ks-accent-soft)
    }

    #view-platform-owner .po-tab.active {
      color: var(--ks-accent-strong);
      border-bottom-color: var(--ks-accent);
      border-bottom-width: 3px
    }

    #view-platform-owner .po-tab:focus-visible {
      outline: 2px solid var(--ks-accent);
      outline-offset: -2px
    }

    #view-platform-owner .po-panel {
      display: none;
      padding: 1.25rem 2rem 3rem
    }

    #view-platform-owner .po-panel.active {
      display: block
    }

    /* ── Sections ───────────────────────────────────────────────────────────── */
    #view-platform-owner .po-section {
      margin-bottom: 1.25rem
    }

    #view-platform-owner .po-section-title {
      font-family: var(--ks-display);
      font-size: .8rem;
      letter-spacing: 3px;
      text-transform: uppercase;
      color: var(--ks-ink);
      margin: 0 0 .35rem
    }

    #view-platform-owner .po-section-note {
      margin: 0;
      font-size: .68rem;
      color: var(--ks-ink-3);
      line-height: 1.55
    }

    /* ── Tables ─────────────────────────────────────────────────────────────── */
    #view-platform-owner .po-table-wrap {
      overflow-x: auto;
      background: var(--ks-surface);
      border: 1px solid var(--ks-line);
      border-radius: 14px;
      box-shadow: 0 1px 4px rgba(0, 0, 0, .06)
    }

    #view-platform-owner .po-table {
      width: 100%;
      min-width: 720px;
      border-collapse: collapse;
      font-size: .74rem
    }

    #view-platform-owner .po-table th {
      text-align: left;
      padding: .8rem 1rem;
      font-size: .6rem;
      font-weight: 700;
      letter-spacing: 1.5px;
      text-transform: uppercase;
      color: var(--ks-ink-3);
      background: var(--ks-canvas);
      border-bottom: 1px solid var(--ks-line);
      white-space: nowrap
    }

    #view-platform-owner .po-table td {
      padding: .75rem 1rem;
      border-bottom: 1px solid var(--ks-line-soft);
      vertical-align: top;
      color: var(--ks-ink)
    }

    #view-platform-owner .po-table tbody tr:hover td {
      background: var(--ks-canvas)
    }

    #view-platform-owner .po-table tr:last-child td {
      border-bottom: none
    }

    #view-platform-owner .po-mono {
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
      font-size: .7rem
    }

    #view-platform-owner .po-detail {
      max-width: 26rem;
      word-break: break-word;
      color: var(--ks-ink-3);
      font-size: .68rem
    }

    #view-platform-owner .po-cell-actions {
      display: flex;
      flex-wrap: wrap;
      gap: .35rem;
      margin-top: .4rem
    }

    /* ── Status pills ───────────────────────────────────────────────────────── */
    #view-platform-owner .po-pill {
      display: inline-block;
      padding: .18rem .6rem;
      border-radius: 999px;
      border: 1px solid transparent;
      font-size: .6rem;
      font-weight: 700;
      letter-spacing: 1.2px;
      text-transform: uppercase
    }

    #view-platform-owner .po-pill-active {
      background: var(--ks-success-bg);
      border-color: var(--ks-success-line);
      color: var(--ks-success)
    }

    #view-platform-owner .po-pill-draft {
      background: var(--ks-canvas-warm);
      border-color: var(--ks-line);
      color: var(--ks-ink-2)
    }

    #view-platform-owner .po-pill-suspended {
      background: var(--ks-warning-bg);
      border-color: var(--ks-warning-line);
      color: var(--ks-warning)
    }

    #view-platform-owner .po-pill-archived {
      /* Archived is terminal, not dangerous, and maps to no semantic status
         family — a deliberately muted plum, declared component-local rather
         than minting a global token for one pill. */
      --po-archived-bg: #f3e8ee;
      --po-archived-ink: #7a2f4f;
      background: var(--po-archived-bg);
      border-color: var(--po-archived-ink);
      color: var(--po-archived-ink)
    }

    #view-platform-owner .po-pill-unknown {
      background: var(--ks-danger-bg);
      border-color: var(--ks-danger-line);
      color: var(--ks-danger)
    }

    /* Feature toggles are the shared .ks-switch component and actions are the
       global .btn system — the .po-toggle class that used to mean BOTH of
       those depending on the panel is retired. */

    /* ── Grant chips ────────────────────────────────────────────────────────── */
    #view-platform-owner .po-chip {
      display: inline-flex;
      align-items: center;
      gap: .4rem;
      padding: .25rem .3rem .25rem .6rem;
      border: 1px solid var(--ks-line);
      border-radius: 999px;
      background: var(--ks-canvas)
    }

    #view-platform-owner .po-chip-revoke {
      appearance: none;
      cursor: pointer;
      font: inherit;
      font-size: .55rem;
      font-weight: 700;
      letter-spacing: 1px;
      text-transform: uppercase;
      padding: .2rem .5rem;
      border-radius: 999px;
      border: none;
      background: var(--ks-danger-bg);
      color: var(--ks-danger)
    }

    #view-platform-owner .po-chip-revoke:hover {
      background: var(--ks-danger-line)
    }

    /* ── Forms ──────────────────────────────────────────────────────────────── */
    #view-platform-owner .po-form {
      display: flex;
      flex-wrap: wrap;
      align-items: flex-end;
      gap: .75rem;
      margin-top: .75rem
    }

    #view-platform-owner .po-field {
      display: flex;
      flex-direction: column;
      gap: .3rem;
      min-width: 15rem;
      flex: 1 1 15rem
    }

    #view-platform-owner .po-field-action {
      flex: 0 0 auto;
      min-width: 0
    }

    #view-platform-owner .po-label {
      font-size: .6rem;
      font-weight: 700;
      letter-spacing: 1.2px;
      text-transform: uppercase;
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-input {
      font: inherit;
      font-size: .78rem;
      padding: .55rem .7rem;
      /* line-strong, not line: the tokens file documents control borders as
         the one place hairlines must clear 3:1. A visible darkening from the
         old #d5d8dd is the point, not a drift. */
      border: 1px solid var(--ks-line-strong);
      border-radius: 10px;
      background: var(--ks-surface);
      color: var(--ks-ink)
    }

    #view-platform-owner .po-input:focus-visible {
      outline: 2px solid var(--ks-accent);
      outline-offset: 1px
    }

    /* ── Lookup result card ─────────────────────────────────────────────────── */
    #view-platform-owner .po-card {
      margin-top: 1rem;
      padding: 1.1rem 1.25rem;
      background: var(--ks-surface);
      border: 1px solid var(--ks-line);
      border-radius: 14px;
      box-shadow: 0 1px 4px rgba(0, 0, 0, .06)
    }

    #view-platform-owner .po-card-title {
      font-weight: 700;
      font-size: .85rem;
      margin-bottom: .6rem
    }

    #view-platform-owner .po-card-meta {
      display: grid;
      grid-template-columns: max-content 1fr;
      gap: .3rem .9rem;
      margin: 0 0 .9rem;
      font-size: .72rem
    }

    #view-platform-owner .po-card-meta dt {
      color: var(--ks-ink-3);
      font-weight: 700;
      font-size: .6rem;
      letter-spacing: 1px;
      text-transform: uppercase;
      align-self: center
    }

    #view-platform-owner .po-card-meta dd {
      margin: 0;
      word-break: break-word
    }

    /* ── States ─────────────────────────────────────────────────────────────── */
    #view-platform-owner .po-loading {
      padding: 1.5rem 0;
      font-size: .74rem;
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-notice {
      padding: .7rem .9rem;
      border-radius: 10px;
      border: 1px solid transparent;
      font-size: .72rem;
      line-height: 1.55;
      margin-bottom: .75rem
    }

    #view-platform-owner .po-notice-empty {
      background: var(--ks-canvas);
      border-color: var(--ks-line);
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-notice-warn {
      background: var(--ks-warning-bg);
      border-color: var(--ks-warning-line);
      color: var(--ks-warning)
    }

    #view-platform-owner .po-notice-error {
      background: var(--ks-danger-bg);
      border-color: var(--ks-danger-line);
      color: var(--ks-danger)
    }

    /* ── Billing ────────────────────────────────────────────────────────────── */
    /* The tone classes are applied from `summary.tone`, which the SERVER sends.
       A console picking its own colours would need a state-to-severity map, and
       getting that wrong means a stopped tenant rendered in the calm colour. */
    #view-platform-owner .po-billing-picker {
      display: flex;
      align-items: center;
      gap: .6rem;
      margin: 1rem 0
    }

    #view-platform-owner .po-billing-select {
      font: inherit;
      padding: .45rem .6rem;
      border: 1px solid var(--ks-line-strong);
      border-radius: 6px;
      background: var(--ks-surface);
      min-width: 16rem
    }

    #view-platform-owner .po-billing-card {
      border: 1px solid var(--ks-line);
      border-left-width: 4px;
      border-radius: 8px;
      padding: 1rem 1.1rem;
      background: var(--ks-surface)
    }

    #view-platform-owner .po-billing-card-head {
      display: flex;
      align-items: baseline;
      justify-content: space-between;
      gap: 1rem;
      flex-wrap: wrap
    }

    #view-platform-owner .po-billing-headline {
      font-family: var(--ks-display);
      font-size: 1.05rem;
      margin: 0
    }

    #view-platform-owner .po-billing-dojo {
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: .78rem;
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-billing-detail {
      margin: .5rem 0 0;
      color: var(--ks-ink-3);
      line-height: 1.5
    }

    #view-platform-owner .po-billing-tone-neutral { border-left-color: var(--ks-line-strong) }
    #view-platform-owner .po-billing-tone-ok { border-left-color: var(--ks-success) }
    #view-platform-owner .po-billing-tone-warn { border-left-color: var(--ks-warning) }
    #view-platform-owner .po-billing-tone-stopped { border-left-color: var(--ks-danger) }

    #view-platform-owner .po-billing-facts {
      display: grid;
      grid-template-columns: max-content 1fr;
      gap: .3rem 1rem;
      margin: .9rem 0 0;
      font-size: .86rem
    }

    #view-platform-owner .po-billing-facts dt {
      color: var(--ks-ink-3);
      font-weight: 600
    }

    #view-platform-owner .po-billing-facts dd {
      margin: 0
    }

    #view-platform-owner .po-billing-note {
      margin: .9rem 0 0;
      padding: .55rem .7rem;
      border-radius: 6px;
      background: var(--ks-canvas-warm);
      color: var(--ks-ink-2);
      font-size: .85rem;
      line-height: 1.45
    }

    #view-platform-owner .po-billing-actions {
      display: flex;
      flex-wrap: wrap;
      gap: .5rem;
      margin-top: 1rem
    }


    /* THE FLEET BREAK-GLASS BANNER.
       Normal state is deliberately quiet — a permanent alarm is an alarm people
       stop reading. The ACTIVE state is deliberately loud: while it holds, every
       systemically-refused tenant on the platform is being served for free, and
       an operator glancing at this screen must not be able to miss it. */
    #view-platform-owner .po-billing-emergency {
      border: 1px solid var(--ks-line);
      border-radius: 8px;
      padding: .9rem 1.1rem;
      background: var(--ks-canvas)
    }

    #view-platform-owner .po-billing-emergency-headline {
      font-family: var(--ks-display);
      font-size: .95rem;
      margin: 0;
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-billing-emergency-detail {
      margin: .45rem 0 0;
      font-size: .87rem;
      color: var(--ks-ink-3);
      line-height: 1.5
    }

    #view-platform-owner .po-billing-emergency-active {
      background: var(--ks-danger-bg);
      border-color: var(--ks-danger);
      border-left-width: 6px
    }

    #view-platform-owner .po-billing-emergency-active .po-billing-emergency-headline {
      color: var(--ks-danger);
      font-size: 1.1rem;
      letter-spacing: .3px
    }

    #view-platform-owner .po-billing-emergency-active .po-billing-emergency-detail {
      color: var(--ks-danger)
    }

    /* The checkout link. Selectable text, deliberately not a button: the
       platform owner is not the person who should complete this checkout, and a
       one-click "open" invites exactly that mistake. */
    #view-platform-owner .po-billing-checkout-url {
      margin: .8rem 0 0;
      padding: .6rem .7rem;
      border-radius: var(--ks-r-control-sm);
      background: var(--ks-canvas-warm);
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: .8rem;
      word-break: break-all;
      user-select: all
    }

    /* ── Mobile ─────────────────────────────────────────────────────────────── */
    @media (max-width:768px) {
      #view-platform-owner .po-header {
        padding: 1.25rem 1rem 1rem
      }

      #view-platform-owner .po-tabs {
        padding: 0 .5rem;
        overflow-x: auto;
        flex-wrap: nowrap
      }

      #view-platform-owner .po-tab {
        padding: 12px 14px;
        font-size: .62rem;
        letter-spacing: 1.2px;
        white-space: nowrap
      }

      #view-platform-owner .po-panel {
        padding: 1rem 1rem 2.5rem
      }

      #view-platform-owner .po-field {
        min-width: 0;
        flex-basis: 100%
      }

      #view-platform-owner .po-card-meta {
        grid-template-columns: 1fr;
        gap: .1rem
      }

      #view-platform-owner .po-card-meta dd {
        margin-bottom: .5rem
      }
    }

    /* ── Create a dojo tenant ────────────────────────────────────────────── */
    /* The create form is a <details>, so it stays out of the way of the tenant
       table until it is wanted. Its fields reuse .po-form / .po-field / .po-label
       from the grants form below it; only the per-field hint is new. */
    #view-platform-owner .po-create > summary {
      cursor: pointer;
      list-style: revert;
      margin-bottom: .5rem
    }

    #view-platform-owner .po-create[open] > summary {
      margin-bottom: .75rem
    }

    #view-platform-owner .po-field-hint {
      margin: .25rem 0 0;
      font-size: .64rem;
      color: var(--ks-ink-3);
      line-height: 1.5
    }

    /* ── Capability flags ─────────────────────────────────────────────────── */
    /* The store address disclosure looks the same as the capability one but is a
       separate class: one row carrying two elements of the same class made every
       existing locator for the capability panel ambiguous. */
    #view-platform-owner .po-store-panel > summary,
    #view-platform-owner .po-capability-panel > summary {
      cursor: pointer;
      font-size: .68rem;
      white-space: nowrap
    }

    #view-platform-owner .po-store-list,
    #view-platform-owner .po-capability-list {
      display: grid;
      gap: .3rem;
      margin-top: .45rem;
      min-width: 15rem
    }

    #view-platform-owner .po-capability {
      display: flex;
      align-items: center;
      gap: .5rem
    }

    #view-platform-owner .po-capability-name {
      flex: 1 1 auto;
      font-size: .68rem
    }

    /* Reads as unavailable rather than as a control somebody has not pressed:
       these flags store a value nothing acts on yet. */
    #view-platform-owner .po-capability-note {
      font-size: .6rem;
      color: var(--ks-warning);
      white-space: nowrap
    }

    @media (prefers-reduced-motion:reduce) {
      #view-platform-owner .po-tab {
        transition: none
      }
    }

    /* ── System health ───────────────────────────────────────────────────────
       Moved here from ks-shipped-surfaces.v1.css (2026-08-16): the panel's only
       mount is inside this console, and the split had one console styled from
       two bundles. Class names unchanged — modules/system-health.mjs and its
       unit tests are untouched. Scoped to the view like everything else here.
       ─────────────────────────────────────────────────────────────────────── */
    #view-platform-owner .ks-health-head {
      display: flex;
      align-items: center;
      gap: .7rem;
      flex-wrap: wrap;
      margin-bottom: .9rem
    }

    /* Status is carried by a word, not only a colour. An operations panel that
       says "everything is fine" in green alone says nothing to a reader who
       cannot separate green from amber. */
    #view-platform-owner .ks-health-overall {
      padding: .25rem .6rem;
      border-radius: 999px;
      font-size: .64rem;
      font-weight: 850;
      letter-spacing: .08em
    }

    #view-platform-owner .ks-health-row {
      display: flex;
      align-items: flex-start;
      gap: .6rem;
      padding: .6rem 0;
      border-bottom: 1px solid var(--ks-line)
    }

    #view-platform-owner .ks-health-row:last-child { border-bottom: 0 }

    #view-platform-owner .ks-health-dot {
      width: .6rem;
      height: .6rem;
      border-radius: 999px;
      margin-top: .3rem;
      flex: 0 0 auto
    }

    /* The text column beside the dot. flex:1 + min-width:0 so a long detail
       sentence wraps instead of pushing the row wide — found unstyled by the
       wiring verifier's every-emitted-class check. */
    #view-platform-owner .ks-health-body { flex: 1; min-width: 0 }

    #view-platform-owner .ks-health-label { font-size: .78rem; font-weight: 700 }
    #view-platform-owner .ks-health-detail { font-size: .72rem; color: var(--ks-ink-3); margin-top: .1rem }

    /* Filled status chips: the status colour as ground, its tint as ink. */
    #view-platform-owner .ks-health-ok { background: var(--ks-success); color: var(--ks-success-bg) }
    #view-platform-owner .ks-health-warn { background: var(--ks-warning); color: var(--ks-warning-bg) }
    #view-platform-owner .ks-health-fail { background: var(--ks-danger); color: var(--ks-danger-bg) }
    /* Unknown is deliberately not grey-as-fine: not knowing is a state that
       needs attention, and it reads closer to warn than to ok. */
    #view-platform-owner .ks-health-unknown { background: var(--ks-ink-2); color: var(--ks-surface) }

    /* ── Tenant media support ────────────────────────────────────────────────
       The panel a platform owner fixes a dojo's pictures from. Visual weight is
       deliberately on the CURRENT binding rather than on the controls: the
       question this screen answers is "what is actually in this slot", and the
       actions are secondary to getting that right.
       ─────────────────────────────────────────────────────────────────────── */
    #view-platform-owner .po-notice-info {
      background: var(--ks-info-bg);
      border-color: var(--ks-info-line);
      color: var(--ks-info)
    }

    #view-platform-owner .po-media-picker {
      display: flex;
      align-items: center;
      gap: .6rem;
      margin-bottom: 1rem;
      flex-wrap: wrap
    }

    #view-platform-owner .po-media-picker-label {
      font-size: .68rem;
      font-weight: 700;
      letter-spacing: .04em;
      text-transform: uppercase;
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-media-context {
      display: flex;
      align-items: center;
      gap: .6rem;
      flex-wrap: wrap;
      margin-bottom: .85rem
    }

    #view-platform-owner .po-media-context-id {
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: .7rem;
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-media-lane {
      margin-bottom: 1.6rem
    }

    #view-platform-owner .po-media-lane-title {
      font-size: .78rem;
      font-weight: 800;
      letter-spacing: .03em;
      text-transform: uppercase;
      color: var(--ks-ink-2);
      margin: 0 0 .6rem
    }

    #view-platform-owner .po-media-row {
      border: 1px solid var(--ks-line);
      border-radius: 12px;
      padding: .9rem;
      margin-bottom: .7rem;
      background: var(--ks-surface)
    }

    #view-platform-owner .po-media-head {
      display: flex;
      align-items: baseline;
      justify-content: space-between;
      gap: .75rem;
      margin-bottom: .55rem
    }

    #view-platform-owner .po-media-slot {
      font-weight: 700;
      font-size: .82rem
    }

    #view-platform-owner .po-media-source {
      font-size: .7rem;
      color: var(--ks-ink-3);
      text-align: right
    }

    #view-platform-owner .po-media-empty {
      font-size: .72rem;
      color: var(--ks-ink-3);
      font-style: italic
    }

    /* Bounded so a hero image cannot push the controls off the screen, and
       object-fit so a wrong-aspect asset still reads as what it is. */
    #view-platform-owner .po-media-preview {
      display: block;
      max-width: 220px;
      max-height: 130px;
      width: auto;
      object-fit: contain;
      border-radius: 8px;
      border: 1px solid var(--ks-line-soft);
      background: var(--ks-canvas);
      margin-bottom: .6rem
    }

    #view-platform-owner .po-media-detail {
      display: grid;
      grid-template-columns: auto 1fr;
      gap: .15rem .7rem;
      margin: 0 0 .7rem;
      font-size: .69rem
    }

    #view-platform-owner .po-media-detail dt {
      color: var(--ks-ink-3);
      font-weight: 600
    }

    #view-platform-owner .po-media-detail dd {
      margin: 0;
      color: var(--ks-ink-2);
      overflow-wrap: anywhere
    }

    #view-platform-owner .po-media-actions {
      display: flex;
      gap: 1rem;
      flex-wrap: wrap;
      align-items: flex-start
    }

    #view-platform-owner .po-media-action {
      display: flex;
      gap: .45rem;
      align-items: center;
      flex-wrap: wrap
    }

    #view-platform-owner .po-media-file {
      font-size: .68rem;
      max-width: 210px
    }

    #view-platform-owner .po-media-remove > summary {
      cursor: pointer;
      font-size: .7rem;
      font-weight: 700;
      color: var(--ks-danger);
      list-style: none
    }

    #view-platform-owner .po-media-remove-body {
      margin-top: .55rem;
      padding: .7rem;
      border: 1px solid var(--ks-danger-line);
      border-radius: 10px;
      background: var(--ks-danger-bg);
      max-width: 380px
    }

    #view-platform-owner .po-media-remove-target {
      font-size: .7rem;
      color: var(--ks-ink-2);
      margin: 0 0 .55rem;
      overflow-wrap: anywhere
    }

    /* The one destructive control on this screen, and it looks like one. */

    @media (max-width:768px) {
      #view-platform-owner .po-media-head {
        flex-direction: column;
        gap: .2rem
      }

      #view-platform-owner .po-media-source {
        text-align: left
      }
    }

    /* ── Moderation cases ──────────────────────────────────────────────────
     *
     * A queue of CASES, not of content. The card shows which tenant, what was
     * objected to and how often; the reported words appear only inside
     * .po-case-revealed, and only after an operator has asked for them — which
     * is recorded. The reveal notice is styled to be read rather than skimmed
     * past, because it is the one line telling a moderator that looking leaves
     * a trace. */

    #view-platform-owner .po-case-list {
      display: flex;
      flex-direction: column;
      gap: .75rem
    }

    #view-platform-owner .po-case-card {
      border: 1px solid var(--ks-line);
      border-radius: 10px;
      background: var(--ks-surface);
      padding: .9rem 1rem
    }

    #view-platform-owner .po-case-head {
      display: flex;
      align-items: baseline;
      gap: .6rem;
      flex-wrap: wrap;
      margin-bottom: .4rem
    }

    #view-platform-owner .po-case-state {
      font-size: .62rem;
      font-weight: 800;
      letter-spacing: .1em;
      text-transform: uppercase;
      padding: 2px 8px;
      border-radius: 999px;
      background: var(--ks-surface-sunken);
      color: var(--ks-ink-2)
    }

    /* A live case is the one that needs a person. A decided one recedes. */
    #view-platform-owner .po-case-state-open,
    #view-platform-owner .po-case-state-reviewing {
      background: var(--ks-accent-soft, var(--ks-surface-sunken));
      color: var(--ks-accent-strong)
    }

    #view-platform-owner .po-case-dojo {
      font-weight: 700;
      font-size: .85rem
    }

    #view-platform-owner .po-case-count,
    #view-platform-owner .po-case-when {
      font-size: .72rem;
      color: var(--ks-ink-3)
    }

    #view-platform-owner .po-case-reasons {
      font-size: .8rem;
      color: var(--ks-ink);
      margin-bottom: .25rem
    }

    /* Provenance and outcome lines inside a reveal: the snapshot banner says
     * where the content came from; the handling lines say what the dojo did. */
    #view-platform-owner .po-case-provenance {
      font-size: .74rem;
      font-weight: 700;
      color: var(--ks-accent-strong);
      margin-bottom: .4rem
    }

    #view-platform-owner .po-case-handling {
      font-size: .72rem;
      color: var(--ks-ink-2);
      margin: .35rem 0 0
    }

    /* Why a case is on the platform's desk at all — the routing policy's
     * grounds, said before anything else is read. */
    #view-platform-owner .po-case-escalation {
      font-size: .74rem;
      font-weight: 700;
      color: var(--ks-accent-strong);
      margin-bottom: .35rem
    }

    /* The reporters' notes — anonymous by construction (the case document
     * never carries who), quoted verbatim, one row per report that had words.
     * Sunken ground so a reporter's text reads as testimony, not as UI. */
    #view-platform-owner .po-case-notes {
      display: grid;
      gap: .4rem;
      margin-bottom: .5rem
    }

    #view-platform-owner .po-case-note {
      background: var(--ks-surface-sunken);
      border-radius: 8px;
      padding: .45rem .6rem
    }

    #view-platform-owner .po-case-note-reason {
      display: block;
      font-size: .68rem;
      font-weight: 700;
      letter-spacing: .04em;
      text-transform: uppercase;
      color: var(--ks-ink-3);
      margin-bottom: .15rem
    }

    #view-platform-owner .po-case-note-text {
      font-size: .82rem;
      color: var(--ks-ink);
      overflow-wrap: anywhere
    }

    /* The path, shown so an operator knows WHICH conversation without being
     * shown what is in it. Monospace because it is an identifier. */
    #view-platform-owner .po-case-subject {
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: .68rem;
      color: var(--ks-ink-3);
      overflow-wrap: anywhere;
      margin-bottom: .5rem
    }

    #view-platform-owner .po-case-notice {
      font-size: .74rem;
      color: var(--ks-ink-2);
      margin-bottom: .5rem
    }

    #view-platform-owner .po-case-actions {
      display: flex;
      gap: .5rem;
      flex-wrap: wrap
    }

    #view-platform-owner .po-case-revealed {
      margin-top: .8rem;
      padding-top: .8rem;
      border-top: 1px solid var(--ks-line);
      display: flex;
      flex-direction: column;
      gap: .55rem
    }

    #view-platform-owner .po-case-comment {
      padding: .5rem .65rem;
      border-radius: 8px;
      background: var(--ks-surface-sunken)
    }

    /* The reported one is marked, so a moderator reading a thread never has to
     * work out which line the case is actually about. */
    #view-platform-owner .po-case-comment-subject {
      border-left: 3px solid var(--ks-accent-strong)
    }

    #view-platform-owner .po-case-comment-meta {
      display: flex;
      gap: .5rem;
      align-items: baseline;
      flex-wrap: wrap;
      font-size: .68rem;
      color: var(--ks-ink-3);
      margin-bottom: .2rem
    }

    #view-platform-owner .po-case-comment-author {
      font-weight: 700;
      color: var(--ks-ink-2)
    }

    #view-platform-owner .po-case-comment-flag {
      font-weight: 800;
      letter-spacing: .08em;
      text-transform: uppercase;
      color: var(--ks-accent-strong)
    }

    #view-platform-owner .po-case-comment-text {
      margin: 0;
      font-size: .85rem;
      color: var(--ks-ink);
      overflow-wrap: anywhere
    }

    #view-platform-owner .po-case-gone,
    #view-platform-owner .po-case-truncated {
      margin: 0;
      font-size: .78rem;
      color: var(--ks-ink-3)
    }

    @media (max-width:768px) {
      #view-platform-owner .po-case-head {
        gap: .35rem
      }

      #view-platform-owner .po-case-actions .btn {
        flex: 1 1 auto
      }
    }

    /* ══════════════════════════════════════════════════════════════════════════
       2026-08-21 DENSITY PASS — the tenants table reads as one row per tenant
       again. The lifecycle transitions keep their exact contract (one real
       button per legal transition, ksDialog confirm, terminal renders none);
       what changes is their weight: quiet compact actions on one line beside
       the pill instead of full-size stacked outline buttons that tripled every
       row. "Not connected yet" is a fact, not an alarm — it reads muted.
       ══════════════════════════════════════════════════════════════════════════ */
    #view-platform-owner .po-cell-actions {
      flex-wrap: nowrap;
      align-items: center;
      gap: .3rem;
      margin-top: .45rem;
    }
    #view-platform-owner .po-cell-actions .btn {
      padding: 3px 9px;
      font-size: .58rem;
      font-weight: 700;
      letter-spacing: .06em;
      border-radius: var(--ks-r-control-sm);
      border-color: var(--ks-line-strong);
      color: var(--ks-ink-2);
      background: transparent;
      text-transform: none;
    }
    #view-platform-owner .po-cell-actions .btn:hover {
      border-color: var(--ks-accent);
      color: var(--ks-accent-strong);
      background: color-mix(in oklab, var(--ks-accent) 6%, transparent);
    }

    #view-platform-owner .po-capability-note {
      color: var(--ks-ink-3);
      font-weight: 600;
    }

/* The explicit door into a tenant. A quiet link, not a button — visiting a
   customer's site is navigation, and the viewing banner takes over there. */
.po-open-dojo {
  display: inline-block;
  margin-top: .35rem;
  font-size: .7rem;
  font-weight: 600;
  color: var(--ks-accent-strong);
  text-decoration: none;
}
.po-open-dojo:hover { text-decoration: underline; }
.po-open-dojo:focus-visible { outline: 2px solid var(--ks-focus-ring); outline-offset: 2px; }

/* ── the platform overview ─────────────────────────────────────────────────
   The command-center tab: one glance says whether anything needs the owner.
   Cards over a responsive grid; every number links to the tab that owns it. */
.po-overview-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1rem;
  align-items: start;
}
.po-overview-card {
  border: 1px solid var(--ks-line);
  border-radius: 10px;
  background: var(--ks-surface);
  padding: 1rem 1.1rem;
  min-width: 0;
}
.po-overview-card h3 {
  margin: 0 0 .6rem;
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ks-ink-3);
}
.po-overview-stat {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  font-size: .8rem;
  color: var(--ks-ink-2);
  padding: .2rem 0;
}
.po-overview-stat strong {
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  color: var(--ks-ink);
}
.po-overview-link {
  appearance: none;
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: .72rem;
  font-weight: 600;
  color: var(--ks-accent-strong);
  cursor: pointer;
}
.po-overview-link:hover { text-decoration: underline; }
.po-overview-link:focus-visible { outline: 2px solid var(--ks-focus-ring); outline-offset: 2px; }
.po-overview-note { font-size: .74rem; color: var(--ks-ink-3); line-height: 1.5; }
/* The full health panel renders inside its overview card through the same
   module the System Health tab uses — one model, two mounts. */
.po-overview-health-mount { min-width: 0; }

/* ── The attention band ─────────────────────────────────────────────────────
   The first thing on the Overview and, on a normal day, the only thing that
   needs reading. It states a verdict and then lists what is behind it; when
   nothing is wrong it stays one line rather than growing a panel of green.

   Severity is carried by a left rule and the verdict's own colour, not by a
   filled card. A console that turns red across a whole block for one open
   moderation case teaches its operator to ignore red. */
.po-attention {
  margin: 0 0 1.25rem;
  padding: .95rem 1.15rem;
  border: 1px solid var(--ks-line);
  border-left: 3px solid var(--ks-line-strong);
  border-radius: var(--radius-md, 10px);
  background: var(--ks-surface);
}
.po-attention-clear { border-left-color: var(--ks-success); }
.po-attention-watch { border-left-color: var(--ks-warning); }
.po-attention-critical { border-left-color: var(--ks-danger); }
.po-attention-reading { border-left-color: var(--ks-line-strong); }

.po-attention-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem .75rem;
}
.po-attention-verdict {
  font-family: var(--ks-display);
  font-size: .95rem;
  font-weight: 800;
  letter-spacing: .5px;
  color: var(--ks-ink);
}
.po-attention-critical .po-attention-verdict { color: var(--ks-danger); }
.po-attention-count { font-size: .76rem; color: var(--ks-ink-3); }
.po-attention-reading .po-attention-head { font-size: .78rem; color: var(--ks-ink-3); }

.po-attention-list {
  list-style: none;
  margin: .8rem 0 0;
  padding: 0;
  display: grid;
  gap: .4rem;
}
/* Each item is a button because each item goes somewhere: the tab that owns
   the work. Full-width and left-aligned so it reads as a row, not a control
   floating in one. */
.po-attention-open {
  appearance: none;
  width: 100%;
  display: block;
  text-align: left;
  border: 1px solid var(--ks-line-soft);
  border-radius: var(--ks-r-control);
  background: var(--ks-surface-sunken);
  padding: .55rem .7rem;
  font: inherit;
  cursor: pointer;
}
.po-attention-open:hover { border-color: var(--ks-line-strong); }
.po-attention-open:focus-visible { outline: 2px solid var(--ks-focus-ring); outline-offset: 2px; }
.po-attention-label {
  display: block;
  font-size: .8rem;
  font-weight: 700;
  color: var(--ks-ink);
}
.po-attention-item-critical .po-attention-label { color: var(--ks-danger); }
.po-attention-detail {
  display: block;
  margin-top: .12rem;
  font-size: .73rem;
  line-height: 1.45;
  color: var(--ks-ink-3);
  overflow-wrap: anywhere;
}

/* The health summary's all-clear line, when every signal reported ok. */
.ks-health-allclear {
  margin: .5rem 0 0;
  font-size: .74rem;
  color: var(--ks-ink-3);
}

/* ── The tenant index ───────────────────────────────────────────────────────
   Six columns, from twelve. The row's job is to be readable at a glance and to
   hold every control it always held; the hierarchy below is what makes those
   two compatible — one strong identity, one status, and the dense material
   (capabilities, services) grouped rather than spread. */
#view-platform-owner .po-identity { min-width: 11rem; }
#view-platform-owner .po-identity-name {
  font-weight: 700;
  font-size: .82rem;
  color: var(--ks-ink);
  line-height: 1.3;
  overflow-wrap: anywhere;
}
#view-platform-owner .po-identity-meta {
  margin-top: .15rem;
  color: var(--ks-ink-3);
}
#view-platform-owner .po-status-cell { white-space: nowrap; }
#view-platform-owner .po-contact,
#view-platform-owner .po-created {
  font-size: .7rem;
  color: var(--ks-ink-3);
  overflow-wrap: anywhere;
}

/* Services at rest: chips for what is on, a count for what is not. The
   controls themselves live in the row's detail. */
#view-platform-owner .po-services-cell { min-width: 9rem; }
#view-platform-owner .po-chips { display: flex; flex-wrap: wrap; gap: .25rem; }
#view-platform-owner .po-chip {
  display: inline-block;
  padding: .12rem .45rem;
  border-radius: 999px;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .3px;
  border: 1px solid transparent;
}
#view-platform-owner .po-chip-on {
  background: var(--ks-success-bg);
  border-color: var(--ks-success-line);
  color: var(--ks-success);
}
#view-platform-owner .po-quiet { font-size: .7rem; color: var(--ks-ink-3); }
#view-platform-owner .po-cell-sub {
  margin-top: .2rem;
  font-size: .65rem;
  color: var(--ks-ink-3);
}

/* Services inside the detail: a name and its control on one line, so the eye
   runs down a list of six rather than across six columns of toggles. */
#view-platform-owner .po-service-group { display: grid; gap: .3rem; }
#view-platform-owner .po-service-group + .po-service-group { margin-top: .55rem; }
#view-platform-owner .po-service {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
}
#view-platform-owner .po-service-name {
  font-size: .72rem;
  color: var(--ks-ink-2);
}
/* The reason, said once for the group it applies to. It was printed beside
   every unavailable switch — four times a row, the same four words — which is
   how a true sentence stops being read. */
#view-platform-owner .po-service-group-parked {
  padding-top: .5rem;
  border-top: 1px dashed var(--ks-line);
}
#view-platform-owner .po-service-group-parked .po-capability-note {
  margin: 0 0 .1rem;
}
#view-platform-owner .po-service-group-parked .po-service-name { color: var(--ks-ink-3); }

/* ── The row detail ─────────────────────────────────────────────────────────
   Opened from the row's Manage button. Three groups, in the order an operator
   works through them: what the dojo offers, what it is entitled to, and — last,
   fenced off, and the only group that changes what a member can reach — its
   lifecycle. */
#view-platform-owner .po-th-actions { width: 1%; }
#view-platform-owner .po-row-actions { text-align: right; white-space: nowrap; }
#view-platform-owner .po-tenant-row-open td { background: var(--ks-canvas); }
#view-platform-owner .po-tenant-detail > td {
  background: var(--ks-surface-sunken);
  padding: 1rem 1.15rem 1.15rem;
  border-bottom: 1px solid var(--ks-line);
}
#view-platform-owner .po-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.1rem;
  align-items: start;
}
#view-platform-owner .po-detail-head {
  margin: 0 0 .2rem;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--ks-ink-3);
}
#view-platform-owner .po-detail-note {
  margin: 0 0 .5rem;
  font-size: .68rem;
  line-height: 1.5;
  color: var(--ks-ink-3);
}
/* The destructive group is fenced, not coloured in. A red panel on every row
   would make the whole table read as dangerous; a red rule on the one group
   that is says exactly as much as it should. */
#view-platform-owner .po-detail-danger {
  border-left: 2px solid var(--ks-danger-line);
  padding-left: .8rem;
}
#view-platform-owner .po-btn-danger {
  border-color: var(--ks-danger-line);
  color: var(--ks-danger);
}
#view-platform-owner .po-btn-danger:hover {
  background: var(--ks-danger-bg);
  border-color: var(--ks-danger);
}

/* ── Shared page grammar ────────────────────────────────────────────────────
   The two responsibilities every tab repeats: introducing a section, and
   saying a region is empty. See assets/js/modules/platform-console.mjs. */
#view-platform-owner .po-empty-state {
  margin: .9rem 0 0;
  padding: 1.5rem 1.25rem;
  border: 1px dashed var(--ks-line-strong);
  border-radius: var(--radius-md, 10px);
  background: var(--ks-surface);
  max-width: 34rem;
}
#view-platform-owner .po-empty-headline {
  margin: 0 0 .3rem;
  font-size: .82rem;
  font-weight: 700;
  color: var(--ks-ink-2);
}
#view-platform-owner .po-empty-body {
  margin: 0;
  font-size: .74rem;
  line-height: 1.6;
  color: var(--ks-ink-3);
}
#view-platform-owner .po-empty-state .btn { margin-top: .7rem; }

/* A section whose controls change who can reach what. The rule is the whole
   treatment: an operations console that colours a whole panel for a permissions
   form teaches its operator that colour means nothing. */
#view-platform-owner .po-section-guarded {
  border-left: 2px solid var(--ks-line-strong);
  padding-left: .85rem;
}

/* Panels are read, not scanned edge to edge. A measure keeps the prose and the
   forms in these tabs from running the full width of a 27-inch monitor while
   the tables beside them legitimately use all of it. */
#view-platform-owner .po-section-note { max-width: 46rem; }

/* The break-glass and the per-dojo lookup are different scopes; they need the
   gap that says so. */
#view-platform-owner .po-billing-emergency { margin-bottom: 1.5rem; }
#view-platform-owner .po-section + .po-billing-picker { margin-top: .3rem; }

/* ── The row detail on a narrow screen ──────────────────────────────────────
   The detail is a <td> spanning a table that carries a min-width, so on a phone
   it was laid out at the TABLE's width rather than the screen's: the panel
   opened with its Capabilities group visible, a column of blank canvas beside
   it, and Services and Lifecycle sitting off the right edge of a horizontally
   scrolling region. Reachable, and unusable — the same defect the admin roster
   fixed by stacking.

   Sticky-left pins the panel to the viewport as the summary row scrolls, and
   the single column keeps every group in the visible width instead of past it.
   The groups keep their order, so nothing is hidden and nothing is reordered
   into a different meaning. */
@media (max-width: 768px) {
  #view-platform-owner .po-tenant-detail > td {
    position: sticky;
    left: 0;
    /* The VISIBLE width of the scroller, not the viewport and not the table.
       100vw overshot by exactly the panel's gutters, which put the service
       switches — right-aligned in their rows — just past the edge: the panel
       stacked correctly and every control it holds was still unreachable.
       .po-panel carries 1rem either side below this breakpoint, and
       .po-table-wrap a 1px border, so this is what is actually on screen. */
    width: calc(100vw - 2rem - 2px);
    max-width: calc(100vw - 2rem - 2px);
    box-sizing: border-box;
  }

  #view-platform-owner .po-detail-grid {
    grid-template-columns: 1fr;
    gap: 1.25rem;
  }

  /* At this width the fence is a full border rather than a left rule: a single
     column means the rule would read as a list marker instead of a boundary. */
  #view-platform-owner .po-detail-danger {
    border-left: none;
    padding-left: 0;
    padding-top: .8rem;
    border-top: 2px solid var(--ks-danger-line);
  }
}

/* ── The audit log ──────────────────────────────────────────────────────────
   A hundred rows of equal weight is a wall. The action carries the row; the
   identifiers stay complete but stop competing with it. */
#view-platform-owner .po-audit-action {
  font-weight: 700;
  color: var(--ks-ink);
  white-space: nowrap;
}
#view-platform-owner .po-audit-when {
  color: var(--ks-ink-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
#view-platform-owner .po-audit-id {
  color: var(--ks-ink-3);
  overflow-wrap: anywhere;
}

/* Grid children, so both carry the min-width floor a grid track needs: without
   it a long storefront address or a wide switch row sets the column's
   min-content and pushes the panel wider than the row it sits in. The same
   `1fr` = minmax(auto, 1fr) trap the home command centre hit. */
#view-platform-owner .po-detail-group { min-width: 0; }
#view-platform-owner .po-services-controls { min-width: 0; }

/* The Payments group: the two acts only the platform owner may perform.
   Same grid-child floor as its siblings above — a merchant category code and a
   comma-separated method list both want to set min-content, and `1fr` is
   minmax(auto, 1fr), which is the trap that widened the home command centre.
   The fields stack because each carries a paragraph of consequence above it,
   and a two-column form would put a compliance decision beside a destructive
   one at the same visual weight. */
#view-platform-owner .po-payments-controls { min-width: 0; display: grid; gap: 1rem; }
#view-platform-owner .po-payments-controls .po-field { display: grid; gap: 0.5rem; }
#view-platform-owner .po-payments-controls .po-input { width: 100%; }


/* ══════════════════════════════════════════════════════════════════════
   dojo-tenant-admin-dashboard.v1.css
   ══════════════════════════════════════════════════════════════════════ */
/* Tenant (dojo) admin dashboard — membership status, settings.
   Scoped to ta-* so nothing here can bleed into the platform-owner dashboard
   (po-*) or the existing admin tables. */

.ta-table { width: 100%; }
.ta-muted { color: var(--ks-ink-3); font-size: .72rem; }

.ta-pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  white-space: nowrap;
}
.ta-pill-active { background: var(--ks-success-bg); color: var(--ks-success); }
.ta-pill-inactive { background: var(--ks-canvas-warm); color: var(--ks-ink-2); }
.ta-pill-rejected { background: var(--ks-danger-bg); color: var(--ks-danger); }
.ta-pill-removed { background: var(--ks-danger-bg); color: var(--ks-danger); }
.ta-pill-unknown { background: var(--ks-canvas-warm); color: var(--ks-ink-2); }

.ta-row-actions { display: flex; gap: .4rem; flex-wrap: wrap; justify-content: flex-end; }
.ta-row-actions .btn { font-size: .62rem; padding: 4px 12px; font-weight: 700; }

.ta-dd-item { cursor: pointer; }

/* Settings form */
.ta-settings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: .9rem 1.2rem;
}
.ta-field { display: flex; flex-direction: column; gap: .3rem; }
.ta-field label { font-size: .7rem; font-weight: 700; color: var(--ks-ink-2); }
.ta-field input[type="text"],
.ta-field input[type="email"],
.ta-field textarea,
.ta-field select {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--ks-line-strong);
  border-radius: var(--ks-r-control);
  font-size: .78rem;
  font-family: inherit;
}
.ta-field textarea { min-height: 84px; resize: vertical; }
.ta-field-hint { font-size: .66rem; color: var(--ks-ink-3); }

.ta-field input[type="color"] {
  width: 100%;
  height: 38px;
  padding: 2px;
  border: 1px solid var(--ks-line-strong);
  border-radius: var(--ks-r-control);
  cursor: pointer;
  background: var(--ks-surface);
}
.ta-brand-presets {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: .9rem;
}
.ta-brand-preset { display: inline-flex; align-items: center; gap: .35rem; }
.ta-brand-preset-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, .15);
  display: inline-block;
}

/* ── Saved palettes ─────────────────────────────────────────────────────────
   The dojo's own library, above the fold of the branding card and visually
   separated from the curated presets: those are the platform's suggestions,
   these are the dojo's own record of what its colours were. Same chip shape so
   applying one reads as the same gesture. */
.ta-brand-library {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--ks-line);
}

.ta-brand-library-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
  margin-bottom: .3rem;
}

.ta-brand-library-title {
  margin: 0;
  font-family: var(--ks-display);
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ks-ink-2);
}

.ta-brand-library-list {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .65rem;
}

.ta-brand-library-empty {
  margin: 0;
}

/* The chip and its delete control are one visual object with two targets, so
   the delete sits inside the chip's border rather than floating beside it. */
.ta-brand-saved {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--ks-line-strong);
  border-radius: 8px;
  overflow: hidden;
  background: var(--ks-surface);
}

.ta-brand-saved .ta-brand-saved-apply {
  border: 0;
  border-radius: 0;
  background: transparent;
}

.ta-brand-saved-remove {
  border: 0;
  border-left: 1px solid var(--ks-line);
  background: transparent;
  color: var(--ks-ink-3);
  /* 32px is the smallest this can be and still be a comfortable touch target
     beside a chip that is itself only 30px tall. */
  min-width: 32px;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.ta-brand-saved-remove:hover,
.ta-brand-saved-remove:focus-visible {
  background: var(--ks-danger-bg, rgba(178, 31, 36, .08));
  color: var(--ks-danger);
}

.ks-sender-list { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .8rem; }
.ks-sender-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  padding: .45rem .6rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-surface);
}
.ks-sender-name { font-size: .78rem; font-weight: 600; color: var(--ks-ink-2); flex: 1; min-width: 180px; }
.ks-sender-badge {
  font-size: .62rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--ks-canvas);
  color: var(--ks-ink-3);
}
.ks-sender-badge-active { background: var(--ks-success-bg); color: var(--ks-success); }
.ks-sender-profile-form {
  display: flex;
  flex-direction: column;
  gap: .55rem;
  padding: .6rem;
  border: 1px dashed var(--ks-line-strong);
  border-radius: 8px;
  width: 100%;
}
.ks-sender-field { display: flex; flex-direction: column; gap: .3rem; }
.ks-sender-field-label { font-size: .7rem; font-weight: 700; color: var(--ks-ink-2); }
.ks-sender-field input[type="text"],
.ks-sender-field input[type="email"] {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--ks-line-strong);
  border-radius: var(--ks-r-control);
  font-size: .78rem;
  font-family: inherit;
}
.ks-sender-field-inline { flex-direction: row; align-items: center; gap: .5rem; font-size: .74rem; color: var(--ks-ink-2); }
.ks-sender-actions { display: flex; gap: .5rem; }
.ks-sender-status { font-size: .72rem; color: var(--ks-ink-3); margin-top: .4rem; }

/* ── Invitations ───────────────────────────────────────────────────────────
   Deliberately the same visual language as the sender-profile rows above:
   both are an admin managing a short list of records on the same tab, and a
   second look would say they were different kinds of thing. */
.ks-invite-list { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .8rem; }

/* The ledger's summary line: what it is, then the states it is in. The counts
   are the quiet half — they inform without competing with the label. */
.ks-invite-history { margin: 0 0 .9rem; }
.ks-invite-history .ks-disclosure-body { padding-bottom: .2rem; }
.ks-invite-history-counts {
  margin-left: auto;
  padding-right: .6rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: none;
  color: var(--ks-ink-3);
}
.ks-invite-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  padding: .45rem .6rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-surface);
}
.ks-invite-who { font-size: .78rem; font-weight: 600; color: var(--ks-ink-2); flex: 1; min-width: 180px; }
.ks-invite-role-badge {
  font-size: .62rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--ks-info-bg);  color: var(--ks-info);
  text-transform: capitalize;
}
.ks-invite-badge {
  font-size: .62rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--ks-canvas);
  color: var(--ks-ink-3);
}
.ks-invite-badge-open { background: var(--ks-warning-bg); color: var(--ks-warning); }
.ks-invite-badge-accepted { background: var(--ks-success-bg); color: var(--ks-success); }

/* The one-time link. Given its own weight on purpose: this is the only moment
   the link exists anywhere outside the admin's clipboard, and a panel that
   looked like every other row would be closed without copying it. */
.ks-invite-fresh {
  padding: .75rem;
  margin-bottom: .8rem;
  border: 2px solid var(--ks-warning);
  border-radius: 10px;
  background: var(--ks-warning-bg);
}
.ks-invite-fresh-title { display: block; font-size: .8rem; font-weight: 800; color: var(--ks-warning); margin-bottom: .3rem; }
.ks-invite-link-row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-top: .5rem; }
.ks-invite-link-input {
  flex: 1;
  min-width: 220px;
  padding: 8px 10px;
  border: 1px solid var(--ks-line-strong);
  border-radius: 8px;
  font-size: .72rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--ks-surface);
}
.ks-invite-form {
  display: flex;
  flex-direction: column;
  gap: .55rem;
  padding: .6rem;
  border: 1px dashed var(--ks-line-strong);
  border-radius: 8px;
  width: 100%;
}
.ks-invite-field { display: flex; flex-direction: column; gap: .3rem; }
.ks-invite-field-label { font-size: .7rem; font-weight: 700; color: var(--ks-ink-2); }
.ks-invite-field input[type="text"],
.ks-invite-field input[type="email"],
.ks-invite-field select {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--ks-line-strong);
  border-radius: var(--ks-r-control);
  font-size: .78rem;
  font-family: inherit;
  background: var(--ks-surface);
}
.ks-invite-actions { display: flex; gap: .5rem; }
.ks-invite-status { font-size: .72rem; color: var(--ks-ink-3); margin-top: .4rem; }

/* The invitee's banner. Fixed to the top of the page rather than placed in the
   flow: whoever opens the link has never seen this site before and should not
   have to find the one thing on it that concerns them. */
.ks-invite-banner {
  position: sticky;
  top: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  padding: .7rem 1rem;
  background: #1a1a2e;
  color: #fff;
  font-size: .82rem;
  box-shadow: 0 2px 12px rgba(0, 0, 0, .25);
}
.ks-invite-banner-title { font-weight: 800; }
.ks-invite-banner-message { flex: 1; min-width: 220px; opacity: .92; }

.ta-switch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: .55rem 1.2rem;
}
/* .ta-switch itself is retired — the privacy grid's rows are the shared
   .ks-switch component now. The grid and its note below still live here. */

.ta-settings-actions {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  margin-top: 1.1rem;
}
.ta-settings-status { font-size: .72rem; color: var(--ks-ink-3); }

/* ── When settings cannot be loaded ────────────────────────────────────────
   Red, unlike the amber the team-access banner uses for "public", because the
   two are not the same kind of news: public is a legitimate state a dojo may
   have chosen, and this is a screen that is not working. It carries its own
   way out, so the administrator is never left with a dead Save and no move. */
.ta-settings-unavailable {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  padding: .85rem 1rem;
  margin-bottom: 1.1rem;
  border: 1px solid var(--ks-danger-line);
  border-radius: 10px;
  background: var(--ks-danger-bg);
}

.ta-settings-unavailable[hidden] { display: none; }
.ta-settings-unavailable-icon { font-size: 1.05rem; line-height: 1.35; }

.ta-settings-unavailable-text {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  min-width: 0;
  flex: 1 1 auto;
}

.ta-settings-unavailable-text strong {
  font-size: .86rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--ks-danger);
}

.ta-settings-unavailable-text span {
  font-size: .74rem;
  color: var(--ks-ink-2);
  line-height: 1.5;
}

/* A read-only field must LOOK read-only. Without this the disabled state is
   carried by the cursor alone, which nobody sees until they have already
   clicked into a box that will not take their typing. */
#admin-tab-settings input:disabled,
#admin-tab-settings select:disabled,
#admin-tab-settings textarea:disabled {
  background: var(--ks-canvas);
  color: var(--ks-ink-2);
  cursor: not-allowed;
  opacity: .72;
}

/* Team access. The state line carries a fact an administrator has to be able to
   read at a glance — whether their dojo is private right now — so it takes
   --ks-ink-2 rather than the .ta-field-hint grey beside it. #view-admin opts
   into the light token block by selector, so that resolves to #475467 here in
   either browser scheme: 7.5:1 on the panel's white. */
.ta-team-access-state {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  margin-bottom: .9rem;
}
.ta-team-access-detail { font-size: .72rem; color: var(--ks-ink-2); margin-top: .75rem; }

/* ── The access-state banner ───────────────────────────────────────────────
   The first thing in the panel, and deliberately not a pill. An owner set a
   code, believed the dojo was private, and it was not — the state has to be
   the thing you cannot miss rather than a small grey badge beside a button.

   Tinted-state tokens, not literals: #view-admin opts into the light token
   block by selector, so --ks-success-* and --ks-warning-* resolve there in
   either browser scheme and are already held to contrast on that surface. */
.ta-access-banner {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  padding: .85rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ks-line);
  border-radius: var(--ks-r-control);
  background: var(--ks-canvas);
}
.ta-access-banner-icon { font-size: 1.1rem; line-height: 1.3; }
.ta-access-banner-text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.ta-access-banner-text strong { font-size: .86rem; font-weight: 800; letter-spacing: -0.01em; }
.ta-access-banner-text span { font-size: .74rem; color: var(--ks-ink-2); line-height: 1.5; }

.ta-access-banner-private {
  border-color: var(--ks-success-line);
  background: var(--ks-success-bg);
}
.ta-access-banner-private strong { color: var(--ks-success); }

/* Amber, not red. Public is a legitimate state that most dojos want — it is
   the DEFAULT — so this reports it plainly rather than as a fault. Red here
   would tell every ungated dojo that something is wrong with it. */
.ta-access-banner-public {
  border-color: var(--ks-warning-line);
  background: var(--ks-warning-bg);
}
.ta-access-banner-public strong { color: var(--ks-warning); }

/* ── The two-state choice ──────────────────────────────────────────────────
   Both options always visible and always labelled, which a single "Turn On"
   button cannot do: that label leaves the reader to work out whether it names
   the current state or the action. Radios, so the semantics and the keyboard
   behaviour come free. */
.ta-access-choice { border: 0; margin: 0 0 1rem; padding: 0; }
.ta-access-choice-legend {
  padding: 0;
  margin-bottom: .5rem;
  font-size: .72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ks-ink-2);
}
.ta-access-choice-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .6rem;
  max-width: 30rem;
}
.ta-access-option {
  display: flex;
  align-items: flex-start;
  gap: .55rem;
  padding: .7rem .8rem;
  border: 1px solid var(--ks-line-strong);
  border-radius: 10px;
  background: var(--ks-surface);
  cursor: pointer;
}
.ta-access-option input { margin-top: .15rem; width: 16px; height: 16px; cursor: pointer; flex-shrink: 0; }
.ta-access-option-body { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.ta-access-option-title { font-size: .8rem; font-weight: 700; color: var(--ks-ink); }
.ta-access-option-note { font-size: .68rem; color: var(--ks-ink-2); }

/* The selected one is unmistakable at a glance, not merely by the dot. */
.ta-access-option:has(input:checked) {
  border-color: var(--ks-accent);
  box-shadow: 0 0 0 1px var(--ks-accent) inset;
}
.ta-access-option:has(input:focus-visible) {
  outline: 2px solid var(--ks-accent);
  outline-offset: 2px;
}
.ta-access-option:has(input:disabled) { cursor: default; opacity: .55; }
.ta-access-choice-busy { opacity: .6; }

@media (max-width: 640px) {
  .ta-access-choice-row { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  .ta-settings-grid, .ta-switch-grid { grid-template-columns: 1fr; }
  .ta-row-actions { justify-content: flex-start; }
}

/* ── Reported-content queue (admin-tab-reports) ──────────────────────────
   The reporter's note renders on sunken ground so it reads as testimony,
   not UI — same treatment the platform console gives case notes. */
.admin-reports-intro { font-size: .78rem; color: var(--ks-ink-2); margin: 0 0 .85rem; max-width: 62ch; }
.admin-reports-empty { font-size: .8rem; color: var(--ks-ink-2); padding: .75rem 0; }

.admin-report-card {
  border: 1px solid var(--ks-line-strong);
  border-radius: 12px;
  background: var(--ks-surface);
  padding: .7rem .85rem;
  margin-bottom: .6rem;
}

.admin-report-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .35rem; }

.admin-report-status {
  font-size: .62rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--ks-surface-sunken);
  color: var(--ks-ink-2);
}
/* Open work is the work; decided reports recede. */
.admin-report-status-new,
.admin-report-status-reviewing {
  background: var(--ks-accent-soft, var(--ks-surface-sunken));
  color: var(--ks-accent-strong);
}

.admin-report-reason { font-size: .8rem; font-weight: 700; color: var(--ks-ink); }
.admin-report-when { font-size: .7rem; color: var(--ks-ink-3); }

.admin-report-note {
  background: var(--ks-surface-sunken);
  border-radius: 8px;
  padding: .45rem .6rem;
  font-size: .8rem;
  color: var(--ks-ink);
  overflow-wrap: anywhere;
  margin-bottom: .45rem;
}

.admin-report-conflict { font-size: .74rem; color: var(--ks-ink-2); margin-bottom: .45rem; }
.admin-report-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* The exact reported comment, unmistakable on the card: accent-edged where
   the reporter's note above it is sunken, labeled so the two member-authored
   texts can never be read as each other. */
.admin-report-subject-comment {
  border: 1px solid var(--ks-line-strong);
  border-left: 3px solid var(--ks-accent);
  border-radius: 8px;
  padding: .5rem .6rem;
  margin-bottom: .45rem;
}
.admin-report-subject-label {
  display: block;
  font-size: .62rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ks-accent-strong);
  margin-bottom: .2rem;
}
.admin-report-subject-meta { display: flex; gap: .5rem; align-items: baseline; margin-bottom: .15rem; }
.admin-report-subject-author { font-size: .74rem; font-weight: 700; color: var(--ks-ink); }
.admin-report-subject-time { font-size: .68rem; color: var(--ks-ink-3); }
.admin-report-subject-text { font-size: .82rem; color: var(--ks-ink); overflow-wrap: anywhere; }
.admin-report-subject-gone { font-size: .76rem; color: var(--ks-ink-2); font-style: italic; }

/* Handled reports fold away — acting on a report is what archives it. */
.admin-reports-handled { margin-top: .85rem; }
.admin-reports-handled-summary {
  cursor: pointer;
  font-size: .78rem;
  font-weight: 700;
  color: var(--ks-ink-2);
  padding: .35rem 0;
}
.admin-reports-handled-summary:focus-visible { outline: 2px solid var(--ks-accent); outline-offset: 2px; }
.admin-reports-handled[open] .admin-reports-handled-summary { margin-bottom: .5rem; }


/* ══════════════════════════════════════════════════════════════════════
   dojo-tenant-admin-promotional.v1.css
   ══════════════════════════════════════════════════════════════════════ */
    /* ──────────────────────────────────────────────────────────────────────────
       Tenant admin — team preview content.

       Token-driven throughout, so this surface follows the same light/dark
       theme as the rest of the dashboard rather than pinning its own palette.

       Status is carried by a chip with its own colour AND its own word. Colour
       alone would leave "live" and "draft" indistinguishable to a reader who
       cannot tell them apart, and this is the one screen where confusing the
       two publishes something.
       ────────────────────────────────────────────────────────────────────────── */

    #promo-admin-root {
      display: flex;
      flex-direction: column;
      gap: 1.5rem;
      max-width: var(--ks-measure, 72rem);
    }

    .promo-intro-title {
      margin: 0 0 .4rem;
      font-size: 1.35rem;
      font-weight: 800;
      letter-spacing: -.01em;
      color: var(--ks-ink);
    }

    .promo-intro-lede {
      margin: 0 0 .35rem;
      max-width: var(--ks-measure-narrow, 60ch);
      color: var(--ks-ink-2);
      line-height: 1.6;
    }

    .promo-intro-note {
      margin: 0;
      font-size: .85rem;
      color: var(--ks-ink-3);
    }

    /* ── Composer ───────────────────────────────────────────────────────────── */
    .promo-card {
      padding: 1.25rem;
      border: 1px solid var(--ks-line);
      border-radius: 14px;
      background: var(--ks-surface-raised, var(--ks-surface));
      box-shadow: var(--ks-shadow-1);
    }

    .promo-card-title {
      margin: 0 0 .3rem;
      font-size: 1.02rem;
      font-weight: 800;
      color: var(--ks-ink);
    }

    .promo-card-lede {
      margin: 0 0 1rem;
      font-size: .88rem;
      color: var(--ks-ink-2);
    }

    .promo-editing-note {
      margin: 0 0 1rem;
      padding: .5rem .75rem;
      border-left: 3px solid var(--ks-accent);
      border-radius: 6px;
      background: var(--ks-accent-soft, var(--ks-surface-sunken));
      font-size: .85rem;
      color: var(--ks-ink);
    }

    .promo-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
      gap: 1rem;
      margin-bottom: 1rem;
    }

    .promo-field { margin-bottom: 1rem; }
    .promo-grid .promo-field { margin-bottom: 0; }

    .promo-label {
      display: block;
      margin-bottom: .35rem;
      font-size: .78rem;
      font-weight: 700;
      letter-spacing: .04em;
      text-transform: uppercase;
      color: var(--ks-ink-2);
    }

    .promo-control {
      width: 100%;
      padding: .6rem .75rem;
      border: 1px solid var(--ks-line);
      border-radius: 9px;
      background: var(--ks-surface);
      color: var(--ks-ink);
      font: inherit;
      font-size: .95rem;
    }

    .promo-control:focus-visible {
      outline: var(--ks-focus-ring, 2px solid var(--ks-accent));
      outline-offset: 2px;
      border-color: var(--ks-accent);
    }

    .promo-textarea {
      resize: vertical;
      min-height: 7rem;
      line-height: 1.55;
    }

    .promo-hint {
      margin: .35rem 0 0;
      font-size: .8rem;
      line-height: 1.5;
      color: var(--ks-ink-3);
    }

    .promo-actions {
      display: flex;
      flex-wrap: wrap;
      gap: .6rem;
      margin-top: .25rem;
    }

    /* ── List ───────────────────────────────────────────────────────────────── */
    .promo-list {
      display: flex;
      flex-direction: column;
      gap: 1.5rem;
    }

    .promo-group-head {
      display: flex;
      align-items: baseline;
      gap: .55rem;
    }

    .promo-group-title {
      margin: 0;
      font-size: 1rem;
      font-weight: 800;
      color: var(--ks-ink);
    }

    .promo-group-count {
      padding: .1rem .5rem;
      border-radius: 999px;
      background: var(--ks-surface-sunken);
      font-size: .75rem;
      font-weight: 700;
      color: var(--ks-ink-2);
    }

    .promo-group-hint {
      margin: .25rem 0 .85rem;
      font-size: .82rem;
      color: var(--ks-ink-3);
    }

    .promo-item {
      padding: 1rem 1.1rem;
      margin-bottom: .75rem;
      border: 1px solid var(--ks-line);
      border-radius: 12px;
      background: var(--ks-surface-raised, var(--ks-surface));
    }

    .promo-item:last-child { margin-bottom: 0; }

    .promo-item-head {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
      gap: .6rem;
    }

    .promo-item-title {
      margin: 0;
      font-size: .98rem;
      font-weight: 750;
      color: var(--ks-ink);
    }

    .promo-chips {
      display: flex;
      flex-wrap: wrap;
      gap: .35rem;
    }

    .promo-chip {
      padding: .18rem .55rem;
      border: 1px solid var(--ks-line-soft, var(--ks-line));
      border-radius: 999px;
      background: var(--ks-surface-sunken);
      font-size: .7rem;
      font-weight: 700;
      letter-spacing: .02em;
      color: var(--ks-ink-2);
      white-space: nowrap;
    }

    .promo-chip-published {
      border-color: var(--ks-success-line);
      background: var(--ks-success-bg);
      color: var(--ks-success);
    }

    .promo-chip-draft {
      border-color: var(--ks-warning-line);
      background: var(--ks-warning-bg);
      color: var(--ks-warning);
    }

    .promo-item-body {
      margin: .6rem 0 0;
      max-width: var(--ks-measure-narrow, 60ch);
      font-size: .9rem;
      line-height: 1.6;
      color: var(--ks-ink-2);
      white-space: pre-wrap;
    }

    .promo-item-actions {
      display: flex;
      flex-wrap: wrap;
      gap: .5rem;
      margin-top: .85rem;
    }

    /* ── Empty ──────────────────────────────────────────────────────────────── */
    .promo-empty-state {
      padding: 2rem 1.25rem;
      border: 1px dashed var(--ks-line);
      border-radius: 12px;
      background: var(--ks-surface-sunken);
      text-align: center;
    }

    .promo-empty {
      margin: 0;
      font-weight: 700;
      color: var(--ks-ink-2);
    }

    .promo-empty-hint {
      margin: .4rem auto 0;
      max-width: 46ch;
      font-size: .87rem;
      line-height: 1.55;
      color: var(--ks-ink-3);
    }

    @media (max-width: 600px) {
      .promo-item-head { align-items: flex-start; flex-direction: column; }
    }

    /* What is attached to a draft. Small on purpose: this is a management
       screen, and the item's own words are what an administrator is checking
       here. The preview is where it is meant to be looked at. */
    .promo-item-figure {
      margin-top: .85rem;
    }

    .promo-item-figure img {
      display: block;
      max-width: 220px;
      max-height: 140px;
      width: auto;
      height: auto;
      border: 1px solid var(--ks-line);
      border-radius: 10px;
      object-fit: cover;
      background: var(--ks-surface-sunken);
    }

    /* The runtime marks a reference it could not resolve. A broken frame says
       more than a gap does: somebody has to know the image is not arriving. */
    .promo-item-figure img[data-media-state="error"] {
      display: none;
    }


/* ══════════════════════════════════════════════════════════════════════
   ks-admin-surface-components.v1.css
   ══════════════════════════════════════════════════════════════════════ */
    /* ══════════════════════════════════════════════════════════════════════════
       ADMIN SURFACE COMPONENTS — the shared controls both operator dashboards
       (#view-admin and #view-platform-owner) are converging on: one switch and
       one confirm/input dialog, instead of the five boolean idioms and the
       native prompt()/confirm() calls they replace.

       Same charter as dojo-tenant-surface-components.v1.css: reads only --ks-*
       names, class-based and view-agnostic, styles no legacy class — it cannot
       change a page that has not adopted it. Kept separate from that file on
       purpose: these are operator-dashboard components, and this file lives in
       the small dashboards bundle rather than the largest one.
       ══════════════════════════════════════════════════════════════════════════ */

    /* ══ SWITCH ═════════════════════════════════════════════════════════════════
       A styled native checkbox — deliberately not a button with role="switch".
       The checkbox is keyboard-correct, label-activatable and form-readable
       with no JavaScript at all; the only script a call site needs is reading
       `.checked` in its own handler. Markup contract (child order is load-
       bearing — the CSS selects `:checked + .ks-switch-track`):

         <label class="ks-switch ks-switch-row">
           <input type="checkbox" class="ks-switch-input" id="…">
           <span class="ks-switch-track" aria-hidden="true">
             <span class="ks-switch-thumb"></span></span>
           <span class="ks-switch-label">Show in Homepage Banner
             <small class="ks-switch-note">optional second line</small></span>
         </label>

       Sites where flipping takes immediate effect (the platform console rows,
       which call the server on change) add role="switch" to the input; save-
       buffered form sites stay plain checkboxes. Same CSS either way.
       ═══════════════════════════════════════════════════════════════════════════ */
    .ks-switch {
      /* The off-track grey is the one deliberate literal in this file. There is
         no global control-state token group yet — the .cal-toggle block this
         replaces recorded the same debt — so the value is a component-local
         custom property a future token pass can re-point in one line. */
      --ks-switch-off: #d1d5db;
      position: relative;
      display: flex;
      align-items: center;
      gap: .7rem;
      cursor: pointer;
      -webkit-user-select: none;
      user-select: none;
    }
    /* The real control, invisible but covering the whole label: every pointer,
       touch and test-runner interaction lands on the actual checkbox rather
       than on decoration that forwards to it. */
    .ks-switch-input {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      margin: 0;
      opacity: 0;
      cursor: inherit;
    }
    .ks-switch-track {
      display: inline-flex;
      align-items: center;
      flex-shrink: 0;
      width: 36px;
      height: 20px;
      border-radius: var(--ks-r-pill);
      background: var(--ks-switch-off);
      transition: background .15s ease;
      /* Decoration only — never a hit target. Without this, the thumb's
         transform promotes the track subtree above the absolutely-positioned
         input in paint order and intercepts the click aimed at the control. */
      pointer-events: none;
    }
    .ks-switch-thumb {
      width: 16px;
      height: 16px;
      margin-left: 2px;
      border-radius: 50%;
      background: var(--ks-surface);
      box-shadow: 0 1px 2px rgba(12, 23, 48, .25);
      transition: transform .15s ease;
    }
    .ks-switch-input:checked + .ks-switch-track { background: var(--ks-accent); }
    .ks-switch-input:checked + .ks-switch-track .ks-switch-thumb { transform: translateX(16px); }
    .ks-switch-input:focus-visible + .ks-switch-track {
      outline: 2px solid var(--ks-focus-ring);
      outline-offset: 2px;
    }
    .ks-switch-label {
      font-size: .82rem;
      color: var(--ks-ink);
      line-height: 1.35;
    }
    .ks-switch-note {
      display: block;
      font-size: .7rem;
      color: var(--ks-ink-3);
    }

    /* Three non-interactive states, visually distinct because they mean
       different things:
       — :disabled alone: cannot be flipped right now (generic).
       — .ks-switch-busy: an operation this switch started is in flight; the
         cursor says "working", the control dims, the position may revert.
       — .ks-switch-static: shows a stored fact about a feature that is not
         connected yet. Full strength on purpose — the position is true and
         readable; only the affordance is withdrawn. The honest treatment for
         a control that is a statement, not an input. */
    .ks-switch:has(.ks-switch-input:disabled) { cursor: not-allowed; }
    .ks-switch:has(.ks-switch-input:disabled) .ks-switch-track { opacity: .55; }
    .ks-switch-busy:has(.ks-switch-input:disabled) { cursor: progress; }
    .ks-switch-static:has(.ks-switch-input:disabled) { cursor: default; }
    .ks-switch-static:has(.ks-switch-input:disabled) .ks-switch-track {
      opacity: 1;
      background: var(--ks-line-strong);
    }
    .ks-switch-static .ks-switch-input:checked + .ks-switch-track { background: var(--ks-ink-3); }

    /* Row variant: a full-width bordered row, words left, switch right — the
       shape the calendar/announcement "banner rows" had, now operable. */
    .ks-switch-row {
      justify-content: space-between;
      padding: .55rem .75rem;
      border: 1px solid var(--ks-line);
      border-radius: var(--ks-r-sm);
      background: var(--ks-surface);
    }
    .ks-switch-row:hover { background: var(--ks-canvas); }
    .ks-switch-row .ks-switch-label { order: 1; }
    .ks-switch-row .ks-switch-track { order: 2; }

    /* Inline variant: compact, switch left, words right — form-grid density. */
    .ks-switch-inline { gap: .55rem; }

    /* ══ DIALOG ═════════════════════════════════════════════════════════════════
       The confirm/input dialog window.ksDialog builds (modules/confirm-dialog.mjs).
       Native <dialog> + showModal(): focus containment, Escape and the top
       layer come from the platform, not from script. One singleton, reused.
       ═══════════════════════════════════════════════════════════════════════════ */
    dialog.ks-dialog {
      width: min(440px, calc(100vw - 2rem));
      padding: 0;
      border: 0;
      border-radius: var(--ks-r-lg);
      background: var(--ks-surface);
      color: var(--ks-ink);
      box-shadow: var(--ks-e3);
    }
    dialog.ks-dialog::backdrop {
      /* ::backdrop custom-property inheritance is still uneven across the
         support floor, so the scrim is a literal: the e4 shadow's navy at a
         scrim opacity. */
      background: rgba(12, 23, 48, .45);
    }
    .ks-dialog-form {
      display: grid;
      gap: .8rem;
      padding: 1.3rem 1.4rem 1.2rem;
    }
    .ks-dialog-title {
      margin: 0;
      font-size: .95rem;
      font-weight: 700;
      letter-spacing: .01em;
    }
    .ks-dialog-danger .ks-dialog-title { color: var(--ks-danger); }
    .ks-dialog-message {
      margin: 0;
      font-size: .82rem;
      line-height: 1.5;
      color: var(--ks-ink-2);
      /* prompt() call sites carried \n\n paragraph breaks; keep them. */
      white-space: pre-line;
      overflow-wrap: anywhere;
    }
    .ks-dialog-field { display: grid; gap: .3rem; }
    .ks-dialog-field-label {
      font-size: .72rem;
      font-weight: 600;
      color: var(--ks-ink-2);
    }
    .ks-dialog-field input,
    .ks-dialog-field select,
    .ks-dialog-field textarea {
      font: inherit;
      font-size: .82rem;
      color: var(--ks-ink);
      background: var(--ks-surface);
      border: 1px solid var(--ks-line-strong);
      border-radius: var(--ks-r-control-sm);
      padding: .5rem .6rem;
    }
    .ks-dialog-field textarea { resize: vertical; min-height: 4.2em; }
    .ks-dialog-field input:focus-visible,
    .ks-dialog-field select:focus-visible,
    .ks-dialog-field textarea:focus-visible {
      outline: 2px solid var(--ks-focus-ring);
      outline-offset: 1px;
    }
    .ks-dialog-error {
      margin: 0;
      font-size: .72rem;
      color: var(--ks-danger);
    }
    .ks-dialog-error:empty { display: none; }
    .ks-dialog-actions {
      display: flex;
      justify-content: flex-end;
      gap: .6rem;
      margin-top: .3rem;
    }

    /* ══ SECTION NAV ════════════════════════════════════════════════════════════
       The map over a long card stack — group headings in the page, and a jump
       nav that always says where you are. Built by ks-section-nav.v1.js;
       markup is generated, so the contract is the class names alone.

       Same charter as everything in this file: --ks-* tokens only, class-based,
       view-agnostic, styles no legacy class.
       ═══════════════════════════════════════════════════════════════════════════ */
    .ks-secnav {
      position: sticky;
      /* Below the admin topbar, which is itself sticky. The variable is the
         shell's own; a page without it just sticks to the top. */
      top: calc(var(--adm-nav-h, 0px) + .5rem);
      z-index: 5;
      margin-bottom: 1rem;
    }
    .ks-secnav-rail {
      display: flex;
      flex-wrap: wrap;
      gap: .35rem;
      padding: .4rem;
      border: 1px solid var(--ks-line);
      border-radius: var(--ks-r-pill, 999px);
      background: color-mix(in oklab, var(--ks-surface) 88%, transparent);
      backdrop-filter: blur(6px);
      -webkit-backdrop-filter: blur(6px);
      width: fit-content;
      max-width: 100%;
    }
    .ks-secnav-link {
      appearance: none;
      border: 1px solid transparent;
      background: transparent;
      color: var(--ks-ink-2);
      font: inherit;
      font-size: .74rem;
      font-weight: 600;
      padding: .35rem .8rem;
      border-radius: var(--ks-r-pill, 999px);
      cursor: pointer;
      /* 44px touch minimum is met by the row's padding envelope; the pill
         itself stays visually light. */
      min-height: 32px;
    }
    .ks-secnav-link:hover { color: var(--ks-ink); background: var(--ks-surface-sunken); }
    .ks-secnav-link:focus-visible { outline: 2px solid var(--ks-focus-ring); outline-offset: 2px; }
    /* aria-current is the state; the style only reads it. */
    .ks-secnav-link[aria-current="true"] {
      color: var(--ks-accent-strong);
      background: var(--ks-surface);
      border-color: var(--ks-line-strong);
      box-shadow: var(--ks-shadow-1, 0 1px 2px rgba(12, 23, 48, .08));
    }

    /* The phone form: same map, native picker. The rail and the picker swap at
       the shell's own rail breakpoint. */
    .ks-secnav-picker { display: none; width: 100%; }
    @media (max-width: 1099px) {
      .ks-secnav-rail { display: none; }
      .ks-secnav-picker { display: block; }
    }

    /* The in-page group heading the nav jumps to. scroll-margin keeps the
       landing clear of the sticky bars, at EVERY width — the media-query-only
       version of this mistake is in the memory book. */
    .ks-secnav-heading {
      margin: 2.2rem 0 .9rem;
      padding-top: .4rem;
      font-size: .7rem;
      font-weight: 800;
      letter-spacing: .09em;
      text-transform: uppercase;
      color: var(--ks-ink-3);
      border-top: 1px solid var(--ks-line);
      scroll-margin-top: calc(var(--adm-nav-h, 0px) + 4.5rem);
    }
    .ks-secnav-heading:first-of-type { margin-top: 1rem; border-top: none; }
    /* The always-display + [hidden] pairing rule, applied at birth. */
    .ks-secnav-heading[hidden] { display: none; }
    .ks-secnav[hidden] { display: none; }

    /* ══ ATTENTION STRIP ════════════════════════════════════════════════════════
       What needs an administrator today, as chips that link to the work.
       Shared surface: the dojo Overview mounts it first; a platform overview
       can mount the same classes over its own counts. Renders nothing when
       nothing needs attention — the empty state IS the design.
       ═══════════════════════════════════════════════════════════════════════════ */
    .ks-attention {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: .5rem;
      margin: 0 0 1.1rem;
    }
    .ks-attention[hidden] { display: none; }
    .ks-attention-label {
      font-size: .7rem;
      font-weight: 800;
      letter-spacing: .08em;
      text-transform: uppercase;
      color: var(--ks-warning);
      margin-right: .25rem;
    }
    .ks-attention-chip {
      appearance: none;
      display: inline-flex;
      align-items: center;
      gap: .45rem;
      border: 1px solid var(--ks-warning-line);
      background: var(--ks-warning-bg);
      color: var(--ks-warning);
      font: inherit;
      font-size: .76rem;
      font-weight: 600;
      padding: .4rem .8rem;
      border-radius: var(--ks-r-pill, 999px);
      cursor: pointer;
      min-height: 36px;
    }
    .ks-attention-chip:hover { border-color: var(--ks-warning); }
    .ks-attention-chip:focus-visible { outline: 2px solid var(--ks-focus-ring); outline-offset: 2px; }
    .ks-attention-count {
      font-weight: 800;
      font-size: .84rem;
      font-variant-numeric: tabular-nums;
    }

    /* ══ VIEWING BANNER ═════════════════════════════════════════════════════════
       The platform owner inside a customer's dojo: whose site this is, and the
       way back. One strip, deliberately quiet — it states context, it does not
       alarm. Band tokens, because it belongs to the PLATFORM chrome rather
       than to the tenant's theme. */
    .ks-viewing-banner {
      display: flex;
      align-items: center;
      justify-content: center;
      flex-wrap: wrap;
      gap: .6rem;
      padding: .45rem 1rem;
      background: var(--ks-band);
      color: var(--ks-band-ink-2, #cbd5e1);
      font-size: .74rem;
    }
    .ks-viewing-banner[hidden] { display: none; }
    .ks-viewing-banner-text strong { color: var(--ks-band-ink, #fff); font-weight: 700; }
    .ks-viewing-banner-return {
      appearance: none;
      border: 1px solid color-mix(in oklab, var(--ks-band-ink, #fff) 35%, transparent);
      background: transparent;
      color: var(--ks-band-ink, #fff);
      font: inherit;
      font-size: .72rem;
      font-weight: 700;
      padding: .3rem .75rem;
      border-radius: var(--ks-r-pill, 999px);
      cursor: pointer;
      min-height: 30px;
    }
    .ks-viewing-banner-return:hover { background: color-mix(in oklab, var(--ks-band-ink, #fff) 12%, transparent); }
    .ks-viewing-banner-return:focus-visible { outline: 2px solid var(--ks-focus-ring); outline-offset: 2px; }


/* ══════════════════════════════════════════════════════════════════════
   ks-communications-admin.v1.css
   ══════════════════════════════════════════════════════════════════════ */
/* Communications card — the GroupMe bridge in admin Settings.
 *
 * Rendered by ks-communications-admin.v1.js into #admin-communications.
 * Same design grammar as the Team Access panel one section up: the
 * connection state is a full-width tinted BANNER (that panel's comment
 * records why a pill was not enough), fields use the shared .ta-field
 * grid, switches are the shared ks-switch component. Tokens only —
 * #view-admin opts into the light token block, so --ks-success-* and
 * --ks-warning-* already hold contrast on this surface in both schemes. */

.ks-comm-banner {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  padding: .85rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ks-line);
  border-radius: 10px;
  background: var(--ks-canvas);
}
.ks-comm-banner-icon .ks-icon { width: 18px; height: 18px; }
.ks-comm-banner-copy { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.ks-comm-banner-copy strong { font-size: .86rem; font-weight: 800; letter-spacing: -0.01em; }
.ks-comm-banner-detail { font-size: .74rem; color: var(--ks-ink-2); line-height: 1.5; }

.ks-comm-banner-connected {
  border-color: var(--ks-success-line);
  background: var(--ks-success-bg);
}
.ks-comm-banner-connected strong,
.ks-comm-banner-connected .ks-comm-banner-icon { color: var(--ks-success); }

/* Neutral, not amber: disconnected is the ordinary resting state of a dojo
   that has not chosen GroupMe, and most never will. Warning color here would
   tell every one of them something is wrong. */
.ks-comm-banner-disconnected .ks-comm-banner-icon { color: var(--ks-ink-3); }

.ks-comm-banner-error {
  border-color: var(--ks-warning-line);
  background: var(--ks-warning-bg);
}
.ks-comm-banner-error strong,
.ks-comm-banner-error .ks-comm-banner-icon { color: var(--ks-warning); }

.ks-comm-categories { margin: .35rem 0 .5rem; }

.ks-comm-sends {
  margin-top: 1rem;
  border-top: 1px solid var(--ks-line);
  padding-top: .7rem;
}
.ks-comm-sends-title {
  font-size: .7rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ks-ink-3);
  margin-bottom: .45rem;
}
.ks-comm-send-row {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .3rem 0;
  font-size: .76rem;
  min-width: 0;
}
.ks-comm-send-kind { font-weight: 700; white-space: nowrap; }
.ks-comm-send-preview {
  color: var(--ks-ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}
.ks-comm-send-status { font-size: .7rem; white-space: nowrap; color: var(--ks-ink-3); }
.ks-comm-send-status-sent { color: var(--ks-success); }
.ks-comm-send-status-failed { color: var(--ks-danger); }

/* Required by the GroupMe API license; kept quiet but legible. */
.ks-comm-attribution {
  margin: 1rem 0 0;
  font-size: .68rem;
  color: var(--ks-ink-3);
}


/* ══════════════════════════════════════════════════════════════════════
   ks-comms-phone-admin.v1.css
   ══════════════════════════════════════════════════════════════════════ */
/* Communications (phone & SMS) card — the dojo's business line in admin Settings.
 *
 * Rendered by ks-comms-phone-admin.v1.js into #admin-comms-phone. Same design
 * grammar as the GroupMe card one section up and the Team Access panel above
 * that: state is a full-width tinted BANNER rather than a pill, because a real
 * dojo owner missed the pill and that lesson is inherited here rather than
 * relearned. Tokens only — #view-admin opts into the light token block, so the
 * success and warning families already hold contrast in both schemes. */

.ks-commsphone-banner {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  padding: .85rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ks-line);
  border-radius: 10px;
  background: var(--ks-canvas);
}
.ks-commsphone-banner-copy { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.ks-commsphone-banner-copy strong { font-size: .86rem; font-weight: 800; letter-spacing: -0.01em; }
.ks-commsphone-banner-detail { font-size: .74rem; color: var(--ks-ink-2); line-height: 1.5; }

.ks-commsphone-banner-live {
  border-color: var(--ks-success-line);
  background: var(--ks-success-bg);
}
.ks-commsphone-banner-live strong { color: var(--ks-success); }

/* PARTIAL IS NOT A WARNING. "Phone live, texting still being approved" is the
   ordinary, expected middle of onboarding — every dojo passes through it, and
   most sit in it for days. Amber here would tell every one of them something is
   wrong when the honest answer is "this is going fine, wait". Neutral canvas
   with an accent rule: visible, unalarming. */
.ks-commsphone-banner-partial {
  border-color: var(--ks-line);
  border-left: 3px solid var(--ks-accent, var(--ks-ink-3));
}
.ks-commsphone-banner-partial strong { color: var(--ks-ink); }

/* Idle is the resting state of a dojo that has not bought a phone. Neutral for
   the same reason the GroupMe card's disconnected state is. */
.ks-commsphone-banner-idle strong { color: var(--ks-ink-2); }

.ks-commsphone-banner-attention {
  border-color: var(--ks-warning-line);
  background: var(--ks-warning-bg);
}
.ks-commsphone-banner-attention strong { color: var(--ks-warning); }

/* ── what the line can do, right now ─────────────────────────────────────── */

.ks-commsphone-caps {
  list-style: none;
  margin: 0 0 1.1rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem .9rem;
}
.ks-commsphone-caps li {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: .76rem;
  color: var(--ks-ink-2);
}
.ks-commsphone-cap-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ks-ink-3);
  flex: none;
}
.ks-commsphone-cap-on { color: var(--ks-ink); }
.ks-commsphone-cap-on .ks-commsphone-cap-dot { background: var(--ks-success); }
.ks-commsphone-cap-off .ks-commsphone-cap-dot { opacity: .45; }
.ks-commsphone-cap-note { font-size: .68rem; color: var(--ks-ink-3); font-style: italic; }

/* ── setup steps ─────────────────────────────────────────────────────────── */

.ks-commsphone-subtitle {
  margin: 0 0 .5rem;
  font-size: .74rem;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ks-ink-3);
}

.ks-commsphone-steps { margin-bottom: 1.1rem; }
.ks-commsphone-step-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .1rem;
}
.ks-commsphone-step {
  padding: .5rem .7rem;
  border-radius: 8px;
  border-left: 2px solid var(--ks-line);
}
.ks-commsphone-step-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .8rem;
}
.ks-commsphone-step-label { font-size: .8rem; font-weight: 700; color: var(--ks-ink); }
.ks-commsphone-step-state {
  font-size: .69rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--ks-ink-3);
  flex: none;
}
.ks-commsphone-step-note {
  display: block;
  margin-top: .12rem;
  font-size: .7rem;
  color: var(--ks-ink-3);
  line-height: 1.45;
}

.ks-commsphone-step-done { border-left-color: var(--ks-success); }
.ks-commsphone-step-done .ks-commsphone-step-state { color: var(--ks-success); }

/* WAITING AND ATTENTION MUST NOT LOOK ALIKE. Waiting is somebody else's turn
   and needs nothing from the dojo; attention means a registration was declined
   and will sit there forever until a person changes something. Rendering both
   as "in progress" is how a dojo waits three weeks for an approval that was
   refused on day one. */
.ks-commsphone-step-waiting { border-left-color: var(--ks-ink-3); }
.ks-commsphone-step-attention {
  border-left-color: var(--ks-warning);
  background: var(--ks-warning-bg);
}
.ks-commsphone-step-attention .ks-commsphone-step-state { color: var(--ks-warning); }
.ks-commsphone-step-idle { border-left-color: transparent; }
.ks-commsphone-step-idle .ks-commsphone-step-label { color: var(--ks-ink-2); }

/* ── numbers ─────────────────────────────────────────────────────────────── */

.ks-commsphone-numbers { margin-bottom: 1.1rem; }
.ks-commsphone-number-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .8rem;
  padding: .45rem 0;
  border-bottom: 1px solid var(--ks-line);
}
.ks-commsphone-number-row:last-child { border-bottom: 0; }
.ks-commsphone-number {
  font-size: .92rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--ks-ink);
  font-variant-numeric: tabular-nums;
}
.ks-commsphone-number-state { font-size: .72rem; color: var(--ks-ink-3); flex: none; }

/* ── usage ───────────────────────────────────────────────────────────────── */

.ks-commsphone-usage-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: .5rem;
  margin-bottom: .5rem;
}
.ks-commsphone-usage-cell {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  padding: .6rem .7rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-canvas);
}
.ks-commsphone-usage-cell strong {
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--ks-ink);
  font-variant-numeric: tabular-nums;
}
.ks-commsphone-usage-cell small { font-size: .69rem; color: var(--ks-ink-3); }

/* The estimate-is-not-a-bill line. Small, present, and never omitted. */
.ks-commsphone-usage-note {
  display: block;
  font-size: .68rem;
  color: var(--ks-ink-3);
  line-height: 1.5;
}

/* Phones: three usage cells do not fit side by side at 360px without the
   numbers colliding with their labels. */
@media (max-width: 480px) {
  .ks-commsphone-usage-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ks-commsphone-step-head { flex-direction: column; gap: .1rem; }
  .ks-commsphone-step-state { align-self: flex-start; }
}

/* ── the settings editor ─────────────────────────────────────────────────── */

.ks-commsphone-editor { margin-bottom: 1.1rem; }

.ks-commsphone-days {
  display: flex;
  flex-direction: column;
  gap: .35rem;
}
.ks-commsphone-day {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr) minmax(0, 1fr);
  align-items: end;
  gap: .5rem;
}
.ks-commsphone-day-label {
  font-size: .74rem;
  font-weight: 800;
  color: var(--ks-ink-2);
  padding-bottom: .45rem;
}
/* 44px on the time inputs: these are tapped on a phone by somebody standing in
   a dojo, and a missed tap here silently changes when the line answers. */
.ks-commsphone-day input[type="time"] { min-height: 44px; }

.ks-commsphone-savebar {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding-top: .3rem;
  margin-bottom: 1.1rem;
}
/* Held outside the panel body it describes, so a re-render cannot throw it away
   mid-read — the rank-history panel's lesson, inherited rather than relearned. */
.ks-commsphone-note { font-size: .74rem; color: var(--ks-ink-2); }

@media (max-width: 480px) {
  /* Stacked: three columns at 360px puts two time inputs in ~90px each, which
     is not tappable and not readable. */
  .ks-commsphone-day { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .ks-commsphone-day-label { grid-column: 1 / -1; padding-bottom: 0; }
}

/* ── who can use the phone ───────────────────────────────────────────────── */

.ks-commsphone-staff {
  padding: .7rem 0;
  border-bottom: 1px solid var(--ks-line);
}
.ks-commsphone-staff:last-child { border-bottom: 0; }
.ks-commsphone-staff-head {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  margin-bottom: .4rem;
}
.ks-commsphone-staff-name { font-size: .84rem; font-weight: 800; color: var(--ks-ink); }
.ks-commsphone-staff-role {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ks-ink-3);
}

.ks-commsphone-staff-caps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: .3rem .8rem;
}
/* A whole row is the hit target, not just the 13px box — this is ticked on a
   phone, and a missed tap on a permission control is a permission not granted
   that the admin believes they granted. */
.ks-commsphone-cap-check {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  min-height: 44px;
  padding: .3rem .4rem;
  border-radius: 6px;
  cursor: pointer;
}
.ks-commsphone-cap-check:hover { background: var(--ks-canvas); }
.ks-commsphone-cap-check input { margin-top: .18rem; flex: none; width: 16px; height: 16px; }
.ks-commsphone-cap-check input:focus-visible {
  outline: 2px solid var(--ks-focus, var(--ks-accent));
  outline-offset: 2px;
}
.ks-commsphone-cap-check span { display: flex; flex-direction: column; font-size: .78rem; color: var(--ks-ink); }
.ks-commsphone-cap-check small { font-size: .68rem; color: var(--ks-ink-3); line-height: 1.4; }
.ks-commsphone-cap-check input[disabled] + span { opacity: .55; }

/* ── contacts ────────────────────────────────────────────────────────────── */

.ks-commsphone-contacts-summary {
  margin: 0 0 .6rem;
  font-size: .78rem;
  color: var(--ks-ink-2);
  line-height: 1.5;
}
.ks-commsphone-contacts-summary strong { color: var(--ks-ink); font-weight: 800; }

.ks-commsphone-contacts {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--ks-line);
  border-radius: 10px;
  overflow: hidden;
}
.ks-commsphone-contact {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .8rem;
  padding: .55rem .8rem;
  border-bottom: 1px solid var(--ks-line);
}
.ks-commsphone-contact:last-child { border-bottom: 0; }
.ks-commsphone-contact-number {
  font-size: .84rem;
  font-weight: 700;
  color: var(--ks-ink);
  font-variant-numeric: tabular-nums;
}
.ks-commsphone-contact-state { font-size: .7rem; color: var(--ks-ink-3); flex: none; }
/* Dimmed AND labelled. A row that is only greyed out reads as loading; the word
   is what says the number is fine and the permission is not. */
.ks-commsphone-contact-blocked .ks-commsphone-contact-number { opacity: .6; }
.ks-commsphone-contact-stopped { color: var(--ks-warning); font-weight: 700; }

.ks-commsphone-optout {
  margin-top: 1rem;
  padding-top: .8rem;
  border-top: 1px solid var(--ks-line);
}
.ks-commsphone-optout .ta-field { margin-bottom: .5rem; }
.ks-commsphone-optout input { min-height: 44px; }

/* ── onboarding ──────────────────────────────────────────────────────────── */

/* A field the registry will reject, marked before it costs a fee and days. */
.ks-commsphone-field-bad input {
  border-color: var(--ks-warning);
  background: var(--ks-warning-bg);
}
.ks-commsphone-field-bad > span { color: var(--ks-warning); font-weight: 700; }

.ks-commsphone-number-choice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .5rem;
  margin-bottom: .8rem;
}
.ks-commsphone-choice {
  display: flex;
  flex-direction: column;
  gap: .12rem;
  min-height: 60px;
  padding: .6rem .75rem;
  border: 1px solid var(--ks-line);
  border-radius: 10px;
  background: transparent;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ks-commsphone-choice strong { font-size: .82rem; font-weight: 800; color: var(--ks-ink); }
.ks-commsphone-choice small { font-size: .69rem; color: var(--ks-ink-3); line-height: 1.4; }
.ks-commsphone-choice:focus-visible { outline: 2px solid var(--ks-focus, var(--ks-accent)); outline-offset: 2px; }
.ks-commsphone-choice-on {
  border-color: var(--ks-accent, var(--ks-ink));
  border-width: 2px;
  background: var(--ks-accent-soft, var(--ks-canvas));
}

/* The porting risk, given room. A list of three short lines is read; a
   paragraph of the same words is not, and the third line is the one that saves
   a dojo its phone number. */
.ks-commsphone-risk {
  margin: .3rem 0 0;
  padding-left: 1.1rem;
  font-size: .74rem;
  line-height: 1.55;
  color: var(--ks-ink-2);
}
.ks-commsphone-risk li { margin-bottom: .15rem; }
.ks-commsphone-risk li:last-child { font-weight: 700; color: var(--ks-ink); }

/* Ring destinations: number and label side by side, stacked on a phone. */
.ks-commsphone-ring {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: .5rem;
  margin-bottom: .35rem;
}
.ks-commsphone-ring input { min-height: 44px; }
@media (max-width: 480px) {
  .ks-commsphone-ring { grid-template-columns: minmax(0, 1fr); }
}


/* ══════════════════════════════════════════════════════════════════════
   ks-ai-sensei-admin.v1.css
   ══════════════════════════════════════════════════════════════════════ */
/* AI Sensei card — the dojo's assistant, in admin Settings.
 *
 * Rendered by ks-ai-sensei-admin.v1.js into #admin-ai-sensei. Same design
 * grammar as the Communications card below it and the GroupMe card above:
 * state is a full-width tinted BANNER rather than a pill, because a real dojo
 * owner missed the pill once and that lesson is inherited here rather than
 * relearned. Tokens only — #view-admin opts into the light token block, so the
 * success and warning families already hold contrast in both schemes.
 *
 * The one bespoke thing here is the RESULT block in the test console. It has to
 * do something the rest of the admin surface never does: show an owner what a
 * stranger will be told, in a way that reads as a quotation rather than as
 * another form field. Hence the accent rule and the quieter metadata beneath
 * it — the answer is the content, everything else is apparatus.
 */

/* ── status banner ─────────────────────────────────────────────────────── */

.ks-sensei-banner {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  padding: .85rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ks-line);
  border-radius: 10px;
  background: var(--ks-canvas);
}
.ks-sensei-banner-copy { font-size: .86rem; font-weight: 800; letter-spacing: -0.01em; }
.ks-sensei-banner-detail { font-size: .74rem; color: var(--ks-ink-2); line-height: 1.5; }

.ks-sensei-banner-live { border-color: var(--ks-success-line); background: var(--ks-success-bg); }
.ks-sensei-banner-live .ks-sensei-banner-copy { color: var(--ks-success); }

/* NOT A WARNING. "On, but not on your website yet" is the ordinary middle of
   setting this up — every dojo passes through it, and testing before going
   live is the behaviour we want. Amber would tell them something is wrong. */
.ks-sensei-banner-ready {
  border-color: var(--ks-line);
  border-left: 3px solid var(--ks-accent, var(--ks-ink-3));
}
.ks-sensei-banner-off { border-color: var(--ks-line); background: var(--ks-surface-sunken); }
.ks-sensei-banner-off .ks-sensei-banner-copy { color: var(--ks-ink-2); }

/* ── notes ─────────────────────────────────────────────────────────────── */

.ks-sensei-note {
  padding: .55rem .8rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  font-size: .78rem;
  background: var(--ks-surface);
  color: var(--ks-ink-2);
}
.ks-sensei-note-ok { border-color: var(--ks-success-line); background: var(--ks-success-bg); color: var(--ks-success); }
.ks-sensei-note-bad { border-color: var(--ks-danger-line); background: var(--ks-danger-bg); color: var(--ks-danger); }

/* ── editors ───────────────────────────────────────────────────────────── */

.ks-sensei-editor {
  padding: 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ks-line);
  border-radius: 10px;
  background: var(--ks-surface);
}
.ks-sensei-heading {
  margin: 1.2rem 0 .3rem;
  font-size: .8rem;
  font-weight: 800;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--ks-ink-2);
}
.ks-sensei-editor > .ks-sensei-heading:first-child { margin-top: 0; }

/* ── folded sections ──────────────────────────────────────────
   The card's table of contents. Each section is a titled bar the owner opens
   on purpose; the bar itself carries the count that says whether to bother.
   The toggle is a real button spanning the bar, so the whole line is the
   target — on a phone there is no small chevron to hunt. */
.ks-sensei-fold { padding: 0; }
.ks-sensei-fold-heading { margin: 0; }
.ks-sensei-fold-toggle {
  display: flex;
  align-items: center;
  gap: .6rem;
  width: 100%;
  min-height: 44px;              /* the touch floor, kept even though the bar is compact */
  padding: .7rem .9rem;
  margin: 0;
  background: none;
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  font: inherit;
  text-align: left;
  color: var(--ks-ink);
}
.ks-sensei-fold.is-open .ks-sensei-fold-toggle { border-radius: 10px 10px 0 0; }
.ks-sensei-fold-toggle:hover { background: var(--ks-surface-sunken); }
.ks-sensei-fold-title { font-size: .82rem; font-weight: 800; letter-spacing: .01em; }
.ks-sensei-fold-badge {
  flex: none;
  padding: .12rem .5rem;
  border: 1px solid var(--ks-line);
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 700;
  color: var(--ks-ink-2);
  background: var(--ks-surface-sunken);
}
/* The one badge allowed to raise its voice: work is waiting behind this bar. */
.ks-sensei-fold-badge-live {
  border-color: var(--ks-warning-line);
  background: var(--ks-warning-bg);
  color: var(--ks-warning);
}
.ks-sensei-fold-chevron {
  flex: none;
  margin-left: auto;
  width: .5rem;
  height: .5rem;
  border-right: 2px solid var(--ks-ink-3);
  border-bottom: 2px solid var(--ks-ink-3);
  transform: rotate(45deg);      /* pointing down: "this opens" */
  transition: transform .15s ease;
}
.ks-sensei-fold.is-open .ks-sensei-fold-chevron { transform: rotate(225deg); }
.ks-sensei-fold-body { padding: .2rem .9rem .9rem; }
.ks-sensei-fold-body > .ks-sensei-heading:first-child { margin-top: 0; }
@media (prefers-reduced-motion: reduce) {
  .ks-sensei-fold-chevron { transition: none; }
}
.ks-sensei-help { margin: 0 0 .8rem; font-size: .76rem; line-height: 1.55; color: var(--ks-ink-2); }
.ks-sensei-empty { margin: .8rem 0 0; font-size: .78rem; color: var(--ks-ink-3); }

.ks-sensei-field { margin-bottom: .9rem; }
.ks-sensei-field-label { display: block; font-size: .76rem; font-weight: 700; margin-bottom: .2rem; }
.ks-sensei-field-hint { font-size: .72rem; color: var(--ks-ink-3); margin-bottom: .35rem; line-height: 1.45; }

/* ── choice groups ─────────────────────────────────────────────────────── */

.ks-sensei-choices { display: flex; flex-wrap: wrap; gap: .45rem; }
.ks-sensei-choice {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  flex: 1 1 9rem;
  min-width: 0;
  padding: .5rem .65rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-canvas);
  text-align: left;
  cursor: pointer;
  color: inherit;
}
.ks-sensei-choice:hover { border-color: var(--ks-line-strong); }
.ks-sensei-choice.is-selected {
  border-color: var(--ks-accent, var(--ks-ink));
  /* Two signals, not one. A colour-only selected state is invisible to a
     sizeable minority of owners, and this control has no other affordance. */
  box-shadow: inset 0 0 0 1px var(--ks-accent, var(--ks-ink));
  background: var(--ks-surface);
}
.ks-sensei-choice-label { font-size: .78rem; font-weight: 700; }
.ks-sensei-choice-note { font-size: .7rem; color: var(--ks-ink-3); line-height: 1.4; }

/* ── toggles ───────────────────────────────────────────────────────────── */

.ks-sensei-toggle { display: flex; align-items: flex-start; gap: .55rem; margin-bottom: .7rem; }
.ks-sensei-toggle input { margin-top: .2rem; flex: none; }
.ks-sensei-toggle-copy { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.ks-sensei-toggle-label { font-size: .78rem; font-weight: 700; }
.ks-sensei-toggle-note { font-size: .72rem; color: var(--ks-ink-3); line-height: 1.45; }
/* A control that is off because the dojo has not bought the thing behind it
   must LOOK unavailable, not merely be unavailable — otherwise the note
   explaining why reads as decoration. */
.ks-sensei-toggle input:disabled + .ks-sensei-toggle-copy .ks-sensei-toggle-label { color: var(--ks-ink-3); }

.ks-sensei-savebar { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }

/* ── approved answers ──────────────────────────────────────────────────── */

.ks-sensei-qaform {
  padding: .9rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ks-line-strong);
  border-radius: 8px;
  background: var(--ks-canvas);
}
.ks-sensei-qalist { display: flex; flex-direction: column; gap: .55rem; margin-top: .9rem; }
.ks-sensei-qa {
  padding: .7rem .8rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-canvas);
}
/* The gap queue is a LEDGER, not a stack of cards: seven questions were seven
   full-width bordered boxes with outsized buttons — a page of chrome around
   fourteen words of content. One bordered list, hairline rows, small controls. */
.ks-sensei-gaplist {
  margin-top: .9rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-canvas);
  overflow: hidden;
}
.ks-sensei-gap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem .75rem;
  padding: .55rem .7rem;
}
.ks-sensei-gap + .ks-sensei-gap { border-top: 1px solid var(--ks-line); }
.ks-sensei-qa-q { font-size: .8rem; font-weight: 700; line-height: 1.45; }
.ks-sensei-gap-q { flex: 1 1 14rem; min-width: 0; font-size: .78rem; font-weight: 700; line-height: 1.45; overflow-wrap: anywhere; }
.ks-sensei-qa-a { margin-top: .25rem; font-size: .76rem; color: var(--ks-ink-2); line-height: 1.55; }
.ks-sensei-qa-flag { margin-top: .3rem; font-size: .7rem; font-weight: 700; color: var(--ks-info); }
.ks-sensei-qa-actions { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .55rem; }
.ks-sensei-gap-actions { display: flex; flex-wrap: nowrap; gap: .4rem; margin-left: auto; }
.ks-sensei-gap-actions .ks-btn { padding: .4rem .65rem; font-size: .7rem; white-space: nowrap; }

/* ── test console result ───────────────────────────────────────────────── */

.ks-sensei-result {
  margin-top: 1rem;
  padding: .85rem 1rem;
  border: 1px solid var(--ks-line);
  border-left: 3px solid var(--ks-accent, var(--ks-ink-3));
  border-radius: 8px;
  background: var(--ks-canvas);
}
.ks-sensei-result-bad {
  border-color: var(--ks-warning-line);
  border-left-color: var(--ks-warning);
  background: var(--ks-warning-bg);
}
.ks-sensei-result-bad .ks-sensei-result-text { color: var(--ks-warning); }
/* The answer itself, sized up: it is the content of this panel and everything
   else on the card is apparatus around it. */
.ks-sensei-result-text { font-size: .88rem; line-height: 1.6; white-space: pre-wrap; }
.ks-sensei-result-meta { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .6rem; }
.ks-sensei-result-tag {
  padding: .12rem .45rem;
  border: 1px solid var(--ks-line);
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 700;
  color: var(--ks-ink-2);
  background: var(--ks-surface);
}
.ks-sensei-result-sources { margin-top: .7rem; }
.ks-sensei-result-sources-head {
  margin-top: .7rem;
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ks-ink-3);
}
.ks-sensei-result-source {
  margin-top: .25rem;
  font-size: .74rem;
  color: var(--ks-ink-2);
  line-height: 1.45;
}

/* ── phones ────────────────────────────────────────────────────────────── */

@media (max-width: 640px) {
  .ks-sensei-choice { flex: 1 1 100%; }
  .ks-sensei-savebar .ks-btn,
  .ks-sensei-qa-actions .ks-btn { flex: 1 1 auto; }
  /* Gap rows keep their compact pair — stacked full-width buttons were most
     of the page the owner called a mess — but the touch floor holds. */
  .ks-sensei-gap-actions { margin-left: 0; width: 100%; }
  .ks-sensei-gap-actions .ks-btn { flex: 1 1 auto; min-height: 40px; }
}

/* ── usage counters ────────────────────────────────────────────────────── */

.ks-sensei-usage { margin-top: .9rem; display: flex; flex-direction: column; }
.ks-sensei-usage-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: .8rem;
  padding: .4rem 0;
  border-bottom: 1px solid var(--ks-line);
  font-size: .78rem;
}
.ks-sensei-usage-row:last-child { border-bottom: 0; }
.ks-sensei-usage-head {
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ks-ink-3);
}
.ks-sensei-usage-row > .ks-sensei-usage-cell:not(:first-child) {
  /* Tabular so a column of counts lines up rather than jittering by digit. */
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-width: 3.5rem;
}

/* ── the Sensei's face ─────────────────────────────────────────────────────
 *
 * A 96px square, because that is what the admin needs to judge a crop the
 * server chose. The stored derivative is 640px, so this is always downscaling —
 * never a blur from stretching something small.
 */


.ks-sensei-face-row {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  /* Wraps rather than squeezing: below ~420px the preview and its controls
   * cannot both hold their width, and a 96px face beside a 40px-wide button is
   * worse than the two stacked. */
  flex-wrap: wrap;
}

.ks-sensei-face {
  flex: 0 0 auto;
  width: 96px;
  height: 96px;
  border-radius: 12px;
  border: 1px solid var(--ks-line);
  background: var(--ks-surface-sunken);
  /* CONTAIN, not cover: this preview's caption promises "the picture that
     appears is the one below", and the stored artwork is now the whole
     Sensei — cropping him here would show the owner a picture that is not
     the one visitors get. */
  object-fit: contain;
}

.ks-sensei-face-monogram {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: .02em;
  color: var(--ks-ink-3);
  /* Deliberately quiet. This is the ABSENCE of a caricature, and a loud
   * placeholder invites an owner to think something is broken. */
  background: var(--ks-surface-sunken);
}

.ks-sensei-face-controls {
  flex: 1 1 16rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: .6rem;
}

/* The native control is hidden from sight but NOT from the accessibility tree
 * or the keyboard: it stays focusable and its label is a real <label for>, so
 * tabbing reaches it and Enter opens the picker exactly as an unstyled input
 * would. `display: none` here would have removed it from both. */
.ks-sensei-face-file {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.ks-sensei-face-pick {
  display: inline-block;
  padding: .5rem .8rem;
  border: 1px dashed var(--ks-line);
  border-radius: 8px;
  font-size: .78rem;
  color: var(--ks-ink-2);
  background: var(--ks-surface-sunken);
  cursor: pointer;
  /* A chosen filename can be long and must not push the card sideways. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
.ks-sensei-face-file:focus-visible + .ks-sensei-face-pick {
  outline: 2px solid var(--ks-accent, var(--ks-ink-1));
  outline-offset: 2px;
}

.ks-sensei-face-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

.ks-sensei-face-note {
  padding: .45rem .7rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  font-size: .76rem;
  background: var(--ks-surface);
  color: var(--ks-ink-2);
}

/* ── knowledge sources ─────────────────────────────────────────────────────
 *
 * What the Sensei has READ, as opposed to the answers a person typed. Two ways
 * in and one list, because to an owner they are the same thing: material the
 * Sensei may quote.
 *
 * Every colour is a token, so the card follows the tenant's theme in both light
 * and dark without a second palette to keep in step.
 */

.ks-sensei-subform {
  margin-top: 1rem;
  padding: .85rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-surface-sunken);
}
.ks-sensei-subform > .ks-sensei-heading:first-child { margin-top: 0; }

.ks-sensei-sourcelist { display: flex; flex-direction: column; gap: .55rem; margin-top: 1rem; }

.ks-sensei-source {
  padding: .7rem .8rem;
  border: 1px solid var(--ks-line);
  border-radius: 8px;
  background: var(--ks-canvas);
}

.ks-sensei-source-head {
  display: flex;
  flex-wrap: wrap;          /* a long document title wraps under its badge
                               instead of pushing the row into a sideways
                               scroll on a phone */
  align-items: baseline;
  gap: .45rem;
}

/* Document or Website, as a quiet badge rather than a second heading: the
   dojo's own title for the thing is what the eye should land on. */
.ks-sensei-source-kind {
  flex: none;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: .1rem .4rem;
  border-radius: 4px;
  border: 1px solid var(--ks-line-strong);
  color: var(--ks-ink-3);
}
.ks-sensei-source-label {
  font-size: .8rem;
  font-weight: 700;
  line-height: 1.45;
  min-width: 0;
  overflow-wrap: anywhere;  /* a pasted URL is one long "word" and would
                               otherwise widen the whole card */
}
.ks-sensei-source-meta { margin-top: .25rem; font-size: .74rem; color: var(--ks-ink-2); line-height: 1.55; }

/* A crawl that did not finish. Warning-toned rather than danger-toned: nothing
   is broken, the Sensei simply knows less of the site than the owner may
   assume, and that distinction is the whole reason the line exists. */
.ks-sensei-source-warn {
  margin-top: .35rem;
  padding: .4rem .55rem;
  border: 1px solid var(--ks-warning-line);
  border-radius: 6px;
  background: var(--ks-warning-bg);
  color: var(--ks-warning);
  font-size: .73rem;
  line-height: 1.5;
}

.ks-sensei-source-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-top: .55rem; }
/* Row controls are quiet: the source's own title is the content here, and
   five uppercase slabs per row were louder than the knowledge they managed. */
.ks-sensei-source-actions .ks-btn { padding: .4rem .65rem; font-size: .7rem; }

/* The third note tone. ok and bad already exist; a partial crawl is neither. */
.ks-sensei-note-warn {
  border-color: var(--ks-warning-line);
  background: var(--ks-warning-bg);
  color: var(--ks-warning);
}

/* The authority a considered-but-unused passage carried — "dojo document",
 * "dojo-approved answer". A quiet suffix rather than a second line: the label
 * is what the owner is scanning for, and the ladder is context for it. */
.ks-sensei-result-source-kind {
  margin-left: .4rem;
  font-size: .68rem;
  font-weight: 600;
  color: var(--ks-ink-3);
}

/* The re-read schedule selector sits inline in a row of actions, so it must
 * size to its content rather than inheriting the form-field full width. */
.ks-sensei-refresh-select { width: auto; display: inline-block; }

/* What a knowledge-gap row says besides the question.
 *
 * The row used to be the question alone, so twenty gaps read as twenty equally
 * urgent lines. How many people asked is the number an owner decides on; when
 * it was last asked separates a live question from one a change already fixed;
 * the channel hints at how long the answer should be. Wraps, because a phone
 * gets the same three facts on however many lines it needs. */
.ks-sensei-gap-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem .5rem;
  align-items: center;
  margin: 0;
  font-size: .7rem;
  color: var(--ks-ink-2);
}

.ks-sensei-gap-count {
  font-weight: 600;
  color: var(--ks-ink);
}

/* A quiet pill rather than another line of prose — the channel is context, not
   an instruction, and it should read as a label at a glance. */
.ks-sensei-gap-channel {
  border: 1px solid var(--ks-line);
  border-radius: 999px;
  padding: .05rem .4rem;
  color: var(--ks-ink-3);
}

/* A paused answer, and why the word carries the meaning.
 *
 * Whether an answer is live is the most consequential thing about it, so it is
 * stated in words in the row as well as shown here. Colour and opacity alone
 * would vanish in monochrome, for a colour-blind reader, and for anyone using a
 * screen reader — three audiences who all need to know the Sensei has stopped
 * saying something. */
.ks-sensei-qa.is-paused {
  opacity: .72;
  border-style: dashed;
}

.ks-sensei-qa-paused {
  font-size: .72rem;
  font-weight: 600;
  color: var(--ks-warning);
  margin-top: .3rem;
}

/* The filter appears only once there are enough answers to need it, so it is
   spaced as an occasional control rather than a permanent header. */
.ks-sensei-qa-filter {
  margin: .6rem 0 .2rem;
  width: 100%;
}


/* ══════════════════════════════════════════════════════════════════════
   dojo-admin-workspace.v1.css
   ══════════════════════════════════════════════════════════════════════ */
    /* ══════════════════════════════════════════════════════════════════════════
       ADMIN WORKSPACE — the classes the 2026-08-16 elevation pass introduced
       while retiring inline styles and emoji from #view-admin. Appended last in
       the manifest on purpose: the light-override and shell files carry
       !important reconciliation blocks, and rules here must win on order.
       Token-clean; anything hex here is data (event colours), not styling.
       ══════════════════════════════════════════════════════════════════════════ */

    /* The min-content trap, closed at the item level. The uploads table's
       seven no-wrap sortable headers give its header row a ~530px min-content
       width, and a grid ITEM's automatic min-width floors at min-content
       regardless of its minmax(0,1fr) TRACK — so the uploads column overflowed
       the page sideways on a phone. Linux font metrics crossed the threshold
       first (CI failed at 541 while macOS sat at 527); the bisecting
       diagnostic named thead > tr as the carrier. min-width:0 lets the items
       shrink so the scroll containers inside them can do their jobs. */
    #view-admin .dash-two-col > * { min-width: 0; }

    /* Anchor for the .sr-only label inside the comments header. sr-only is
       position:absolute; with no positioned ancestor its containing block is
       the PAGE, so the 1px invisible box sat at its static position ~530px
       into the 600px table, escaped every scroll container between (none of
       them was its containing block), and horizontally scrolled the entire
       document — by an amount that moved with font metrics, which is why CI
       failed at 541 while macOS sat at 527. Found by bisecting stylesheet
       RULES after three DOM-level hunts came back empty. */
    #view-admin .upload-list-table th { position: relative; }

    /* Sortable column headers are real buttons now — keyboard-operable, and
       the sorted state is aria-sort + .sorted, not onclick-text grepping. */
    #view-admin .th-sort {
      appearance: none;
      background: none;
      border: 0;
      padding: 0;
      font: inherit;
      color: inherit;
      letter-spacing: inherit;
      text-transform: inherit;
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: .3rem;
    }
    #view-admin .th-sort:focus-visible {
      outline: 2px solid var(--ks-accent);
      outline-offset: 2px;
      border-radius: 4px;
    }
    #view-admin .upload-list-table th.sorted .th-sort { color: var(--ks-accent-strong); }

    /* The video thumbnail is a button (it opens the match); the reset keeps
       the existing .upload-thumb layout rules doing the visual work. */
    #view-admin button.upload-thumb {
      appearance: none;
      background: none;
      border: 0;
      padding: 0;
      font: inherit;
      cursor: pointer;
      display: block;
    }
    #view-admin button.upload-thumb:focus-visible {
      outline: 2px solid var(--ks-accent);
      outline-offset: 2px;
    }

    /* Calendar legend, rebuilt from fourteen inline style attributes. The
       --chip value is the shared event-type colour vocabulary — data carried
       by the markup, painted here. */
    #view-admin .cal-legend {
      display: flex;
      gap: 1.25rem;
      flex-wrap: wrap;
      padding: .6rem 1rem;
      background: var(--ks-canvas);
      border: 1px solid var(--ks-line);
      border-radius: 10px;
      margin: 0 1.25rem;
    }
    #view-admin .cal-legend-item {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: .72rem;
      font-weight: 700;
      color: var(--ks-ink-2);
    }
    #view-admin .cal-legend-chip {
      width: 14px;
      height: 14px;
      border-radius: 4px;
      background: var(--chip, var(--ks-line-strong));
      display: inline-block;
    }

    /* Month stepping: icon buttons with real labels; the flipped chevron is
       the previous-month arrow. */
    #view-admin .cal-month-nav { padding: 6px 13px; }
    .ks-icon-flip { transform: scaleX(-1); }

    /* Controls that were spans/divs with onclick are real buttons now; this
       reset lets the classes they already carried keep doing the visual work.
       The rows gain focus rings they never had. */
    #view-admin button.adm-three-dot,
    #view-admin button.adm-dd-item,
    #view-admin button.dash-view-all-link,
    #view-admin button.admin-filter-clear,
    #view-admin button.close-x {
      appearance: none;
      background: none;
      border: 0;
      padding: 0;
      font: inherit;
      color: inherit;
      text-align: inherit;
      cursor: pointer;
    }
    #view-admin button.adm-three-dot:focus-visible,
    #view-admin button.adm-dd-item:focus-visible,
    #view-admin button.dash-view-all-link:focus-visible,
    #view-admin button.admin-filter-clear:focus-visible,
    #view-admin button.close-x:focus-visible {
      outline: 2px solid var(--ks-accent);
      outline-offset: 2px;
      border-radius: var(--ks-r-control-sm);
    }
    /* Menu items are block rows inside the dropdown. */
    #view-admin button.adm-dd-item { display: flex; width: 100%; }

    /* The restore-previous-palette chip sits with the presets but reads as an
       undo, not another suggestion. */
    #view-admin .ta-brand-previous {
      border-style: dashed;
      margin-left: .4rem;
    }

    /* The topbar search glyph is sprite markup now, not a ::before emoji. */
    #view-admin .admin-search-icon {
      position: absolute;
      left: 10px;
      top: 50%;
      transform: translateY(-50%);
      pointer-events: none;
      color: var(--ks-band-ink-3);
      width: .8em;
      height: .8em;
    }

    /* ══ EDITOR WORKSPACE (2026-08-17) ═══════════════════════════════════════
       The announcement/spotlight/calendar editors' shared structure: editor
       card beside an always-visible preview, fields grouped under quiet
       titles, and a status chip answering the first question — what is the
       homepage showing right now? Every rule reads tokens; the two swatch
       classes keep their existing size/ring styles and only pick up the
       button reset here (they were spans with onclick).
       ═══════════════════════════════════════════════════════════════════════ */
    #view-admin .ed-workspace {
      display: grid;
      grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
      gap: 1.25rem;
      align-items: start;
      padding: 1.25rem 1.75rem 0;
    }
    #view-admin .ed-workspace > * { min-width: 0; }
    @media (max-width: 1080px) {
      #view-admin .ed-workspace { grid-template-columns: minmax(0, 1fr); padding: 1rem .5rem 0; }
    }

    #view-admin .ed-editor-card,
    #view-admin .ed-preview-card { margin: 0; }
    #view-admin .ed-side {
      display: flex;
      flex-direction: column;
      gap: 1.25rem;
      position: sticky;
      top: 1rem;
    }
    #view-admin .ed-subtabs { margin-bottom: .9rem; }

    /* The status chip. Live wears the success family; hidden stays quiet. */
    #view-admin .ed-status {
      display: inline-flex;
      align-items: center;
      gap: .45rem;
      padding: .3rem .7rem;
      border-radius: var(--ks-r-pill);
      border: 1px solid var(--ks-line);
      background: var(--ks-canvas);
      font-size: .7rem;
      font-weight: 700;
      color: var(--ks-ink-2);
      margin-bottom: .9rem;
    }
    #view-admin .ed-status-dot {
      width: 8px; height: 8px;
      border-radius: 50%;
      background: var(--ks-ink-3);
      flex-shrink: 0;
    }
    #view-admin .ed-status.is-live {
      border-color: var(--ks-success-line);
      background: var(--ks-success-bg);
      color: var(--ks-success);
    }
    #view-admin .ed-status.is-live .ed-status-dot { background: var(--ks-success); }
    #view-admin .ed-status.is-hidden .ed-status-dot { background: var(--ks-ink-3); }

    /* Field groups: quiet titles, hairline separation, breathing room. */
    #view-admin .ed-group { padding: .9rem 0 .2rem; border-top: 1px solid var(--ks-line-soft); }
    #view-admin .ed-group:first-of-type { border-top: 0; padding-top: 0; }
    #view-admin .ed-group-title {
      display: flex;
      align-items: center;
      gap: .4rem;
      margin: 0 0 .65rem;
      font-size: .66rem;
      font-weight: 800;
      letter-spacing: .12em;
      text-transform: uppercase;
      color: var(--ks-ink-2);
    }
    #view-admin .ed-group .cal-fg { margin-bottom: .55rem; }
    #view-admin .ed-group .ks-switch-row { margin-top: .5rem; }
    #view-admin .ed-group .ta-settings-unavailable { margin-top: .5rem; }

    #view-admin .ed-hint {
      font-size: .55rem;
      font-weight: 600;
      color: var(--ks-ink-3);
      text-transform: none;
      letter-spacing: 0;
    }
    #view-admin .ed-note { font-size: .64rem; color: var(--ks-ink-3); margin-top: .25rem; line-height: 1.5; }
    #view-admin .ed-note-block { margin-top: .45rem; }
    #view-admin .ed-note-actions { margin-top: .45rem; display: flex; gap: .4rem; flex-wrap: wrap; }
    #view-admin .ed-mode-line {
      font-size: .68rem;
      color: var(--ks-ink-3);
      margin: -.2rem 0 .8rem;
      line-height: 1.5;
    }

    /* Swatches became buttons; their size/ring styles live with the classes. */
    #view-admin .ed-swatch-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-top: .2rem; }
    #view-admin .ed-swatches { display: flex; gap: .3rem; flex-wrap: wrap; }
    #view-admin button.cal-color-swatch,
    #view-admin button.ann-color-swatch { appearance: none; padding: 0; }
    #view-admin button.cal-color-swatch:focus-visible,
    #view-admin button.ann-color-swatch:focus-visible {
      outline: 2px solid var(--ks-accent);
      outline-offset: 2px;
    }
    #view-admin .ann-color-swatch[data-color="#FFFFFF"] { border: 1px solid var(--ks-line); }
    #view-admin .ed-custom-color {
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 3px;
      font-size: .62rem;
      font-weight: 700;
      color: var(--ks-ink-3);
    }
    #view-admin .ed-custom-color input[type="color"] {
      width: 26px; height: 26px;
      border: none; cursor: pointer;
      border-radius: var(--ks-r-control-sm); padding: 1px;
      background: none;
    }
    #view-admin .ed-color-preview {
      width: 26px; height: 26px;
      border-radius: 6px;
      border: 2px solid var(--ks-line);
      flex-shrink: 0;
    }

    #view-admin .ed-actions { display: flex; align-items: center; gap: .7rem; margin-top: 1rem; flex-wrap: wrap; }
    #view-admin .ed-actions .cal-save-btn { flex: 1; min-width: 180px; margin: 0; }
    #view-admin .ed-align-end { display: flex; align-items: flex-end; }
    #view-admin .ed-row-tail { margin-top: .3rem; }

    /* Preview shells: the homepage's dark band as backdrop, so the preview
       reads exactly as it will on the page. The announcement preview keeps
       its long-standing ids — the smart-style machinery writes its colors. */
    #view-admin .ed-ann-preview {
      padding: .8rem 1rem;
      border-radius: 10px;
      border: 1px solid var(--ks-band-edge);
      background: linear-gradient(135deg, rgba(6, 13, 30, .95), rgba(10, 19, 36, .98));
      min-height: 52px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      transition: all .3s ease;
    }
    #view-admin .ed-ann-preview #smart-ann-preview-text {
      font-size: .78rem;
      font-weight: 700;
      color: var(--ks-band-accent);
      text-align: center;
      line-height: 1.4;
    }
    #view-admin .ed-spotlight-preview-shell {
      padding: 1rem;
      border-radius: 10px;
      border: 1px solid var(--ks-band-edge);
      background: var(--ks-band);
    }
    /* The banner is display:none until .visible on the homepage; the preview
       copy always shows. Scoped to the shell so nothing leaks to the page. */
    #view-admin .ed-spotlight-preview-shell .ks-spotlight-banner { display: block; }

    /* Calendar dressing that used to be inline. */
    #view-admin .ed-section-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 1rem;
      flex-wrap: wrap;
      gap: .5rem;
    }
    #view-admin .ed-section-head-title { display: flex; align-items: center; gap: .6rem; }
    #view-admin .ed-section-head-actions { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
    #view-admin .ed-title-tight { margin-bottom: 0; }
    #view-admin .cal-month-label {
      font-family: var(--ks-display);
      font-size: .95rem;
      font-weight: 800;
      color: var(--ks-ink);
      min-width: 170px;
      text-align: center;
      letter-spacing: 1.5px;
    }
    #view-admin .cal-today-btn { padding: 5px 14px; font-size: .65rem; font-weight: 800; letter-spacing: 1px; }
    #view-admin .cal-add-btn { font-size: .65rem; padding: 6px 16px; font-weight: 800; }
    #view-admin .cal-dates-badge {
      margin-top: .4rem;
      padding: .45rem .7rem;
      background: var(--ks-info-bg);
      border: 1px solid var(--ks-info-line);
      border-radius: 8px;
      font-size: .65rem;
      font-weight: 700;
      color: var(--ks-info);
      line-height: 1.4;
    }
    #view-admin .cal-editor-col { display: flex; flex-direction: column; gap: 1.25rem; height: 100%; }
    #view-admin .cal-upcoming-card { flex: 1; min-height: 0; overflow-y: auto; }

    /* ══════════════════════════════════════════════════════════════════════════
       2026-08-21 MODERNIZATION PASS — the greeting scoped to Overview, the
       account entry on the topbar, the calendar editor's resting state, and
       the swatch controls brought up to the design system. Same layering rule
       as everything above: this file is last in the manifest and wins on order.
       ══════════════════════════════════════════════════════════════════════════ */

    /* ── Topbar account entry ─────────────────────────────────────────────────
       Third flex child after brand and search; the search keeps its width and
       the button rides the same dark band the topbar owns. */
    #view-admin .admin-topbar-account {
      border-color: rgba(255, 255, 255, .22);
      color: var(--ks-band-ink);
      background: rgba(255, 255, 255, .05);
      flex-shrink: 0;
    }
    #view-admin .admin-topbar-account:hover {
      background: rgba(255, 255, 255, .12);
      border-color: rgba(255, 255, 255, .38);
      color: var(--ks-band-ink);
    }

    /* The greeting inside the Overview panel: the panel already provides top
       rhythm, so the block keeps only enough of its own. */
    #view-admin .admin-tab-content .admin-welcome { padding-top: .35rem; }

    /* ── The calendar editor's resting state ──────────────────────────────────
       An editor is a response to intent. At rest the column offers one clear
       card: what tapping a day does, and the one button that starts from
       nothing. */
    #view-admin .cal-panel-idle {
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
      gap: .55rem;
      padding: 2.25rem 1.5rem 2.5rem;
    }
    #view-admin .cal-panel-idle-icon {
      width: 34px;
      height: 34px;
      color: var(--ks-ink-3);
      margin-bottom: .25rem;
    }
    #view-admin .cal-panel-idle-title {
      font-family: var(--ks-display);
      font-weight: 800;
      font-size: .95rem;
      letter-spacing: .06em;
      color: var(--ks-ink);
    }
    #view-admin .cal-panel-idle-copy {
      font-size: .74rem;
      line-height: 1.55;
      color: var(--ks-ink-2);
      max-width: 30ch;
      margin: 0 0 .5rem;
    }

    /* ── Swatches, brought up to the system ───────────────────────────────────
       Small circular dots in a quiet row: no scale-jump on hover, selection is
       the classic dot-with-ring (a surface gap, then ink), and the custom
       picker reads as one more labelled control instead of an appendix. Shared
       by the calendar event colour and the announcement text colour. */
    #view-admin .ed-swatch-row {
      display: flex;
      align-items: center;
      gap: .6rem;
      flex-wrap: wrap;
    }
    #view-admin .ed-swatches { gap: .45rem; align-items: center; }
    #view-admin button.cal-color-swatch,
    #view-admin button.ann-color-swatch {
      width: 22px;
      height: 22px;
      border-radius: 50%;
      border: 1px solid rgba(16, 24, 40, .18);
      box-shadow: none;
      transition: box-shadow .15s ease;
    }
    #view-admin button.cal-color-swatch:hover,
    #view-admin button.ann-color-swatch:hover {
      transform: none;
      box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 18%, transparent);
    }
    #view-admin button.cal-color-swatch.selected,
    #view-admin button.ann-color-swatch.selected {
      border-color: rgba(16, 24, 40, .18);
      box-shadow: 0 0 0 2px var(--ks-surface), 0 0 0 4px var(--ks-ink);
    }
    #view-admin .ed-custom-color {
      display: inline-flex;
      align-items: center;
      gap: .45rem;
      padding: .3rem .6rem;
      border: 1px solid var(--ks-line);
      border-radius: 999px;
      font-size: .68rem;
      font-weight: 700;
      color: var(--ks-ink-2);
      cursor: pointer;
      background: var(--ks-surface);
    }
    #view-admin .ed-custom-color:hover { border-color: var(--ks-line-strong); color: var(--ks-ink); }
    #view-admin .ed-custom-color input[type="color"] {
      width: 22px;
      height: 22px;
      padding: 0;
      border: 1px solid rgba(16, 24, 40, .18);
      border-radius: 50%;
      background: none;
      cursor: pointer;
    }
    #view-admin .ed-color-preview {
      width: 22px;
      height: 22px;
      border-radius: 50%;
      border: 1px solid rgba(16, 24, 40, .18);
    }

    /* ── The calendar month, lightened ────────────────────────────────────────
       The weekday strip was a full navy band with 900-weight letters — heavier
       than the data below it. Column labels are wayfinding, not a headline:
       quiet uppercase on the card's own surface, and the grid keeps the
       spotlight. */
    #view-admin .dash-cal-weekdays {
      background: transparent;
      --ks-ink: var(--adm-ink);
      --ks-ink-2: var(--adm-ink-2);
      --ks-ink-3: var(--adm-ink-3);
      border-bottom: 1px solid var(--ks-line);
    }
    #view-admin .dash-cal-weekday {
      padding: .55rem .25rem;
      font-size: .62rem;
      font-weight: 800;
      letter-spacing: .12em;
      color: var(--ks-ink-3);
    }

    /* The editor panel joins the card family: the off-palette emerald tint it
       carried was drift from before the design system settled. */
    #view-admin .dash-cal-edit-panel { border-color: var(--ks-line); }

    /* The trailing preview dot said what the selection ring and the custom
       input's own swatch already say; three tellings of one colour is noise.
       Hidden, not removed: the JS keeps writing it, and anything else that
       reads it still can. */
    #view-admin .ed-color-preview { display: none; }

    /* The Overview tables' scroll wrappers only scrolled under the 900px media
       query, so a desktop between the two-column grid's floor and its comfort
       width CLIPPED both tables mid-column with no way to reach the rest — the
       repo rule is that wide content scrolls inside its own container at every
       width, not only on phones. */
    #view-admin .upload-list-scroll,
    #view-admin .dash-users-scroll {
      overflow-x: auto;
      -webkit-overflow-scrolling: touch;
    }

    /* display:flex on the idle card out-specifies the UA's [hidden] rule, so
       the attribute must win explicitly or the card never actually hides. */
    #view-admin .cal-panel-idle[hidden] { display: none; }

    /* ── Phone topbar: two honest rows ────────────────────────────────────────
       Brand and the account entry share the first row; the search takes a full
       row beneath instead of collapsing to a stub between them. The brand
       title truncates rather than stacking a word per line. */
    @media (max-width: 700px) {
      #view-admin .admin-topbar { flex-wrap: wrap; row-gap: .6rem; }
      #view-admin .admin-topbar-brand { flex: 1 1 auto; min-width: 0; }
      #view-admin .admin-topbar-brand > div { min-width: 0; }
      #view-admin .admin-topbar-brand-title {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      #view-admin .admin-topbar-account { order: 2; }
      #view-admin .admin-topbar-search {
        order: 3;
        flex-basis: 100%;
        margin: 0;
        max-width: none;
      }
    }

    /* The header-with-action row — the last inline-style stragglers from the
       pre-elevation era, retired 2026-08-25. One class, both dashboards. */
    #view-admin .admin-section-title-row {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: .5rem;
      margin-bottom: .7rem;
    }
