/* ============================================================================
   KOSIS — assets/css/kosis-pages.css
   ----------------------------------------------------------------------------
   v2.1 · 11 Jun 2026

   Page-level component + utility classes created by the separation-of-concerns
   refactor. As each page is refactored, its inline style="..." declarations are
   lifted out into reusable classes here.

   Why a separate file (not kosis-dashboard.css)?
     · kosis-brand.css            -> design tokens (variables)
     · kosis-dashboard.css        -> layout + chrome (sidebar, topbar, buttons)
     · kosis-brand-extensions.css -> larger brand components (.ks-card, tabs)
     · kosis-pages.css  (THIS)    -> small per-page components + spacing utils
   Keeping the refactor's additions here makes them easy to find and review,
   and avoids bloating the layout sheet.

   LOAD ORDER (head-dashboard.php): this file loads LAST, so it can rely on the
   tokens and components defined by the three sheets above. On pages that also
   load kosis-song.css, THIS sheet still loads last, so its small modifiers can
   adjust shared song-page components (e.g. .ks-audio-menu--corner).

   SECTIONS
     1. Spacing / layout utilities (shared)
     2. Messages page (Phase 1)
     3. Polls page (Phase 2)
     4. Home page (Phase 3)
     5. Audit log page (Phase 4)
     6. Set lists page (Phase 5)
     7. Practice lists page (Phase 6)
     8. Members page (Phase 7)
     9. Song page (Phase 8)
    10. Events page (Phase 9)
    11. Messages feed: announcements, noticeboard, comments, reactions,
        unread badges, pin, inline edit, context menu (Session 18 PR1)
    12. Messages live updates: new-posts pill + nav badges (Session 18 PR1, Stage C)
   ----------------------------------------------------------------------------
   Also in v1.7: a mobile fix for the practice-list 3-dots options popover
   (Section 7) that was being clipped/hidden on phones — see the @media block
   at the end of Section 7.
   ========================================================================= */


/* -- 1. Spacing / layout utilities (shared across pages) ------------------- */

/*  The single most-repeated inline style in the app is a bottom margin of one
    "md" spacing unit on a block that sits above another. Extracted so pages
    don't carry style="margin-bottom:var(--ks-space-md)" everywhere. */
.ks-mb-md { margin-bottom: var(--ks-space-md); }
.ks-mb-sm { margin-bottom: var(--ks-space-sm); }

/*  Top-margin counterparts (used to space form labels/buttons below the
    element above them). */
.ks-mt-md { margin-top: var(--ks-space-md); }
.ks-mt-sm { margin-top: var(--ks-space-sm); }

/*  A centered, width-capped page column. Pages wrap their content in this so it
    aligns to the same 1280px column as the topbar (matching Home). Previously
    this was 1100px, which made Polls narrower than Home; widened to 1280px for
    a consistent page width across the app. */
.ks-page-narrow {
    max-width: 1280px;
    margin: 0 auto;
}

/*  A two-equal-column grid with a small gap. Used by setlist forms
    (date + note side by side). */
.ks-split-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--ks-space-sm);
}
/* On phones, stack the two-column form pairs so each field is full-width and
   comfortably tappable instead of cramped side-by-side. */
@media (max-width: 560px) {
    .ks-split-2 { grid-template-columns: 1fr; }
}

/*  Zero-margin helpers for headings/paragraphs that sit in a flex row where
    the default block margin would misalign them. */
.ks-m0 { margin: 0; }
.ks-h2--flush { margin-bottom: 0; }
.ks-hidden { display: none; }

/*  Body copy inside a hero (navy) card: 13px, cream-on-navy inherited from
    .ks-card--hero. The first variant has no margin; the spaced variant adds a
    md bottom margin when the hero has content beneath the text. */
.ks-hero-text       { margin: 0; font-size: 13px; }

/*  Small muted note inside an empty-state card (e.g. "available to officers").
    Shared across pages that show a permission/empty hint under a heading. */
.ks-empty-note {
    margin: var(--ks-space-sm) 0 0;
    color: var(--ks-slate);
    font-size: 12px;
}

/*  Intro paragraph at the top of a white card body: slate, 13px, md gap below.
    (About card, Upcoming-events lead-in, etc.) */
.ks-card-intro {
    color: var(--ks-slate);
    font-size: 13px;
    margin: 0 0 var(--ks-space-md);
}

/*  Smaller note paragraph in a card body: slate, 12px, md gap below.
    (Documents card lead-in, etc.) */
.ks-card-note {
    color: var(--ks-slate);
    font-size: 12px;
    margin: 0 0 var(--ks-space-md);
}

/*  Parchment-filled button variant (used for the audit-log Reset action and
    other low-emphasis secondary actions on a white surface). */
.btn--parchment {
    background: var(--ks-parchment);
    color: var(--ks-navy);
    border: 1px solid var(--ks-border);
}

/*  Circular initials avatar (navy fill, cream text). The base .avatar-small
    sizing/shape lives in the existing styles; this sets the Messages colours
    and dimensions that were previously inline. */
.ks-msg-avatar {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--ks-navy);
    color: var(--ks-cream);
    font-size: 11px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
}

/*  Empty-state paragraph inside the "no messages yet" card. */
.ks-msg-empty {
    margin: 0;
    color: var(--ks-slate);
    font-size: 13px;
}

/*  The "Post message" submit button on the new-message form: pushed down a
    little and left-aligned within the form grid. */
.ks-msg-submit {
    margin-top: var(--ks-space-md);
    align-self: flex-start;
}


/* -- 3. Polls page (Phase 2) ----------------------------------------------- */

/*  Meta line under a poll question ("Vote by ... N votes"). */
.ks-poll-meta {
    color: var(--ks-slate);
    font-size: 11px;
    margin: 0 0 var(--ks-space-md);
}

/*  A single vote option, rendered as a full-width clickable button with the
    label on the left and the vote count on the right. */
.ks-poll-option {
    width: 100%;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-input);
    padding: 8px 12px;
    font-family: var(--ks-font-sans);
    font-size: 12px;
    color: var(--ks-navy);
    text-align: left;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.ks-poll-option:hover { background: var(--ks-parchment); }

/*  The right-aligned vote count inside a poll option. */
.ks-poll-option__count {
    color: var(--ks-slate);
    font-size: 11px;
}

/*  The thin progress-bar track beneath each option. */
.ks-poll-bar {
    height: 4px;
    background: var(--ks-border);
    border-radius: 2px;
    overflow: hidden;
    margin-top: 4px;
}

/*  The filled portion of the progress bar. NOTE: its WIDTH is dynamic
    (per-poll percentage) so the width percentage stays inline in the markup —
    everything else (colour, height, block display) lives here. */
.ks-poll-bar__fill {
    display: block;
    height: 100%;
    background: var(--ks-gold);
}



/* -- 5. Audit log page (Phase 4) ------------------------------------------- */
/*  This whole section was previously an inline <style> block inside
    pages/audit-log.php; it now lives here so the page carries no embedded CSS.
    The integrity-banner colours, which used to be inline + dynamic, are now
    driven by .is-ok / .is-warn modifier classes (the page chooses the class). */

/*  Heading tweak: the page title sits tight above its intro paragraph. */
.ks-audit-title { margin-bottom: 4px; }

/*  Filter toolbar: a wrapping row of labelled fields + actions. */
.ks-audit-toolbar {
    display: flex; flex-wrap: wrap; gap: var(--ks-space-sm); align-items: flex-end;
    background: #fff; border: 0.5px solid var(--ks-border); border-radius: var(--ks-radius-md);
    padding: var(--ks-space-md); margin-bottom: var(--ks-space-md);
}
.ks-audit-field { display: flex; flex-direction: column; gap: 4px; }
.ks-audit-field label { font-size: 10px; letter-spacing: .5px; text-transform: uppercase; color: var(--ks-slate); }
.ks-audit-field select, .ks-audit-field input {
    border: 1px solid var(--ks-border); border-radius: 8px; padding: .5rem .6rem;
    font-family: var(--ks-font-sans); font-size: .85rem; background: #fff; color: var(--ks-navy);
}
.ks-audit-actions { display: flex; gap: 6px; margin-left: auto; align-items: flex-end; }
.ks-audit-actions .btn { width: auto; min-height: 0; padding: .5rem .9rem; font-size: .85rem; line-height: 1.15; }

/*  Results table. */
.ks-audit-table { width: 100%; border-collapse: collapse; background: #fff;
    border: 0.5px solid var(--ks-border); border-radius: var(--ks-radius-md); overflow: hidden; }
.ks-audit-table th { text-align: left; font-size: 10px; letter-spacing: .6px; text-transform: uppercase;
    color: var(--ks-slate); font-weight: 500; padding: .7rem .85rem; background: var(--ks-parchment);
    border-bottom: 1px solid var(--ks-border); }
.ks-audit-table td { padding: .7rem .85rem; border-bottom: 0.5px solid var(--ks-border);
    font-size: .85rem; color: var(--ks-navy); vertical-align: top; }
.ks-audit-table tr:last-child td { border-bottom: none; }

/*  Column-width helpers for the header cells (presentational, static). */
.ks-audit-col-entry  { width: 64px; }
.ks-audit-col-when   { width: 170px; }
.ks-audit-col-actor  { width: 150px; }
.ks-audit-col-action { width: 90px; }
.ks-audit-col-type   { width: 90px; }

/*  Cell helpers. */
.ks-audit-entry { font-family: var(--ks-font-sans); font-size: .8rem; color: var(--ks-slate); white-space: nowrap; }
.ks-audit-item  { font-weight: 500; }

/*  Pill badge used in the Action column (colours set inline from the
    per-action map, since they vary by action — that stays data-driven). */
.ks-badge { display: inline-block; font-size: 11px; font-weight: 500; padding: 2px 8px;
    border-radius: 999px; border: 0.5px solid; white-space: nowrap; }

.ks-audit-when { color: var(--ks-slate); font-size: .78rem; white-space: nowrap; }
.ks-audit-details { color: var(--ks-slate); font-size: .8rem; line-height: 1.45; }

/*  Empty state. */
.ks-audit-empty { text-align: center; padding: var(--ks-space-lg); color: var(--ks-slate); }
.ks-audit-empty__glyph { font-size: 30px; color: var(--ks-gold); margin-bottom: 8px; }
.ks-audit-empty__title { color: var(--ks-navy); }
.ks-audit-empty__note  { color: var(--ks-slate); font-size: 12px; margin: 6px 0 0; }

/*  Pager. */
.ks-audit-pager { display: flex; justify-content: space-between; align-items: center;
    margin-top: var(--ks-space-md); font-size: .85rem; color: var(--ks-slate); }
.ks-audit-pager a { color: var(--ks-sage); text-decoration: none; padding: .4rem .8rem;
    border: 1px solid var(--ks-border); border-radius: 8px; }
.ks-audit-pager a.disabled { color: var(--ks-muted); pointer-events: none; border-color: var(--ks-border); }
.ks-audit-pager__nav { display: flex; gap: 6px; }

/*  Top result summary + per-page chooser (sits right under the filter toolbar
    so the count and page size are visible before scrolling to the table). */
.ks-audit-summary { display: flex; justify-content: space-between; align-items: center;
    flex-wrap: wrap; gap: 10px; margin: var(--ks-space-md) 0 var(--ks-space-sm);
    font-size: .85rem; color: var(--ks-slate); }
.ks-audit-summary__count strong { color: var(--ks-navy); }
.ks-audit-perpage { display: flex; align-items: center; gap: 8px; margin: 0; }
.ks-audit-perpage__lbl { font-size: .85rem; color: var(--ks-slate); white-space: nowrap; text-transform: none; }
.ks-audit-perpage__select { padding: .35rem .6rem; border: 1px solid var(--ks-border);
    border-radius: 8px; background: #fff; color: var(--ks-navy);
    font-family: var(--ks-font-sans); font-size: .85rem; line-height: 1.15; min-height: 0; cursor: pointer; }

/*  Integrity banner. Colours are chosen via .is-ok / .is-warn so nothing is
    inline; the page sets the modifier from the verification result. */
.ks-audit-integrity {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 14px; margin-bottom: var(--ks-space-md);
    border-radius: var(--ks-radius-md);
    border: 0.5px solid;
}
.ks-audit-integrity.is-ok   { background: #F0FBF4; border-color: #BBE6C8; }
.ks-audit-integrity.is-warn { background: #FDECEC; border-color: #E8B4B4; }
.ks-audit-integrity__icon { flex-shrink: 0; }
.ks-audit-integrity.is-ok   .ks-audit-integrity__icon,
.ks-audit-integrity.is-ok   .ks-audit-integrity__title { color: #166534; }
.ks-audit-integrity.is-warn .ks-audit-integrity__icon,
.ks-audit-integrity.is-warn .ks-audit-integrity__title { color: #8B2E2E; }
.ks-audit-integrity__body { flex: 1; min-width: 0; }
.ks-audit-integrity__title { display: block; font-size: 13px; }
.ks-audit-integrity__reason { font-size: 12px; color: var(--ks-slate); }
/* Session 48: secondary database-wide (global chain) status line. Muted and
   smaller than the per-choir reason above it — a choir director reads the
   per-choir line first; this tells them whether the wider database is intact. */
.ks-audit-integrity__global { font-size: 11px; color: var(--ks-slate); opacity: 0.85; margin-top: 2px; }
.ks-audit-integrity__global strong { font-weight: 600; }
.ks-audit-integrity__tag { flex-shrink: 0; font-size: 11px; color: var(--ks-muted); font-family: var(--ks-font-sans); }

/*  Mobile: collapse the table into stacked rows. */
@media (max-width: 760px) {
    .ks-audit-table thead { display: none; }
    .ks-audit-table, .ks-audit-table tbody, .ks-audit-table tr, .ks-audit-table td { display: block; width: 100%; }
    .ks-audit-table tr { border-bottom: 1px solid var(--ks-border); padding: .4rem 0; }
    .ks-audit-table td { border: none; padding: .25rem .85rem; }
    .ks-audit-actions { width: 100%; }
}


/* -- 6. Set lists page (Phase 5) ------------------------------------------- */

/*  Empty-state glyph for "no active set lists yet". */
.ks-setlist-empty__glyph { font-size: 32px; color: var(--ks-gold); margin-bottom: var(--ks-space-sm); }
.ks-setlist-empty__title { color: var(--ks-navy); }

/*  Manager row beneath a set-list feature card: the Edit disclosure on the
    left (flexes to fill) and the Delete action on the right. */
.ks-setlist-manage {
    display: flex;
    gap: var(--ks-space-sm);
    margin-top: var(--ks-space-sm);
}
.ks-setlist-manage__edit { flex: 1; }

/*  The <summary> toggles inside set-list cards and the practice-list /
    add-song disclosures share this sage, list-style-free look. */
.ks-summary-toggle {
    cursor: pointer;
    color: var(--ks-sage);
    font-size: 12px;
    font-family: var(--ks-font-sans);
    list-style: none;
}

/*  Repertoire section header: title on the left, Export link on the right. */
.ks-rep-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: var(--ks-space-lg) 0 var(--ks-space-md);
}
.ks-rep-head__export {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 600;
    color: var(--ks-navy);
    background: #fff;
    border: 1px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    box-shadow: 0 1px 2px rgba(31,41,82,.05);
    text-decoration: none;
    transition: background 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
}
.ks-rep-head__export svg { color: var(--ks-gold); }
.ks-rep-head__export:hover {
    background: var(--ks-parchment);
    border-color: var(--ks-gold);
    text-decoration: none;
}

/* Unified list-page header row: title on the left, controls (Export + Per page)
   on the right, one line. Applied to Set lists / Repertoire / Practice lists so
   the three tabs read identically and there's no tall empty band under the tab
   strip (the old layout stacked h2, then Per page alone, then content). */
.ks-list-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--ks-space-md);
    flex-wrap: wrap;
    margin: 0 0 var(--ks-space-md);
}
.ks-list-head__right {
    display: flex;
    align-items: center;
    gap: var(--ks-space-md);
}
.ks-list-head .ks-perpage { margin-top: 0; }

/*  Stacked list container (practice lists, shared-with-you lists). */
.ks-stack { display: flex; flex-direction: column; gap: 6px; }

/*  "Shared with you" subsection divider + heading. */
.ks-shared-divider {
    border-top: 0.5px solid var(--ks-border);
    margin-top: var(--ks-space-md);
    padding-top: var(--ks-space-md);
}
.ks-shared-heading {
    font-size: 11px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--ks-slate);
    margin: 0 0 var(--ks-space-sm);
}

/*  A small muted count line (e.g. "N tracks", composer line). */
.ks-meta-sm { color: var(--ks-slate); font-size: 11px; }

/*  Left gap for the share-count pill when it follows the track count inline.
    (.ks-shared-pill itself is defined in kosis-song.css; this only adds the
    spacing that used to be an inline margin-left.) */
.ks-shared-pill--gap { margin-left: 6px; }

/*  Inline "+ New practice list" / add-song form row. */
.ks-inline-form {
    display: flex;
    gap: 6px;
    margin-top: var(--ks-space-sm);
}
.ks-inline-form__input { flex: 1; min-width: 0; }

/*  A song row in the "All songs" list: the row wrapper holds the clickable
    link (which fills the row) and an optional manager Delete form. */
.ks-song-row {
    display: flex;
    gap: var(--ks-space-sm);
    align-items: center;
    border-top: 0.5px solid var(--ks-border);
}
.ks-song-row__link {
    display: flex;
    gap: var(--ks-space-md);
    align-items: center;
    padding: var(--ks-space-sm) 0;
    text-decoration: none;
    flex: 1;
    min-width: 0;
}
.ks-song-row__thumb {
    width: 36px;
    height: 36px;
    border-radius: 8px;
    background: var(--ks-navy);
    color: var(--ks-gold);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    flex-shrink: 0;
}
.ks-song-row__main { flex: 1; min-width: 0; }
.ks-song-row__title {
    display: block;
    font-family: var(--ks-font-serif);
    font-size: 13px;
    color: var(--ks-navy);
}
.ks-song-row__composer { margin: 2px 0 0; color: var(--ks-slate); font-size: 11px; }
.ks-song-row__delete { margin: 0; flex-shrink: 0; }

/*  Small spacing helpers used by the add-song / edit forms. */
.ks-field-gap { margin-top: var(--ks-space-sm); }
.ks-field-gap-xs { margin-bottom: 6px; }


/* -- 7. Practice lists page (Phase 6) -------------------------------------- */
/*  NOTE: a mobile bug fix was added to the TAIL of this section in Phase 8 —
    see "MOBILE FIX (<=639px)" below — to stop the 3-dots list-options
    popover from being clipped/hidden on phones (both the overview cards and
    the list-detail header). CSS-only; practice-lists.php was not changed. */
/*  practice-lists.php also loads kosis-song.css, which already defines the
    bulk of this page's components: .ks-breadcrumb, .ks-pl-grid/.ks-pl-card,
    .ks-pl-icon, .ks-pl-head__body, .ks-audio-menu/.ks-audio-dots/.ks-audio-pop,
    .ks-pop-item(+--danger), .ks-play-all, .ks-play-btn, .ks-shared-pill,
    .ks-voice-chip(s), .ks-share-member*, .ks-member-list.
    This section only adds (a) the bits that were inline-only on this page and
    (b) small modifiers for those shared components where this page differs.
    Because kosis-pages.css loads AFTER kosis-song.css, these modifiers win. */

/*  Generic page title spacing (the three views each open with an .ks-h1).
    Replaces inline style="margin:0 0 4px" / "margin:0 0 2px". */
