/*
 * Squad Party — the one stylesheet, loaded after bootstrap.min.css.
 *
 * Part one is docs/DESIGN.md's override block, copied VERBATIM. It is not edited
 * here: tests/Feature/StylesheetTest.php asserts this file contains that block
 * character for character, so the document stays the source and this stays the
 * copy. Change a token in docs/DESIGN.md and re-copy.
 *
 * Part two is phase 6's own: the frame docs/UI-LAYOUT.md §1 describes, which is
 * layout rather than tokens and therefore has no home in that document.
 *
 * Part three is phase 7's, and it is one rule: a token docs/DESIGN.md sets that
 * Bootstrap declares and never reads.
 *
 * Phase 38 rewrote part one for ClayUI and rotated three token names through
 * parts 2-14. docs/DESIGN.md §0 now means something different by each of
 * --bs-body-bg, --bs-secondary-bg and --bs-tertiary-bg than it did before
 * 2026-09-05, so a rule copied from an older commit will paint the wrong surface.
 */

/* ═══ 1. docs/DESIGN.md — the override block, verbatim ══════════════════════ */

:root,
[data-bs-theme="light"] {
  --bs-body-bg: #f0f2f5;              /* ground — §0, and body needs no repaint */
  --bs-body-bg-rgb: 240,242,245;
  --bs-secondary-bg: #ffffff;         /* surface — cards, sheets, dropdowns */
  --bs-secondary-bg-rgb: 255,255,255;
  --bs-tertiary-bg: #f4f6f9;          /* sunken — inputs, selects, tracks */
  --bs-tertiary-bg-rgb: 244,246,249;

  --bs-heading-color: #1a202c;
  --bs-emphasis-color: #1a202c;
  --bs-emphasis-color-rgb: 26,32,44;
  --bs-body-color: #1a202c;
  --bs-body-color-rgb: 26,32,44;
  --bs-secondary-color: #616e81;      /* #718096 is 3.58:1 on #f0f2f5 */
  --bs-secondary-color-rgb: 97,110,129;
  --bs-tertiary-color: #718096;

  --bs-border-color: #d9dfe8;
  --bs-border-color-translucent: rgba(26,32,44,.12);

  --bs-primary: #2563eb;
  --bs-primary-rgb: 37,99,235;
  --bs-primary-text-emphasis: #2563eb;
  --bs-primary-bg-subtle: #ebf2ff;
  --bs-primary-border-subtle: #a8c1f7;

  --bs-link-color: #2563eb;
  --bs-link-color-rgb: 37,99,235;
  --bs-link-hover-color: #1d4ed8;
  --bs-link-hover-color-rgb: 29,78,216;

  --bs-success: #6cc8c1;  --bs-success-rgb: 108,200,193;
  --bs-warning: #ffe100;  --bs-warning-rgb: 255,225,0;
  --bs-danger:  #cd040b;  --bs-danger-rgb: 205,4,11;
  --bs-info:    #a673eb;  --bs-info-rgb: 166,115,235;

  --bs-success-text-emphasis: #147839;
  --bs-warning-text-emphasis: #9f5411;
  --bs-danger-text-emphasis:  #be123c;
  --bs-info-text-emphasis:    #6b46c1;
  --bs-success-bg-subtle: #c7f5d9;
  --bs-warning-bg-subtle: #fde6c8;
  --bs-danger-bg-subtle:  #ffe4e6;
  --bs-info-bg-subtle:    #e9d8fd;
  --bs-success-border-subtle: #afd8bf;
  --bs-warning-border-subtle: #dfcab0;
  --bs-danger-border-subtle:  #e0c9ca;
  --bs-info-border-subtle:    #cdbedf;

  --bs-form-valid-color: #147839;
  --bs-form-valid-border-color: #147839;
  --bs-form-invalid-color: #be123c;
  --bs-form-invalid-border-color: #cd040b;

  --bs-border-radius: 0.75rem;        /* §6 — the kit asks for the same 12px */
  --bs-border-radius-lg: 1rem;        /* §6 — and the same 16px on a card */

  --bs-box-shadow-sm: 0 1px 3px rgba(0,0,0,.04), 0 1px 2px rgba(0,0,0,.02);
  --bs-box-shadow-lg: 0 10px 25px -5px rgba(37,99,235,.12), 0 8px 10px -6px rgba(0,0,0,.04);

  --bs-focus-ring-color: #2563eb;     /* solid, not a halo — §9.3 */
  --bs-focus-ring-width: 0.1875rem;
}

[data-bs-theme="dark"] {
  --bs-body-bg: #181920;
  --bs-body-bg-rgb: 24,25,32;
  --bs-secondary-bg: #232530;
  --bs-secondary-bg-rgb: 35,37,48;
  --bs-tertiary-bg: #1a1c24;
  --bs-tertiary-bg-rgb: 26,28,36;

  --bs-heading-color: #f1f5f9;
  --bs-emphasis-color: #f1f5f9;
  --bs-emphasis-color-rgb: 241,245,249;
  --bs-body-color: #f1f5f9;
  --bs-body-color-rgb: 241,245,249;
  --bs-secondary-color: #8e96a4;
  --bs-secondary-color-rgb: 142,150,164;
  --bs-tertiary-color: #6b7481;

  --bs-border-color: #2f3240;
  --bs-border-color-translucent: rgba(255,255,255,.12);

  --bs-primary: #2f6bff;              /* indicator only — 4.49877:1 under white */
  --bs-primary-rgb: 47,107,255;
  --bs-primary-text-emphasis: #60a5fa;
  --bs-primary-bg-subtle: #1e2a4a;
  --bs-primary-border-subtle: #1a3b8c;

  --bs-link-color: #60a5fa;
  --bs-link-color-rgb: 96,165,250;
  --bs-link-hover-color: #88bcfb;
  --bs-link-hover-color-rgb: 136,188,251;

  --bs-success-text-emphasis: #34d399;
  --bs-warning-text-emphasis: #fb923c;
  --bs-danger-text-emphasis:  #f87171;
  --bs-info-text-emphasis:    #c084fc;
  --bs-success-bg-subtle: #143d2b;
  --bs-warning-bg-subtle: #422917;
  --bs-danger-bg-subtle:  #3b1b1b;
  --bs-info-bg-subtle:    #2e1a47;
  --bs-success-border-subtle: #48685a;
  --bs-warning-border-subtle: #6c584a;
  --bs-danger-border-subtle:  #664d4d;
  --bs-info-border-subtle:    #5c4c6f;

  --bs-form-valid-color: #34d399;
  --bs-form-valid-border-color: #34d399;
  --bs-form-invalid-color: #f87171;
  --bs-form-invalid-border-color: #f87171;

  --bs-box-shadow-sm: 0 2px 5px rgba(0,0,0,.3);
  --bs-box-shadow-lg: 0 12px 30px -5px rgba(0,0,0,.6), 0 8px 12px -6px rgba(47,107,255,.15);

  --bs-focus-ring-color: #2f6bff;
}

/* Focus: Bootstrap's halo drops the native outline and composites to #bad4fa
   on the sunken #f4f6f9 — 1.40:1, under the 3:1 floor. §9.3 */
:focus-visible {
  outline: 0.1875rem solid var(--bs-focus-ring-color);
  outline-offset: 0.125rem;
  box-shadow: none;
}

/* .form-control:focus is (0,2,0) and beat the rule above from phase 6 to 38. It
   also repaints the fill to --bs-body-bg, which is the page since §0 changed, so
   a focused field jumped out of its own sunken surface. Both halves, at (0,2,0). */
.form-control:focus,
.form-select:focus {
  background-color: var(--bs-tertiary-bg);
  box-shadow: inset 0 2px 4px rgba(0,0,0,.05), inset 0 1px 2px rgba(0,0,0,.04);
}
[data-bs-theme="dark"] .form-control:focus,
[data-bs-theme="dark"] .form-select:focus {
  box-shadow: inset 0 2px 4px rgba(0,0,0,.4), inset 0 1px 2px rgba(0,0,0,.3);
}
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
  border-color: var(--bs-focus-ring-color);
}
.form-check-input:focus { box-shadow: none; }
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible {
  outline: 0.1875rem solid var(--bs-focus-ring-color);
  outline-offset: 0.125rem;
}

/* Components set these on themselves, so a :root value never reaches them. §9.2 */
.card {
  --bs-card-bg: var(--bs-secondary-bg);
  --bs-card-border-radius: var(--bs-border-radius-lg);
  --bs-card-border-color: var(--bs-border-color);
  --bs-card-box-shadow: 0 4px 18px -2px rgba(15,23,42,.07), 0 2px 6px -1px rgba(15,23,42,.04);
}
[data-bs-theme="dark"] .card {
  --bs-card-box-shadow: 0 6px 20px -2px rgba(0,0,0,.45), 0 2px 6px -1px rgba(0,0,0,.3);
}

/* The other four surfaces Bootstrap wires to --bs-body-bg, which is now the
   ground. .modal and .offcanvas are deliberately absent — §9.2. */
.dropdown-menu { --bs-dropdown-bg: var(--bs-secondary-bg); --bs-dropdown-link-active-bg: var(--bs-primary); }
.list-group { --bs-list-group-bg: var(--bs-secondary-bg); --bs-list-group-active-bg: var(--bs-primary); --bs-list-group-active-border-color: var(--bs-primary); }
.nav-pills { --bs-nav-pills-link-active-bg: var(--bs-primary); }
.pagination { --bs-pagination-active-bg: var(--bs-primary); --bs-pagination-active-border-color: var(--bs-primary); }
.progress { --bs-progress-bg: var(--bs-tertiary-bg); --bs-progress-bar-bg: var(--bs-primary); }
.badge { --bs-badge-border-radius: var(--bs-border-radius-pill); }

/* --bs-primary does not reach these thirteen selectors. §9.2 */
.btn-primary {
  --bs-btn-color: #fff;
  --bs-btn-bg: #2563eb;
  --bs-btn-border-color: #2563eb;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #1d4ed8;
  --bs-btn-hover-border-color: #1d4ed8;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #1a45be;
  --bs-btn-active-border-color: #1a45be;
  --bs-btn-disabled-color: #fff;
  --bs-btn-disabled-bg: #2563eb;
  --bs-btn-disabled-border-color: #2563eb;
  --bs-btn-box-shadow: 0 4px 12px rgba(37,99,235,.32), inset 0 1px 1px rgba(255,255,255,.35);
}
[data-bs-theme="dark"] .btn-primary {
  --bs-btn-bg: #2d67f5;               /* the kit's #2f6bff shaded 4% — §1.4 */
  --bs-btn-border-color: #2d67f5;
  --bs-btn-hover-bg: #255fe6;
  --bs-btn-hover-border-color: #255fe6;
  --bs-btn-active-bg: #2154ca;
  --bs-btn-active-border-color: #2154ca;
  --bs-btn-disabled-bg: #2d67f5;
  --bs-btn-disabled-border-color: #2d67f5;
  --bs-btn-box-shadow: 0 4px 14px rgba(47,107,255,.4), inset 0 1px 1px rgba(255,255,255,.25);
}
.btn-danger {
  --bs-btn-color: #fff;
  --bs-btn-bg: #cd040b;               /* Bootstrap draws #dc3545 without this */
  --bs-btn-border-color: #cd040b;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #b4040a;
  --bs-btn-hover-border-color: #b4040a;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #a40309;
  --bs-btn-active-border-color: #a40309;
  --bs-btn-disabled-color: #fff;
  --bs-btn-disabled-bg: #cd040b;
  --bs-btn-disabled-border-color: #cd040b;
}
[data-bs-theme="dark"] .btn-danger {
  --bs-btn-color: #101318;
  --bs-btn-bg: #e1686d;
  --bs-btn-border-color: #e1686d;
  --bs-btn-hover-color: #101318;
  --bs-btn-hover-bg: #e67f83;
  --bs-btn-hover-border-color: #e67f83;
  --bs-btn-active-color: #101318;
  --bs-btn-active-bg: #e99296;
  --bs-btn-active-border-color: #e99296;
  --bs-btn-disabled-color: #101318;
  --bs-btn-disabled-bg: #e1686d;
  --bs-btn-disabled-border-color: #e1686d;
}
.btn { --bs-btn-focus-box-shadow: none; }   /* :focus-visible does this now */

/* 5.3.8 declares --bs-btn-box-shadow and reads it only under :focus-visible, so
   a resting button's glow draws nothing without this. Same defect as the card. */
.btn-primary { box-shadow: var(--bs-btn-box-shadow); }

.form-check-input:checked {
  background-color: var(--bs-primary);
  border-color: var(--bs-primary);
}
.form-control, .form-select, .form-check-input {
  background-color: var(--bs-tertiary-bg);
  border-color: #838b93;   /* #d9dfe8 on #ffffff is 1.34:1 — not a boundary */
}
.form-control, .form-select {
  box-shadow: inset 0 2px 4px rgba(0,0,0,.05), inset 0 1px 2px rgba(0,0,0,.04);
}
[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select,
[data-bs-theme="dark"] .form-check-input {
  border-color: #6b7481;
}
[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select {
  box-shadow: inset 0 2px 4px rgba(0,0,0,.4), inset 0 1px 2px rgba(0,0,0,.3);
}
/* The dark boundary above is (0,2,0) and later than .form-control:focus, so it
   won and the focused border stayed neutral in dark while going accent in light. */
[data-bs-theme="dark"] .form-control:focus,
[data-bs-theme="dark"] .form-select:focus,
[data-bs-theme="dark"] .form-check-input:focus {
  border-color: var(--bs-focus-ring-color);
}

/* Member colours: the one set that is not a --bs-* override. §3 */
:root,
[data-bs-theme="light"] {
  --member-1: #7c1c0e;
  --member-2: #bf7608;
  --member-3: #0e712c;
  --member-4: #074b53;
  --member-5: #651eb6;
  --member-6: #df2099;
}
[data-bs-theme="dark"] {
  --member-1: #e63519;
  --member-2: #faca82;
  --member-3: #1ce358;
  --member-4: #13d6ec;
  --member-5: #a063e6;
  --member-6: #eb75c0;
}

/* Every animation in §8 degrades to its end state, not to a shorter one. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ═══ 2. Phase 6 — the frame, docs/UI-LAYOUT.md §1 ══════════════════════════ */

/* Bootstrap ships .btn and .form-control at 38px at the default line-height, and
   CLAUDE.md §6's 44px floor is not negotiable. inline-flex because min-height
   alone leaves an inline-block's label sitting at the top of the box.
   textarea.form-control is spelled out rather than covered by .form-control:
   Bootstrap sets its own min-height at that weight, so one class lost to a class
   plus a type and every textarea computed 38px. Found 2026-09-04, the hour
   ui-audit.js learned to weigh a selector list branch by branch. */
.btn,
.form-control,
.form-select,
textarea.form-control {
  min-height: 44px;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* --- The header ---------------------------------------------------------- */

/* Sticky rather than fixed: it is one row and the body scrolls under it, so a
   fixed header would cost a matching padding nobody would keep in step. */
.sq-header {
  position: sticky;
  top: 0;
  z-index: 1020;
  background-color: var(--bs-secondary-bg);
  /* The hairline as a shadow and not a border, because a border is 1px of
     HEIGHT: the header would be 57px and everything that subtracts 56px from
     the viewport would be a pixel short. Measured — the assistant's short
     thread scrolled by exactly that. */
  box-shadow: 0 var(--bs-border-width) 0 var(--bs-border-color);
  padding-top: env(safe-area-inset-top);
}

/* Translucency lives with the blur and not in the rule above it: 94% of a
   surface with nothing filtered behind it is a muddier surface, not a lighter
   one, and a browser without backdrop-filter then still reads AA against an
   opaque header. The kit's own two numbers.

   Prefixed as well as not: Safari shipped the unprefixed property only in 18,
   so an iPhone a version or two behind takes the opaque branch — which is the
   designed fallback rather than a defect, but it is not what these phones can
   actually draw. */
@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .sq-header {
    background-color: color-mix(in srgb, var(--bs-secondary-bg) 94%, transparent);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }
}

.sq-header__bar {
  min-height: 56px;
}

/* Bootstrap 5.3 ships no min-width utility -- checked against the vendored
   stylesheet -- and .text-truncate inside a flex child does nothing without one.
   The same shape .sq-squad__details carries. */
.sq-header__title {
  min-width: 0;
}

/* A placeholder mark, and the app icon rather than a second asset: it is already
   precached by the worker, so the header costs no request it was not making. */
.sq-header__logo {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
}

/* The 44px floor measured on the element that receives the tap, not on the 24px
   glyph inside it -- CLAUDE.md §6. */
.sq-header__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--bs-border-radius);
  background-color: transparent;
  color: var(--bs-body-color);
  cursor: pointer;
}

/* --- The drawer ----------------------------------------------------------- */

/* A left sheet, not Bootstrap's offcanvas -- docs/DESIGN.md §9.5. The bottom bar
   stays: this is the same registry rendered a second way for the modules a thumb
   reaches less often, not a replacement for it. */
.sq-drawer {
  margin: 0;
  margin-inline-end: auto;
  /* The inset is the drawer's and no longer the nav's: something sits below the
     nav now, and the gap above it read as a mistake. */
  padding: 0 0 env(safe-area-inset-bottom);
  width: min(20rem, 84vw);
  max-width: none;
  height: 100dvh;
  max-height: none;
  border: 0;
  border-radius: 0;
  background-color: var(--bs-secondary-bg);
  box-shadow: var(--bs-box-shadow-lg);
  color: var(--bs-body-color);
  overflow-y: auto;
  /* docs/DESIGN.md §8: sheet in at 240ms, exit out at 160ms. `display` and
     `overlay` need allow-discrete or a <dialog> snaps away with no exit at all;
     a browser without it simply shows and hides, which is the end state. */
  transform: translateX(-100%);
  transition:
    transform 160ms ease-in,
    display 160ms ease-in allow-discrete,
    overlay 160ms ease-in allow-discrete;
}

.sq-drawer[open] {
  transform: translateX(0);
  transition:
    transform 240ms cubic-bezier(0.2, 0, 0, 1),
    display 240ms cubic-bezier(0.2, 0, 0, 1) allow-discrete,
    overlay 240ms cubic-bezier(0.2, 0, 0, 1) allow-discrete;
}

@starting-style {
  .sq-drawer[open] {
    transform: translateX(-100%);
  }
}

/* One dimming for the four sheets, and a literal: Bootstrap's --bs-backdrop-bg
   lives on .modal-backdrop, and no --bs-* name at :root is a backdrop. */
.sq-drawer::backdrop,
.sq-assign::backdrop,
.sq-bild::backdrop,
.sq-models__sheet::backdrop {
  background-color: rgba(0, 0, 0, 0.45);
}

/* The same 56px and the same inset as .sq-header, because the note asked for the
   two headers to line up and a drawer opening over a bar half its height reads
   as a different app. */
.sq-drawer__bar {
  min-height: 56px;
  margin-top: env(safe-area-inset-top);
  border-bottom: var(--bs-border-width) solid var(--bs-border-color);
}

.sq-drawer__nav {
  padding: 0.5rem 0;
}

.sq-drawer__link {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 48px;
  padding: 0 1rem;
  color: var(--bs-body-color);
  text-decoration: none;
}

/* Weight and a rule down the edge, never fill alone -- CLAUDE.md §6, state is
   not signalled by colour by itself, and aria-current carries it for anything
   not looking. */
.sq-drawer__link--current {
  box-shadow: inset 3px 0 0 var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
  font-weight: 600;
}

/* Said in words by the visually-hidden span beside it; the dimming is the second
   signal and never the only one. */
.sq-drawer__link--unreachable {
  color: var(--bs-secondary-color);
  cursor: default;
}

/* The rule belongs to whichever control is showing, not to a wrapper: on a
   desktop neither is revealed and a wrapper would rule an empty drawer foot. */
.sq-drawer__link--button,
.sq-drawer__ios {
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}

/* The same resets .sq-you__row carries: a list row that is an action, not a link. */
.sq-drawer__link--button {
  width: 100%;
  border-inline: 0;
  border-bottom: 0;
  background: none;
  text-align: start;
}

/* Body ink and not --bs-secondary-color: on the one platform with no button this
   instruction is the content, not a dimmed aside. */
.sq-drawer__ios {
  padding: 0.75rem 1rem;
  color: var(--bs-body-color);
}

/* --- The splash ----------------------------------------------------------- */

/* Off unless the inline head script decided this is a launch rather than the
   fourth tab tapped. Every internal navigation is a real page load, so without
   that flag it would play on all of them. */
.sq-splash {
  display: none;
}

/* Dismissed by the animation and not by a script: animation-fill-mode leaves it
   transparent and out of the way, so a script that never loads cannot strand the
   splash over the app. Under prefers-reduced-motion the global rule at the top of
   this file cuts the duration to 0.01ms and it lands straight on that end state,
   which loses nothing -- a splash carries no information. */
[data-sq-splash='show'] .sq-splash {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The GROUND and not --bs-body-bg: `body` is painted with --bs-tertiary-bg,
     so the surface colour would fade out to a shade one step off what is
     underneath it and read as a flash. docs/DESIGN.md §0. */
  background-color: var(--bs-body-bg);
  animation: sq-splash-out 900ms cubic-bezier(0.2, 0, 0, 1) forwards;
}

[data-sq-splash='show'] .sq-splash__logo {
  animation: sq-splash-mark 900ms cubic-bezier(0.2, 0, 0, 1) forwards;
}

/* Held opaque to 66% and gone by 100%: under a second in total, which is what
   docs/BUILD-PLAN.md phase 19 asked for. visibility rather than display so the
   fade has something to fade. */
@keyframes sq-splash-out {
  0%, 66% { opacity: 1; visibility: visible; }
  100% { opacity: 0; visibility: hidden; }
}

@keyframes sq-splash-mark {
  0% { opacity: 0; transform: scale(0.86); }
  40% { opacity: 1; transform: scale(1); }
  100% { opacity: 1; transform: scale(1); }
}

/* --- "You", and the one panel behind it ---------------------------------- */

/* <details>, not Bootstrap's dropdown: docs/DESIGN.md §9.5 keeps the bundle for
   what genuinely needs Popper, and a panel pinned to the header edge does not. */
.sq-you {
  position: relative;
}

.sq-you__trigger {
  display: flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
  cursor: pointer;
  list-style: none;
}

/* Both are needed: Firefox reads list-style, Chromium reads the pseudo-element. */
.sq-you__trigger::-webkit-details-marker {
  display: none;
}

/* docs/DESIGN.md §7's sheet level, both modes — hairline, tint and the sheet
   shadow together, because a menu floats over the card it opened from and the
   matrix has no level between card and sheet.
   14rem and not 12rem since phase 22: fixes.md item 13 asked for a menu rather
   than a column of buttons, and a row is now an icon, a gap and a label.
   overflow:hidden so a row's hover fill stops at the panel's own radius. */
.sq-you__panel {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + 0.25rem);
  z-index: 1021;
  min-width: 14rem;
  padding-block: 0.25rem;
  overflow: hidden;
  background-color: var(--bs-secondary-bg);
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--bs-box-shadow-lg);
}

/* 48px and not the 44px floor, for the reason .sq-drawer__link is 48px: these
   are read as a list, and a list of floor-height rows reads as cramped. The
   button needs the four resets an <a> does not — it is a row inside a form
   because logging out is a POST. */
.sq-you__row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 48px;
  padding: 0 1rem;
  border: 0;
  background: none;
  color: var(--bs-body-color);
  text-align: start;
  text-decoration: none;
}

/* --bs-tertiary-bg is the one fill that moves in both modes: it is lighter than
   the panel in light and darker than it in dark, where the panel is already
   --bs-secondary-bg. */
.sq-you__row:hover {
  background-color: var(--bs-body-bg);
}

/* A full-bleed row inside a clipping container needs its ring drawn INWARD.
   docs/DESIGN.md §9.3 offsets the ring 2px outside the box; these rows are
   exactly as wide as the panel that clips them, so the sides are cut away and
   the top and bottom read as two stray rules across the screen behind. Measured
   in the browser at 390px — the drawer has drawn it that way since phase 19,
   which is why one rule covers both. */
.sq-you__row:focus-visible,
.sq-drawer__link:focus-visible {
  outline-offset: calc(-1 * var(--bs-focus-ring-width));
}

/* Logout is last and separated — fixes.md item 13. The rule is on the list item
   so the separator is not a fourth thing in the tab order. */
.sq-you__last {
  margin-top: 0.25rem;
  padding-top: 0.25rem;
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}

/* --- The body ------------------------------------------------------------ */

/* Clears the floating dock: 74px is its measured 62px — 48px tabs inside 6px of
   padding and a hairline — plus the 12px it floats off the bottom, and then the
   device's own inset and one gutter so the last card does not end flush against
   it. **This rule owns that clearance.** `.sq-dock`'s sticky offset is the only
   other place the number appears, it has to be the same expression or the
   composer settles at the end of a scroll, and StylesheetTest holds the pair
   together — docs/DECISIONS.md → the-frames-clearance-has-one-owner.

   Both paddings are declared here rather than left to `.py-3` in the markup, and
   that is not a style preference: Bootstrap's spacing utilities are `!important`,
   so `.py-3` beat this calc() outright and the body scrolled under the bar. The
   audit caught it; the screen at rest did not, because one card does not scroll.
   Step 3 either way — docs/DESIGN.md §6. */
.sq-main {
  padding-top: 1rem;
  padding-bottom: calc(74px + env(safe-area-inset-bottom) + 1rem);
}

/* The assistant is the one screen that has to fill the frame. Only the header
   is subtracted here; the gutter and the dock's clearance are this element's own
   padding and the box model takes them off, which is what stops them being
   spelled a second time further down. */
.sq-main:has(> .sq-chat__screen) {
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - 56px - env(safe-area-inset-top));
}