.ks-pl-page-title { margin: 0 0 4px; }
/*  Detail-view list title sits a touch tighter (2px) above its meta line. */
.ks-pl-detail-title { margin: 0 0 2px; }
/*  "Shared with you" overview subsection heading spacing. */
.ks-pl-section-head { margin: var(--ks-space-lg) 0 var(--ks-space-sm); }

/*  Error flash specific to this page (red box). The success flash uses the
    shared .kosis-flash--success; this is the inline-red error variant. */
.ks-flash-error {
    margin-bottom: var(--ks-space-md);
    background: #FDECEC;
    border: 0.5px solid #E8B4B4;
    color: #8B2E2E;
}

/*  Breadcrumb links on this page were given inline sage colour; the shared
    .ks-breadcrumb a rule already colours them, so no extra class is needed.
    The final non-link crumb (current list name) is muted slate. */
.ks-breadcrumb .cur-list { color: var(--ks-slate); }

/*  SHARE view -------------------------------------------------------------- */

/*  The member search box spans the full card width with border-box sizing. */
.ks-member-search {
    width: 100%;
    margin-bottom: var(--ks-space-sm);
    box-sizing: border-box;
}

/*  The action row at the bottom of the share form (Save + Cancel). */
.ks-share-actions {
    display: flex;
    gap: var(--ks-space-sm);
    margin-top: var(--ks-space-md);
}

/*  The fine-print line under the share actions. */
.ks-share-note {
    color: var(--ks-slate);
    font-size: 11px;
    margin: var(--ks-space-sm) 0 0;
}

/*  Owned practice-list cards need position:relative so the 3-dots menu can be
    pinned to their top-right corner, and the clickable link fills the card. */
.ks-pl-card--owned { position: relative; }
.ks-pl-card__link {
    display: flex;
    align-items: center;
    gap: var(--ks-space-md);
    text-decoration: none;
    flex: 1;
    min-width: 0;
}
/*  The text column inside the card reserves space on the right so a long title
    never slides under the 3-dots button. */
.ks-pl-card__text { flex: 1; min-width: 0; padding-right: 24px; }
/*  Small top gap above the "Shared with N" pill inside a card. */
.ks-pl-card__sharedrow { margin-top: 6px; }

/*  Modifier: pin the audio/list 3-dots menu to the card's top-right corner.
    (.ks-audio-menu is position:relative by default in kosis-song.css.) */
.ks-audio-menu--corner {
    position: absolute;
    top: 10px;
    right: 10px;
}

/*  The Rename disclosure inside a 3-dots popover: a compact inline form. */
.ks-pop-rename-form {
    display: flex;
    gap: 6px;
    padding: 6px 8px 2px;
}
.ks-pop-rename-form__input {
    font-size: 12px;
    padding: 6px 8px;
    flex: 1;
    min-width: 0;
}
.ks-pop-rename-form__btn { font-size: 12px; padding: 6px 10px; }
/*  A <summary> styled as a .ks-pop-item row (no marker, pointer cursor). */
.ks-pop-summary { cursor: pointer; list-style: none; }

/*  DETAIL view ------------------------------------------------------------- */

/*  The list header: icon + title/meta + Play-all + options menu in a row. */
.ks-pl-detail-head {
    display: flex;
    align-items: flex-start;
    gap: var(--ks-space-md);
}
.ks-pl-detail-head__meta { color: var(--ks-slate); font-size: 12px; margin: 0; }
.ks-pl-detail-head__sharedrow { margin-top: 8px; }
.ks-pl-detail-link { text-decoration: none; }