/* The login and offline screens have no nav to clear, so they centre instead. */
.sq-solo {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* 390px is the width the frame is designed at; a login card stretched across a
   laptop is a different design, not the same one wider. */
.sq-solo > .card {
  width: 100%;
  max-width: 24rem;
  margin-inline: auto;
}

/* --- The widget grid, phase 19 note 9 ------------------------------------- */

/* auto-fit and a floor rather than `repeat(2, 1fr)`: two to a row is what 390px
   gives, and the same rule widens to three on a tablet without a breakpoint.
   The cells stretch, so two widgets in a row are the same height -- two cards of
   different heights side by side is the defect no audit script catches. */
.sq-widgets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 1rem;
}

/* The hero, and the whole of what makes it one here. It names no module: which
   offer won is ModuleRegistry::dashboard()'s answer. */
.sq-widgets__wide {
  grid-column: 1 / -1;
}

/* --- A widget is a link, phase 23 ----------------------------------------- */

/* The whole card is one <a>, so it inherits a link's colour and underline and
   has to be told not to. The transition is docs/DESIGN.md §8's micro step; the
   global prefers-reduced-motion rule at the top of this file already cuts it. */
.sq-widget {
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease-out;
}

/* The BORDER and not the fill, which is the obvious answer and the wrong one:
   the fill both modes would use is --bs-secondary-bg, where docs/DESIGN.md §1.6
   measured the muted meta ink at 4.26:1 -- under the floor, on a card whose every
   body line is muted meta, and Bootstrap's text utilities are !important so they
   cannot be lifted for the duration of a press. Light mode already reserves the
   pixel by colouring the border transparent, so nothing shifts. */
.sq-widget[href]:hover,
.sq-widget[href]:active {
  --bs-card-border-color: var(--bs-link-color);
}

/* Above the title and not beside it: at 390px a cell is ~171px and the title has
   ~139px of it, which "Assistenten" at the 20px card-title step fills. Beside a
   24px icon and a gap it wrapped. */
.sq-widget__icon {
  display: block;
  margin-bottom: 0.5rem;
}

/* Accent, and only when the card goes somewhere: a module that mounts no screen
   draws the same card and must not advertise a link it does not have.
   docs/DESIGN.md §1.6 and §1.7 measure this ink at 5.51:1 and 4.65:1. */
.sq-widget[href] .sq-widget__icon {
  color: var(--bs-link-color);
}

/* Pushed to the bottom so two cards in a row line their update lines up with
   each other rather than each floating under a body of its own length. The
   padding is the minimum gap for the tallest card in the row, which has no free
   space left to push into. */
.sq-widget .card-body {
  display: flex;
  flex-direction: column;
}

.sq-widget__updated {
  margin: auto 0 0;
  padding-top: 0.5rem;
}

/* --- The crew grid, phase 19 note 8 --------------------------------------- */

/* Two profile cards to a row, stacked rather than the row of phase 5: a name, a
   number and an address beside a 64px circle left about 150px for the text at
   390px, and every one of them truncated. */
.sq-crew {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 1rem;
}

.sq-crew__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
}

/* The avatar and, for a member sharing their position, a dot at the ring's
   corner: 16px like the calendar's, in the emphasis green rather than
   --bs-success, which is 1.98:1 on a light card — 5.56:1 and 7.92:1 measured.
   Its ring is the card surface, so it separates from the slot colour behind
   it. Decoration — the line under the name is what says it. */
.sq-crew__face {
  position: relative;
  display: inline-flex;
}

.sq-crew__presence {
  position: absolute;
  inset-inline-end: 6px;
  inset-block-end: 6px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background-color: var(--bs-success-text-emphasis);
  box-shadow: 0 0 0 2px var(--bs-secondary-bg);
}

/* min-width: 0 on the column, or .text-truncate inside it does nothing --
   the same shape .sq-header__title and .sq-squad__details carry.

   The gap is not decoration: without it "Sedd nyss" and "Inga kontaktuppgifter"
   sit line-on-line in the same size and colour and read as one wrapped
   sentence. Two facts, two lines, and the eye has to be told which is which. */
.sq-crew__details {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
  width: 100%;
}

/* The name is the way to a member's page, and 44px is measured on the element
   that receives the tap -- CLAUDE.md §6, scripts/ui-audit.js audit 1. Block and
   not inline-flex, because a flex container cannot put an ellipsis on the text
   inside it and a long name in a 9.5rem column has to truncate.

   Bootstrap's own link colour and underline are left alone: the contacts under
   it are already drawn that way, and a name that reads as plain text is a link
   nobody knows is there. */
.sq-crew__name {
  display: block;
  min-height: 44px;
  overflow: hidden;
  line-height: 44px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Contact details are a group, and further from the presence line than its own
   two rows are from each other. */
.sq-crew__contacts {
  margin-top: 0.25rem;
}

.sq-crew__contact {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- The bottom navigation ----------------------------------------------- */

/* Ours, not Bootstrap's navbar — docs/DESIGN.md §9.4. One-handed use puts the
   navigation under the thumb, which is the whole reason it is at the bottom.

   A floating pill since phase 39, 12px off three edges. The device's own inset
   moved out of a padding and into `bottom`, which is what makes it a gap under
   the dock rather than dead space inside it. `max-width` because a pill ruled
   across a laptop is a bar again. */
.sq-nav {
  position: fixed;
  inset-inline: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  z-index: 1030;
  max-width: 400px;
  margin-inline: auto;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  padding: 0.375rem 0.5rem;
  background-color: var(--bs-secondary-bg);
  box-shadow: var(--bs-box-shadow-lg);
}

/* Behind @supports for the reason the header's is, plus one of its own:
   backdrop-filter repaints on every scroll frame and this element is over the
   scrolling content on every screen. The dock reads without it. */
@supports (backdrop-filter: blur(16px)) or (-webkit-backdrop-filter: blur(16px)) {
  .sq-nav {
    background-color: color-mix(in srgb, var(--bs-secondary-bg) 95%, transparent);
    -webkit-backdrop-filter: blur(16px);
    backdrop-filter: blur(16px);
  }
}

/* Equal columns, because a tab that is wider than its neighbour reads as more
   important than its neighbour. The dock is narrower than the bar it replaced,
   so phase 13's numbers moved: 390px of viewport is 366px of dock, less its
   hairline and its 8px of padding, is **348px of columns**. Five is 69.6px,
   SIX is 58px and still clears "Kalender", SEVEN is 49.7px and wraps it.
   Phase 39, measured at 390 x 844.

   What holds it is the 1fr floor, so do NOT "fix" this to minmax(0, 1fr) the way
   the calendar grid in section 4 is: 1fr floors at min-content, so "Kalender"
   keeps its 54.3px and the six others give up 0.8px each. Under minmax(0, 1fr)
   every column would be exactly 53.6px and that label would wrap to two lines.

   So the dock's capacity is set by its LONGEST LABEL, not by the count, and the
   number to check before adding a seventh module is 348 / n against the widest
   label at .small -- 49.7px against 54.3px, which is where this stops working.
   docs/DECISIONS.md -> nav-holds-seven-tabs, whose seven is the BAR's and is
   six here. The cap is five either way. */
.sq-nav__list {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}

.sq-nav__tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  min-height: 48px;
  min-width: 44px;
  border-radius: var(--bs-border-radius);
  color: var(--bs-secondary-color);
  text-align: center;
  text-decoration: none;
}

/* The shape .sq-drawer__link--current already carries, turned a quarter: a rule
   drawn inward so becoming current moves nothing, a subtle fill, and the weight.
   CLAUDE.md §6 — never colour by itself, and aria-current carries it for
   anything not looking. */
.sq-nav__tab--current {
  box-shadow: inset 0 3px 0 var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
  font-weight: 600;
}

/* A module that mounts no route. WCAG 1.4.3 exempts an inactive control from the
   text floor; this still clears 3:1 in both modes, and the weight, the missing
   indicator and aria-disabled carry it for anyone who reads no colour at all. */
.sq-nav__tab--unreachable {
  color: var(--bs-tertiary-color);
  cursor: default;
}

.sq-nav__label {
  line-height: 1.2;
}

/* ═══ 3. Phase 7 — the dashboard, docs/UI-LAYOUT.md §2 ══════════════════════ */

/* docs/DESIGN.md §7 puts the light-mode card separation on --bs-card-box-shadow.
   5.3.8 declares that variable on .card and no rule anywhere reads it, so the
   shadow was configured and never drawn: a #ffffff card sat on the #f7f7f7
   ground at 1.07:1 with a transparent border, which is no separation at all.
   Measured on the dashboard at 390px. Dark was never affected — .card's own
   border shorthand does read --bs-card-border-color. */
.card {
  box-shadow: var(--bs-card-box-shadow);
}

/* ═══ 4. Phase 9 — the calendar, docs/UI-LAYOUT.md §3 ═══════════════════════ */

/* --- The month header ----------------------------------------------------- */

/* Three columns rather than justify-content: the title stays centred on the card
   whether the month is "maj" or "september", which space-between does not give
   you. */
.sq-cal__head {
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
}

.sq-cal__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--bs-link-color);
  text-decoration: none;
  border-radius: var(--bs-border-radius);
}

/* --- The grid ------------------------------------------------------------- */

/* One template for the headings and the days, so a heading cannot drift out of
   line with the column under it. At 390px this is 44.86px a column, which is the
   tap floor with 0.86px to spare — the gutter, the card padding and the gap are
   already at their smallest allowed step, so nothing here is free. */
/* minmax(0, 1fr) and not 1fr: 1fr floors at min-content, and a cell's
   min-content is its dot row — four 16px dots, which demanded 64px a column and
   put the whole grid 170px past a 390px viewport. Measured in the browser; it is
   invisible in a snapshot because the markup is identical either way.

   The grid alone spans the card's full width, and the card keeps its padding for
   everything else. docs/DESIGN.md §3 fixes the dot at 16px and §10 fixes one
   slot per member, so a 390px screen has to find the width somewhere: without
   this the four dots overlap by 6.8px each and read as one smear, and with it
   they overlap by 5.7px and read as four. That is the whole reason it is here. */
.sq-cal__weekdays,
.sq-cal__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
  margin-inline: calc(-1 * var(--bs-card-spacer-x));
}

.sq-cal__weekdays {
  text-align: center;
}

/* Bootstrap gives abbr[title] a dotted underline. Seven of them across a calendar
   header reads as seven errors, and the title is still announced. */
.sq-cal__weekdays abbr {
  text-decoration: none;
  cursor: default;
}

/* --- A day ---------------------------------------------------------------- */

/* The transparent border is a track, not decoration: --best colours it in, so
   becoming the best day cannot move the grid by two pixels. It was the shape
   .sq-nav__tab carried too until phase 39 gave that one an inset shadow, which
   needs no track at all.

   The 4px of side padding is what the dot row overhangs into. Dots are 16px in
   columns of about 9px, so they overlap deliberately — docs/DESIGN.md §3 puts the
   2px ring there for exactly that — and this padding is what keeps the overhang
   inside the cell rather than inside a horizontal scrollbar. */
.sq-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 0;
  min-height: 52px;
  padding: 2px 4px;
  color: var(--bs-body-color);
  background-color: transparent;
  border: var(--bs-border-width) solid transparent;
  border-radius: var(--bs-border-radius);
}

/* Neither tappable nor announced — they exist to keep the weeks whole. At
   --bs-secondary-color and not --bs-tertiary-color, which is 3.32:1 on the
   surface and under the 4.5:1 a 14px number owes. */
.sq-day--outside {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  color: var(--bs-secondary-color);
}

/* docs/DESIGN.md §10: the fill and the edge. Measured, they are 1.34:1 and 1.85:1
   against the card in light and 1.03:1 and 2.10:1 in dark — under every floor
   there is — so the highlight does not rest on them. The number carries it in
   weight and ink, which is what survives a bad screen and a reader who sees no
   colour at all. */
.sq-day--best {
  background-color: var(--bs-primary-bg-subtle);
  border-color: var(--bs-primary-border-subtle);
}

.sq-day--best .sq-day__number {
  font-weight: 700;
  color: var(--bs-primary-text-emphasis);
}

/* Inset, so the selection reads inside the cell and cannot be mistaken for the
   focus ring, which docs/DESIGN.md §9.3 draws outside with an offset. 4.10:1 on
   the best-day fill, its worst ground. */
.sq-day--selected {
  box-shadow: inset 0 0 0 2px var(--bs-primary);
}

/* Underline, not a colour: today and the selected day are usually the same cell,
   and a second colour treatment on one cell says nothing the first did not. */
.sq-day--today .sq-day__number {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 2px;
}

.sq-day__number {
  line-height: 1;
}

/* Present or absent, never a different colour — a day either has something on it
   or it does not, and the count is in the label. Kept in the flow when empty, so
   no cell is three pixels shorter than the one beside it. */
.sq-day__events {
  width: 60%;
  height: 3px;
  background-color: var(--bs-secondary-color);
  border-radius: var(--bs-border-radius-pill);
}

.sq-day__events--none {
  background-color: transparent;
}

/* --- The member dots, docs/DESIGN.md §3 and §10 --------------------------- */