/*  A single track row in the list-detail track list. */
.ks-track-row {
    display: flex;
    align-items: center;
    gap: var(--ks-space-sm);
    padding: 10px 0;
    border-bottom: 0.5px solid var(--ks-border);
}
.ks-track-row__main { flex: 1; min-width: 0; }
.ks-track-row__title {
    display: block;
    font-family: var(--ks-font-serif);
    font-size: 13px;
    color: var(--ks-navy);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ks-track-row__meta { color: var(--ks-slate); font-size: 11px; }
.ks-track-row__open {
    color: var(--ks-sage);
    font-size: 11px;
    text-decoration: none;
    flex-shrink: 0;
}
.ks-track-row__remove { margin: 0; flex-shrink: 0; }

/*  Empty-list note inside the track card. */
.ks-track-empty { color: var(--ks-slate); font-size: 13px; margin: 0; }

/*  The fixed-height spacer that keeps page content clear of the player bar. */
.ks-player-spacer { height: 96px; }


/*  MOBILE FIX (≤639px): the 3-dots list-options popover was being clipped on
    phones. Two causes: (a) kosis-dashboard.css applies `.ks-card{overflow-x:
    hidden}` at this breakpoint, which cropped the absolutely-positioned popover
    that lives inside the card; (b) the popover's 220px min-width could run off
    the right edge of a narrow card. Fix, scoped to the practice-list menus
    only: let these specific cards show overflow, and clamp the popover so it
    always sits fully on-screen, opening from the menu's right edge.
    (Applies to BOTH the overview cards' corner menu and the detail header
    menu — the user reported the buttons hidden in both paths.) */
@media (max-width: 639px) {
    /*  Stop the card from cropping the popover. Scoped to practice-list cards
        + the detail header so other dashboard cards keep their containment. */
    .ks-pl-card--owned,
    .ks-pl-detail-head { overflow: visible; }

    /*  Popover: anchored to the menu's right edge, width clamped to the
        viewport so it can never spill off-screen or be clipped. */
    .ks-pl-card--owned .ks-audio-pop,
    .ks-pl-detail-head .ks-audio-pop {
        right: 0;
        left: auto;
        min-width: 0;
        width: max-content;
        max-width: min(240px, calc(100vw - 32px));
        box-sizing: border-box;
    }

    /*  The corner menu sits 10px in from the card's top-right; pull the popover
        a touch in from the screen edge so its shadow isn't flush to the bezel. */
    .ks-audio-menu--corner .ks-audio-pop { right: 0; }
}

/* -- 8. Members page (Phase 7) --------------------------------------------- */
/*  This whole section was previously an inline <style> block inside
    pages/members.php; it now lives here so the page carries no embedded CSS.
    A handful of values stay inline in the markup because they are genuinely
    dynamic (data-driven): the capacity-pill fill width, the voice-circle
    conic-gradient, and the per-voice distribution-bar width/colour + legend
    dot colour. Everything structural/presentational lives here. */

/*  Two-column page layout: member list (wide) + side panel (narrow). */
.members-layout {
    max-width: 1280px; margin: 0 auto;
    display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(310px, 0.85fr);
    gap: 1.35rem; align-items: start;
    min-width: 0;
}
/*  Both grid tracks must be allowed to shrink below their content size, or a
    wide child (a long email, the tab strip) forces the whole page wider than
    the viewport and you get horizontal scrolling. */
.members-layout > * { min-width: 0; }
.panel { display: flex; flex-direction: column; gap: 1.15rem; min-width: 0; }

/*  Summary card: total count on the left, capacity pill on the right. */
.summary-card { min-height: 90px; display: flex; align-items: center; justify-content: space-between; background: #fff; }
.summary-number { font-size: 1.75rem; line-height: 1; font-weight: 500; margin-bottom: .25rem; font-family: var(--ks-font-serif); color: var(--ks-navy); }
.summary-label { color: var(--ks-slate); font-size: .9rem; }

/*  Capacity pill — GOLD fill per mockup. The fill WIDTH is dynamic
    (per-choir percentage) so it stays inline on the <span>. */
.capacity-pill { width: 180px; height: 9px; border-radius: 999px; background: var(--ks-border); overflow: hidden; }
.capacity-pill span { display: block; height: 100%; background: var(--ks-gold); }

/*  Search form with leading magnifier icon. */
.search-form { position: relative; }
.search-form svg { position: absolute; left: .95rem; top: 50%; transform: translateY(-50%); color: var(--ks-slate); }
.search-input { width: 100%; border: 1px solid var(--ks-border); border-radius: 10px; padding: .85rem 1rem .85rem 2.55rem; font-family: var(--ks-font-sans); font-size: .92rem; outline: none; background: #fff; color: var(--ks-navy); }
.search-input:focus { border-color: var(--ks-sage); box-shadow: 0 0 0 3px rgba(74,111,92,.15); }

/*  "Select members" pseudo-control bar. */
.select-bar { display: flex; align-items: center; justify-content: center; border: 1px solid var(--ks-border); border-radius: 10px; padding: .82rem; color: var(--ks-navy); background: #fff; font-size: .9rem; font-family: var(--ks-font-sans); }

/*  Session 35: bulk-remove toolbar. The action variant spreads the select-all
    control and the Remove button to opposite ends instead of centering. */
.select-bar--actions { justify-content: space-between; gap: .75rem; }
.select-all-line { display: flex; align-items: center; gap: .5rem; cursor: pointer; color: var(--ks-navy); font-size: .9rem; }
.select-all-line input { width: 16px; height: 16px; cursor: pointer; accent-color: var(--ks-sage); }
/*  Destructive action — brick-red per the brand danger tone. Disabled until at
    least one member is selected (state handled by the inline script). */
.bulk-remove-btn {
    border: none; border-radius: 8px; background: #B85450; color: #fff;
    padding: .55rem .95rem; font-family: var(--ks-font-sans); font-size: .82rem;
    font-weight: 600; cursor: pointer; transition: background .15s, opacity .15s;
}
.bulk-remove-btn:hover:not(:disabled) { background: #a04844; }
.bulk-remove-btn:disabled { opacity: .45; cursor: default; }
/*  Per-row select checkbox + a same-width spacer used on rows that can't be
    selected (your own row), so every avatar stays vertically aligned. */
.member-select { width: 16px; height: 16px; margin-top: 11px; cursor: pointer; flex-shrink: 0; accent-color: var(--ks-sage); }
.member-select-spacer { width: 16px; flex-shrink: 0; display: inline-block; }

/*  Session 36a: voice-section tabs.
    The select-all control now pairs a checkbox with a small scope dropdown
    (this section / all sections). */
.select-all-group { display: flex; align-items: center; gap: .5rem; }
.select-all-scope {
    border: 1px solid var(--ks-border); border-radius: 7px; background: #fff;
    color: var(--ks-navy); font-family: var(--ks-font-sans); font-size: .8rem;
    padding: .3rem .45rem; cursor: pointer;
}

/*  Tab strip: section buttons sitting left-to-right. On narrow screens the
    strip scrolls horizontally (no wrap) so the tabs never crowd or stack —
    the PWA-friendly treatment for small viewports. max-width:100% + min-width:0
    keep the strip itself from widening the page when its tabs overflow.
    The tab region is position:relative so we can paint a right-edge fade that
    hints "there's more — swipe →" when tabs overflow off-screen (see below). */
#ks-members-tabs { position: relative; }
.ks-mtabs {
    display: flex; gap: .4rem; margin: 1.1rem 0 1rem;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: thin; padding-bottom: 2px;
    max-width: 100%; min-width: 0;
    scroll-snap-type: x proximity;
}
.ks-mtab { scroll-snap-align: start; }
/*  Right-edge fade: a gradient pinned to the top-right of the tab region,
    exactly as tall as the tab row, that fades the page colour over the last
    tab so it's visibly "cut off" — a clear cue that the strip scrolls. It sits
    above the strip but ignores pointer events so taps/swipes pass through. The
    arrow glyph reinforces the hint. Only shown on touch-narrow screens where
    the tabs actually overflow; hidden once the strip is enhanced off (no JS)
    is irrelevant here since the cue is purely decorative. */
.ks-mtabs-fade {
    position: absolute; top: 1.1rem; right: 0; height: 2.1rem;
    width: 44px; pointer-events: none;
    display: none;   /* shown at the mobile breakpoint below */
    align-items: center; justify-content: flex-end;
    background: linear-gradient(to right, rgba(253,250,241,0), var(--ks-cream) 75%);
    color: var(--ks-slate); font-size: .9rem; padding-right: 4px;
}
.ks-mtab {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; gap: .4rem;
    border: 1px solid var(--ks-border); border-radius: 999px;
    background: #fff; color: var(--ks-slate);
    font-family: var(--ks-font-sans); font-size: .82rem; font-weight: 500;
    padding: .45rem .85rem; cursor: pointer; white-space: nowrap;
    transition: background .15s, color .15s, border-color .15s;
}
.ks-mtab:hover { border-color: var(--ks-sage); color: var(--ks-navy); }
.ks-mtab.is-active {
    background: var(--ks-navy); color: var(--ks-cream); border-color: var(--ks-navy);
}
/*  Count pill inside each tab. */
.ks-mtab__count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.25rem; height: 1.25rem; padding: 0 .35rem;
    border-radius: 999px; font-size: .72rem; font-weight: 600;
    background: var(--ks-border); color: var(--ks-navy);
}
.ks-mtab.is-active .ks-mtab__count { background: rgba(253,250,241,.25); color: var(--ks-cream); }

/*  Tab panels. Progressive enhancement: by DEFAULT (no JS) every panel is
    shown, so the page degrades to the familiar stacked list and every section
    is reachable. Once the script runs it adds .ks-mtabs-on to the list region,
    which switches on the one-at-a-time tab behaviour. */
.ks-mtab-panel { display: block; }
.ks-mtabs-on .ks-mtab-panel { display: none; }
.ks-mtabs-on .ks-mtab-panel.is-active { display: block; }
/*  Without JS the tab strip itself is just decorative (can't switch), so hide
    it until enhanced — the stacked panels below are fully usable on their own. */
.ks-mtabs { display: none; }
.ks-mtabs-on .ks-mtabs { display: flex; }

/*  Voice-group section heading (uppercase, hairline underline). */
.group-title {
    font-size: .78rem;
    color: var(--ks-slate);
    font-family: var(--ks-font-serif);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .12em;
    margin: 1.25rem 0 .65rem;
    padding-bottom: .5rem;
    border-bottom: 1.5px solid var(--ks-border);
}

/*  Member list + a single member row. */
.member-list { display: flex; flex-direction: column; gap: .85rem; }
.member-row {
    display: flex;
    align-items: flex-start;            /* top-align L + R so the avatar and the
                                           edit dropdowns line up with the name row */
    justify-content: space-between;
    padding: .9rem 1rem;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: 12px;
    box-shadow: 0 1px 4px rgba(31,41,82,.05);
    transition: box-shadow .15s, border-color .15s;
}
.member-row:hover { box-shadow: 0 3px 10px rgba(31,41,82,.09); border-color: var(--ks-sage); }
.member-left { display: flex; align-items: flex-start; gap: .75rem; min-width: 0; flex: 1; }
/*  The text column inside .member-left must be allowed to shrink, or its
    nowrap name/email would force the whole row (and page) wider than the
    viewport — the cause of horizontal scrolling on mobile. */
.member-left > div:last-child { min-width: 0; flex: 1; }
.member-avatar { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--ks-cream); background: var(--ks-navy); font-size: .82rem; font-weight: 600; flex-shrink: 0; overflow: hidden; margin-top: 1px; }
.member-avatar img { width: 100%; height: 100%; object-fit: cover; }
.member-name { font-size: .92rem; font-weight: 500; color: var(--ks-navy); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.member-email { color: var(--ks-slate); font-size: .78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.star { color: var(--ks-gold); font-size: 1.1rem; }
.position-badge { color: var(--ks-navy); font-size: .82rem; font-weight: 500; margin-top: .08rem; }
.voice-badge { color: var(--ks-slate); font-size: .76rem; margin-top: .12rem; }

/*  Per-row action cluster: the inline edit form + Remove + star. Pinned to the
    TOP of the row (align-self) so the Edit button lines up with the member's
    name, not floating in the vertical centre of a tall card. */
.member-actions { display: flex; align-items: flex-start; align-self: flex-start; gap: .45rem; }
/*  Session 36a: the edit controls are tucked behind an "Edit" disclosure to
    declutter the row. Collapsed, it's a compact pill that shrinks to fit its
    label (so it doesn't reserve a wide empty column). Open, it widens to hold
    the labeled form. Native <details>, no JS; the normal submit (with
    scroll-keep) is untouched. */
.ks-edit-disclosure { display: inline-block; }
.ks-edit-disclosure[open] { width: 168px; }
.ks-edit-disclosure__toggle {
    display: inline-flex; align-items: center; gap: .35rem;
    list-style: none; cursor: pointer; white-space: nowrap;
    border: 1px solid var(--ks-sage); border-radius: 999px;
    background: rgba(74,111,92,.08); color: var(--ks-sage);
    font-family: var(--ks-font-sans); font-size: .78rem; font-weight: 600;
    padding: .4rem .9rem;
    transition: border-color .15s, color .15s, background .15s;
}
.ks-edit-disclosure__toggle::-webkit-details-marker { display: none; }
.ks-edit-disclosure__toggle:hover { background: var(--ks-sage); color: var(--ks-cream); }
.ks-edit-disclosure__toggle svg { flex-shrink: 0; }
/*  Open state: solid sage so it clearly reads as the active/expanded control. */
.ks-edit-disclosure[open] > .ks-edit-disclosure__toggle {
    background: var(--ks-sage); color: var(--ks-cream); border-color: var(--ks-sage);
    border-radius: 8px;
}

/*  Session 36a: the edit form stacks vertically inside the disclosure, with a
    small label above each dropdown so it's obvious what each one controls.
    The gap is generous so the three labelled fields don't feel crowded. */
.member-edit-form { display: flex; flex-direction: column; gap: .95rem; align-items: stretch; width: 100%; margin-top: 1.25rem; }
.ks-edit-field { display: flex; flex-direction: column; gap: .3rem; }
.ks-edit-field__label {
    font-size: .68rem; letter-spacing: .04em; text-transform: uppercase;
    color: var(--ks-slate); font-weight: 600;
}
/* Director email-allowed toggle: checkbox + label inline, on one row, with a
   readable (non-uppercase) label since it reads as a sentence. */
.ks-edit-field--check {
    flex-direction: row;
    align-items: center;
    gap: .5rem;
    cursor: pointer;
}
.ks-edit-field--check input[type="checkbox"] {
    width: 16px; height: 16px; accent-color: var(--ks-sage); cursor: pointer;
    flex: 0 0 auto;
}
.ks-edit-field--check .ks-edit-field__label {
    text-transform: none; letter-spacing: 0; font-size: .8rem;
    color: var(--ks-navy); font-weight: 500;
}
.member-edit-form select {
    width: 100%;
    border: 1px solid var(--ks-border); border-radius: 8px;
    padding: .45rem 1.9rem .45rem .55rem;   /* right room for the chevron */
    font-family: var(--ks-font-sans); font-size: .8rem; color: var(--ks-navy);
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    background-color: #fff;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236B7480' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right .55rem center;
    background-size: 14px 14px;
}
/*  Save + Cancel sit side by side at the foot of the form, with a hairline
    above them to separate the actions from the last field. Save is the sage
    primary; Cancel is a quiet outline that just collapses the disclosure. */
.ks-edit-actions-row {
    display: flex; gap: .5rem; margin-top: .35rem; padding-top: .7rem;
    border-top: 0.5px solid var(--ks-border);
}
.ks-edit-save {
    flex: 1; border: none; border-radius: 8px;
    background: var(--ks-sage); color: var(--ks-cream);
    padding: .55rem .85rem; font-family: var(--ks-font-sans);
    font-size: .78rem; font-weight: 600; cursor: pointer;
}
.ks-edit-save:hover { background: #3d5e4d; }
.ks-edit-cancel {
    flex: 0 0 auto; border: 1px solid var(--ks-border); border-radius: 8px;
    background: #fff; color: var(--ks-slate);
    padding: .55rem .85rem; font-family: var(--ks-font-sans);
    font-size: .78rem; font-weight: 500; cursor: pointer;
    transition: border-color .15s, color .15s, background .15s;
}
.ks-edit-cancel:hover { border-color: var(--ks-slate); color: var(--ks-navy); background: var(--ks-parchment); }

/*  Empty-voice note + section divider. */
.empty-note { color: var(--ks-slate); font-size: .9rem; line-height: 1.5; padding: .85rem 0; font-style: italic; }
.section-divider { height: 1px; background: var(--ks-border); margin: .85rem 0; }

/*  Side-panel headings + body copy. */
.side-title { font-size: 1rem; font-weight: 600; margin-bottom: .8rem; font-family: var(--ks-font-serif); color: var(--ks-navy); }
.side-text { color: var(--ks-slate); font-size: .9rem; line-height: 1.5; }
.choice-row { display: flex; align-items: center; justify-content: space-between; padding: .7rem 0; border-bottom: 1px solid var(--ks-border); font-size: .9rem; color: var(--ks-navy); }
.checkbox-line { display: flex; gap: .55rem; align-items: center; font-size: .85rem; color: var(--ks-slate); margin-top: .7rem; }
.read-more { color: var(--ks-sage); text-decoration: none; font-size: .85rem; display: inline-block; margin-top: .7rem; padding: .5rem 1rem; border: 1px solid var(--ks-navy); border-radius: 6px; color: var(--ks-navy); }
.read-more:hover { background: var(--ks-navy); color: var(--ks-cream); text-decoration: none; }

/*  Invite-to-Kosis hero (navy bg, gold accents, cream text). */
.invite-hero { background: var(--ks-navy); color: var(--ks-cream); border-radius: var(--ks-radius-md); padding: 1.25rem; position: relative; overflow: hidden; }
.invite-hero .side-title { color: var(--ks-cream); }
.invite-hero .side-text { color: var(--ks-muted); }
.invite-hero .invite-label { font-family: var(--ks-font-sans); font-size: 10px; font-weight: 500; letter-spacing: 1.5px; text-transform: uppercase; color: var(--ks-gold); margin: 1rem 0 .35rem; display: block; }
.invite-hero .invite-message-box { padding: .75rem; background: var(--ks-navy-lift); border-radius: 8px; font-size: .78rem; line-height: 1.55; color: var(--ks-cream); white-space: pre-line; font-family: var(--ks-font-sans); }
.invite-hero .invite-code { font-family: 'SF Mono', Monaco, monospace; font-weight: 600; color: var(--ks-gold); letter-spacing: .08em; word-break: break-all; padding: .55rem .75rem; background: var(--ks-navy-lift); border-radius: 6px; font-size: .85rem; display: inline-block; }
.invite-hero .invite-link { color: var(--ks-muted); font-size: .78rem; word-break: break-all; padding: .55rem .75rem; background: var(--ks-navy-lift); border-radius: 6px; }
.invite-hero .invite-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: 1rem; }
.invite-hero .btn-gold { background: var(--ks-gold); color: var(--ks-navy); border: none; padding: .55rem 1rem; border-radius: 6px; font-family: var(--ks-font-sans); font-size: .8rem; font-weight: 500; cursor: pointer; }
.invite-hero .btn-gold:hover { background: #c19a45; }
.invite-hero .btn-gold-outline { background: transparent; color: var(--ks-gold); border: 1px solid var(--ks-gold); padding: .55rem 1rem; border-radius: 6px; font-family: var(--ks-font-sans); font-size: .8rem; font-weight: 500; cursor: pointer; }
.invite-hero .btn-gold-outline:hover { background: var(--ks-gold); color: var(--ks-navy); }
.invite-hero .qr-wrap { display: flex; gap: .9rem; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid rgba(212, 169, 85, 0.2); }
.invite-hero .qr-image { width: 100px; height: 100px; border-radius: 8px; background: #fff; padding: .35rem; flex-shrink: 0; }
.invite-hero .qr-image img { width: 100%; height: 100%; display: block; object-fit: contain; }
.invite-hero .qr-title { font-size: .82rem; font-weight: 600; color: var(--ks-cream); margin-bottom: .25rem; font-family: var(--ks-font-serif); }
.invite-hero .qr-note { color: var(--ks-muted); font-size: .72rem; line-height: 1.45; margin: 0; }
.invite-hero .invite-scripture { display: block; margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid rgba(212, 169, 85, 0.2); font-family: var(--ks-font-serif); font-style: italic; font-size: .82rem; color: var(--ks-gold); text-align: center; }

/* ── Session 57 ("S15"): regenerate-invite button inside the navy invite hero ── */
.invite-hero .invite-regen { margin-top: .75rem; }
.invite-hero .invite-regen .btn { display: inline-flex; align-items: center; justify-content: center; gap: .4rem; }
/* The navy-outline button needs light treatment on the dark hero background. */
.invite-hero .invite-regen .btn--navy-outline { color: var(--ks-cream); border-color: rgba(253, 250, 241, 0.45); background: transparent; }
.invite-hero .invite-regen .btn--navy-outline:hover { background: rgba(253, 250, 241, 0.10); border-color: var(--ks-cream); }
.invite-hero .invite-regen .btn svg { width: 14px; height: 14px; }

/* ── Session 57 ("S15"): Danger zone card (delete-choir) ── */
.ks-danger-card { border: 1px solid rgba(184, 84, 80, 0.45); }
.ks-danger-card__title { color: var(--ks-brick, #B85450); }
.ks-danger-form { margin-top: .5rem; }
.ks-danger-form__label { display: block; font-size: .78rem; color: var(--ks-slate); margin-bottom: .4rem; line-height: 1.45; }
.ks-danger-form__input { width: 100%; margin-bottom: .6rem; }

/*  Voice distribution bars (side panel). The fill WIDTH and COLOUR are
    dynamic (per voice) so they stay inline on each .bar span. */
.distribution-row { margin-bottom: .75rem; }
.distribution-head { display: flex; justify-content: space-between; font-size: .82rem; color: var(--ks-navy); margin-bottom: .3rem; }
.bar { height: 6px; background: var(--ks-border); border-radius: 999px; overflow: hidden; }
.bar span { display: block; height: 100%; border-radius: 999px; }

/*  Voice-group overview donut. The conic-gradient BACKGROUND is dynamic
    (per-choir voice mix) so it stays inline on .voice-circle. */
.voice-circle-card { text-align: center; overflow: hidden; }
.voice-circle-wrap { display: flex; align-items: center; justify-content: center; padding: .8rem 0 1rem; }
.voice-circle { width: 220px; height: 220px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: inset 0 0 0 1px rgba(255,255,255,.35), 0 10px 24px rgba(31,41,82,.16); }
.voice-circle-inner { width: 126px; height: 126px; border-radius: 50%; background: rgba(255,255,255,.92); display: flex; align-items: center; justify-content: center; flex-direction: column; padding: .75rem; box-shadow: 0 8px 18px rgba(31,41,82,.08); }
.voice-circle-label { font-size: .9rem; font-weight: 700; color: var(--ks-navy); line-height: 1.2; font-family: var(--ks-font-serif); }
.voice-circle-sub { font-size: .72rem; color: var(--ks-slate); margin-top: .25rem; }
.voice-legend { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: .45rem .7rem; margin-top: .6rem; text-align: left; }
.voice-legend-item { display: flex; align-items: center; justify-content: space-between; gap: .55rem; color: var(--ks-navy); font-size: .8rem; }
.voice-legend-left { display: flex; align-items: center; gap: .45rem; min-width: 0; }
/*  Legend dot COLOUR is dynamic (per voice) so it stays inline. */
.voice-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }

/*  Capacity summary text block. */
.capacity-text { color: var(--ks-slate); font-size: .84rem; line-height: 1.55; }

/*  Session 36b: "+ Add member" action row + button. The row gives the button a
    little breathing room above the bulk-remove toolbar; the button itself is a
    gold pill (the page's primary positive action, distinct from the brick-red
    destructive Remove control). */
.ks-members-addrow { margin: 0 0 .9rem; }
.ks-add-member-btn {
    display: inline-flex; align-items: center; gap: .45rem;
    border: none; border-radius: 999px;
    background: var(--ks-gold); color: var(--ks-navy);
    font-family: var(--ks-font-sans); font-size: .85rem; font-weight: 600;
    padding: .6rem 1.1rem; cursor: pointer;
    transition: background .15s, box-shadow .15s, transform .05s;
    box-shadow: 0 1px 3px rgba(31,41,82,.14);
}
.ks-add-member-btn:hover { background: #c19a45; box-shadow: 0 2px 7px rgba(31,41,82,.2); }
.ks-add-member-btn:active { transform: translateY(1px); }
.ks-add-member-btn svg { flex-shrink: 0; }

/*  Session 36b: "Add member" invite modal. Hidden by default; .is-open shows it
    as a centred overlay. The inner .invite-hero reuses the existing sidebar-hero
    styles, so only the modal shell (overlay, backdrop, panel, head, close, intro)
    is defined here. */
.ks-invite-modal {
    display: none;
    position: fixed; inset: 0; z-index: 1000;
    align-items: flex-start; justify-content: center;
    padding: 4vh 1rem;
}
.ks-invite-modal.is-open { display: flex; }
.ks-invite-modal__backdrop {
    position: absolute; inset: 0;
    background: rgba(31,41,82,.55);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.ks-invite-modal__panel {
    position: relative; z-index: 1;
    width: 100%; max-width: 480px; max-height: 92vh; overflow-y: auto;
    background: var(--ks-cream); border-radius: var(--ks-radius-md);
    padding: 1.4rem 1.4rem 1.6rem;
    box-shadow: 0 18px 50px rgba(31,41,82,.32);
    animation: ksInviteIn .18s ease-out;
}
@keyframes ksInviteIn {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: translateY(0); }
}
.ks-invite-modal__head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; margin-bottom: .4rem;
}
.ks-invite-modal__title {
    margin: 0; font-family: var(--ks-font-serif); color: var(--ks-navy);
    font-size: 1.25rem; font-weight: 600;
}
.ks-invite-modal__close {
    flex-shrink: 0; border: none; background: none; cursor: pointer;
    color: var(--ks-slate); font-size: 1.6rem; line-height: 1;
    padding: 0 .25rem; border-radius: 6px;
    transition: color .15s, background .15s;
}
.ks-invite-modal__close:hover { color: var(--ks-navy); background: rgba(31,41,82,.06); }
.ks-invite-modal__intro {
    margin: 0 0 1.1rem; color: var(--ks-slate);
    font-size: .88rem; line-height: 1.55;
}
/*  Inside the modal the invite hero shouldn't add its own outer margin; the
    panel already provides the padding. */
.ks-invite-modal .invite-hero { margin: 0; }

/*  Responsive: single column under 1000px; stacked rows + iOS-safe inputs
    under 640px (16px font prevents focus auto-zoom on iOS). */
@media (max-width: 1000px) { .members-layout { grid-template-columns: 1fr; } }
@media (max-width: 640px) {
    .member-row { flex-direction: column; align-items: stretch; gap: .55rem; }
    /*  Edit + star share one row at the foot of the card: Edit fills the space,
        the star sits to its right, vertically centred with the button — no more
        disconnected "button on the left, star floating below" look. A hairline
        above separates the actions from the member's details. */
    .member-actions {
        flex-direction: row; align-items: center; gap: .6rem;
        padding-top: .6rem; border-top: 0.5px solid var(--ks-border);
    }
    .ks-edit-disclosure { display: block; flex: 1; width: auto; }
    .ks-edit-disclosure[open] { width: auto; }
    .ks-edit-disclosure__toggle { justify-content: center; padding: .7rem; font-size: 15px; min-height: 44px; border-radius: 10px; }
    .member-edit-form { flex-direction: column; align-items: stretch; width: 100%; gap: .85rem; }
    .member-edit-form select {
        width: 100%; font-size: 16px; padding: .75rem 1rem; min-height: 48px; border-radius: 10px;
        -webkit-appearance: none; appearance: none;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23667085' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
        background-repeat: no-repeat; background-position: right .85rem center; padding-right: 2.5rem;
    }
    .ks-edit-save, .ks-edit-cancel { padding: .8rem; font-size: 15px; min-height: 46px; border-radius: 10px; }
    /*  Star sits inline beside the Edit button (not stretched/centred below). */
    .star { align-self: center; flex: 0 0 auto; font-size: 1.4rem; }
    /*  Tab-strip scroll affordance: show the right-edge fade + arrow, and keep a
        thin always-visible scrollbar so it's unmistakable the voices scroll
        sideways (people were missing Bass / Unassigned off the right edge). */
    .ks-mtabs-fade { display: flex; }
    .ks-mtabs {
        scrollbar-width: thin;
        scrollbar-color: var(--ks-border) transparent;
        padding-right: 36px;   /* leave room so the last tab isn't hidden under the fade */
    }
    .ks-mtabs::-webkit-scrollbar { height: 5px; -webkit-appearance: none; }
    .ks-mtabs::-webkit-scrollbar-thumb { background: var(--ks-border); border-radius: 999px; }
}
.ks-label__hint  { color: var(--ks-slate); font-weight: 400; }

/*  A full-width textarea with border-box sizing (Edit-song-details notes). */
.ks-textarea--full { width: 100%; box-sizing: border-box; }

/*  SONG HEADER ------------------------------------------------------------- */
/*  Grid layout: the icon is a fixed first column; the title + body sit in the
    second column. On desktop the icon spans both rows so title and body stack
    neatly beside it. On mobile (below) the body drops to a full-width second
    row that spans UNDER the icon, so there is no tall empty column. */
.ks-song-head {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--ks-space-md);
    row-gap: 4px;
    align-items: start;
}
.ks-song-head__icon {
    grid-row: 1 / span 2;        /* icon spans the title + body rows on desktop */
    width: 48px; height: 48px;
    border-radius: 10px;
    background: var(--ks-navy);
    color: var(--ks-gold);
    display: flex; align-items: center; justify-content: center;
    font-size: 20px;
    flex-shrink: 0;
}
.ks-song-head__body { grid-column: 2; min-width: 0; }
.ks-song-title    { grid-column: 2; margin: 0 0 4px; align-self: center; }
.ks-song-composer { color: var(--ks-slate); font-size: 13px; margin: 0; }
.ks-song-composer__label { color: var(--ks-navy); font-weight: 600; }
.ks-song-notes    { color: var(--ks-navy); font-size: 13px; margin: var(--ks-space-sm) 0 0; line-height: 1.5; }

/*  EDIT SONG DETAILS (manager disclosure) --------------------------------- */
/*  The trigger is a real button-styled summary (parchment chip) rather than a
    bare text link, so it reads as an action. */
.ks-song-edit { margin-top: var(--ks-space-sm); }
.ks-song-edit__toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    list-style: none;
    background: var(--ks-parchment);
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    color: var(--ks-navy);
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 500;
    padding: 7px 12px;
}
.ks-song-edit__toggle::-webkit-details-marker { display: none; }
.ks-song-edit__toggle:hover { border-color: var(--ks-sage); color: var(--ks-sage); }
.ks-song-edit__toggle svg { flex-shrink: 0; }
/*  Open state: tint the chip so it's clear the editor is expanded. */
.ks-song-edit[open] .ks-song-edit__toggle {
    background: #fff;
    border-color: var(--ks-sage);
    color: var(--ks-sage);
}
.ks-song-edit__form { margin-top: var(--ks-space-sm); max-width: 420px; }
.ks-song-edit__label {
    display: block;
    font-size: 11px;
    color: var(--ks-slate);
    margin-bottom: 3px;
}
.ks-song-edit__req { color: #B83A3A; }
.ks-song-edit__opt { font-weight: 400; }
.ks-song-edit__input,
.ks-song-edit__textarea {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 10px;
}
.ks-song-edit__textarea { font-family: var(--ks-font-sans); line-height: 1.5; }
/*  Save + Cancel sit together at the right edge of the form. */
.ks-song-edit__actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/*  MOBILE (≤640px): the icon + title share the top row, then the body (composer
    / notes / edit) spans the FULL width on the second row — flowing under the
    icon instead of leaving a tall empty column beside it. The icon no longer
    spans two rows here; it only occupies row 1 next to the title. */
@media (max-width: 640px) {
    .ks-song-head {
        column-gap: 10px;
        row-gap: 8px;
        align-items: center;
    }
    .ks-song-head__icon {
        grid-row: 1;              /* row 1 only — don't reserve a tall column */
        grid-column: 1;
        width: 40px; height: 40px;
        font-size: 18px;
        border-radius: 9px;
    }
    .ks-song-title {
        grid-row: 1;
        grid-column: 2;
        font-size: 18px;
        line-height: 1.25;
        margin: 0;
    }
    /*  Body spans both columns on row 2, so it uses the space under the icon. */
    .ks-song-head__body {
        grid-row: 2;
        grid-column: 1 / -1;
    }
    .ks-song-edit__form { max-width: 100%; }
    .ks-song-edit__actions { flex-direction: column-reverse; }
    .ks-song-edit__actions .btn { width: 100%; }
}
/*  The big clickable "open this sheet" button that fills the row. */
.ks-sheet-open {
    display: flex;
    align-items: center;
    gap: 10px;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    flex: 1;
    min-width: 0;
    color: var(--ks-navy);
    font-family: var(--ks-font-sans);
}
/*  Edit-audio form inside the popover. */
.ks-edit-form { padding: 6px 8px 2px; }
.ks-edit-actions { display: flex; gap: 6px; }


/* -- 10. Events page (Phase 9) --------------------------------------------- */
/*  pages/events.php is the largest page. Its calendar / priority / tooltip
    styling already lives in kosis-dashboard.css (.ks-cal__*, .ks-prio-badge,
    .ks-info*, .ks-prio-select, .ks-viewtoggle*); the event-type pill is
    .ks-pill-badge (kosis-brand-extensions.css) and the scripture band is
    .ks-footer-scripture (also dashboard). This section adds the remaining
    inline-only bits: the header/filter bar, the export card, the event-list
    cards, the song-program list, and the create/edit form.

    Genuinely dynamic values stay inline in the markup (data-driven): the
    per-priority chip / badge / agenda-bar / legend-dot / tooltip-dot colours
    and the priority <option> colours. The per-$view filter-form display toggle
    is handled with .ks-hidden, and disabled move-buttons use :disabled here
    instead of an inline opacity. */

/*  Small shared input/label helpers used across the events forms. */
.ks-input--auto { width: auto; }
.ks-input--sm   { font-size: 12px; padding: 6px 8px; }
.ks-label--xs   { font-size: 11px; }

/*  HEADER + FILTER BAR ----------------------------------------------------- */
.ks-events-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--ks-space-sm);
    margin-bottom: var(--ks-space-md);
    scroll-margin-top: 16px;   /* clear the sticky topbar when #ks-events-top is the scroll target */
}
.ks-events-bar__filters {
    display: flex;
    gap: var(--ks-space-sm);
    flex-wrap: wrap;
}
/*  Both filter forms are a small inline flex row that wraps. The range form is
    hidden in calendar view via the .ks-hidden utility (no inline display). */
.ks-events-filterform {
    display: flex;
    gap: var(--ks-space-sm);
    flex-wrap: wrap;
    align-items: center;
}

/*  EXPORT CARD ------------------------------------------------------------- */
.ks-export-row {
    display: flex;
    align-items: flex-end;
    gap: var(--ks-space-sm);
    flex-wrap: wrap;
}
.ks-export-note {
    color: var(--ks-slate);
    font-size: 11px;
    margin: 8px 0 0;
}

/*  Live-calendar subscribe (0006b) — set apart from the one-time download
    buttons above with a divider so the two actions read as distinct. */
.ks-export-subscribe {
    margin-top: var(--ks-space-md);
    padding-top: var(--ks-space-md);
    border-top: 1px solid var(--ks-border);
}
.ks-subscribe-box {
    margin-top: 4px;
}
.ks-subscribe-box .ks-export-row {
    margin-top: 10px;
}
.ks-subscribe-box .ks-input {
    flex: 1 1 260px;
    min-width: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    color: var(--ks-slate);
}

/*  EVENT LIST -------------------------------------------------------------- */

/*  Empty-state card. */
.ks-events-empty { padding: var(--ks-space-lg); margin-bottom: var(--ks-space-md); }
.ks-events-empty__title { color: var(--ks-navy); }
.ks-events-empty__note  { color: var(--ks-slate); font-size: 12px; margin: var(--ks-space-sm) 0 0; }

/*  A single event card + its top row (badges/title on the left, actions right). */
.ks-event-card { margin-bottom: var(--ks-space-sm); scroll-margin-top: 16px; }
.ks-event-card__top {
    display: flex;
    gap: var(--ks-space-md);
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
}
.ks-event-card__main {
    display: flex;
    gap: var(--ks-space-md);
    align-items: flex-start;
    flex: 1;
    min-width: 240px;
}
.ks-event-card__text { flex: 1; min-width: 0; }
.ks-event-title {
    display: block;
    font-family: var(--ks-font-serif);
    font-size: 15px;
    color: var(--ks-navy);
}
.ks-event-when { margin: 2px 0 0; color: var(--ks-slate); font-size: 12px; }
.ks-event-desc { margin: var(--ks-space-sm) 0 0; color: var(--ks-navy); font-size: 13px; line-height: 1.5; }
.ks-event-actions { display: flex; gap: var(--ks-space-sm); align-items: center; }

/*  MOBILE (≤640px): on a phone the type pill + priority badge were holding
    their intrinsic width in the flex row, so the title/date text block wrapped
    onto a new line and left a big empty gap beside the badges. Here the badges
    sit together on their own compact top row (vertically centred so the pill
    and priority chip share a baseline), and the title/date take the full width
    of the next line with clear breathing room — a tidy two-tier card header. */
@media (max-width: 640px) {
    .ks-event-card__top { gap: var(--ks-space-sm); }
    .ks-event-card__main {
        flex-wrap: wrap;
        align-items: center;   /* pill + priority share a vertical centre line */
        gap: 8px;              /* even gap between the two badges */
        min-width: 0;          /* allow it to shrink on a narrow card */
    }
    /*  Badges shrink to their label width and stay on the same row. */
    .ks-event-card__main > .ks-pill-badge,
    .ks-event-card__main > .ks-prio-badge {
        flex: 0 0 auto;
    }
    /*  Title + date take the whole next line, with a clean gap under the
        badges so the two tiers read as distinct. */
    .ks-event-card__text {
        flex: 1 1 100%;
        min-width: 0;
        margin-top: 4px;
    }
    /*  Slightly larger, tighter title line on mobile for a cleaner hierarchy. */
    .ks-event-title { font-size: 16px; line-height: 1.3; }
    .ks-event-when  { margin-top: 3px; line-height: 1.4; }
    /*  Edit/Delete actions go full-width-friendly: drop below the badges row
        and align left so they don't crowd the badges on a narrow card. */
    .ks-event-card__top .ks-event-actions {
        flex-basis: 100%;
        margin-top: 2px;
    }
}

/*  SONG PROGRAM ------------------------------------------------------------ */
.ks-event-program {
    border-top: 0.5px solid var(--ks-border);
    margin-top: var(--ks-space-md);
    padding-top: var(--ks-space-md);
}
.ks-event-program__none { color: var(--ks-slate); font-size: 12px; margin: 0; }
.ks-event-program__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: var(--ks-space-sm);
}
.ks-event-program__label {
    font-family: var(--ks-font-sans);
    font-size: 11px;
    letter-spacing: .5px;
    text-transform: uppercase;
    color: var(--ks-slate);
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/*  Two small .ks-info tooltip modifiers used by the Program tooltip: cancel the
    uppercase/letter-spacing it would inherit from the label, and let the body
    text be a block. */
.ks-info__pop--normalcase { text-transform: none; letter-spacing: normal; }
.ks-info__txt--block { display: block; }

/*  The ordered program list. First item has no top rule (was an inline
    conditional border on $i===0); :first-child handles it here. */
.ks-program-list { list-style: none; margin: 0; padding: 0; }
.ks-program-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 0;
    border-top: 0.5px solid var(--ks-border);
}
.ks-program-item:first-child { border-top: none; }
.ks-program-num {
    flex-shrink: 0;
    width: 22px; height: 22px;
    border-radius: 50%;
    background: var(--ks-navy);
    color: var(--ks-gold);
    display: flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 600;
}
.ks-program-body { flex: 1; min-width: 0; }
.ks-program-title { font-family: var(--ks-font-serif); font-size: 14px; color: var(--ks-navy); }
.ks-program-composer { color: var(--ks-slate); font-size: 12px; }
.ks-program-note { color: var(--ks-sage); font-size: 12px; font-style: italic; margin-top: 2px; }
.ks-program-noteedit { margin-top: 4px; }

/*  The move-up / move-down / remove control cluster on a program row. */
.ks-program-controls {
    display: flex;
    align-items: center;
    gap: 2px;
    flex-shrink: 0;
}
/*  A bare icon button (arrows, ×). Disabled state dims + disables the cursor
    via :disabled — no inline opacity needed. */
.ks-iconbtn {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--ks-slate);
    font-size: 14px;
    padding: 2px 4px;
}
.ks-iconbtn:disabled { opacity: .3; cursor: default; }
/*  Larger move-up/down arrows — the 14px icon buttons were a too-small tap
    target on touch. Bigger glyph + a roomier, min-sized hit area. */
.ks-iconbtn--lg {
    font-size: 19px;
    line-height: 1;
    padding: 5px 9px;
    min-width: 32px;
    min-height: 32px;
    border-radius: var(--ks-radius-sm);
}
.ks-iconbtn--lg:hover:not(:disabled) { background: var(--ks-cream); }
/*  Cancel button inside the note editor sits next to Save. */
.ks-noteedit-cancel { margin-left: 6px; }

/*  The "add a song from repertoire" form row (dashed divider above). */
.ks-program-add {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    align-items: center;
    margin-top: var(--ks-space-sm);
    border-top: 0.5px dashed var(--ks-border);
    padding-top: var(--ks-space-sm);
}
.ks-program-add__song { flex: 1; min-width: 160px; }
.ks-program-add__note { flex: 1; min-width: 120px; }

/*  CREATE / EDIT FORM ------------------------------------------------------ */
.ks-event-form { margin: var(--ks-space-lg) 0 var(--ks-space-md); scroll-margin-top: 16px; }
/*  In edit mode the card gets a gold highlight ring (was an inline conditional
    border/box-shadow); the .is-editing modifier carries it. */
.ks-event-form.is-editing {
    border: 1px solid var(--ks-gold);
    box-shadow: 0 0 0 3px rgba(212, 169, 85, .15);
}
/*  Form title sits with a md gap by default; tighter (2px) when editing, since
    the edit note follows immediately beneath it. */
.ks-event-form__title { margin-bottom: var(--ks-space-md); }
.ks-event-form.is-editing .ks-event-form__title { margin-bottom: 2px; }
.ks-event-form__editnote { color: var(--ks-sage); font-size: 12px; margin: 0 0 var(--ks-space-md); }

/*  A 11px slate helper line under a form control. */
.ks-form-hint { color: var(--ks-slate); font-size: 11px; margin: 4px 0 0; }

/*  The form's submit/cancel button row. */
.ks-form-actions {
    display: flex;
    gap: var(--ks-space-sm);
    margin-top: var(--ks-space-md);
}

/*  An xs summary-toggle variant (11px) used by the per-song "Edit note"
    disclosure (the base .ks-summary-toggle is 12px). */
.ks-summary-toggle--xs { font-size: 11px; }




/* -- 11. Messages feed: announcements, noticeboard, comments, reactions ----
   (Session 18 PR1 + revision) Real two-channel feed with comments, any-emoji
   reactions, unread tab badges, pin, inline edit, and a per-post/-comment
   context menu. Section 2's .ks-msg-avatar / .ks-msg-empty / .ks-msg-submit
   are reused. No inline styles on messages.php. */

/*  Unread count badge on a channel tab. */
.ks-tab-badge {
    display: inline-block;
    min-width: 17px;
    padding: 0 5px;
    margin-left: 4px;
    border-radius: 999px;
    background: #B85450;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 17px;
    text-align: center;
    vertical-align: middle;
}

/*  A single feed post card. */
.ks-feed-post {
    margin-bottom: var(--ks-space-md);
    scroll-margin-top: 16px;
    position: relative;
    /* Make each post read as its own distinct panel: a clearer border, a soft
       resting shadow that separates it from the page and from neighbours, and
       a subtle lift on hover. */
    border: 1px solid var(--ks-border);
    border-radius: var(--ks-radius-md, 12px);
    box-shadow: 0 1px 2px rgba(31, 41, 82, 0.05),
                0 4px 12px rgba(31, 41, 82, 0.05);
    transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease;
}
.ks-feed-post:hover {
    box-shadow: 0 2px 4px rgba(31, 41, 82, 0.08),
                0 10px 24px rgba(31, 41, 82, 0.10);
    border-color: #d9d3c4;
}
/*  A slim accent strip along the very top edge gives every card a consistent
    "header" anchor and a touch of brand colour, helping the eye chunk the feed
    into separate posts. Pinned + unread states recolour this strip. */
.ks-feed-post::after {
    content: "";
    position: absolute;
    top: 0; left: 16px; right: 16px;
    height: 3px;
    border-radius: 0 0 3px 3px;
    background: var(--ks-sage);
    opacity: .55;
}
.ks-feed-post.is-pinned { border-color: var(--ks-gold); }
.ks-feed-post.is-pinned::after { background: var(--ks-gold); opacity: .9; }

/*  "Pinned" ribbon at the top of a pinned post. */
.ks-feed-post__pinned {
    font-size: 11px;
    font-weight: 600;
    color: var(--ks-gold);
    margin-bottom: 6px;
    letter-spacing: .3px;
}

/*  Head row: avatar + byline on the left, context menu on the right. */
.ks-feed-post__head {
    display: flex;
    align-items: flex-start;
    gap: var(--ks-space-sm);
    margin-bottom: var(--ks-space-sm);
}
.ks-feed-post__byline { flex: 1; min-width: 0; }
.ks-feed-post__author { display: block; font-size: 13px; color: var(--ks-navy); }
.ks-feed-post__date   { color: var(--ks-slate); font-size: 11px; }
.ks-edited { color: var(--ks-muted); font-style: italic; }

/*  Post body text. */
.ks-feed-post__body {
    color: var(--ks-navy);
    font-size: 14px;
    line-height: 1.55;
    white-space: pre-wrap;
    margin-bottom: var(--ks-space-sm);
}

/*  CONTEXT MENU (⋯) — shared by posts and comments. ----------------------- */
.ks-ctx { position: relative; flex-shrink: 0; }
.ks-ctx__btn {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--ks-slate);
    font-size: 18px;
    line-height: 1;
    padding: 2px 8px;
    border-radius: 6px;
}
.ks-ctx__btn:hover { background: var(--ks-parchment); color: var(--ks-navy); }
.ks-ctx__menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 30;
    min-width: 140px;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    box-shadow: 0 6px 20px rgba(31, 41, 82, 0.16);
    padding: 4px;
    display: flex;
    flex-direction: column;
}
.ks-ctx__menu[hidden] { display: none; }
.ks-ctx__form { margin: 0; }
.ks-ctx__item {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
    font-family: var(--ks-font-sans);
    font-size: 13px;
    color: var(--ks-navy);
    padding: 7px 10px;
    border-radius: 6px;
}
.ks-ctx__item:hover { background: var(--ks-parchment); }
.ks-ctx__item--danger { color: #B85450; }
.ks-ctx__item--danger:hover { background: #FDECEC; }
.ks-ctx--sm .ks-ctx__btn { font-size: 15px; padding: 0 6px; }

/*  INLINE EDIT form (shown when Edit is chosen). ------------------------- */
.ks-edit-inline { margin-bottom: var(--ks-space-sm); }
.ks-edit-inline[hidden] { display: none; }
.ks-edit-inline__actions {
    display: flex;
    align-items: center;
    gap: var(--ks-space-sm);
    margin-top: 6px;
}

/*  REACTIONS --------------------------------------------------------------- */
.ks-reax {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding-top: var(--ks-space-sm);
    border-top: 0.5px solid var(--ks-border);
}
.ks-reax__form { margin: 0; }
.ks-reax__chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: var(--ks-cream);
    border: 0.5px solid var(--ks-border);
    border-radius: 999px;
    padding: 2px 9px;
    cursor: pointer;
    font-size: 12px;
    line-height: 1.6;
    transition: background 120ms, border-color 120ms;
}
.ks-reax__chip:hover { background: var(--ks-parchment); }
.ks-reax__chip.is-mine { background: #EEF3FF; border-color: var(--ks-navy); }
.ks-reax__emoji { font-size: 13px; }
.ks-reax__count { color: var(--ks-slate); font-size: 11px; font-weight: 600; }

.ks-reax__add { position: relative; }
.ks-reax__addbtn {
    background: none;
    border: 0.5px dashed var(--ks-border);
    border-radius: 999px;
    padding: 2px 9px;
    cursor: pointer;
    font-size: 12px;
    color: var(--ks-slate);
}
.ks-reax__addbtn:hover { border-color: var(--ks-sage); color: var(--ks-navy); }
.ks-reax__palette {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 0;
    z-index: 20;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    width: 248px;
    max-width: 80vw;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    box-shadow: 0 6px 20px rgba(31, 41, 82, 0.16);
    padding: 10px;
}
.ks-reax__palette[hidden] { display: none; }

/* =============================================================================
   Mobile (≤640px) — Announcements / Noticeboard POST reaction palette.
   -----------------------------------------------------------------------------
   The post palette opens UPWARD (bottom: 100%) from the "+ 😊" button at the
   BOTTOM of a post. On a phone a short post (or one near the top of the scroll)
   left no room above, so the palette overflowed upward and overlapped the post
   above it, reading as a detached floating box. Flip it to open DOWNWARD here
   (there is always room below the reaction row) and clamp its width to the
   viewport so it never clips sideways. Mirrors the DM-stream palette fix. */
@media (max-width: 640px) {
    .ks-reax__palette {
        bottom: auto;
        top: calc(100% + 6px);
        left: 0;
        right: auto;
        width: auto;
        min-width: 220px;
        max-width: calc(100vw - 32px);
    }
}
.ks-reax__choice {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    padding: 4px;
    border-radius: 6px;
}
.ks-reax__choice:hover { background: var(--ks-parchment); }
.ks-reax__custom {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 6px 0 0;
    width: 100%;
    border-top: 0.5px solid var(--ks-border);
    padding-top: 8px;
}
.ks-reax__custominput {
    flex: 1 1 auto;
    min-width: 0;
    height: 30px;
    box-sizing: border-box;
    border: 1px solid var(--ks-border);
    border-radius: 8px;
    padding: 0 8px;
    font-size: 16px;          /* fits one or two emoji comfortably */
    line-height: 30px;
    text-align: center;
}
.ks-reax__custominput:focus {
    outline: none;
    border-color: var(--ks-sage);
    box-shadow: 0 0 0 2px rgba(74, 111, 92, 0.18);
}
.ks-reax__customadd {
    flex: 0 0 auto;
    height: 30px;
    box-sizing: border-box;
    background: var(--ks-sage);
    color: var(--ks-cream);
    border: none;
    border-radius: 8px;
    padding: 0 12px;
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
    font-family: var(--ks-font-sans);
}
.ks-reax__customadd:hover { filter: brightness(1.05); }

/*  COMMENTS ---------------------------------------------------------------- */
.ks-cmt { margin-top: var(--ks-space-sm); }
.ks-cmt__toggle {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--ks-sage);
    font-size: 12px;
    font-family: var(--ks-font-sans);
    padding: 4px 0;
}
.ks-cmt__toggle:hover { text-decoration: underline; }
.ks-cmt__list { display: flex; flex-direction: column; gap: var(--ks-space-sm); margin: var(--ks-space-sm) 0; }
.ks-cmt__list[hidden] { display: none; }
.ks-cmt__item { display: flex; gap: var(--ks-space-sm); align-items: flex-start; }
.ks-cmt__avatar {
    width: 28px; height: 28px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--ks-sage);
    color: var(--ks-cream);
    font-size: 10px;
    font-weight: 600;
    display: flex; align-items: center; justify-content: center;
}
.ks-cmt__main {
    flex: 1;
    min-width: 0;
    background: var(--ks-cream);
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    padding: 8px 10px;
}
.ks-cmt__byline { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
.ks-cmt__byline strong { font-size: 12px; color: var(--ks-navy); }
.ks-cmt__date { color: var(--ks-slate); font-size: 10px; }
.ks-cmt__body { display: block; color: var(--ks-navy); font-size: 13px; line-height: 1.5; white-space: pre-wrap; }
.ks-cmt__form { display: flex; gap: 6px; margin-top: var(--ks-space-sm); }
.ks-cmt__input {
    flex: 1;
    min-width: 0;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-input);
    padding: 8px 12px;
    font-family: var(--ks-font-sans);
    font-size: 13px;
    color: var(--ks-navy);
}
.ks-cmt__input:focus { outline: none; border-color: var(--ks-sage); }
.ks-cmt__send {
    background: var(--ks-navy);
    color: var(--ks-cream);
    border: none;
    border-radius: var(--ks-radius-input);
    padding: 8px 16px;
    cursor: pointer;
    font-size: 12px;
    font-family: var(--ks-font-sans);
    flex-shrink: 0;
}
.ks-cmt__send:hover { background: var(--ks-navy-lift); }


/* -- 12. Messages live updates: new-posts pill + nav badges (Session 18 C-1) --
   The "N new posts" pill sits at the top of the feed container and is revealed
   by the poller when fresh content is staged. The nav badges live on the
   sidebar Messages item and the mobile "More" sheet. */

/*  Feed container is the positioning context for the floating pill.
    It is also inset horizontally by one card-padding unit so the announcement /
    noticeboard POST cards line up with the inset content on the other Messages
    tabs (the hero card above it, and the Direct / Section list rows, which all
    sit inside a .ks-card padded by --ks-space-lg). Without this the feed posts
    ran full-bleed to the 1280px column edge while the list rows were inset,
    which read as "different sizing" when clicking between the four tabs. Using
    the same token as the card padding keeps the gutter identical on every tab
    and at every viewport (PWA: one stable content margin across the app). */
.ks-feed {
    position: relative;
    scroll-margin-top: 80px;   /* clear the sticky topbar when #ks-feed-top is targeted */
    padding-left: var(--ks-space-lg);
    padding-right: var(--ks-space-lg);
}

/* ── Feed filter + pagination bar (announcements / noticeboard) ───────────
   A contained toolbar above the feed: search, sort, per-page, date presets +
   range, posted-by, and quick toggles. Server-side; brand-consistent (cream /
   sage / gold). Stacks to one column on phones. The date inputs carry
   .ks-pick-on-click (kosis.js §19 opens the native picker on tap). */
.ks-feedfilter { }
/* The collapsed header ("Filters" with an optional active-count pill). The
   whole card is a <details>; the <summary> is the always-visible toggle. */
.ks-feedfilter > summary.ks-feedfilter__summary {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    list-style: none;
    font-family: var(--ks-font-sans);
    font-size: 14px;
    font-weight: 600;
    color: var(--ks-navy);
    user-select: none;
}
.ks-feedfilter__summary::-webkit-details-marker { display: none; }
.ks-feedfilter__summary svg { color: var(--ks-sage); flex: none; }
.ks-feedfilter__summary-label { flex: none; }
.ks-feedfilter__summary-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px;
    border-radius: 999px; background: var(--ks-gold); color: var(--ks-navy);
    font-size: 11px; font-weight: 700; line-height: 1;
}
.ks-feedfilter__summary-chevron {
    margin-left: auto; color: var(--ks-slate); font-size: 20px; line-height: 1;
    transform: rotate(90deg); transition: transform 150ms ease;
}
.ks-feedfilter[open] > .ks-feedfilter__summary .ks-feedfilter__summary-chevron {
    transform: rotate(-90deg);
}
/* Space between the summary and the form only when expanded. */
.ks-feedfilter[open] > .ks-feedfilter__form { margin-top: var(--ks-space-md); }
.ks-feedfilter__form {
    display: flex;
    flex-direction: column;
    gap: var(--ks-space-sm);
}
.ks-feedfilter__row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--ks-space-sm);
}
/* Search box with a leading magnifier. The WRAPPER carries the border and the
   icon + input sit inside it as in-flow flex items — no absolute positioning,
   so the icon can never drift onto the text regardless of row height or
   viewport. The input itself is borderless and transparent (the wrapper is the
   visible field). */
.ks-feedfilter__search {
    display: flex;
    align-items: center;
    gap: 8px;
    align-self: flex-end;
    flex: 1 1 240px;
    min-width: 0;
    box-sizing: border-box;
    border: 1px solid var(--ks-border);
    border-radius: 10px;
    background: #fff;
    padding: 0 12px;
}
.ks-feedfilter__search:focus-within {
    border-color: var(--ks-sage);
    box-shadow: 0 0 0 3px rgba(74,111,92,.15);
}
.ks-feedfilter__search svg {
    flex: none;
    color: var(--ks-slate);
    pointer-events: none;
}
.ks-feedfilter__searchinput {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    border: 0;
    outline: none;
    background: transparent;
    padding: 9px 0;
    font-family: var(--ks-font-sans); font-size: 13px; color: var(--ks-navy);
    line-height: 1.3;
}
.ks-feedfilter__searchinput:focus {
    outline: none;
}
/* A labelled field (label above the control). */
.ks-feedfilter__field { display: flex; flex-direction: column; gap: 3px; }
.ks-feedfilter__lbl {
    font-size: 10px; letter-spacing: .5px; text-transform: uppercase;
    color: var(--ks-slate); font-weight: 600;
}
.ks-feedfilter__select {
    border: 1px solid var(--ks-border); border-radius: 8px;
    padding: 8px 10px; font-family: var(--ks-font-sans); font-size: 13px;
    color: var(--ks-navy); background: #fff; cursor: pointer;
}
.ks-feedfilter__select:focus { outline: none; border-color: var(--ks-sage); }
.ks-feedfilter__date {
    border: 1px solid var(--ks-border); border-radius: 8px;
    padding: 7px 10px; font-family: var(--ks-font-sans); font-size: 13px;
    color: var(--ks-navy); background: #fff; cursor: pointer;
}
.ks-feedfilter__date:focus { outline: none; border-color: var(--ks-sage); }
/* Date-preset chips: a wrapping pill group; the active one fills navy. */
.ks-feedfilter__presets {
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    flex: 1 1 auto;
}
.ks-feedfilter__chip {
    border: 0.5px solid var(--ks-border); border-radius: 999px;
    background: var(--ks-cream); color: var(--ks-slate);
    font-family: var(--ks-font-sans); font-size: 12px; font-weight: 500;
    padding: 6px 12px; cursor: pointer;
    transition: background 120ms, color 120ms, border-color 120ms;
}
.ks-feedfilter__chip:hover {
    background: rgba(31,41,82,.05); color: var(--ks-navy);
    border-color: var(--ks-sage);
}
.ks-feedfilter__chip.is-active {
    background: var(--ks-navy); color: var(--ks-cream); border-color: var(--ks-navy);
}
/* Quick toggles (Just mine / Pinned only / Has comments). */
.ks-feedfilter__toggle {
    display: inline-flex; align-items: center; gap: 6px;
    font-family: var(--ks-font-sans); font-size: 13px; color: var(--ks-navy);
    cursor: pointer; white-space: nowrap; padding-bottom: 2px;
}
.ks-feedfilter__toggle input { width: 15px; height: 15px; accent-color: var(--ks-sage); cursor: pointer; }
/* Apply + Clear sit at the end of the last row. */
.ks-feedfilter__actions {
    display: flex; gap: 6px; align-items: center; margin-left: auto;
}
.ks-feedfilter__apply { font-size: 13px; }
.ks-feedfilter__clear {
    font-size: 13px; color: var(--ks-slate); text-decoration: none;
    border: 1px solid var(--ks-border); background: #fff;
}
.ks-feedfilter__clear:hover { border-color: var(--ks-slate); color: var(--ks-navy); }
/* Result summary line ("N posts match · page X of Y"). */
.ks-feedfilter__result {
    margin: var(--ks-space-sm) 0 0;
    color: var(--ks-slate); font-size: 12px;
}