/* One column per squad member, in slot order, whether or not they answered: §10
   makes position the signal that depends on nobody's cones. minmax(0, 1fr)
   columns rather than a fixed pitch, so the row crowds instead of overflowing.
   Measured at 390px: four members sit at a 10.3px pitch and overlap by 5.7px,
   six sit at 6.6px and overlap by 9.4px — still no overflow, but only 6.6px of
   each dot is left. Four is the squad and six is the ceiling. */
.sq-day__dots {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: center;
  width: 100%;
  min-width: 0;
  height: 16px;
}

/* The ring is load-bearing rather than decoration. --member-2 on the best-day
   fill is 2.69:1, under the 3:1 non-text floor; with the ring the dot's immediate
   ground is --bs-body-bg, where docs/DESIGN.md §3 measured 3.37:1. It also
   separates the dots from each other, which is what lets them overlap at all. */
.sq-dot {
  width: 16px;
  height: 16px;
  justify-self: center;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--bs-secondary-bg);
}

/* Filled is free, hollow is busy, nothing is nobody answered. Shape and not
   colour — docs/UI-LAYOUT.md §3, and the third state is what
   DayAvailability::silentCount() exists to make drawable. */
.sq-dot--free {
  background-color: currentcolor;
}

.sq-dot--busy {
  background-color: transparent;
  border: 2px solid currentcolor;
}

/* An unanswered slot holds its place and draws nothing at all. */
.sq-dot--silent {
  box-shadow: none;
}

.sq-dot--member-1 { color: var(--member-1); }
.sq-dot--member-2 { color: var(--member-2); }
.sq-dot--member-3 { color: var(--member-3); }
.sq-dot--member-4 { color: var(--member-4); }
.sq-dot--member-5 { color: var(--member-5); }
.sq-dot--member-6 { color: var(--member-6); }

/* --- The member avatar ---------------------------------------------------- */

/* The one exception to docs/CONVENTIONS.md §6 rule 2, and a deliberate one:
   this block IS shared across five screens, because a member has to look the
   same wherever they appear. It replaced .sq-wall__who and .sq-item__who, which
   were the same drawing written twice and drifting -- one a circle, one a pill.

   The colour is the RING and the picture sits inside it. A photograph does not
   take over the job docs/DESIGN.md §3 gave the slot colour: half the squad will
   have no picture, and a face is not a signal four people read at 28px. */
.sq-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  padding: 1px;
  border: 2px solid currentcolor;
  border-radius: 50%;
  font-weight: 600;
  overflow: hidden;
}

/* 2px of ring plus 1px of padding, so a picture is 6px narrower than its circle
   and the colour reads as a frame around it rather than as an edge on it. */
.sq-avatar__picture {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
}

/* The ring takes the slot colour and the ink does not -- docs/CONVENTIONS.md §9
   rule 3. Measured on --bs-body-bg the six slots run 3.61 to 10.35, so member-2
   and member-6 clear the 3:1 a ring owes and miss the 4.5:1 that bold 13px
   owes, and initials are text. */
.sq-avatar__initials {
  color: var(--bs-body-color);
  line-height: 1;
}

/* A list row is 44px and an avatar has to leave room for a label beside it. */
.sq-avatar--sm {
  width: 28px;
  height: 28px;
  font-size: 0.6875rem;
}

.sq-avatar--md {
  width: 36px;
  height: 36px;
  font-size: 0.8125rem;
}

/* The account page's preview, where the point is to see what was cropped. */
.sq-avatar--lg {
  width: 96px;
  height: 96px;
  border-width: 3px;
  padding: 2px;
  font-size: 2rem;
}

/* Nobody assigned. --bs-secondary-color is 4.95:1 light and 6.62:1 dark on a
   card -- over the 3:1 WCAG 1.4.11 asks of a control's own boundary, and the
   answer .sq-item__box and .sq-compose__pick already carry. Dashed and a plus,
   because an empty circle on its own is a state nobody can name. */
.sq-avatar--none {
  border-style: dashed;
  color: var(--bs-secondary-color);
}

/* Sized here rather than on the element: one drawing serves sm and md, which is
   the same reason .sq-avatar__picture carries no width or height either. */
.sq-avatar--none > svg {
  width: 55%;
  height: 55%;
}

/* --- The mode switch ------------------------------------------------------ */

/* The kit's segmented control on the .btn-group the view already has: the
   fieldset is the sunken track and the labels are its segments. margin-inline
   and not margin, because the element also carries .mt-3 and every Bootstrap
   spacing utility is !important — a shorthand here once declared a margin-top
   it never got. No inner shadow: the composer's .sq-dock__modes has none and
   two tracks should look like two of one thing. */
.sq-cal__modes {
  display: flex;
  gap: 0.25rem;
  margin-inline: 0;
  padding: 0.25rem;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
}

/* Through the component's own variables, docs/DESIGN.md §9.2. Body ink and not
   the muted meta on the sunken fill — the composer measured that at 4.26:1 in
   light. The checked segment fills with the accent under --bs-white; in dark it
   takes the same 4% shade .btn-primary does, because white on the kit's blue
   is 4.4988:1 — docs/DECISIONS.md → the-dark-primary-fill-is-shaded-because-4-4988-is-not-4-5.
   Measured: 5.17:1 checked light, 4.81:1 dark; the fill is 4.77:1 and 3.54:1
   against the track, over the 3:1 a state boundary owes. */
.sq-cal__mode {
  --bs-btn-color: var(--bs-body-color);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--bs-body-color);
  --bs-btn-hover-bg: var(--bs-secondary-bg);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--bs-white);
  --bs-btn-active-bg: var(--bs-primary);
  --bs-btn-active-border-color: var(--bs-primary);
  font-weight: 600;
}

[data-bs-theme="dark"] .sq-cal__mode {
  --bs-btn-active-bg: #2d67f5;
  --bs-btn-active-border-color: #2d67f5;
}

/* Bootstrap's group rules are four classes deep — a -1px margin-left and squared
   inner corners — so the segment is named at five, which is also what the markup
   is: a label following its radio. The corner is the track's minus its padding. */
.sq-cal__modes.btn-group > .btn-check + .btn.sq-cal__mode {
  flex: 1 1 0;
  margin-left: 0;
  border-radius: calc(var(--bs-border-radius) - 0.25rem);
}

/* Weight is the second signal beside the fill, and the lift the third. */
.sq-cal__modes > .btn-check:checked + .sq-cal__mode {
  box-shadow: var(--bs-box-shadow-sm);
  font-weight: 700;
}

/* .btn-check:focus-visible + .btn sets outline:0 in the vendored stylesheet, and
   our :focus-visible lands on the input, which .btn-check has already clipped to
   nothing. Without this the switch has no visible keyboard focus at all. */
.btn-check:focus-visible + .btn {
  outline: var(--bs-focus-ring-width) solid var(--bs-focus-ring-color);
  outline-offset: 0.125rem;
}

/* --- The selected day ----------------------------------------------------- */

.sq-cal__event + .sq-cal__event {
  margin-top: 0.5rem;
}

.sq-cal__time {
  display: inline-block;
  min-width: 4rem;
}

/* <details> over a modal — docs/DESIGN.md §9.5. The summary is a control, so it
   owes the same 44px floor every other control on this screen holds. */
.sq-cal__summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  color: var(--bs-link-color);
  cursor: pointer;
}

/* ═══ 5. Phase 10 — notifications, docs/MODULES.md §3 ═══════════════════════ */

/* The one line the screen says back after an action. --bs-emphasis-color rather
   than the body colour: it is the newest thing on the page and has to read as
   an answer rather than as more prose, and DESIGN.md §1 has no token for a
   "notice" surface that would not be a parallel vocabulary. */
.sq-notice {
  color: var(--bs-emphasis-color);
  font-weight: 600;
}

/* The keygen command is the whole point of that alert, so three things about it
   are not cosmetic. Bootstrap ships code at 0.875em, which compounds to under
   14px — docs/DESIGN.md §5 floors meta text there, and this is text somebody has
   to retype. --bs-code-color is not retokenised anywhere, so left alone it draws
   Bootstrap's #d63384 on the alert's own background, which is the §9.2 trap
   again; inheriting the alert's colour is AA because the alert's text already
   is. And a path may not break mid-word on a 390px screen. */
.alert code {
  font-size: 1em;
  color: inherit;
  overflow-wrap: anywhere;
}

/* ═══ 6. Phase 11 — lists, docs/UI-LAYOUT.md §4 ════════════════════════════ */

/* --- The head, phase 33 --------------------------------------------------- */

/* Phase 32's lever on a second card: .card declares both spacers on itself, so
   overriding them on the same element works only because the shell loads
   app.css after the vendored file. 12px sideways and not 16 is what keeps two
   tabs on one row at 390px -- 315px of pills into 319px of card body. */
.sq-head {
  --bs-card-spacer-y: 0.5rem;
  --bs-card-spacer-x: 0.75rem;
}

/* --- The list switcher ---------------------------------------------------- */

/* Wraps rather than scrolls. docs/UI-LAYOUT.md §7 forbids horizontal scroll
   outright, and a squad with six lists would overflow a 390px row otherwise. */
.sq-lists__switch {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Not .nav-pills, which docs/DESIGN.md §9.2 retokenises for a nav component
   this is not: it is a wrapping row of links. Written out because the 44px
   floor is ours to hold, and padding-block is the one owner of the height --
   no utility on the element sets it too. docs/CONVENTIONS.md §6. */
.sq-lists__tab {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.875rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-pill);
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
  text-decoration: none;
  cursor: pointer;
  list-style: none;
}

/* Both are needed: Firefox reads list-style, Chromium reads the pseudo-element. */
.sq-lists__tab::-webkit-details-marker {
  display: none;
}

/* Weight and ink, not fill alone -- CLAUDE.md §6, state is never signalled by
   colour by itself, and aria-current carries it for anything not looking. */
.sq-lists__tab--current {
  border-color: var(--bs-primary);
  border-width: 2px;
  color: var(--bs-primary);
  font-weight: 600;
}

/* Beside the find controls while it is shut, and a line of its own once it is
   open: a disclosure that opens into the 90px left over on its row has nowhere
   to draw its form. Full width was unconditional until phase 33. */
.sq-lists__new {
  flex: 0 0 auto;
}

.sq-lists__new[open] {
  width: 100%;
}

/* Phase 19 note 11: it read as another list, because it was drawn with the same
   pill the list tabs are. A filled button at --bs-border-radius-sm -- 4px,
   Bootstrap's own value and untouched by docs/DESIGN.md §6 -- is far enough from
   a 22px pill to read as a control.

   .btn-primary and not the .btn-secondary the phase asked for: §9.2 retokenises
   exactly two variants, tests/Feature/StylesheetTest.php enforces that, and
   .btn-secondary would have shipped Bootstrap's #6c757d beside six measured
   colours. docs/DECISIONS.md -> new-list-button-is-primary. */
.sq-lists__add {
  display: inline-flex;
  align-items: center;
  border-radius: var(--bs-border-radius-sm);
  cursor: pointer;
  list-style: none;
}

.sq-lists__add::-webkit-details-marker {
  display: none;
}

.sq-lists__new[open] > .sq-lists__add {
  margin-bottom: 0.5rem;
}

.sq-lists__form {
  display: flex;
  gap: 0.5rem;
}

.sq-lists__form .form-control,
.sq-lists__form .form-select {
  flex: 1 1 auto;
  min-width: 0;
}

.sq-lists__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* --- Finding a row, phase 33 ---------------------------------------------- */

/* Find, filter and "+ Ny lista" on one line rather than two: the button had a
   52px row of its own with 225px of nothing beside it, which is a row this
   screen would rather spend on an item. It wraps like the switcher above it. */
.sq-lists__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem;
}

/* Not drawn without app.js: a search box that answers a keystroke with nothing
   is worse than none, and the new-list disclosure is outside this group so it
   is still there when this is not. [data-sq-js] is on the root, so it survives
   a region swap. The 12rem basis takes the pair to its own line rather than
   squeezing the search box. */
.sq-find {
  display: none;
  flex: 1 1 12rem;
  gap: 0.5rem;
}

[data-sq-js] .sq-find {
  display: flex;
}

.sq-find .form-control {
  flex: 1 1 auto;
  min-width: 0;
}

/* Bootstrap's .form-select is width:100%, which in a flex row would leave the
   search box nothing. */
.sq-find .form-select {
  flex: 0 0 auto;
  width: auto;
}

/* --- A row ---------------------------------------------------------------- */

.sq-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border-top: 1px solid var(--bs-border-color);
}

.sq-item:first-child {
  border-top: 0;
}

/* An author rule beats the UA stylesheet's [hidden] whatever its specificity, so
   .sq-item's own display:flex would leave a filtered row on the screen. */
.sq-item[hidden] {
  display: none;
}

/* A flex item and not display:contents. contents drops the element from the box
   tree, and a <form> that is not a box is a <form> some assistive technology
   stops reporting -- the row does not need that risk for one line of layout. */
.sq-item__form {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
}

/* The whole line is the tap target, which is what makes a twelve-item list
   one-handed. 44px is CLAUDE.md §6's floor measured on the element that
   receives the tap, and that element is this button. */