@media (max-width: 640px) {
    /* On a phone every field goes full-width and the toggles stack so the bar
       is comfortable to tap (iOS-safe 16px inputs avoid focus auto-zoom). */
    .ks-feedfilter__row { flex-direction: column; align-items: stretch; }
    .ks-feedfilter__search,
    .ks-feedfilter__field { flex: 1 1 100%; }
    .ks-feedfilter__searchinput,
    .ks-feedfilter__select,
    .ks-feedfilter__date { font-size: 16px; }
    .ks-feedfilter__presets { width: 100%; }
    .ks-feedfilter__chip { flex: 1 1 auto; text-align: center; }
    .ks-feedfilter__actions { margin-left: 0; width: 100%; }
    .ks-feedfilter__apply { flex: 1; }
}

/*  The "N new posts" pill — centered, sticky near the top, click to load. */
.ks-feed-newpill {
    position: sticky;
    top: 8px;
    z-index: 25;
    display: block;
    margin: 0 auto var(--ks-space-sm);
    padding: 6px 16px;
    border: none;
    border-radius: 999px;
    background: var(--ks-navy);
    color: var(--ks-cream);
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(31, 41, 82, 0.22);
    transition: background 120ms, transform 120ms;
}
.ks-feed-newpill:hover { background: var(--ks-navy-lift); transform: translateY(-1px); }
.ks-feed-newpill[hidden] { display: none; }

/*  Sidebar / More-sheet unread badge on the Messages nav item. The nav-link is
    not positioned by default; the badge sits inline at the end of the row on
    desktop, and is pinned for the collapsed (icon-only) rail. */
.ks-nav-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    margin-left: auto;
    padding: 0 5px;
    border-radius: 999px;
    background: #B85450;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}
.ks-nav-badge[hidden] { display: none; }

/*  When the desktop sidebar is collapsed to the icon rail, the full label is
    hidden; pin the badge to the top-right of the icon instead of inline. */
.ks-sidebar.collapsed .nav-link { position: relative; }
.ks-sidebar.collapsed .nav-link .ks-nav-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    margin-left: 0;
    min-width: 16px;
    height: 16px;
    font-size: 9px;
}

/*  Mobile "More" button gets a small dot when an overflow item has unread. */
.ks-more-btn { position: relative; }
.ks-more-dot {
    position: absolute;
    top: 6px;
    right: calc(50% - 16px);
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #B85450;
}
.ks-more-dot[hidden] { display: none; }

/*  In the More sheet, the Messages row badge sits before the chevron. */
.ks-more-item .ks-nav-badge { margin-left: auto; }


/* -- 11b. "Who reacted" link + popover (Session 18 PR1) -------------------- */
/*  A small text link under the reaction chips; clicking it opens a panel that
    lists each emoji and the names of everyone who used it. Replaces the plain
    title-tooltip with an explicit, tappable disclosure (mobile-friendly). */
.ks-reax__who { position: relative; flex-basis: 100%; margin-top: 4px; }
.ks-reax__wholink {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--ks-sage);
    font-family: var(--ks-font-sans);
    font-size: 11px;
    padding: 2px 0;
}
.ks-reax__wholink:hover { text-decoration: underline; }
.ks-reax__whopop {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 0;
    z-index: 22;
    min-width: 200px;
    max-width: 280px;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    box-shadow: 0 6px 20px rgba(31, 41, 82, 0.16);
    padding: 10px 12px;
}
.ks-reax__whopop[hidden] { display: none; }
.ks-reax__whohead {
    font-size: 10px;
    letter-spacing: .5px;
    text-transform: uppercase;
    color: var(--ks-slate);
    margin-bottom: 6px;
}
.ks-reax__whorow {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 3px 0;
}
.ks-reax__whoemoji { font-size: 15px; flex-shrink: 0; line-height: 1.3; }
.ks-reax__whonames { font-size: 12px; color: var(--ks-navy); line-height: 1.4; }


/* -- 11c. Avatar photos in chat (Session 18 PR1) -------------------------- */
/*  When a user has a profile photo, the avatar circle holds an <img> instead
    of initials. The circle already sets size + radius + overflow on
    .ks-msg-avatar / .ks-cmt__avatar; this just makes the image fill it. */
.ks-msg-avatar,
.ks-cmt__avatar { overflow: hidden; }
.ks-avatar-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: 50%;
}


/* -- 11d. Unread post styling + comment reactions (Session 18 PR1, Step 2) -- */

/*  An UNREAD post. Accessibility: we never rely on colour alone. An unread
    post carries (1) a thick high-contrast NAVY left bar, (2) a clear cool-tint
    background, and (3) a text "NEW" badge — so it is unmistakable for everyone,
    including users with colour-vision deficiency. Cleared client-side only
    after the post has genuinely been read (dwell), never by the poller. */
.ks-feed-post.is-unread {
    border-left: 5px solid var(--ks-navy);
    background: #EEF2FB;            /* cool tint: high contrast vs cream cards */
    box-shadow: inset 0 0 0 1px rgba(31, 41, 82, 0.10),
                0 4px 12px rgba(31, 41, 82, 0.08);
}
.ks-feed-post.is-unread::after { background: var(--ks-navy); opacity: 1; }
/*  Text badge — the primary, colour-independent cue. */
.ks-feed-post__unreaddot {
    position: absolute;
    top: 12px;
    right: 12px;
    width: auto;
    height: auto;
    padding: 2px 8px;
    border-radius: 6px;
    background: var(--ks-navy);
    color: #fff;
    font-family: var(--ks-font-sans);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .8px;
    line-height: 1.5;
    box-shadow: 0 1px 4px rgba(31, 41, 82, 0.30);
}
.ks-feed-post__unreaddot::before { content: "NEW"; }
/*  A gentle one-time pulse draws the eye without being distracting; respects
    reduced-motion preferences. */
@media (prefers-reduced-motion: no-preference) {
    .ks-feed-post.is-unread { animation: ksUnreadIn .5s ease-out; }
    @keyframes ksUnreadIn {
        from { background: #DCE4F7; }
        to   { background: #EEF2FB; }
    }
}
/*  When the post becomes read, fade the unread affordances out smoothly so the
    change is perceptible (not an instant pop). JS adds .is-clearing briefly. */
.ks-feed-post.is-clearing {
    transition: background .6s ease, border-color .6s ease, box-shadow .6s ease;
    border-left-color: var(--ks-border);
    background: var(--ks-surface, #fff);
    box-shadow: none;
}

/*  Comment reactions — a more compact version of the post reaction chips,
    shown under each comment body. */
.ks-creax {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
}
.ks-creax__form { margin: 0; }
.ks-creax__chip {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: 999px;
    padding: 1px 7px;
    cursor: pointer;
    font-size: 11px;
    line-height: 1.6;
}
.ks-creax__chip:hover { background: var(--ks-parchment); }
.ks-creax__chip.is-mine { background: #EEF3FF; border-color: var(--ks-navy); }
.ks-creax__emoji { font-size: 12px; }
.ks-creax__count { color: var(--ks-slate); font-size: 10px; font-weight: 600; }

.ks-creax__add { position: relative; }
.ks-creax__addbtn {
    background: none;
    border: 0.5px dashed var(--ks-border);
    border-radius: 999px;
    width: 22px;
    height: 20px;
    cursor: pointer;
    font-size: 13px;
    line-height: 1;
    color: var(--ks-slate);
    padding: 0;
}
.ks-creax__addbtn:hover { border-color: var(--ks-sage); color: var(--ks-navy); }
/*  Reuse the post palette styles for the comment palette (same look). Match the
    post palette's FIXED width so the six emoji + custom-emoji input lay out in a
    tidy horizontal wrap, rather than collapsing to a narrow near-vertical column
    (a bare max-width let the flex items shrink the box to almost one-per-row). */
.ks-creax__palette {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 0;
    z-index: 20;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    width: 248px;
    max-width: 80vw;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    box-shadow: 0 6px 20px rgba(31, 41, 82, 0.16);
    padding: 10px;
}
.ks-creax__palette[hidden] { display: none; }

/* =============================================================================
   Mobile (≤640px) — Announcements feed COMMENTS + comment reactions.
   -----------------------------------------------------------------------------
   The feed-comment block (.ks-cmt / .ks-creax) never had phone rules, unlike DM
   reactions (.ks-dm-reax). On a narrow screen three things read as "off":
     (1) the comment column is indented by a 28px avatar + gaps, leaving little
         width, so the body + reaction chips feel cramped;
     (2) the comment reaction chips could overflow that tight column;
     (3) the "+" reaction palette (.ks-creax__palette) opens left:0 and, from an
         already-indented comment, its fixed 248px width ran off the right edge
         and was clipped.
   This mirrors the DM-reaction mobile treatment (Session 46) for the feed side.
   ========================================================================== */
@media (max-width: 640px) {
    /*  (1) Reclaim width: a smaller avatar + tighter gap on phones. */
    .ks-cmt__item { gap: 8px; }
    .ks-cmt__avatar { width: 24px; height: 24px; font-size: 9px; }
    .ks-cmt__main { padding: 7px 9px; }

    /*  (2) Keep the reaction row snug and wrapping inside the comment box. */
    .ks-cmt .ks-creax {
        gap: 4px;
        margin-top: 5px;
        max-width: 100%;
    }
    /*  Touch has no hover: keep the bare "+" reachable (faint, not invisible);
        full opacity once the row has chips or the control is focused. */
    .ks-cmt .ks-creax__add { opacity: .6; }
    .ks-cmt .ks-creax__add:focus-within { opacity: 1; }
    .ks-cmt .ks-creax__addbtn { width: 26px; height: 24px; font-size: 15px; }

    /*  (3) Stop the comment palette clipping off the right edge. It opens
        upward from a left origin; clamp it to the viewport and let it wrap so
        it never exceeds the screen, regardless of how deep the comment sits. */
    .ks-cmt .ks-creax__palette {
        width: auto;
        min-width: 220px;
        max-width: calc(100vw - 32px);
        left: 0;
        right: auto;
    }
}


/* -- 11e. Custom-emoji hint (Session 18 PR1, Step 3 follow-up) ------------- */
/*  Small helper under the "type any emoji" field, telling users they can use
    their device's native emoji keyboard / OS picker. */
.ks-reax__customhint {
    display: block;
    width: 100%;
    margin-top: 6px;
    color: var(--ks-slate);
    font-size: 10px;
    line-height: 1.45;
    text-align: left;
}


/* -- 13. @mention highlight + autocomplete picker (Session 18 PR1, Step 4) -- */

/*  Inline highlight for a valid @mention inside a post/comment body. Uses the
    brand sage on a soft tint so it reads as a link-like token without shouting.
    Colour is paired with weight (500) so it's distinguishable without relying
    on colour alone. */
.ks-mention {
    color: var(--ks-navy);
    background: rgba(212, 169, 85, 0.28);   /* soft gold tint — high contrast on cream/white */
    font-weight: 600;
    padding: 1px 5px;
    border-radius: 5px;
    box-shadow: inset 0 -1px 0 rgba(212, 169, 85, 0.9);  /* subtle underline cue, not colour-only */
    white-space: nowrap;
}

/*  The floating autocomplete picker (one reused element appended to <body>,
    positioned under the active textarea by kosis.js §12). */
.ks-mention-picker {
    position: absolute;
    z-index: 60;
    max-width: 360px;
    max-height: 280px;
    overflow-y: auto;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    box-shadow: 0 8px 28px rgba(31, 41, 82, 0.18);
    padding: 4px;
    font-family: var(--ks-font-sans);
}
.ks-mention-picker[hidden] { display: none; }

.ks-mention-picker__group {
    font-size: 10px;
    letter-spacing: .6px;
    text-transform: uppercase;
    color: var(--ks-slate);
    padding: 6px 8px 2px;
}
.ks-mention-picker__item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border-radius: 6px;
    cursor: pointer;
}
/* Selected / hovered item — clear navy fill so the active choice is obvious.
   Text + handle + label flip to light, and the section-glyph avatar inverts
   too, so everything stays readable on the navy background. */
.ks-mention-picker__item.is-active,
.ks-mention-picker__item:hover {
    background: var(--ks-navy);
}
.ks-mention-picker__item.is-active .ks-mention-picker__handle,
.ks-mention-picker__item:hover .ks-mention-picker__handle,
.ks-mention-picker__item.is-active .ks-mention-picker__label,
.ks-mention-picker__item:hover .ks-mention-picker__label {
    color: #fff;
}
.ks-mention-picker__item.is-active .ks-mention-picker__av--glyph,
.ks-mention-picker__item:hover .ks-mention-picker__av--glyph {
    background: rgba(255, 255, 255, 0.18);
    color: #fff;
}

.ks-mention-picker__av {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--ks-parchment);
}
.ks-mention-picker__av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ks-mention-picker__av--glyph {
    color: var(--ks-sage);
    font-weight: 600;
    font-size: 13px;
    text-transform: uppercase;
}
.ks-mention-picker__text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.ks-mention-picker__handle { font-size: 13px; color: var(--ks-navy); font-weight: 500; }
.ks-mention-picker__label  {
    font-size: 11px;
    color: var(--ks-slate);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/* -- 28. Linked set lists on an event (Session 28, "Tier 2") --------------- */
/*  A linked set list is a REFERENCE pool shown on the event card: "this event
    draws from set list X". It is visually distinct from the Program (the
    authoritative rundown) below it — a soft sage-tinted card with a clear
    "From set list:" label — so members read it as the pool, not the running
    order. Its songs reuse .ks-program-list markup but in a muted variant. */
.ks-event-setlists {
    margin: 14px 0 4px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.ks-event-setlist {
    border: 0.5px solid var(--ks-border);
    border-left: 3px solid var(--ks-sage);
    border-radius: var(--ks-radius-sm);
    background: rgba(74, 111, 92, 0.05);   /* faint sage wash — "reference", not the program */
    padding: 10px 12px;
}
.ks-event-setlist__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 6px;
}
.ks-event-setlist__name {
    font-size: 13px;
    color: var(--ks-sage);
    font-weight: 600;
    letter-spacing: .2px;
}
/*  Muted variant of the program list for the reference songs: lighter numbers,
    no heavy weight — it should read as "the pool", subordinate to the Program. */
.ks-program-list--linked .ks-program-num {
    background: rgba(74, 111, 92, 0.16);
    color: var(--ks-sage);
}
.ks-program-list--linked .ks-program-title {
    font-weight: 500;
}
/*  Unlink button sits below its set list, right-aligned, with breathing room. */
.ks-event-setlist__unlinkform {
    margin-top: 8px;
    text-align: right;
}
/*  The "Add songs from a set list…" picker row at the foot of the linked block. */
.ks-event-setlist__link {
    margin-top: 2px;
}

/*  Option A: a linked set list is now a light tag (its songs live in the
    Program). A small hint explains where the songs went. */
.ks-event-setlist__hint {
    display: block;
    font-family: var(--ks-font-sans);
    font-size: 11px;
    color: var(--ks-slate);
    margin-top: 2px;
}

/*  Export-programs box: title + intro note above the From/To pickers. */
.ks-export-title {
    display: block;
    font-family: var(--ks-font-sans);
    font-size: 14px;
    font-weight: 600;
    color: var(--ks-navy);
    margin-bottom: 2px;
}
.ks-export-note--top {
    margin: 0 0 12px;
}

/*  "By time period" grouping headers in the events list view
    (This week / This month / Later). A quiet, sticky-feeling section label. */
.ks-events-group {
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    color: var(--ks-slate);
    margin: 20px 2px 8px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--ks-border);
}
.ks-events-group:first-child {
    margin-top: 4px;
}

/* ───────────────────────────────────────────────────────────────────────────
   Session 30 — Recurring events: form controls + "repeats" badge.
   ─────────────────────────────────────────────────────────────────────────── */

/* The "Repeats every week on Tue…" line under an event's date. */
.ks-event-recurs {
    margin: 2px 0 0;
    font-size: 13px;
    color: var(--sage, #4A6F5C);
    display: flex;
    align-items: center;
    gap: 6px;
}
.ks-event-recurs__icon {
    font-size: 14px;
    line-height: 1;
}

/* Recurrence form block. */
.ks-recur {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.ks-recur__opts {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px 14px;
    background: var(--cream, #FDFAF1);
    border: 1px solid rgba(31, 41, 82, 0.10);
    border-radius: 10px;
}
.ks-recur__row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}
.ks-recur__lbl {
    font-weight: 600;
    color: var(--navy, #1F2952);
    min-width: 48px;
}
.ks-recur__interval {
    max-width: 80px;
}
.ks-recur__unit {
    color: #6B7480;
    font-size: 14px;
}

/* Weekday checkbox chips (Weekly). */
.ks-recur__weekdays {
    gap: 6px;
}
.ks-recur__wd {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 9px;
    border: 1px solid rgba(31, 41, 82, 0.18);
    border-radius: 8px;
    background: #fff;
    cursor: pointer;
    font-size: 13px;
    user-select: none;
}
.ks-recur__wd input {
    margin: 0;
    cursor: pointer;
}
.ks-recur__wd:hover {
    border-color: var(--gold, #D4A955);
}

.ks-recur__hint {
    margin: 0;
}

/* The "Ends" row gets its own framed sub-panel so its boundary is obvious
   against the cream .ks-recur__opts background (the user found it hard to see
   where the box was). A white fill + clear border + label on its own line. */
.ks-recur__end {
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: #fff;
    border: 1px solid rgba(31, 41, 82, 0.22);
    border-radius: 9px;
    box-shadow: inset 0 0 0 1px rgba(212, 169, 85, 0.10);
}

/* Scope chooser shown when editing a recurring event (this / following / all). */
.ks-recur-scope {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px 14px;
    margin-bottom: 4px;
    background: #FBF6E9;
    border: 1px solid var(--gold, #D4A955);
    border-radius: 10px;
}
.ks-recur-scope__lbl {
    font-weight: 700;
    color: var(--navy, #1F2952);
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.ks-recur-scope__opt {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}
.ks-recur-scope__opt input:disabled + span {
    opacity: 0.45;
    cursor: not-allowed;
}

/* Disabled recurrence controls (when Repeat = Does not repeat, or weekday
   chips when not Weekly): visibly inert. */
.ks-recur__opts input:disabled,
.ks-recur__opts select:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
/* Weekday chip disabled state: make the WHOLE box read as not-allowed —
   uniformly across the label, the checkbox, and the day text — so hovering
   anywhere on the box (not just the checkmark) shows the blocked cursor. */
.ks-recur__wd:has(input:disabled) {
    opacity: 0.5;
    cursor: not-allowed;
    border-color: rgba(31, 41, 82, 0.12);
    background: #F6F4ED;
}
.ks-recur__wd:has(input:disabled):hover {
    border-color: rgba(31, 41, 82, 0.12);   /* cancel the gold hover when disabled */
}
.ks-recur__wd input:disabled,
.ks-recur__wd input:disabled + span {
    cursor: not-allowed;
}
.ks-recur__wd input:disabled + span {
    opacity: 1;   /* opacity now lives on the whole chip, not just the text */
}

/* Collapsed timezone selector (Session 30): the per-event timezone now hides
   behind a one-line summary that defaults to the choir's zone, so the common
   case is friction-free while the override stays one click away. */
.ks-tz-collapse {
    margin-top: 2px;
}
.ks-tz-collapse__current {
    margin: 0 0 6px;
    font-size: 13px;
    color: #6B7480;
}
.ks-tz-collapse__current strong {
    color: var(--navy, #1F2952);
}
.ks-tz-collapse__summary {
    cursor: pointer;
    font-size: 13px;
    color: #6B7480;
    list-style: none;
    padding: 0;
    display: inline-block;
}
.ks-tz-collapse__summary::-webkit-details-marker { display: none; }
.ks-tz-collapse__summary strong {
    color: var(--navy, #1F2952);
}
.ks-tz-collapse__action {
    display: inline-block;
    margin-left: 8px;
    padding: 7px 16px;
    border: 1px solid var(--sage, #4A6F5C);
    border-radius: 999px;
    background: var(--sage, #4A6F5C);
    color: #fff;
    font-size: 13px;
    font-weight: 700;
    line-height: 1.4;
    white-space: nowrap;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(31, 41, 82, 0.18);
    transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform 0.05s ease;
}
.ks-tz-collapse__action::before {
    content: "\1F30D";  /* globe — signals a timezone control at a glance */
    margin-right: 6px;
}
.ks-tz-collapse__action:hover {
    background: #3C5C4C;
    border-color: #3C5C4C;
    box-shadow: 0 2px 6px rgba(31, 41, 82, 0.26);
}
.ks-tz-collapse__action:active {
    transform: translateY(1px);
}
.ks-tz-collapse[open] .ks-tz-collapse__action {
    /* Once open, the button reads as the active/pressed state. */
    background: #fff;
    color: var(--sage, #4A6F5C);
}
.ks-tz-collapse[open] .ks-tz-collapse__action:hover {
    background: #F0F4F1;
}

/* ───────────────────────────────────────────────────────────────────────────
   Session 30 (polish) — events list: New-event shortcut, results count,
   pagination, danger button, disabled-button state.
   ─────────────────────────────────────────────────────────────────────────── */

/* "+ New event" shortcut button in the filter bar: pushed to the far right so
   it's always reachable without scrolling to the form at the bottom. */
.ks-events-bar__new {
    margin-left: auto;
    white-space: nowrap;
}
@media (max-width: 640px) {
    /* On a phone the filter row wraps; let the button take the full width so
       it reads as a clear primary action rather than a cramped chip. */
    .ks-events-bar__new {
        margin-left: 0;
        flex-basis: 100%;
        text-align: center;
    }
}

/* Results-count / page-indicator line above the event list. */
.ks-events-count {
    margin: 0 2px var(--ks-space-sm);
    color: var(--ks-slate);
    font-size: 12px;
}
.ks-events-count strong { color: var(--ks-navy); }

/* Prev / Next pager beneath the event list. */
.ks-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--ks-space-md);
    margin: var(--ks-space-md) 0 var(--ks-space-sm);
    flex-wrap: wrap;
}
.ks-pager__status {
    color: var(--ks-slate);
    font-size: 12px;
    min-width: 110px;
    text-align: center;
}

/* A disabled pager control (rendered as a <span> so it can't be clicked). */
.btn.is-disabled {
    opacity: 0.45;
    cursor: default;
    pointer-events: none;
}

/* ────────────────────────────────────────────────────────────────────────────
   Session 30 CP3 — calendar drag-to-reschedule.
   Draggable chips get a grab cursor; the drop target highlights; the calendar
   dims briefly while the move is posting. All scoped to the manager-only
   [data-ks-cal-reschedule] calendar.
   ──────────────────────────────────────────────────────────────────────────── */

/* Grab affordance on draggable chips (managers only carry [draggable]). */
.ks-cal__chip[draggable="true"],
.ks-cal__wchip[draggable="true"] {
    cursor: grab;
}
.ks-cal__chip.is-dragging,
.ks-cal__wchip.is-dragging {
    opacity: 0.45;
    cursor: grabbing;
}

/* Drop-target highlight while a chip hovers over a day cell / week column. */
.ks-cal__day.ks-drop-hover {
    outline: 2px dashed var(--ks-gold, #D4A955);
    outline-offset: -2px;
    background: rgba(212, 169, 85, 0.10);
}
.ks-cal__weekcol.ks-drop-hover {
    background: rgba(212, 169, 85, 0.12);
    box-shadow: inset 0 0 0 2px var(--ks-gold, #D4A955);
}

/* Calendar dimmed + non-interactive while a reschedule POST is in flight. */
.ks-cal--busy {
    opacity: 0.6;
    pointer-events: none;
}

/* ── Session 30 CP3 — lightweight live-update banner ────────────────────────
   Shown at the top of the calendar card when the poll detects the choir's
   events changed elsewhere. Unobtrusive (sage tint, not an alarm); the button
   reloads to show the change. Hidden by the [hidden] attribute until revealed. */
.ks-cal__liveban {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin: -4px 0 14px;
    padding: 9px 14px;
    background: rgba(74, 111, 92, 0.10);          /* --ks-sage tint */
    border: 1px solid rgba(74, 111, 92, 0.35);
    border-radius: 10px;
    font-size: 0.92rem;
    color: var(--ks-navy, #1F2952);
}
.ks-cal__liveban[hidden] { display: none; }
.ks-cal__liveban-txt { font-weight: 500; }
.ks-cal__liveban-btn {
    margin-left: auto;
    padding: 5px 14px;
    border: 1px solid var(--ks-sage, #4A6F5C);
    border-radius: 999px;
    background: var(--ks-sage, #4A6F5C);
    color: #fff;
    font: inherit;
    font-size: 0.86rem;
    font-weight: 600;
    cursor: pointer;
}
.ks-cal__liveban-btn:hover { filter: brightness(1.06); }
.ks-cal__liveban-btn:focus-visible {
    outline: 2px solid var(--ks-gold, #D4A955);
    outline-offset: 2px;
}

/* ── Polls: live results (Session 32, Tier 3 CP1) ─────────────────────────── */
/* Poll cards size to their own content instead of stretching to the tallest
   card in the grid row. Without this, a poll with few options is forced as
   tall as a neighbour with many options, leaving an empty gap below the
   Delete button. Scoped to the polls grid (.ks-page-narrow) so other pages'
   equal-height card grids are unaffected. */
.ks-page-narrow .ks-grid--2 { align-items: start; }
/* Voter names under an option (named polls / manager view). */
.ks-poll-voters {
    margin: 2px 0 0;
    font-size: 0.78rem;
    line-height: 1.4;
    color: var(--ks-slate, #5B6470);
}
/* "Vote to see the results" note shown to members who haven't voted. */
.ks-poll-novote {
    margin-top: 8px;
    font-style: italic;
}
/* Inline tag in the meta line marking a poll whose voter names are hidden. */
.ks-poll-hidden-tag {
    font-size: 0.78rem;
    color: var(--ks-sage, #4A6F5C);
    font-weight: 500;
}
/* "Hide voter names" checkbox on the create form. */
.ks-poll-check {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 0.9rem;
    color: var(--ks-navy, #1F2952);
    cursor: pointer;
}
.ks-poll-check input[type="checkbox"] {
    margin-top: 2px;
    flex-shrink: 0;
}
.ks-poll-check__hint {
    display: block;
    margin-top: 2px;
    font-size: 0.78rem;
    font-weight: 400;
    color: var(--ks-slate, #5B6470);
}
/* Delete-poll form on a poll card (manager view). */
.ks-poll-delete {
    margin: 10px 0 0;
    padding-top: 8px;
    border-top: 0.5px solid var(--ks-border, #E4E0D5);
}

/* ── Polls: dynamic option builder (Session 32, Tier 3 CP6) ──────────────── */
.ks-optbuilder {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
/* A row: the option input grows, the remove button sits to its right. */
.ks-optbuilder__row {
    display: flex;
    align-items: center;
    gap: 8px;
}
.ks-optbuilder__input {
    flex: 1;
    min-width: 0;
}
/* Remove (×) — quiet by default, brick-red on hover (matches the delete
   language). Disabled (at the 2-option minimum) fades and ignores clicks. */
.ks-optbuilder__remove {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 0.5px solid var(--ks-border, #E4E0D5);
    border-radius: var(--ks-radius-input, 8px);
    background: transparent;
    color: var(--ks-slate, #5B6470);
    cursor: pointer;
    transition: color 120ms ease, border-color 120ms ease, background 120ms ease, opacity 120ms ease;
}
.ks-optbuilder__remove:hover {
    color: #B85450;
    border-color: #B85450;
}
.ks-optbuilder__remove:disabled {
    opacity: 0.32;
    cursor: default;
    pointer-events: none;
}
/* "+ Add option" — sage ghost action, matching the brand's inline-action style. */
.ks-optbuilder__add {
    align-self: flex-start;
    margin-top: 2px;
    padding: 6px 4px;
    border: 0;
    background: transparent;
    color: var(--ks-sage, #4A6F5C);
    font-family: var(--ks-font-sans, inherit);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}
.ks-optbuilder__add:hover { text-decoration: underline; }

/* ── Polls: filter chips (Session 32) ──────────────────────────────── */
.ks-poll-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.ks-poll-filter__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: 0.5px solid var(--ks-border, #E4E0D5);
    border-radius: 999px;
    background: var(--ks-cream, #FDFAF1);
    color: var(--ks-slate, #5B6470);
    font-size: 12px;
    font-weight: 500;
    text-decoration: none;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.ks-poll-filter__chip:hover {
    background: rgba(31, 41, 82, 0.05);
    color: var(--ks-navy, #1F2952);
    text-decoration: none;
}
.ks-poll-filter__chip.is-active {
    background: var(--ks-navy, #1F2952);
    border-color: var(--ks-navy, #1F2952);
    color: var(--ks-cream, #FDFAF1);
}
.ks-poll-filter__count {
    font-size: 11px;
    font-weight: 600;
    opacity: 0.7;
}
.ks-poll-filter__chip.is-active .ks-poll-filter__count { opacity: 0.85; }

/* ── Polls: "Remove my vote" (member, retractable polls) ──────────────── */
.ks-poll-removevote {
    margin-top: 8px;
}
.ks-poll-removevote__btn {
    padding: 4px 0;
    border: 0;
    background: transparent;
    color: var(--ks-slate, #5B6470);
    font-family: var(--ks-font-sans, inherit);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    text-decoration: underline;
}
.ks-poll-removevote__btn:hover { color: #B85450; }

/* ── Polls: non-responder line (managers only) ─────────────────────── */
.ks-poll-nonvoters {
    margin: 10px 0 0;
    padding-top: 8px;
    border-top: 0.5px solid var(--ks-border, #E4E0D5);
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--ks-slate, #5B6470);
}
.ks-poll-nonvoters__label {
    font-weight: 600;
    color: var(--ks-navy, #1F2952);
}
.ks-poll-nonvoters--done {
    color: var(--ks-sage, #4A6F5C);
    font-style: italic;
}

/* ── Polls: closed (deadline passed) state (Session 32) ───────────────── */
/* The whole card is gently de-emphasised once voting has closed. */
.ks-poll-card--closed {
    background: var(--ks-parchment, #F6F1E5);
}
/* "Voting closed" tag in the meta line. */
.ks-poll-closed-tag {
    font-size: 0.78rem;
    font-weight: 600;
    color: #B85450;
}
/* Static (non-clickable) option row shown on a closed poll. Mirrors the
   .ks-poll-option layout but without button affordance (no pointer/hover). */
.ks-poll-option--static {
    cursor: default;
}
.ks-poll-option--static:hover {
    /* No interactive feedback — this is a read-only result row. */
    background: inherit;
}

/* ── Polls: multi-select ("pick all that apply") (Session 33) ─────────── */
/* Meta-line tag marking a poll as multiple-choice. */
.ks-poll-multi-tag {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--ks-sage, #4A6F5C);
}
/* A checkbox option row on an open multi-select poll. */
.ks-poll-checkopt {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 0.5px solid var(--ks-border, #E4E0D5);
    border-radius: 8px;
    background: var(--ks-cream, #FDFAF1);
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}
.ks-poll-checkopt:hover {
    background: rgba(31, 41, 82, 0.04);
}
.ks-poll-checkopt input[type="checkbox"] {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    accent-color: var(--ks-sage, #4A6F5C);
    cursor: pointer;
}
.ks-poll-checkopt__label {
    flex: 1 1 auto;
}
/* An option the current member has already chosen. */
.ks-poll-checkopt.is-picked {
    border-color: var(--ks-sage, #4A6F5C);
    background: rgba(74, 111, 92, 0.08);
}
/* Single-select: highlight the option the member currently holds. */
.ks-poll-option.is-picked {
    border-color: var(--ks-sage, #4A6F5C);
    background: rgba(74, 111, 92, 0.08);
}
/* "Save my choices" button sits just below the checkbox list. */
.ks-poll-multi__save {
    margin-top: 4px;
    width: auto;
    align-self: flex-start;
}
.ks-poll-multi {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
/* A checkbox row disabled because the per-member cap has been reached. */
.ks-poll-checkopt.is-capped {
    opacity: 0.5;
    cursor: not-allowed;
}
.ks-poll-checkopt.is-capped input[type="checkbox"] {
    cursor: not-allowed;
}


/* -- Documents & Links page (Session 38, "Tier 5") ------------------------- */
/*  A flat list of choir links. The Home About-row already defines .doc-list /
    .doc-item / .doc-left / .external (kosis-dashboard.css); this section adds
    the dedicated page's card, the manager add-form, the per-row Remove layout,
    the two-line link (label + host), and the empty state. */

.ks-docs-card { }

/*  Manager add-link form: label + URL + button on one row, wrapping on narrow
    screens. A hairline separates it from the list below. */
.ks-doc-add {
    margin: 0 0 var(--ks-space-md);
    padding-bottom: var(--ks-space-md);
    border-bottom: 0.5px solid var(--ks-border);
}
.ks-doc-add__row {
    display: flex;
    gap: var(--ks-space-sm);
    flex-wrap: wrap;
    align-items: center;
}
.ks-doc-add__label { flex: 1 1 200px; min-width: 0; }
.ks-doc-add__url   { flex: 2 1 260px; min-width: 0; }
.ks-doc-add__btn   { flex: 0 0 auto; white-space: nowrap; }

/*  The list + a single row (the link fills the row; managers get a Remove
    button to its right). */
.ks-doc-list { gap: 8px; }
.ks-doc-row {
    display: flex;
    align-items: stretch;
    gap: var(--ks-space-sm);
    flex-wrap: wrap;   /* lets the expanded Edit panel drop to a full-width row */
}
/*  The clickable link grows to fill the row. .doc-item base styles come from
    kosis-dashboard.css; here we let the label + host stack on two lines. */
.ks-doc-link { flex: 1; min-width: 0; }
.ks-doc-link .doc-left { align-items: flex-start; gap: 10px; min-width: 0; }
.ks-doc-link__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.3;
}
.ks-doc-link__label {
    color: var(--ks-navy);
    font-size: 13px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ks-doc-link__url {
    color: var(--ks-slate);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/*  Remove form sits flush at the row's right edge, vertically centred. */
.ks-doc-del {
    margin: 0;
    display: flex;
    align-items: center;
    flex-shrink: 0;
}
/*  Manager action cluster (Edit + Remove) to the right of the link. */
.ks-doc-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
/*  Edit disclosure: the collapsed toggle is a quiet bordered button matching
    the Remove button; opening it drops a full-width edit panel onto the next
    row (the parent .ks-doc-row wraps), so the form never squeezes into the
    narrow action column. */
.ks-doc-edit { display: inline-block; }
.ks-doc-edit__toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    list-style: none;
    cursor: pointer;
    border: 1px solid var(--ks-border);
    border-radius: 8px;
    background: #fff;
    color: var(--ks-slate);
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 600;
    padding: .5rem .8rem;
    white-space: nowrap;
    transition: color .15s, border-color .15s, background .15s;
}
.ks-doc-edit__toggle::-webkit-details-marker { display: none; }
.ks-doc-edit__toggle svg { flex-shrink: 0; }
.ks-doc-edit__toggle:hover,
.ks-doc-edit__toggle:focus-visible {
    color: var(--ks-sage);
    border-color: var(--ks-sage);
    background: rgba(74,111,92,.06);
    outline: none;
}
.ks-doc-edit[open] > .ks-doc-edit__toggle {
    background: var(--ks-sage);
    color: var(--ks-cream);
    border-color: var(--ks-sage);
}
/*  The edit panel takes the full width of the row (flex-basis:100%) and sits
    below the link + actions once the row has wrapped. */
.ks-doc-edit__form {
    flex-basis: 100%;
    width: 100%;
    margin-top: var(--ks-space-sm);
    padding: var(--ks-space-md);
    background: var(--ks-parchment);
    border: 0.5px solid var(--ks-border);
    border-radius: 10px;
    display: flex;
    flex-direction: column;
    gap: var(--ks-space-sm);
}
.ks-doc-edit__field { display: flex; flex-direction: column; gap: .3rem; }
.ks-doc-edit__label {
    font-size: .68rem; letter-spacing: .04em; text-transform: uppercase;
    color: var(--ks-slate); font-weight: 600;
}
.ks-doc-edit__actions {
    display: flex; justify-content: flex-end; gap: .5rem;
    margin-top: .2rem;
}
.ks-doc-edit__cancel {
    border: 1px solid var(--ks-border); border-radius: 8px;
    background: #fff; color: var(--ks-slate);
    padding: .5rem .9rem; font-family: var(--ks-font-sans);
    font-size: 12px; font-weight: 500; cursor: pointer;
    transition: border-color .15s, color .15s, background .15s;
}
.ks-doc-edit__cancel:hover { border-color: var(--ks-slate); color: var(--ks-navy); background: #fff; }
.ks-doc-edit__save { flex: 0 0 auto; }

/*  A proper bordered destructive button (not bare text): quiet by default,
    brick-red on hover/focus to match the brand danger tone. Icon + label. */
.ks-doc-del__btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--ks-border);
    border-radius: 8px;
    background: #fff;
    color: var(--ks-slate);
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 600;
    padding: .5rem .8rem;
    cursor: pointer;
    white-space: nowrap;
    transition: color .15s, border-color .15s, background .15s;
}
.ks-doc-del__btn svg { flex-shrink: 0; }
.ks-doc-del__btn:hover,
.ks-doc-del__btn:focus-visible {
    color: #B85450;
    border-color: #B85450;
    background: #FDECEC;
    outline: none;
}

/*  Empty state. */
.ks-docs-empty {
    text-align: center;
    padding: var(--ks-space-lg) var(--ks-space-md);
}
.ks-docs-empty__glyph { font-size: 30px; margin-bottom: 8px; }
.ks-docs-empty__title { color: var(--ks-navy); display: block; }
.ks-docs-empty__note  { color: var(--ks-slate); font-size: 12px; margin: 6px auto 0; max-width: 360px; }

@media (max-width: 640px) {
    /*  Stack the add-form controls full-width with iOS-safe sizing. */
    .ks-doc-add__label, .ks-doc-add__url, .ks-doc-add__btn { flex: 1 1 100%; }
    .ks-doc-add__btn { min-height: 44px; }
    /*  On a phone the Edit + Remove buttons collapse to icon-only squares so
        the link tile keeps the width; the labels are hidden but kept for
        screen readers via aria-label on each button. */
    .ks-doc-del__btn,
    .ks-doc-edit__toggle { padding: .6rem; min-height: 44px; min-width: 44px; justify-content: center; }
    .ks-doc-del__txt,
    .ks-doc-edit__txt { display: none; }
}

/* =============================================================================
   Direct messages (Session 40 — "Tier 5" DMs)
   List view (conversations + new-message picker) and chat view (bubbles).
   Brand: navy bubbles for the viewer's own messages, parchment for the other
   person; gold/sage accents consistent with the rest of the app.
   ========================================================================== */

/* ── New-message picker (a <details> disclosure) ── */
.ks-dm-new { display: block; }
.ks-dm-new__toggle {
    display: inline-flex; align-items: center; cursor: pointer;
    list-style: none;
}
.ks-dm-new__toggle::-webkit-details-marker { display: none; }
.ks-dm-new__panel { margin-top: var(--ks-space-md); }
.ks-dm-new__hint { color: var(--ks-slate); font-size: 13px; margin: 0 0 var(--ks-space-sm); }

/*  Search box above the people grid. */
.ks-dm-search {
    width: 100%; box-sizing: border-box; margin: 0 0 var(--ks-space-sm);
    padding: 9px 12px; font: inherit; font-size: 14px;
    border: 1px solid #D8CFB6; border-radius: 10px;
    background: var(--ks-cream); color: var(--ks-navy);
}
.ks-dm-search:focus { outline: none; border-color: var(--ks-gold); background: #fff; }
.ks-dm-search__empty { color: var(--ks-slate); font-size: 13px; margin: var(--ks-space-sm) 0 0; }

/*  The candidate people grid: tappable cards, one per choir member. */
.ks-dm-people {
    display: grid; gap: var(--ks-space-sm);
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.ks-dm-person { margin: 0; }
.ks-dm-person__btn {
    display: flex; align-items: center; gap: 10px; width: 100%;
    padding: 10px 12px; text-align: left; cursor: pointer;
    background: var(--ks-cream); border: 1px solid #E7DFC9;
    border-radius: 10px; color: var(--ks-navy);
    transition: border-color .15s, background .15s, box-shadow .15s;
}
.ks-dm-person__btn:hover,
.ks-dm-person__btn:focus-visible {
    border-color: var(--ks-gold); background: #fff;
    box-shadow: 0 2px 8px rgba(31,41,82,.08); outline: none;
}
.ks-dm-person__avatar {
    flex: 0 0 auto; width: 36px; height: 36px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    overflow: hidden; background: var(--ks-sage); color: #fff;
    font-size: 13px; font-weight: 600;
}
.ks-dm-person__avatar .ks-avatar-img { width: 100%; height: 100%; object-fit: cover; }
.ks-dm-person__text { display: flex; flex-direction: column; min-width: 0; }
.ks-dm-person__name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ks-dm-person__role { font-size: 12px; color: var(--ks-slate); }

/* ── Conversation list ── */
.ks-dm-list { display: flex; flex-direction: column; }
.ks-dm-listitem {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 6px; border-bottom: 1px solid #EFE9DA;
    text-decoration: none; color: var(--ks-navy);
    transition: background .15s;
}
.ks-dm-listitem:last-child { border-bottom: 0; }
.ks-dm-listitem:hover { background: var(--ks-cream); }
.ks-dm-listitem__avatar {
    flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    overflow: hidden; background: var(--ks-sage); color: #fff;
    font-size: 15px; font-weight: 600;
}
.ks-dm-listitem__avatar .ks-avatar-img { width: 100%; height: 100%; object-fit: cover; }
.ks-dm-listitem__main { flex: 1 1 auto; min-width: 0; }
.ks-dm-listitem__top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.ks-dm-listitem__name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ks-dm-listitem__when { flex: 0 0 auto; font-size: 12px; color: var(--ks-slate); }
.ks-dm-listitem__preview {
    display: block; font-size: 13px; color: var(--ks-slate);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px;
}
.ks-dm-listitem.is-unread .ks-dm-listitem__name { font-weight: 700; }
.ks-dm-listitem.is-unread .ks-dm-listitem__preview { color: var(--ks-navy); font-weight: 600; }
.ks-dm-listitem__badge {
    flex: 0 0 auto; min-width: 20px; height: 20px; padding: 0 6px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ks-gold); color: var(--ks-navy);
    font-size: 12px; font-weight: 700; border-radius: 999px;
}

/* ── Chat view ── */
.ks-dm {
    display: flex; flex-direction: column;
    background: #fff; border: 1px solid #E7DFC9; border-radius: 14px;
    overflow: hidden; height: calc(100vh - 220px); min-height: 420px;
}
.ks-dm-head {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 16px; border-bottom: 1px solid #EFE9DA;
    background: var(--ks-cream);
}
.ks-dm-back {
    flex: 0 0 auto; font-size: 20px; line-height: 1; text-decoration: none;
    color: var(--ks-navy); padding: 4px 8px; border-radius: 8px;
}
.ks-dm-back:hover { background: #fff; }
.ks-dm-head__avatar {
    flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    overflow: hidden; background: var(--ks-sage); color: #fff;
    font-size: 14px; font-weight: 600;
}
.ks-dm-head__avatar .ks-avatar-img { width: 100%; height: 100%; object-fit: cover; }
.ks-dm-head__name { font-family: var(--ks-font-head, 'Cinzel', serif); font-weight: 600; color: var(--ks-navy); }

.ks-dm-stream {
    flex: 1 1 auto; overflow-y: auto; padding: 16px;
    display: flex; flex-direction: column; gap: 4px;
    background:
        radial-gradient(circle at 1px 1px, rgba(31,41,82,.04) 1px, transparent 0) 0 0 / 22px 22px;
}

/*  Day separator. */
.ks-dm-daysep {
    text-align: center; margin: 12px 0 6px; position: relative;
}
.ks-dm-daysep span {
    display: inline-block; font-size: 11px; color: var(--ks-slate);
    background: var(--ks-cream); padding: 2px 10px; border-radius: 999px;
}

/*  A message row; mine align right. */
.ks-dm-row { display: flex; align-items: flex-end; gap: 8px; max-width: 78%; }
.ks-dm-row--mine { align-self: flex-end; flex-direction: row-reverse; }
.ks-dm-avatar {
    flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    overflow: hidden; background: var(--ks-sage); color: #fff;
    font-size: 11px; font-weight: 600;
}
.ks-dm-avatar .ks-avatar-img { width: 100%; height: 100%; object-fit: cover; }
.ks-dm-bubble {
    background: var(--ks-cream); border: 1px solid #E7DFC9;
    border-radius: 14px; padding: 8px 12px; min-width: 0;
}
.ks-dm-row--mine .ks-dm-bubble {
    background: var(--ks-navy); border-color: var(--ks-navy); color: #fff;
}
.ks-dm-bubble__body { font-size: 14px; line-height: 1.4; word-wrap: break-word; overflow-wrap: anywhere; }
.ks-dm-bubble__meta { font-size: 11px; color: var(--ks-slate); margin-top: 3px; text-align: right; }
.ks-dm-row--mine .ks-dm-bubble__meta { color: rgba(255,255,255,.7); }

.ks-dm-empty { text-align: center; color: var(--ks-slate); padding: 28px 12px; margin: auto 0; }

/* ── CP2: per-message actions (reactions, context menu, inline edit) ── */
/*  The bubble + its controls share a vertical wrapper so the context menu and
    reactions stack under the bubble and align to the correct side. */
.ks-dm-bubblewrap { display: flex; flex-direction: column; min-width: 0; position: relative; }
.ks-dm-row--mine .ks-dm-bubblewrap { align-items: flex-end; }

/*  Context menu (⋯) for a DM message: floats at the top-outer corner of the
    bubble, appearing on hover/focus-within (and stays put once its menu opens).
    Reuses .ks-ctx / .ks-ctx__menu from the feed; here we just place it. */
.ks-dm-ctx {
    position: absolute; top: -6px; z-index: 5;
    opacity: 0; transition: opacity .12s;
}
.ks-dm-row .ks-dm-ctx { right: -6px; }            /* their messages: menu on the right */
.ks-dm-row--mine .ks-dm-ctx { left: -6px; right: auto; }   /* mine: menu on the left */
.ks-dm-bubblewrap:hover .ks-dm-ctx,
.ks-dm-bubblewrap:focus-within .ks-dm-ctx { opacity: 1; }
.ks-dm-ctx .ks-ctx__btn {
    background: #fff; border: 1px solid #E7DFC9; border-radius: 999px;
    width: 24px; height: 24px; display: inline-flex; align-items: center;
    justify-content: center; padding: 0; font-size: 14px; color: var(--ks-slate);
    box-shadow: 0 1px 4px rgba(31,41,82,.12);
}
.ks-dm-ctx .ks-ctx__btn:hover { color: var(--ks-navy); }
/*  Touch devices have no hover: keep the trigger faintly visible so it's
    reachable without a pointer. */
@media (hover: none) {
    .ks-dm-ctx { opacity: .55; }
}

/*  Inline edit form inside a bubble wrapper. */
.ks-dm-edit {
    width: 100%; max-width: 420px; margin-top: 4px;
    background: #fff; border: 1px solid #E7DFC9; border-radius: 12px;
    padding: 10px;
}
.ks-dm-edit .ks-textarea {
    width: 100%; box-sizing: border-box; resize: vertical;
    border: 1px solid #D8CFB6; border-radius: 8px; padding: 8px 10px;
    font: inherit; font-size: 14px; line-height: 1.4; background: var(--ks-cream);
}
.ks-dm-edit .ks-textarea:focus { outline: none; border-color: var(--ks-gold); background: #fff; }

/*  Reactions row beneath a bubble. Reuses .ks-creax chips; align to the
    message's side and give a little top gap. */
.ks-dm-reax { margin-top: 4px; }
.ks-dm-row--mine .ks-dm-reax { justify-content: flex-end; }

/*  Session 46 — the reaction "+" add-button.
    Previously the "+" was always fully visible under EVERY bubble, even ones
    with no reactions, so each message carried a stray disconnected "+" on its
    own line (clearly visible in the mobile screenshots). Mirror the ⋯ context
    menu's behaviour instead: the "+" is hidden by default and only appears when
    the bubble is hovered or focused, so a bubble with no reactions shows no
    orphan glyph. Existing reaction CHIPS always stay visible (they're real
    content); only the empty "add" affordance is hover-gated. On touch (no
    hover) we keep it faintly visible so it's still reachable. */
.ks-dm-reax .ks-creax__add {
    opacity: 0;
    transition: opacity .12s;
}
.ks-dm-bubblewrap:hover .ks-dm-reax .ks-creax__add,
.ks-dm-bubblewrap:focus-within .ks-dm-reax .ks-creax__add,
.ks-dm-reax .ks-creax__add:focus-within {
    opacity: 1;
}
/*  When the row has at least one reaction chip, the row is meaningful content,
    so keep the "+" visible alongside the chips (it no longer reads as an
    orphan). :not(:only-child) = there's a sibling chip before the add button. */
.ks-dm-reax .ks-creax__add:not(:only-child) { opacity: 1; }

/*  Compose bar. */
.ks-dm-compose {
    display: flex; align-items: flex-end; gap: 8px;
    padding: 10px 12px; border-top: 1px solid #EFE9DA; background: #fff;
}
.ks-dm-input {
    flex: 1 1 auto; resize: none; border: 1px solid #D8CFB6;
    border-radius: 12px; padding: 10px 12px; font: inherit; font-size: 14px;
    line-height: 1.4; max-height: 160px; background: var(--ks-cream); color: var(--ks-navy);
}
.ks-dm-input:focus { outline: none; border-color: var(--ks-gold); background: #fff; }
.ks-dm-send { flex: 0 0 auto; min-height: 40px; }

@media (max-width: 640px) {
    .ks-dm { height: calc(100vh - 180px); border-radius: 10px; }
    .ks-dm-row { max-width: 88%; }
    .ks-dm-people { grid-template-columns: 1fr; }
}

/* =============================================================================
   Session 40 CP3 — Section + group channels.
   Channels reuse the .ks-dm-* chat shell; these classes cover the channel-only
   extras: the sender-name line above other people's bubbles, the channel-type
   tag in the header, and the manager member-management panel + group-channel
   creation picker.
   ========================================================================== */

/*  Sender name above another member's bubble (channels show it; DMs don't). */
.ks-dm-sender {
    font-size: 12px; font-weight: 600; color: var(--ks-sage);
    margin: 0 0 2px 2px;
}

/*  Channel-type tag in the chat header ("Section" / "Group"). */
.ks-ch-tag {
    display: inline-block; margin-left: 8px; padding: 1px 8px;
    font-size: 11px; font-weight: 600; letter-spacing: .02em;
    border-radius: 999px; vertical-align: middle;
    background: rgba(74,111,92,.14); color: var(--ks-sage);
}
.ks-ch-tag--group { background: rgba(212,169,85,.18); color: #9a7322; }

/*  List icon bubble (🎵 section / 💬 group) in the channels list. */
.ks-ch-icon {
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 18px; background: var(--ks-cream);
}

/*  "Members" toggle button in the channel header (manager only). */
.ks-ch-memtoggle {
    margin-left: auto; flex: 0 0 auto;
    background: #fff; border: 1px solid #E7DFC9; border-radius: 999px;
    padding: 4px 12px; font: inherit; font-size: 13px; font-weight: 600;
    color: var(--ks-navy); cursor: pointer;
}
.ks-ch-memtoggle:hover { border-color: var(--ks-gold); }

/*  Member-management panel (collapsed by default; manager only). */
.ks-ch-members {
    border-bottom: 1px solid #EFE9DA; background: var(--ks-cream);
    padding: 12px 14px;
}
.ks-ch-members__title {
    margin: 0 0 8px; font-size: 13px; font-weight: 700; color: var(--ks-navy);
}
.ks-ch-members__list { list-style: none; margin: 0 0 10px; padding: 0; max-height: 260px; overflow-y: auto; }
/*  Column header row, aligned to the member rows below (avatar slot + name
    flexes + role + action gutter), so "Name" / "Position" sit over their
    columns. */
.ks-ch-members__head {
    display: flex; align-items: center; gap: 8px; padding: 0 0 4px;
    border-bottom: 1px solid #E0D6BC; margin-bottom: 2px;
    font-size: 11px; font-weight: 700; letter-spacing: .03em;
    text-transform: uppercase; color: var(--ks-slate);
}
.ks-ch-members__head .col-avatar { flex: 0 0 28px; }
.ks-ch-members__head .col-name   { flex: 1 1 auto; }
.ks-ch-members__head .col-role   { flex: 0 0 auto; }
.ks-ch-members__head .col-act    { flex: 0 0 64px; text-align: right; }
.ks-ch-member {
    display: flex; align-items: center; gap: 8px; padding: 5px 0;
    border-bottom: 1px solid #EFE9DA;
}
.ks-ch-member:last-child { border-bottom: 0; }
.ks-ch-member__avatar {
    flex: 0 0 auto; width: 28px; height: 28px; border-radius: 999px;
    overflow: hidden; background: #fff; display: inline-flex;
    align-items: center; justify-content: center; font-size: 12px;
    font-weight: 700; color: var(--ks-navy);
}
.ks-ch-member__avatar .ks-avatar-img { width: 100%; height: 100%; object-fit: cover; }
.ks-ch-member__name { flex: 1 1 auto; min-width: 0; font-size: 14px; color: var(--ks-navy); }
.ks-ch-member__role { flex: 0 0 auto; }
.ks-ch-member__act { flex: 0 0 64px; margin: 0; text-align: right; }
.ks-ch-member__tag {
    margin-left: 6px; padding: 0 6px; font-size: 10px; font-weight: 600;
    border-radius: 999px; background: rgba(212,169,85,.2); color: #9a7322;
}
.ks-ch-member__act { flex: 0 0 auto; margin: 0; }
.ks-ch-member__remove {
    background: none; border: 0; padding: 2px 6px; cursor: pointer;
    font: inherit; font-size: 13px; color: var(--ks-brick); font-weight: 600;
}
.ks-ch-member__remove:hover { text-decoration: underline; }

/*  Add-member: a searchable people picker (reuses .ks-dm-people styling).
    Replaces the old dropdown so a 50-member section is filterable, not a long
    <select>. The grid + cards are styled by the .ks-dm-people/.ks-dm-person
    rules above; here we just give the wrapper a capped height. */
.ks-ch-addmember { margin-top: 8px; border-top: 1px solid #E0D6BC; padding-top: 8px; }
.ks-ch-addmember__search {
    width: 100%; box-sizing: border-box; padding: 7px 12px; font: inherit;
    font-size: 14px; border: 1px solid #D8CFB6; border-radius: 999px;
    background: #fff; margin-bottom: var(--ks-space-sm);
}
.ks-ch-addmember__search:focus { outline: none; border-color: var(--ks-gold); }
.ks-ch-addmember__people {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 6px; max-height: 200px; overflow-y: auto;
}
.ks-ch-addmember__empty { color: var(--ks-slate); font-size: 13px; margin: 6px 0 0; }

/*  Group-channel creation: the member checkbox picker. */
.ks-ch-pick {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 6px; margin: 6px 0 4px; max-height: 240px; overflow-y: auto;
}
.ks-ch-pick__item {
    display: flex; align-items: center; gap: 8px; padding: 6px 8px;
    border: 1px solid #E7DFC9; border-radius: 10px; background: #fff;
    cursor: pointer; font-size: 14px;
}
.ks-ch-pick__item:hover { border-color: var(--ks-gold); }
.ks-ch-pick__avatar {
    flex: 0 0 auto; width: 26px; height: 26px; border-radius: 999px;
    overflow: hidden; background: var(--ks-cream); display: inline-flex;
    align-items: center; justify-content: center; font-size: 11px;
    font-weight: 700; color: var(--ks-navy);
}
.ks-ch-pick__avatar .ks-avatar-img { width: 100%; height: 100%; object-fit: cover; }
.ks-ch-pick__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/*  Read-only note for a manager viewing a channel they're not a member of. */
.ks-ch-readonly {
    margin: 0; padding: 10px 12px; border-top: 1px solid #EFE9DA;
    background: #fff; font-size: 13px; font-style: italic;
}

/*  Member role label in the panel (right-aligned, muted). */
.ks-ch-member__role { flex: 0 0 auto; font-size: 12px; color: var(--ks-slate); }
.ks-ch-member__you {
    margin-left: 6px; padding: 0 6px; font-size: 10px; font-weight: 600;
    border-radius: 999px; background: rgba(74,111,92,.16); color: var(--ks-sage);
}

/*  BUG-4 FIX — reaction palette inside the chat stream.
    The shared .ks-creax__palette opens UPWARD (bottom: 100%). Inside the
    scrollable .ks-dm-stream the FIRST/top messages have no room above, so the
    palette was clipped by the stream's overflow. In the chat stream we open the
    palette DOWNWARD instead, which always has room (newest messages are at the
    bottom and the stream scrolls). Reactions live under the bubble here, so
    downward is also visually natural. We also give it the SAME fixed width as
    the Announcements palette (.ks-reax__palette) so the six emoji + the custom
    input lay out in a tidy horizontal wrap rather than a narrow column. */
.ks-dm-reax .ks-creax__palette {
    bottom: auto;
    top: calc(100% + 6px);
    width: auto;
    min-width: 220px;
    max-width: calc(100vw - 32px);
    padding: 10px;
}
/*  Mine align to the right edge; theirs to the left (matches bubble side). */
.ks-dm-row--mine .ks-dm-reax .ks-creax__palette { left: auto; right: 0; }

/*  System notice line (e.g. "Sarah added Mario"): a centered, muted pill that
    sits between messages — not a bubble. No actions, no avatar. */
.ks-dm-sys {
    align-self: center; text-align: center; margin: 8px 0;
    max-width: 90%;
}
.ks-dm-sys span {
    display: inline-block; font-size: 12px; color: var(--ks-slate);
    background: rgba(31,41,82,.06); border-radius: 999px;
    padding: 3px 12px; line-height: 1.4;
}

/* =============================================================================
   Session 46 — chat polish: optimistic "sending" bubble + mobile alignment.
   -----------------------------------------------------------------------------
   (1) .ks-dm-pending — the provisional bubble kosis.js §22 paints the instant
       Send is tapped, before the server round-trip. It looks like a normal
       outgoing bubble but slightly faded with a "sending…" meta, so the user
       sees their message immediately even on a slow connection (Hostinger).
       It's replaced by the real render when the response lands.
   (2) Mobile (≤640px) fixes for the message controls that read as "out of
       line" on phones: the ⋯ context button and the "+" reaction add-button
       floated awkwardly around the bubble. On narrow/touch screens we dock
       them tidily to the bubble instead of relying on hover/absolute offsets.
   ========================================================================== */

/*  Provisional outgoing bubble (optimistic send). Faded until the server
    confirms; the meta line reads "… · sending…" (set in JS). */
.ks-dm-pending .ks-dm-bubble {
    opacity: .72;
}
.ks-dm-pending .ks-dm-bubble__meta {
    font-style: italic;
}

@media (max-width: 640px) {
    /*  No hover on touch: the ⋯ trigger can't rely on bubble-hover to appear,
        and floating it at top:-6px made it collide with the bubble above. Dock
        it just inside the bubble's top-outer corner at a steady, tappable size
        so it reads as "this bubble's menu", not a stray glyph. */
    .ks-dm-ctx {
        opacity: 1;                 /* always visible on touch */
        top: -2px;
    }
    .ks-dm-row .ks-dm-ctx      { right: -2px; }
    .ks-dm-row--mine .ks-dm-ctx { left: -2px; right: auto; }
    .ks-dm-ctx .ks-ctx__btn {
        width: 26px; height: 26px; font-size: 13px;
    }

    /*  Reactions row: keep the chips + "+" add-button snug against the bubble's
        side (right for mine, left for theirs) instead of stretching across the
        row, so a lone "+" no longer floats disconnected under the bubble. */
    .ks-dm-reax {
        gap: 4px;
        margin-top: 3px;
        max-width: 100%;
    }
    .ks-dm-row--mine .ks-dm-reax { justify-content: flex-end; }
    .ks-dm-row:not(.ks-dm-row--mine) .ks-dm-reax { justify-content: flex-start; }
    /*  Touch has no hover: show the empty "+" faintly (not invisible) so it's
        reachable, but muted enough that it doesn't read as a stray glyph; it
        goes fully opaque once tapped/focused. A row that already has chips keeps
        the "+" fully visible (handled by the :not(:only-child) rule above). */
    .ks-dm-reax .ks-creax__add { opacity: .5; }
    .ks-dm-reax .ks-creax__add:focus-within { opacity: 1; }
    /*  Slightly larger touch target for the add-reaction "+" on phones. */
    .ks-dm-reax .ks-creax__addbtn {
        width: 26px; height: 24px; font-size: 15px;
    }

    /*  Give the bubble wrapper a hair more right/left room so the docked ⋯
        button never overlaps the bubble text at the corner. */
    .ks-dm-bubblewrap { padding-top: 2px; }
}

@media (max-width: 640px) {
    .ks-ch-pick { grid-template-columns: 1fr; }
}

/*  Session 47 — per-page size chooser (Set lists / Repertoire / Practice tabs)
    A small inline-flex row that sits under each tab's pager. Plain GET form;
    the <select> submits its form on change (onchange=this.form.submit()). */
.ks-perpage {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: var(--ks-space-sm);
}
.ks-perpage__lbl {
    font-family: var(--ks-font-sans);
    font-size: 11px;
    letter-spacing: .4px;
    color: var(--ks-sage);
    font-weight: 600;
}
.ks-perpage__select {
    font-family: var(--ks-font-sans);
    font-size: 12px;
    color: var(--ks-navy);
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    padding: 5px 8px;
    cursor: pointer;
}
.ks-perpage__select:focus {
    outline: none;
    border-color: var(--ks-gold);
}

/* Top-of-list variant of the per-page chooser (setlists tabs). Sits above the
   list, right-aligned, with the inner form carrying the inline-flex layout that
   the footer .ks-perpage form has directly. */
.ks-perpage--top {
    display: flex;
    justify-content: flex-end;
    margin: 0 0 var(--ks-space-sm);
}
.ks-perpage__form {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* ── Session 50: Choir settings — per-area permission matrix ────────────────
   The matrix lists each feature area as a row; each row shows role "chips".
   Director-tier chips are locked-on (checked + disabled); grantable chips are
   real toggles. Layout is a simple two-column grid that collapses on mobile. */
.ks-settings-readonly {
    font-family: var(--ks-font-sans);
    font-size: 13px;
    line-height: 1.5;
    color: var(--ks-navy);
    background: var(--ks-cream);
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    padding: 10px 12px;
    margin-bottom: var(--ks-space-md);
}
.ks-settings-form {
    margin-top: var(--ks-space-md);
}
.ks-settings-matrix {
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    overflow: hidden;
}
.ks-settings-matrix__head {
    display: grid;
    grid-template-columns: 160px 1fr 1fr;
    gap: 12px;
    padding: 10px 14px;
    background: var(--ks-navy);
    color: #fff;
    font-family: var(--ks-font-sans);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .5px;
    text-transform: uppercase;
}
.ks-settings-row {
    display: grid;
    grid-template-columns: 160px 1fr 1fr;
    gap: 12px;
    padding: 12px 14px;
    border-top: 0.5px solid var(--ks-border);
    align-items: start;
}
.ks-settings-row:nth-child(odd) {
    background: #fff;
}
.ks-settings-row:nth-child(even) {
    background: var(--ks-cream);
}
.ks-settings-row__area {
    font-family: var(--ks-font-sans);
    font-size: 14px;
    font-weight: 600;
    color: var(--ks-navy);
}
.ks-settings-row__roles {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.ks-settings-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--ks-font-sans);
    font-size: 12px;
    color: var(--ks-navy);
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: 999px;
    padding: 5px 11px 5px 9px;
    cursor: pointer;
    user-select: none;
    transition: background .12s, border-color .12s;
}
.ks-settings-chip input {
    margin: 0;
    cursor: pointer;
}
.ks-settings-chip.is-on {
    background: rgba(74, 111, 92, 0.12);   /* sage tint */
    border-color: var(--ks-sage);
    color: var(--ks-sage);
    font-weight: 600;
}
.ks-settings-chip--locked {
    background: rgba(31, 41, 82, 0.06);    /* navy tint */
    border-color: var(--ks-navy);
    color: var(--ks-navy);
    font-weight: 600;
    cursor: not-allowed;
}
.ks-settings-chip--locked input {
    cursor: not-allowed;
}
.ks-settings-actions {
    margin-top: var(--ks-space-md);
}
.ks-settings-note {
    margin-top: var(--ks-space-md);
}
/* Session 64: "who may allow email notifications" section in Choir settings. */
.ks-settings-emailtoggle {
    margin-top: var(--ks-space-lg);
    padding-top: var(--ks-space-md);
    border-top: 0.5px solid var(--ks-border);
}
.ks-settings-emailtoggle .ks-h3 { margin: 0 0 6px; }
.ks-emailtoggle-roles {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}
.ks-emailtoggle-people {
    margin-top: 14px;
}
.ks-emailtoggle-people__label {
    display: block;
    font-size: 12px;
    color: var(--ks-slate);
    font-weight: 600;
    margin-bottom: 6px;
}
@media (max-width: 640px) {
    .ks-settings-matrix__head { display: none; }
    .ks-settings-row {
        grid-template-columns: 1fr;
        gap: 8px;
    }
}

/* ── Session 50 (expanded): per-area individual-member grants (column 3) ──── */
.ks-settings-row__people {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ks-people {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ks-people__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.ks-people__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 600;
    color: var(--ks-gold);
    background: rgba(212, 169, 85, 0.12);   /* gold tint */
    border: 0.5px solid var(--ks-gold);
    border-radius: 999px;
    padding: 4px 6px 4px 11px;
}
.ks-people__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--ks-gold);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
}
.ks-people__remove:hover {
    background: var(--ks-gold);
    color: #fff;
}
.ks-people__search {
    position: relative;
}
.ks-people__input {
    font-size: 12px;
    padding: 6px 9px;
    width: 100%;
    max-width: 240px;
}
.ks-people__results {
    position: absolute;
    z-index: 30;
    top: calc(100% + 2px);
    left: 0;
    width: 240px;
    max-width: 100%;
    max-height: 220px;
    overflow-y: auto;
    background: #fff;
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
    box-shadow: 0 6px 18px rgba(31, 41, 82, 0.14);
}
.ks-people__result {
    display: block;
    width: 100%;
    text-align: left;
    padding: 8px 11px;
    border: none;
    background: transparent;
    font-family: var(--ks-font-sans);
    font-size: 13px;
    color: var(--ks-navy);
    cursor: pointer;
}
.ks-people__result:hover {
    background: var(--ks-cream);
}
.ks-people__noresult {
    padding: 8px 11px;
    font-family: var(--ks-font-sans);
    font-size: 12px;
    color: var(--ks-sage);
}
.ks-people__empty {
    font-family: var(--ks-font-sans);
    font-size: 12px;
    color: var(--ks-sage);
    font-style: italic;
}

/* ── Tier 7: Platform super-admin console ───────────────────────── */
.ks-sa-tablewrap {
    overflow-x: auto;
}
.ks-sa-table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--ks-font-sans);
    font-size: 13px;
}
.ks-sa-table th {
    text-align: left;
    padding: 8px 10px;
    border-bottom: 1px solid var(--ks-border);
    color: var(--ks-sage);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .4px;
    white-space: nowrap;
}
.ks-sa-table td {
    padding: 9px 10px;
    border-bottom: 0.5px solid var(--ks-border);
    color: var(--ks-navy);
    vertical-align: middle;
}
.ks-sa-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.ks-sa-cid {
    color: var(--ks-sage);
    font-size: 11px;
    margin-left: 6px;
}
.ks-sa-pill {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 9px;
    border-radius: 999px;
    border: 0.5px solid transparent;
    white-space: nowrap;
}
.ks-sa-pill--ok {
    background: #EAF5EE;
    color: #2F6B45;
    border-color: #B6DBC4;
}
.ks-sa-pill--warn {
    background: #FBF4E6;
    color: #8a6d2b;
    border-color: #E7D7AE;
}
.ks-sa-admins {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: var(--ks-space-md);
}
.ks-sa-admin {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 12px;
    background: var(--ks-cream);
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
}
/* Session 69: Choir-creators request row — stack Grant/Deny buttons vertically
   (one on top of the other) so the pair reads cleanly and stays usable on
   narrow/mobile screens. The row stays a flex row; the button form is a fixed
   column on the right. */
.ks-sa-admin form {
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex-shrink: 0;
}
.ks-sa-admin form .btn {
    width: 100%;
}
.ks-sa-admin__id {
    display: flex;
    flex-direction: column;
    gap: 1px;
    flex: 1 1 auto;
    min-width: 0;
}
.ks-sa-admin__email {
    font-size: 12px;
    color: var(--ks-sage);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ks-sa-admin__you {
    font-size: 11px;
    font-weight: 700;
    color: var(--ks-gold);
    text-transform: uppercase;
    letter-spacing: .4px;
}
.ks-sa-grant {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}
.ks-sa-grant__input {
    flex: 1 1 240px;
    max-width: 320px;
}
@media (max-width: 640px) {
    .ks-sa-grant__input { max-width: 100%; }
}
.ks-sa-enter {
    color: var(--ks-navy);
    font-family: var(--ks-font-sans);
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
    text-decoration: none;
    padding: 4px 10px;
    border: 0.5px solid var(--ks-border);
    border-radius: 999px;
}
.ks-sa-enter:hover {
    background: var(--ks-navy);
    color: #fff;
    border-color: var(--ks-navy);
}
/* Choir-creators tab extras */
.ks-sa-reqdot {
    display: inline-block;
    min-width: 18px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--ks-brick, #B85450);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    text-align: center;
    line-height: 18px;
}
.ks-sa-slotnum { width: 64px; text-align: center; padding: 4px 6px; }
.ks-sa-slotform { display: inline-flex; }
.ks-sa-rowactions { display: flex; gap: 8px; align-items: center; justify-content: flex-end; }
.ks-sa-reqmsg { display: block; font-style: italic; color: var(--ks-slate); font-size: 12px; margin-top: 2px; }

/* ── Tier 7: "acting as platform admin" banner (in-choir view) ─────── */
.ks-platform-banner {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 var(--ks-space-md);
    padding: 10px 14px;
    background: #FBF4E6;            /* gold tint, matches edit/warn family */
    border: 0.5px solid var(--ks-gold);
    border-radius: var(--ks-radius-sm);
    color: #6b5320;
    font-family: var(--ks-font-sans);
    font-size: 13px;
    line-height: 1.4;
}
.ks-platform-banner svg { flex-shrink: 0; color: var(--ks-gold); }
.ks-platform-banner strong { color: var(--ks-navy); }
.ks-platform-banner__back {
    margin-left: auto;
    white-space: nowrap;
    color: var(--ks-navy);
    font-weight: 700;
    text-decoration: none;
}
.ks-platform-banner__back:hover { text-decoration: underline; }
@media (max-width: 640px) {
    .ks-platform-banner { flex-wrap: wrap; }
    .ks-platform-banner__back { margin-left: 0; }
}

/* ── Monster: Choir map / globe view ────────────────────────── */
.ks-globe-card {
    padding: 0;
    overflow: hidden;
}
.ks-globe-map {
    width: 100%;
    height: 460px;
    background: #e8eef2;
}
.ks-globe-map .leaflet-container {
    font-family: var(--ks-font-sans);
}
.ks-globe-fallback {
    margin: 0;
    padding: 14px 16px;
    font-family: var(--ks-font-sans);
    font-size: 13px;
    color: var(--ks-sage);
}
.ks-globe-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.ks-globe-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    background: var(--ks-cream);
    border: 0.5px solid var(--ks-border);
    border-radius: var(--ks-radius-sm);
}
.ks-globe-row--muted {
    background: #fff;
    opacity: .8;
}
.ks-globe-row__pin {
    font-size: 15px;
    line-height: 1;
}
.ks-globe-row__body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    flex: 1 1 auto;
    min-width: 0;
}
.ks-globe-row__body strong {
    color: var(--ks-navy);
    font-size: 14px;
}
.ks-globe-row__place {
    font-size: 12px;
    color: var(--ks-sage);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ks-globe-row__members {
    font-size: 12px;
    color: var(--ks-sage);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.ks-globe-empty {
    text-align: center;
    padding: 28px 18px;
}
.ks-globe-empty__glyph {
    font-size: 34px;
    margin-bottom: 6px;
}
.ks-globe-empty__title {
    display: block;
    color: var(--ks-navy);
    margin-bottom: 4px;
}
.ks-globe-empty__note {
    margin: 0 auto;
    max-width: 420px;
    font-size: 13px;
    color: var(--ks-sage);
}