.sq-item__toggle {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 44px;
  padding: 0.5rem 0;
  border: 0;
  background: none;
  color: inherit;
  text-align: left;
}

/* Shape, not colour: the box is drawn empty and the tick inside it is what
   appears. aria-pressed on the button says the same thing out loud. */
.sq-item__box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  /* --bs-border-color is 1.34:1 on a card -- measured, and far under the 3:1 a
     component outline owes under WCAG 1.4.11. The empty box IS the component
     boundary, so it is drawn in the secondary ink instead: 5.18:1 light, 5.11:1
     dark. Bootstrap's own .form-check-input has the same hairline; that is not
     a licence, CLAUDE.md §6 makes the floor ours to verify. */
  border: 2px solid var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
  color: transparent;
}

.sq-item__label {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Struck, dimmed, and the tick drawn -- three signals, none of them colour
   alone. The row keeps its place until the next render sinks it, so a tap made
   offline is visibly undoable where it happened. */
.sq-item--klar .sq-item__box {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary);
  color: var(--bs-white);
}

.sq-item--klar .sq-item__label {
  color: var(--bs-secondary-color);
  text-decoration: line-through;
}

/* --- Assigning a row, phase 34 -------------------------------------------- */

/* The circle is 28px and CLAUDE.md §6's floor is 44, so the button is the floor
   and the avatar sits in the middle of it. Drawn only with the script for
   docs/CONVENTIONS.md §6's reason: without app.js it opens nothing. */
.sq-item__assign {
  display: none;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--bs-border-radius);
  background: none;
  color: inherit;
}

[data-sq-js] .sq-item__assign {
  display: inline-flex;
}

/* The other half of that rule: what the script REPLACES is not drawn beside it.
   The ⋮ menu's <select> and the assignee inside a task's own text are both the
   no-script way to the same write, and a screen offering two at once is a screen
   where neither reads as the way. */
[data-sq-js] [data-sq-noscript] {
  display: none;
}

/* --- The row menu --------------------------------------------------------- */

/* The same <details> panel the header already uses, for the same reason:
   docs/DESIGN.md §9.5 keeps the bundle for what genuinely needs Popper, and a
   panel pinned to its own row's edge does not. Bootstrap's .card sets no
   overflow, so nothing clips it. */
.sq-menu {
  position: relative;
  flex: 0 0 auto;
}

.sq-menu__trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--bs-border-radius);
  color: var(--bs-secondary-color);
  cursor: pointer;
  list-style: none;
}

.sq-menu__trigger::-webkit-details-marker {
  display: none;
}

/* docs/DESIGN.md §7's sheet level in both modes: a menu floats over the card it
   opened from, and the matrix has no level between card and sheet. Same values
   as .sq-you__panel, because it is the same thing on a different row. */
.sq-menu__panel {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + 0.25rem);
  /* Above .sq-nav at 1030, not below it. The panel opens downward, so the last
     row of a full list would otherwise draw underneath the fixed bottom bar --
     .sq-item is position:relative with no z-index, so it opens no stacking
     context and this number competes with the frame's directly. */
  z-index: 1031;
  width: max-content;
  max-width: min(18rem, calc(100vw - 3rem));
  padding: 0.75rem;
  background-color: var(--bs-secondary-bg);
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--bs-box-shadow-lg);
}

.sq-menu__row {
  display: flex;
  gap: 0.5rem;
}

/* Not .btn-outline-secondary: docs/DESIGN.md §9.2 forbids every .btn-outline-*
   by name, because none of them is retokenised and reaching for one silently
   ships Bootstrap's palette. */
.sq-menu__move {
  flex: 1 1 0;
  border: 1px solid var(--bs-border-color);
  color: var(--bs-body-color);
}

.sq-menu__assign {
  display: flex;
  gap: 0.5rem;
}

.sq-menu__assign .form-select {
  flex: 1 1 auto;
  min-width: 0;
}

/* --- The add field -------------------------------------------------------- */

/* Always visible, always last. docs/UI-LAYOUT.md §4: anything that costs a tap
   between items makes a twelve-item shopping list feel like work. */
.sq-add {
  display: flex;
  gap: 0.5rem;
  margin: 0;
}

.sq-add .form-control {
  flex: 1 1 auto;
  min-width: 0;
}

/* The label is two words and the field beside it is elastic, so without this the
   button wraps to two lines at 390px and the row grows to 74px. Measured. */
.sq-add .btn,
.sq-lists__form .btn,
.sq-menu__assign .btn {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* ═══ 7. Phase 12 — tasks, docs/UI-LAYOUT.md §5 ════════════════════════════ */

/* The row itself is phase 11's .sq-item, unchanged: docs/UI-LAYOUT.md §5 is
   §4's vocabulary with a different middle, and this section is only the middle.
   Everything else here — the toggle, the box, the ⋮ panel, the add field — is
   already drawn by section 6. */

/* Two lines inside the tap target, so the title and its meta move together and
   the whole row stays one 44px control rather than becoming three. */
.sq-task__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 0.125rem;
}

/* Wraps rather than scrolls: an assignee pill, a separator and a date is more
   than 240px at 390px once the ⋮ and the box have taken theirs, and
   docs/UI-LAYOUT.md §7 forbids horizontal scroll outright. --bs-secondary-color
   is 4.95:1 light and 6.62:1 dark on a card — §1.6 and §1.7, "muted meta". */
.sq-task__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.375rem;
  color: var(--bs-secondary-color);
  font-size: 0.8125rem;
  line-height: 1.3;
}

/* The kit's status badge on Bootstrap's own .badge, and the fourth block drawn
   on more than one screen — docs/CONVENTIONS.md §6 rule 7. The word is the
   signal, the pill and a <strong>'s weight the second, and the ink only the
   third. Plain, it is a kind, and a chip: docs/DESIGN.md §7's subtle level on
   the raised surface, because the sunken fill measured 1.04:1 on the light
   page and drew nothing there. The dot is a pseudo-element in currentcolor. */
.sq-status {
  --bs-badge-color: var(--bs-body-color);
  --bs-badge-font-size: 0.8125rem;
  --bs-badge-font-weight: 600;
  --bs-badge-padding-x: 0.5rem;
  --bs-badge-padding-y: 0.1875rem;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border: var(--bs-border-width) solid var(--bs-border-color);
  background-color: var(--bs-secondary-bg);
  box-shadow: var(--bs-box-shadow-sm);
}

.sq-status::before {
  content: '';
  flex: 0 0 auto;
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background-color: currentcolor;
}

/* Försenad. The tag trio the kit's red maps onto — docs/DESIGN.md §1.6 and §1.7.
   Measured on a card: #be123c on #ffe4e6 is 5.24:1, #f87171 on #3b1b1b 5.59:1. */
.sq-status--danger {
  --bs-badge-color: var(--bs-danger-text-emphasis);
  border-color: var(--bs-danger-border-subtle);
  background-color: var(--bs-danger-bg-subtle);
}

/* A done task's meta is deliberately NOT dimmed further. --bs-tertiary-color
   clears 3:1 and not 4.5:1 — it is what an inactive control uses, and WCAG
   1.4.3's exemption does not reach this: the row is a live button that unchecks
   the task, and "who it was for" is still text somebody reads. The title's
   strike, dimming and tick are already the three signals. */

/* The due-date form is the assign form with a different control in it, so it
   borrows .sq-menu__assign's row and only has to say which part is elastic —
   that rule names .form-select, and <input type="date"> is a .form-control. */
.sq-menu__assign .form-control {
  flex: 1 1 auto;
  min-width: 0;
}

/* The assignee drawn inside a task's own text, which the sheet replaces when
   there is a script. inline-flex so the circle keeps the centring it had while
   .sq-task__meta was its direct parent. */
.sq-task__who {
  display: inline-flex;
  align-items: center;
}

/* --- The assign sheet, phase 34 ------------------------------------------- */

/* One per screen and drawn by both, which docs/CONVENTIONS.md §6 rule 4 asks to
   be argued rather than assumed: assigning a member is one interaction the app
   has in exactly two places, it draws .sq-avatar, and two sheets that drift is
   the defect the shared avatar was extracted to stop.

   Centred like .sq-models__sheet rather than edged like .sq-drawer — it is a
   choice and not a place. docs/DESIGN.md §7. */
.sq-assign {
  width: min(24rem, calc(100vw - 2rem));
  max-height: min(32rem, calc(100dvh - 4rem));
  padding: 1rem;
  border: 0;
  border-radius: var(--bs-border-radius-lg);
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
  box-shadow: var(--bs-box-shadow-lg);   /* §7: a sheet lifts as the drawer does */
  overflow-y: auto;
}

.sq-assign__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* The ✕ the drawer closes with, not the model sheet's labelled button: this bar
   is narrow and the grid under it wants the width. */
.sq-assign__close {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--bs-border-radius);
  background: none;
  color: var(--bs-secondary-color);
}

/* Written by app.js from the row that opened the sheet, so it is empty markup
   until it is not, and empty markup may not take a line's height. */
.sq-assign__what {
  margin: 0 0 0.75rem;
  color: var(--bs-secondary-color);
  font-size: 0.875rem;
}

.sq-assign__what:empty {
  display: none;
}

/* Three across at 390px: the sheet is 358px wide, 326px inside its padding, so
   a cell is 104px and holds a 36px circle over a name that may wrap. */
.sq-assign__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
}

.sq-assign__choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.375rem;
  min-height: 44px;
  padding: 0.5rem 0.25rem;
  border: 2px solid transparent;
  border-radius: var(--bs-border-radius);
  background: none;
  color: var(--bs-body-color);
  font-size: 0.8125rem;
}

/* Two seen signals and neither is colour alone — the ring is drawn and the name
   goes bold — over an aria-current that says it out loud. The border is there
   at 2px transparent on every cell, so marking one moves nothing. */
.sq-assign__choice[aria-current='true'] {
  border-color: var(--bs-primary);
  font-weight: 600;
}

.sq-assign__name {
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* ═══ 8. Phase 13 — the wall, docs/UI-LAYOUT.md §6 ══════════════════════════ */

/* --- Writing something ---------------------------------------------------- */

/* One row, picker left and Publicera right. Phase 13 gave the native input its
   own full-width row so it could name its own files and phase 21 measured that
   12rem truncated that name; item 16 takes the input out of the flow instead,
   and the naming moves to [data-sq-picked], which costs nothing while empty. */
.sq-compose__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* A label drawn as a button, holding the 44px floor on its own -- the input it
   wraps is 1px inside a clip rect. --bs-border-color is 1.27:1 on a card, far
   under the 3:1 WCAG 1.4.11 asks of the boundary that identifies a control, so
   the outline is the secondary ink at a measured 4.95:1 light and 6.62:1 dark --
   the answer .sq-item__box and .sq-account__danger already carry. */
.sq-compose__pick {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 1rem;
  border: 1px solid var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
  font-weight: 600;
  cursor: pointer;
}

/* A visually-hidden input cannot draw a focus ring, and CLAUDE.md §6 does not
   let one go missing: :focus-within moves it out to the label around it. */
.sq-compose__pick:focus-within {
  outline: 0.1875rem solid var(--bs-focus-ring-color);
  outline-offset: 0.125rem;
}

.sq-compose__tools .btn {
  flex: 0 0 auto;
  white-space: nowrap;
}

.sq-compose__picked {
  flex: 1 1 100%;
  color: var(--bs-body-color);
}

.sq-compose__picked:empty {
  display: none;
}

/* --- The timeline --------------------------------------------------------- */

.sq-wall > li + li {
  margin-top: 0.5rem;
}

/* Two events in a row are one rail and not two -- fixes.md item 20. A post card
   on either side keeps its gap, because docs/DECISIONS.md ->
   the-fold-is-a-run-and-it-reads-forwards has an event BREAK a member's run, and
   a rail running past a card would thread the two back together. */
.sq-wall__event + .sq-wall__event {
  margin-top: 0;
}

/* --- A recorded event ----------------------------------------------------- */

/* One line, muted, no avatar -- docs/UI-LAYOUT.md §6. It is deliberately NOT a
   card: a feed where every row is a card is a feed where nothing is a post, and
   the difference between "somebody wrote this" and "this happened" is the whole
   reason the timeline holds both. --bs-secondary-color is 4.62:1 on the ground
   in light and 7.22:1 in dark -- docs/DESIGN.md §1.6 and §1.7, "muted meta". */
.sq-wall__event {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.625rem;
  padding: 0.3125rem 0.5rem 0.3125rem 0;
  line-height: 1.35;
}

/* The rail, on the <li> and not on the mark: it has to span the row's padding as
   well as its content, or two stacked events draw two lines with a gap between
   them and the timeline reads as dashes. */
.sq-wall__event::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 4px;
  width: 2px;
  background-color: currentcolor;
}

/* The node on the rail, and still the second signal that this row is a different
   kind from the cards around it -- indentation and ink alone are both
   colour-adjacent. WCAG 1.4.11 does not reach a decoration, so this borrows the
   3:1 input-boundary ink rather than claiming a text ratio. Hollow since the
   rail: filled in the same ink it now sits on, it read as a bulge in the line.
   10px against the rail's 4px offset is what puts the two on one centre. */
.sq-wall__mark {
  position: relative;
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  margin-top: 0.25rem;
  border: 2px solid currentcolor;
  border-radius: var(--bs-border-radius-pill);
  background-color: var(--bs-body-bg);
}

/* --- A member card, phase 24 ---------------------------------------------- */

/* One header over a run of that member's posts -- fixes.md item 11. The header
   is not a Bootstrap .card-header: --bs-card-cap-bg is one value for the whole
   app and this background is one of six, so retokenising the component would
   mean overriding it back per slot anyway. docs/DESIGN.md §9.2. */
.sq-wall__head {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.375rem var(--bs-card-spacer-x);
  border-top: 3px solid currentcolor;
  /* NOT --bs-card-inner-border-radius: 5.3.8 derives that from --bs-border-radius
     and this app overrides --bs-card-border-radius to the lg step, so the vendor
     variable is 2px smaller than the corner it is supposed to sit inside. */
  border-radius: calc(var(--bs-card-border-radius) - var(--bs-border-width))
                 calc(var(--bs-card-border-radius) - var(--bs-border-width)) 0 0;
}

/* The slot is set on the header and never on the card: color inherits, and a
   member-coloured .card would tint the post text with it.

   color-mix and not twelve hand-picked hexes -- --member-N already switches
   between modes, so the tint follows dark mode for free and cannot drift from
   the ring beside it. Same support era as the :has() this stylesheet already
   ships (both 2023), and the tint degrades to no background rather than to an
   unreadable one. 12% is where the ink measurements below still clear AA. */
.sq-wall__post--member-1 .sq-wall__head { color: var(--member-1); background-color: color-mix(in srgb, var(--member-1) 12%, var(--bs-card-bg)); }
.sq-wall__post--member-2 .sq-wall__head { color: var(--member-2); background-color: color-mix(in srgb, var(--member-2) 12%, var(--bs-card-bg)); }
.sq-wall__post--member-3 .sq-wall__head { color: var(--member-3); background-color: color-mix(in srgb, var(--member-3) 12%, var(--bs-card-bg)); }
.sq-wall__post--member-4 .sq-wall__head { color: var(--member-4); background-color: color-mix(in srgb, var(--member-4) 12%, var(--bs-card-bg)); }
.sq-wall__post--member-5 .sq-wall__head { color: var(--member-5); background-color: color-mix(in srgb, var(--member-5) 12%, var(--bs-card-bg)); }
.sq-wall__post--member-6 .sq-wall__head { color: var(--member-6); background-color: color-mix(in srgb, var(--member-6) 12%, var(--bs-card-bg)); }

/* Heading ink and not the slot -- docs/CONVENTIONS.md §9 rule 3: a member colour
   may ring text and may not be text. Sized by §6's rule rather than by the h2 it
   is, because docs/DESIGN.md §5 has no step for a heading inside a card. */
.sq-wall__by {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--bs-heading-color);
}

/* Body ink and not --bs-secondary-color: measured on the six light tints, the
   muted meta runs 3.97-4.32 and every one of them is under 4.5 -- the same wall
   phase 18 hit on --bs-secondary-bg, from the other side. Body ink is 5.10-5.56
   there and 9.11-14.92 in dark. */
.sq-wall__tally {
  flex: 0 0 auto;
  color: var(--bs-body-color);
}

/* --- A post --------------------------------------------------------------- */

/* An entry is a .card-body and .card sets --bs-card-spacer-y to 1rem, so six
   pixels off it is twelve off every entry on the page. On the card and not on
   .sq-wall__entry, so the header keeps reading the same x gutter. */
.sq-wall__post {
  --bs-card-spacer-y: 0.625rem;
}

/* A rule only between entries, so an unfolded card is unchanged and a folded one
   says where each post ends without a second header. */
.sq-wall__entry + .sq-wall__entry {
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}

/* Pulled up by the padding the 44px trigger adds, and now down as well: the row
   is a 13px timestamp next to a 44px tap target, so it opens a finger of dead
   space at BOTH ends. The bottom pull stops short of the glyph -- the ⋮ is
   centred in 44px and leaves 14px clear under it. */
.sq-wall__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin: -0.5rem 0 -0.625rem;
}

/* overflow-wrap and not word-break: a pasted URL has to break somewhere, and
   word-break: break-all would also break ordinary Swedish mid-word. */
.sq-wall__text {
  margin: 0;
  overflow-wrap: anywhere;
}

/* --- The pictures --------------------------------------------------------- */

/* Two across, which is what 390px minus the card's padding divides into without
   a tile becoming a thumbnail. A third row is unreachable by Album::MAX_PER_POST. */
.sq-wall__album {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.375rem;
  margin: 0.5rem 0 0;
}

.sq-wall__album img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--bs-border-radius);
  object-fit: cover;
}

/* One picture keeps its own shape: it is the subject, not a tile in a set, and
   cropping the only photo in a post to 4:3 is how a receipt loses its total. */
.sq-wall__album[data-sq-count='1'] {
  grid-template-columns: minmax(0, 1fr);
}

.sq-wall__album[data-sq-count='1'] img {
  height: auto;
}

.sq-wall__album:not([data-sq-count='1']) img {
  aspect-ratio: 4 / 3;
}

/* Three is 2 + 1, and the odd one leads rather than trails: a wide first tile
   reads as deliberate where a half-width last one reads as a missing fourth. */
.sq-wall__album[data-sq-count='3'] > li:first-child {
  grid-column: span 2;
}

.sq-wall__album[data-sq-count='3'] > li:first-child img {
  aspect-ratio: 16 / 9;
}

/* --- The replies ---------------------------------------------------------- */

/* A rule and an indent, not a card inside a card: docs/DESIGN.md §7 gives a card
   one elevation and nesting a second one under it flattens both. */
.sq-wall__replies {
  margin: 0.5rem 0 0;
  padding-inline-start: 0.75rem;
  border-inline-start: 2px solid var(--bs-border-color);
}

.sq-wall__reply + .sq-wall__reply {
  margin-top: 0.5rem;
}

.sq-wall__reply time {
  display: block;
  font-size: 0.8125rem;
}

.sq-wall__replyform {
  margin-top: 0.5rem;
}

/* --- Paging --------------------------------------------------------------- */

/* Wraps rather than scrolls -- docs/UI-LAYOUT.md §7 forbids horizontal scroll
   outright, and both links are here at once on every page but the first. */
.sq-wall__pager {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
}

.sq-wall__pager .btn {
  flex: 1 1 10rem;
}

/* ===========================================================================
   Gänget and Mitt konto — phase 15
   =========================================================================== */

/* Bootstrap 5.3 ships no min-width utility, and .text-truncate inside a flex
   child does nothing without one. Verified against the vendored stylesheet. */
.sq-squad__details {
  min-width: 0;
}

/* --- The colour picker ---------------------------------------------------- */

/* Phase 19 note 6: it had to READ as a picker, and six loose circles under a
   legend do not. A bordered well at the input's own boundary ink puts a frame
   round the six the way .form-control frames a field, and space-between spreads
   them across it instead of leaving a ragged tail. The two boundary hexes are
   .form-control's own, light then dark, and StylesheetTest holds them equal: no
   --bs-* name is the input boundary, --bs-border-color being 1.34:1 on a card. */
.sq-colours {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem;
  border: var(--bs-border-width) solid #838b93;
  border-radius: var(--bs-border-radius);
}

[data-bs-theme='dark'] .sq-colours {
  border-color: #6b7481;
}

/* A slot somebody else holds. Dimmed AND struck through: docs/UI-LAYOUT.md §7
   does not let opacity carry a state alone, and the label says whose it is. */
.btn-check:disabled + .sq-colours__swatch {
  cursor: not-allowed;
  opacity: 0.55;
}

.btn-check:disabled + .sq-colours__swatch::after {
  content: "";
  position: absolute;
  width: 34px;
  height: 2px;
  background-color: var(--bs-body-color);
  transform: rotate(-45deg);
}

/* 44px is the floor and the whole label is the tap target, so the swatch is
   drawn inside it rather than being it — CLAUDE.md §6. */
.sq-colours__swatch {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
}

.sq-colours__swatch::before {
  content: "";
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background-color: currentcolor;
}

/* Checked is a ring around the swatch and not a change of fill: the fill IS the
   colour being chosen, so it cannot also carry the selection. */
.btn-check:checked + .sq-colours__swatch {
  box-shadow: 0 0 0 3px var(--bs-secondary-bg), 0 0 0 5px var(--bs-body-color);
}

/* And a second, non-colour signal, because the ring is still a visual one:
   docs/UI-LAYOUT.md §7 does not let selection ride on appearance alone. */
/* Taller than it is wide, or the two borders make a symmetrical "v" that reads
   as a chevron rather than as a tick. Measured at 390px: 7 × 12 is the smallest
   pair that still reads as a check mark. */
.btn-check:checked + .sq-colours__swatch::after {
  content: "";
  position: absolute;
  width: 7px;
  height: 12px;
  border-right: 3px solid var(--bs-secondary-bg);
  border-bottom: 3px solid var(--bs-secondary-bg);
  rotate: 45deg;
  translate: 0 -12%;
}

.btn-check:focus-visible + .sq-colours__swatch {
  outline: 3px solid var(--bs-body-color);
  outline-offset: 2px;
}

/* The live preview, with no JavaScript at all. :has() is the whole mechanism —
   where it is missing the preview simply does not follow the radio, and what is
   saved is still right, so this degrades to nothing rather than to wrong. */
.sq-account:has(#sq-farg-member-1:checked) .sq-account__preview .sq-avatar { color: var(--member-1); }
.sq-account:has(#sq-farg-member-2:checked) .sq-account__preview .sq-avatar { color: var(--member-2); }
.sq-account:has(#sq-farg-member-3:checked) .sq-account__preview .sq-avatar { color: var(--member-3); }
.sq-account:has(#sq-farg-member-4:checked) .sq-account__preview .sq-avatar { color: var(--member-4); }
.sq-account:has(#sq-farg-member-5:checked) .sq-account__preview .sq-avatar { color: var(--member-5); }
.sq-account:has(#sq-farg-member-6:checked) .sq-account__preview .sq-avatar { color: var(--member-6); }

/* The disclosure that guards the location delete — phase 28. A quiet control,
   because opening it is not the destructive act: the red button inside it is,
   and two red buttons stacked would make the first one look like the second.

   Not .btn-outline-secondary. docs/DESIGN.md §9.2 forbids every .btn-outline-*
   by name — none is retokenised, so reaching for one silently ships Bootstrap's
   palette. Same finding and same answer as .sq-menu__move and .sq-compose__pick:
   the border is drawn in the secondary ink, which is 4.95:1 light and 6.62:1
   dark, rather than in --bs-border-color's measured 1.27:1. */
.sq-account__danger {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 0.75rem;
  border: 1px solid var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
  font-weight: 600;
  /* Both are needed: Firefox reads list-style, Chromium reads the pseudo-element. */
  list-style: none;
}

.sq-account__danger::-webkit-details-marker {
  display: none;
}

/* --- The cropper ---------------------------------------------------------- */

/* A square viewport the picture is dragged behind. The frame is round because
   the result is: cropping to a circle against a square window is how somebody
   cuts off their own chin without seeing it happen. */
.sq-crop__frame {
  position: relative;
  width: 100%;
  max-width: 260px;
  aspect-ratio: 1;
  margin-inline: auto;
  overflow: hidden;
  border: 2px solid var(--bs-secondary-color);
  border-radius: 50%;
  touch-action: none;
  background-color: var(--bs-body-bg);
}

/* Positioned by the script in CSS pixels, which is why CropBox stores fractions
   of the source instead: these numbers never leave the browser. */
.sq-crop__image {
  position: absolute;
  transform-origin: 0 0;
  user-select: none;
  -webkit-user-drag: none;
}

/* ═══ 9. Phase 16 — ideas ═══════════════════════════════════════════════════ */

/* A card and not phase 11's .sq-item, which is a one-line row with a checkbox
   at its head. An idea is a title, a paragraph, a link and an author, and
   bending .sq-item around that would change the shape two other screens draw.
   docs/CONVENTIONS.md §6 rule 2 -- this block belongs to this screen. The ⋮ menu
   IS shared, and the phase 16 log argues why. */
.sq-idea {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding-block: 0.75rem;
  border-top: 1px solid var(--bs-border-color);
}

.sq-idea:first-child {
  border-top: 0;
}

.sq-idea__voting {
  flex: 0 0 auto;
  margin: 0;
}

/* The tap target is this button and it is square at the floor -- CLAUDE.md §6,
   measured on the element that receives the tap and not on the 18px triangle.
   --bs-secondary-color and not --bs-border-color for the frame: the latter is
   1.34:1 on a card, under the 3:1 a component boundary owes under WCAG 1.4.11.
   Section 6's .sq-item__box already made that call. */
.sq-idea__vote {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  width: 44px;
  min-height: 44px;
  padding-block: 0.25rem;
  border: 1px solid var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
}

/* Voted is three signals and colour is the third: the triangle is drawn FILLED
   in the markup, the number goes bold, and only then does the ink change. The
   fill is what survives greyscale, and aria-pressed says it out loud. */
.sq-idea__vote[aria-pressed='true'] {
  border-color: var(--bs-primary);
  color: var(--bs-primary);
}

.sq-idea__vote[aria-pressed='true'] .sq-idea__count {
  font-weight: 700;
}

.sq-idea__count {
  font-size: 0.8125rem;
  line-height: 1;
}

.sq-idea__main {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  flex: 1 1 auto;
  min-width: 0;
}

/* overflow-wrap and not text-truncate: a proposal is the one thing on this
   screen a reader has to be able to read in full, and "Åka till Åre i vinter
   om…" is a title that has stopped being one. */
.sq-idea__title {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.sq-idea__body {
  color: var(--bs-secondary-color);
  overflow-wrap: anywhere;
}

/* The floor again: a link is a tap target, and this one sits between two lines
   of text where a 20px hit area would be a mis-tap on somebody else's row. The
   host is all that shows, so it is ellipsised rather than wrapped -- the whole
   URL is on the element for anyone who wants it. */
.sq-idea__link {
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  min-height: 44px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Wraps rather than scrolls -- docs/UI-LAYOUT.md §7 forbids horizontal scroll
   outright, and an avatar, a name and "Gjord" is more than the 250px left at
   390px once the vote button and the ⋮ have taken theirs. */
.sq-idea__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.375rem;
  color: var(--bs-secondary-color);
  font-size: 0.8125rem;
  line-height: 1.3;
}

/* Struck, dimmed, and the word "Gjord" beside it -- three signals, none of them
   colour alone. The idea keeps its place on the board until the next render
   sinks it below the open ones, so a tap is visibly undoable where it happened. */
.sq-idea--klar .sq-idea__title {
  color: var(--bs-secondary-color);
  text-decoration: line-through;
}

/* --- The edit panel ------------------------------------------------------- */

/* .sq-menu__panel is width:max-content, and a <textarea>'s max-content is its
   default 20 cols -- about 180px, which would make the panel narrower than the
   two buttons under it. Stating the form's width settles the panel's. */
.sq-idea__edit {
  display: grid;
  gap: 0.5rem;
  width: min(15rem, calc(100vw - 6rem));
}

/* --- The add disclosure --------------------------------------------------- */

/* A disclosure and not §4's always-visible field: an idea is a title plus two
   optional things, and three inputs standing open under every board is a screen
   whose bottom third is a form nobody is filling in. */
.sq-ideas__add {
  width: 100%;
  list-style: none;
}

.sq-ideas__add::-webkit-details-marker {
  display: none;
}

.sq-ideas__form {
  display: grid;
  gap: 0.5rem;
}

/* ═══ 10. Phase 17 — prylar ═════════════════════════════════════════════════ */

/* Below a card title and above body -- and docs/DESIGN.md §5 has four steps,
   none of which is "section heading". So this is the Body step with weight
   rather than a fifth size: an h3 left alone would draw Bootstrap's fluid 23.1px
   at 390px, which is a step the scale deliberately does not have. */
.sq-shelf__heading {
  margin-block: 0 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--bs-heading-color);
}

/* Not phase 11's .sq-item and not phase 16's .sq-idea: a thing leads with a
   56px picture and has no toggle at its head at all. docs/CONVENTIONS.md §6
   rule 2 -- this block belongs to this screen. The ⋮ menu IS shared, which
   rule 5 already argued. */
.sq-thing {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding-block: 0.75rem;
  border-top: 1px solid var(--bs-border-color);
}

.sq-thing:first-child {
  border-top: 0;
}

/* One box whether or not there is a picture, because a list where half the rows
   start 56px further left reads as two lists. The stored file is bounded on its
   long edge and not square, so the frame crops rather than the upload -- which
   keeps the bytes somebody photographed and leaves the framing a decision the
   screen can change without a re-upload. */
.sq-thing__photo {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
}

.sq-thing__photo--tom {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bs-secondary-color);
}

.sq-thing__main {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  flex: 1 1 auto;
  min-width: 0;
}

/* overflow-wrap and not text-truncate: "Vinterdäck 205/55 R16" is the whole
   point of the row, and a name that has stopped being one is not an inventory. */
.sq-thing__name {
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Beside the name rather than under it: the count is part of what the thing IS,
   and the nbsp in the markup keeps "× 4" off two lines. */
.sq-thing__count {
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--bs-secondary-color);
  white-space: nowrap;
}

/* Wraps rather than scrolls -- docs/UI-LAYOUT.md §7 forbids horizontal scroll,
   and a place, an avatar and "Delad av Bea" is more than the 250px a 390px row
   leaves once the picture and the ⋮ have taken theirs. 4.95:1 light and 6.62:1
   dark on a card -- docs/DESIGN.md §1.6 and §1.7, "muted meta". */
.sq-thing__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.375rem;
  color: var(--bs-secondary-color);
  font-size: 0.8125rem;
  line-height: 1.3;
}

/* --- The edit panel ------------------------------------------------------- */

/* Wider than .sq-idea__edit's 15rem because a file input draws a native button
   AND a filename inside the field. .sq-menu__panel is max-content, so stating
   the form's width settles the panel's, and the 6rem keeps it clear of the
   screen edge at 390px. */
.sq-thing__edit {
  display: grid;
  gap: 0.5rem;
  width: min(16rem, calc(100vw - 6rem));
}

/* The whole line is the tap target, not the 16px box inside it -- CLAUDE.md §6
   measured on the element that receives the tap. */
.sq-thing__drop {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  margin: 0;
}

/* --- The add disclosure --------------------------------------------------- */

/* A disclosure and not docs/UI-LAYOUT.md §4's always-visible field: a thing is
   five inputs, and five standing open under every shelf is a screen whose
   bottom half is a form nobody is filling in. */
.sq-things__add {
  width: 100%;
  list-style: none;
}

.sq-things__add::-webkit-details-marker {
  display: none;
}

.sq-things__form {
  display: grid;
  gap: 0.5rem;
}

/* ═══ 11. Phase 18 — assistenten ════════════════════════════════════════════ */

/* The title and the ⋮ on one line, which the shelf gets for free from its rows
   and this screen does not: there is one menu here and it belongs to the whole
   thread rather than to any turn in it. */
.sq-chat__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

/* Who can read this, said on every screenful rather than only where the mode was
   chosen: this is the app's first surface where the same layout means "everyone"
   or "nobody else" depending on a link two elements up. */
.sq-chat__scope {
  margin-bottom: 0.75rem;
}

.sq-chat__empty {
  margin-bottom: 0.75rem;
}

/* --- The turns ------------------------------------------------------------ */

.sq-chat {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: 0;
}

/* A row and not a bubble with a tail. The shared thread has four possible
   speakers, so the usual mine-right/yours-left split has nothing to say about
   the other three -- and a tail at 390px costs horizontal room the text needs.
   Every turn is named instead, which is the signal that survives four people. */
.sq-chat__turn {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 0.625rem 0.75rem;
  border-radius: var(--bs-border-radius);
  background-color: transparent;
}

/* The one visual difference between a person and the machine, and it is a
   surface rather than a colour: the sunken fill, the step a field takes inside a
   card — 1.08:1 light and 1.12:1 dark against the card, with body ink on it at
   15.07:1 and 15.52:1. Measured 2026-09-05. */
.sq-chat__turn--assistant {
  background-color: var(--bs-tertiary-bg);
}

/* Body ink and NOT the muted meta colour every other row uses, because this row
   also sits on the tinted turn: --bs-secondary-color there measures 4.78:1 in
   light -- over the floor on this palette, under it on the one before, and not
   a margin to build a name on. Measured in the browser, not estimated. */
.sq-chat__who {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  color: var(--bs-body-color);
  font-size: 0.8125rem;
  line-height: 1.3;
}

/* The icon reads at the same size as the avatar beside it in the other kind of
   turn, so the two heads line up rather than stepping. */
.sq-chat__mark {
  display: inline-flex;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
}

.sq-chat__mark svg {
  width: 100%;
  height: 100%;
}

/* overflow-wrap and not word-break: a model that answers with a URL has to break
   it somewhere, and break-all would also break ordinary Swedish mid-word. */
.sq-chat__text {
  overflow-wrap: anywhere;
}

/* Bounded on the tall edge rather than the wide one: a portrait photograph would
   otherwise push the composer off the screen on its own. */
.sq-chat__picture {
  max-width: 100%;
  max-height: 220px;
  width: auto;
  border-radius: var(--bs-border-radius);
  object-fit: contain;
}

/* --- The composer, docked: phase 35, fixes.md item 21 --------------------- */

/* Filling `.sq-main` is what the frame's arithmetic here used to spell out by
   hand — 100dvh less the header, the gutters and the bar — and the point of the
   swap is that the next phase to move the dock will not have to find this rule.
   Without the height a two-message thread leaves the composer mid-screen. */
.sq-chat__screen {
  display: flex;
  flex-direction: column;
  flex: 1;
}

/* Sticky and NOT fixed: a fixed dock needs the thread to carry a bottom padding
   equal to its own height, which changes with the textarea and with a wrapped
   row. Sticky reserves that space itself, so nothing is ever unreachable -- and
   the offset is exactly .sq-main's padding-bottom, so the dock does not settle
   by a few pixels when the thread is scrolled to its end. */
.sq-dock {
  position: sticky;
  bottom: calc(74px + env(safe-area-inset-bottom) + 1rem);
  margin-top: auto;
  padding: 0.5rem;
  gap: 0.5rem;
}

.sq-dock__row {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.sq-dock__send {
  flex: 0 0 auto;
  margin-inline-start: auto;
}

/* --- The two tools -------------------------------------------------------- */

/* One box for both, whether it is a label around a checkbox or a button. The
   secondary ink and not --bs-border-color for the outline, which is 1.27:1 on a
   card and under the 3:1 WCAG 1.4.11 asks of a control's boundary -- measured
   4.95:1 light and 6.62:1 dark, the answer .sq-compose__pick already carries. */
.sq-dock__tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
  background: none;
  color: var(--bs-body-color);
  cursor: pointer;
}

/* A clipped checkbox cannot draw its own ring, so it moves out to the box around
   it -- .sq-compose__pick's shape, and the button shares the rule. */
.sq-dock__tool:focus-within {
  outline: 0.1875rem solid var(--bs-focus-ring-color);
  outline-offset: 0.125rem;
}

/* Two signals: the fill, and a glyph that swaps to a checked twin -- the second
   is the one that survives colour blindness. 5.51:1 light and 4.65:1 dark. */
.sq-dock__tool:has(:checked),
.sq-dock__tool--attached {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary);
  color: var(--bs-secondary-bg);
}

.sq-dock__glyph {
  display: inline-flex;
}

.sq-dock__glyph--on {
  display: none;
}

.sq-dock__tool:has(:checked) .sq-dock__glyph,
.sq-dock__tool--attached .sq-dock__glyph {
  display: none;
}

.sq-dock__tool:has(:checked) .sq-dock__glyph--on,
.sq-dock__tool--attached .sq-dock__glyph--on {
  display: inline-flex;
}

/* Not drawn when there is no script to open it -- docs/CONVENTIONS.md §6, and
   [data-sq-noscript] on the field it replaces is the other half. */
.sq-dock__pic {
  display: none;
}

[data-sq-js] .sq-dock__pic {
  display: inline-flex;
}

/* The answer to "did the picture come with it?", and it lives out here because
   the sheet closes on the pick -- fixes.md item 30. */
.sq-dock__attached {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  overflow-wrap: anywhere;
}

/* Neither .btn-danger nor a bare link: removing an attachment nobody has sent
   yet is not the destructive act .btn-danger is reserved for -- docs/DESIGN.md
   §1.5 -- and the 44px floor is why this is a button and not an <a>. Underlined
   rather than tinted, because CLAUDE.md §6 does not let a control be signalled
   by colour alone. Body ink on the dock: 16.32:1 light, 13.90:1 dark. */
.sq-dock__drop {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 0.5rem;
  border: 0;
  border-radius: var(--bs-border-radius);
  background: none;
  color: var(--bs-body-color);
  font-size: 0.8125rem;
  text-decoration: underline;
}

/* --- Which thread ---------------------------------------------------------- */

/* A track with two links in it, and they stay links: the mode is where you are,
   so it belongs in the URL. Not .btn-group -- docs/DESIGN.md §9.2. */
.sq-dock__modes {
  display: flex;
  gap: 0.125rem;
  padding: 0.125rem;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
}

/* Body ink and not the muted meta colour: --bs-secondary-color measures 4.26:1
   on this track in light, under the floor. 5.48:1 and 10.67:1 as it stands. */
.sq-dock__mode {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding-inline: 0.5rem;
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
  text-decoration: none;
  font-size: 0.8125rem;
  font-weight: 400;
}

/* Three signals: the surface, the weight, and the aria-current a reader hears. */
.sq-dock__mode--open {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-heading-color);
  font-weight: 600;
}

/* --- The picture sheet ----------------------------------------------------- */

/* Centred like the other two sheets -- docs/DESIGN.md §7. It holds no field: two
   inputs named bild post two parts and PHP keeps the last of them. */
.sq-bild {
  width: min(24rem, calc(100vw - 2rem));
  padding: 1rem;
  border: 0;
  border-radius: var(--bs-border-radius-lg);
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
  box-shadow: var(--bs-box-shadow-lg);
}

.sq-bild__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.sq-bild__close {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--bs-border-radius);
  background: none;
  color: var(--bs-secondary-color);
}

/* Stacked and not side by side: "Välj från galleriet" wraps in half of a
   24rem sheet at 390px, and a two-line label beside a one-line one reads as a
   hierarchy that is not there. These two are equals. */
.sq-bild__actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* The same measured answer as .sq-compose__pick, .sq-menu__move and
   .sq-account__danger: docs/DESIGN.md §9.2 forbids every .btn-outline-* by name,
   and two .btn-primary would make two equal doors look like a choice with a
   default. The border is the secondary ink and not --bs-border-color, which is
   under the 3:1 floor -- re-measured on the ClayUI palette at 5.18:1 light and
   5.11:1 dark on the sheet, label 16.32:1 and 13.90:1. 2026-09-05. */
.sq-bild__source {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 1rem;
  border: 1px solid var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
  background: none;
  color: var(--bs-body-color);
  font-weight: 600;
}

/* --- The model picker ----------------------------------------------------- */

.sq-chat__tools {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

/* Neither .btn-primary nor .btn-secondary: docs/DESIGN.md §9.2 forbids the
   second and the first would give a card header two competing calls to action.
   The 44px floor is the whole reason this is not a link.

   Filled rather than outlined, and measured rather than assumed:
   --bs-border-color on the card is 1.35:1 in dark mode, so a hairline border
   would be the only thing saying "control" and it would be under the 3:1 floor.
   A surface says it at 5.48:1 light and 10.67:1 dark — docs/DESIGN.md §1.6. */
.sq-chat__models {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.75rem;
  border: 0;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  font-size: 0.875rem;
}

/* docs/DESIGN.md §7: a sheet lifts further than a card, and this one is centred
   rather than edged like .sq-drawer, because it is a choice and not a place. */
.sq-models__sheet {
  width: min(30rem, calc(100vw - 2rem));
  max-height: min(36rem, calc(100dvh - 4rem));
  padding: 1rem;
  border: 0;
  border-radius: var(--bs-border-radius-lg);
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
  box-shadow: var(--bs-box-shadow-lg);
  overflow-y: auto;
}

.sq-models__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.sq-models__now,
.sq-models__loading {
  margin-bottom: 0.75rem;
}

.sq-models__loading {
  padding: 0.625rem 0.75rem;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  font-size: 0.875rem;
}

.sq-models__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.75rem 0;
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}

.sq-models__row:first-child {
  border-top: 0;
}

.sq-models__what {
  min-width: 0;
}

.sq-models__name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.375rem;
  font-weight: 600;
}

/* Body ink and not the muted meta on this surface: the muted ink on the sunken
   fill is 4.78:1 in light, and phase 18 shipped it under the floor once. */
.sq-models__badge {
  padding: 0.125rem 0.5rem;
  border-radius: var(--bs-border-radius-pill);
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  font-size: 0.75rem;
  font-weight: 500;
}

.sq-models__use {
  min-height: 44px;
  white-space: nowrap;
}

/* ═══ 12. Phase 21 — the file control, fixes.md item 10 ═════════════════════ */

/* Bootstrap paints ::file-selector-button in --bs-tertiary-bg, which is the
   field's own sunken fill, so the button sits at 1:1 against its field — the
   "empty space with an outline" fixes.md item 10 reported. A control inside a
   field reads the surface token and the subtle lift, docs/DESIGN.md §7. */
.form-control[type='file']::file-selector-button {
  background-color: var(--bs-secondary-bg);
  box-shadow: var(--bs-box-shadow-sm);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231a202c' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M20.2 11.1l-8.6 8.6a5.4 5.4 0 0 1-7.6-7.6l8.6-8.6a3.6 3.6 0 0 1 5.1 5.1l-8.6 8.6a1.8 1.8 0 0 1-2.5-2.5l7.9-7.9'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: left 0.75rem center;
  background-size: 1rem 1rem;
  padding-left: 2.25rem;
  border-start-start-radius: var(--bs-border-radius);
  border-end-start-radius: var(--bs-border-radius);
}

/* currentColor cannot reach a background-image, so the glyph ships twice — the
   shape Bootstrap itself uses for .form-select's caret. THE TWO STROKE HEXES ARE
   --bs-body-color, light then dark, written out because a data URI cannot read a
   custom property: move them if §1.1 or §1.3 moves. Stroke geometry at 1.75 to
   match src/Kernel/View/Icon.php, which a pseudo-element cannot call. */
[data-bs-theme='dark'] .form-control[type='file']::file-selector-button {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f1f5f9' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M20.2 11.1l-8.6 8.6a5.4 5.4 0 0 1-7.6-7.6l8.6-8.6a3.6 3.6 0 0 1 5.1 5.1l-8.6 8.6a1.8 1.8 0 0 1-2.5-2.5l7.9-7.9'/%3e%3c/svg%3e");
}

/* ═══ 13. Phase 27 — the swipe, fixes.md item 4 ═════════════════════════════ */

/* The window a track slides inside: without the clip a committed swipe pushes a
   card's width past the viewport edge and the page grows a horizontal scrollbar.
   pan-y and not none keeps vertical panning the browser's, and one rule here
   covers the day cells docs/DECISIONS.md → the-swipe-is-ours-not-a-library names
   — touch-action resolves against ancestors, so a cell inherits its card's. */
[data-sq-swipe] {
  overflow: hidden;
  touch-action: pan-y;
}

/* A mouse drag across the grid would otherwise select every day number it
   crossed and leave the month highlighted blue behind the pointer. */
[data-sq-swipe].sq-swipe--dragging {
  user-select: none;
}

/* ═══ 14. Phase 29 — the map, fixes.md item 9 ═══════════════════════════════ */

/* Every override of Leaflet's own stylesheet below is scoped to .sq-map__canvas,
   and that is not tidiness. leaflet.css is a module stylesheet, so the shell
   renders it AFTER app.css — an equal-specificity rule here would lose the
   cascade to the vendored file every time. One extra class buys the win outright
   and confines it to this screen. */

/* The date strip: 44px of tap target either side of a centred title. The
   calendar's head is the same shape and this is deliberately a second copy —
   CLAUDE.md §5 extracts on the third occurrence, and a third caller should take
   both of these with it. */
.sq-map__head {
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
}

.sq-map__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--bs-link-color);
  text-decoration: none;
  border-radius: var(--bs-border-radius);
}

/* 260px and not a viewport fraction: Leaflet claims one-finger drag inside its
   own box, so a map that filled the screen would trap the page scroll on a
   phone. The card keeps room above and below it to scroll from, and the
   alternative is a gesture-handling plugin this project is not adding. */
.sq-map__canvas {
  height: 260px;
  border-radius: var(--bs-border-radius);
  /* Leaflet's panes are absolutely positioned and would square off the corners. */
  overflow: hidden;
}

/* Leaflet's #ddd, and its Helvetica stack, both belong to a widget that assumed
   it was the page. */
.sq-map__canvas.leaflet-container {
  background-color: var(--bs-tertiary-bg);
  font-family: inherit;
}

/* An OSM raster is a white rectangle, and at night in a dark shell it is the
   brightest thing on the phone. Only the tile pane is dimmed: the markers and
   the lines are in other panes, so every colour this app chose stays exactly as
   docs/DESIGN.md §3 measured it. */
[data-bs-theme="dark"] .sq-map__canvas .leaflet-tile-pane {
  filter: brightness(0.78);
}

/* 26px, or 30px with a touch pointer, against CLAUDE.md §6's 44px floor. Hiding
   the control instead would leave pinch as the only way to zoom, which is no way
   at all from a keyboard.

   THREE classes and not two, and scripts/ui-audit.js is what said so: two ties
   with .leaflet-touch .leaflet-bar a and loses on sheet order, because leaflet
   is a module stylesheet and loads last. The buttons rendered at 30px and the
   rule read as though it had worked. */
.sq-map__canvas.leaflet-container .leaflet-bar a {
  width: 44px;
  height: 44px;
  line-height: 44px;
  background-color: var(--bs-secondary-bg);
  border-bottom-color: var(--bs-border-color);
  color: var(--bs-body-color);
}

.sq-map__canvas.leaflet-container .leaflet-bar a:hover,
.sq-map__canvas.leaflet-container .leaflet-bar a:focus {
  background-color: var(--bs-tertiary-bg);
}

/* The kit's bar and not Leaflet's: its 2px rgba(0,0,0,.2) edge and 4px corners
   read as another app's widget in both modes. The subtle level, docs/DESIGN.md
   §7 — a control, not a sheet. The anchors paint their own corners, so they take
   the inner radius or their squares show through the bar's. */
.sq-map__canvas.leaflet-container .leaflet-bar {
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  box-shadow: var(--bs-box-shadow-sm);
}

.sq-map__canvas.leaflet-container .leaflet-bar a:first-child {
  border-start-start-radius: calc(var(--bs-border-radius) - var(--bs-border-width));
  border-start-end-radius: calc(var(--bs-border-radius) - var(--bs-border-width));
}

.sq-map__canvas.leaflet-container .leaflet-bar a:last-child {
  border-end-start-radius: calc(var(--bs-border-radius) - var(--bs-border-width));
  border-end-end-radius: calc(var(--bs-border-radius) - var(--bs-border-width));
}

/* The credit, which is a licence condition and is drawn under the map rather
   than in its corner. Leaflet's own control is an 84 x 16 anchor in a #333 chip:
   below CLAUDE.md §6's 44px floor, which is non-negotiable, and unthemeable
   without fighting the vendored sheet on every property. A row under the map is
   a 44px target, reads in both modes off our own tokens, and still puts the
   credit where anybody looking at the map sees it. */
.sq-map__credit {
  display: flex;
  align-items: center;
}

/* The floor is measured on the element that receives the tap, so it goes on the
   anchor and not on the row around it — scripts/ui-audit.js audit 1. */
.sq-map__credit a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* Leaflet writes stroke and fill as presentation attributes, which any stylesheet
   rule outranks. currentcolor is what lets a member's line be their own colour in
   both modes without a hex ever reaching the script — the values are per mode,
   docs/DESIGN.md §3, and only the slot name travels. */
.sq-map__line {
  fill: none;
  stroke: currentcolor;
}

/* The circle a coarse reading is drawn as. Faint, because it is the size of the
   doubt and not the size of the person. */
.sq-map__halo {
  fill: currentcolor;
  fill-opacity: 0.12;
  stroke: currentcolor;
  stroke-opacity: 0.5;
}

/* The pin is docs/DESIGN.md §3's dot at map scale: the ring carries the slot
   colour and the ink does not. The opaque body-bg fill is what makes it legible
   over any tile rather than over a lucky one, and the initials are the second
   signal the colour is not allowed to be alone. */
.sq-map__pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 3px solid currentcolor;
  border-radius: 50%;
  background-color: var(--bs-secondary-bg);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1;
}

.sq-map__pin > span {
  color: var(--bs-body-color);
}

/* ===========================================================================
   Notiser — the feed, phase 37
   =========================================================================== */

/* The rail is on the <li> and not on the link inside it: it has to span the
   row's padding as well as its content, or two stacked rows draw two lines with
   a gap between them and the timeline reads as dashes. */
.sq-feed__row {
  position: relative;
}

.sq-feed__row::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 4px;
  width: 2px;
  background-color: var(--bs-secondary-color);
}

/* A whole row is the tap target, which is why the link and not the row carries
   the 44px floor -- docs/UI-LAYOUT.md §7 measures the element that receives the
   tap. Deliberately NOT .sq-wall__event with a different name: there the event
   is a muted aside beside posts, and here the notice is the content. */
.sq-feed__link {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  min-height: 44px;
  padding: 0.5rem 0.5rem 0.5rem 0;
  color: inherit;
  text-decoration: none;
}

.sq-feed__link:hover .sq-feed__body {
  text-decoration: underline;
}

/* The node on the rail. Hollow and in the muted ink, so it reads as part of the
   line rather than as a bulge in it -- the wall's mark, and its measurements:
   10px against the rail's 4px offset is what puts the two on one centre. */
.sq-feed__mark {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  margin-top: 0.5rem;
  border: 2px solid var(--bs-secondary-color);
  border-radius: var(--bs-border-radius-pill);
  background-color: var(--bs-body-bg);
}

/* Bootstrap 5.3 ships no min-width utility, and a long sentence in a flex child
   overflows the row without one. Verified against the vendored stylesheet. */
.sq-feed__text {
  min-width: 0;
}

/* A row and not a line since phase 41: the kind is a pill, and a pill on a text
   baseline sits a pixel off the time beside it. */
.sq-feed__meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
