/* Sunwave controls: the app's one button, on one closed size scale.
   ------------------------------------------------------------------
   Written 2026-08-28. An audit found 9 different min-heights in use across
   public/css (30, 32, 34, 36, 40, 42, 44, 46, 48) and buttons whose height was
   whatever `padding + line-height` happened to add up to. Two controls in the
   same row routinely rendered at different heights.

   Three sizes, nothing between them:

     .sw-btn--sm   36px  dense rows and toolbars ONLY, never a lone action
     .sw-btn       44px  the default (40px on a mouse, see the pointer query)
     .sw-btn--lg   48px  one per screen, the main action

   Height comes from min-height. Padding is horizontal only, so the height is
   the scale's and not the label's. A button, a chip, a select trigger and an
   input on the same row share one size.

   Loaded straight after tokens.css, so it is available on every surface
   (admin, tech, tasks and the customer-facing pages). Every value carries a
   var(--sw-*, #fallback) because the fallback is what renders before the
   stylesheet lands. */

.sw-btn {
  appearance: none;
  text-decoration: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  min-height: 44px;
  padding: 0 16px;
  border: 0;
  border-radius: var(--sw-radius-button, 14px);
  font-family: inherit;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  color: var(--sw-ink, #373B4D);
  background: var(--sw-fill, #E6E4DF);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, opacity 120ms ease;
}

/* Sizes. Only these three exist. */
.sw-btn--sm { min-height: 36px; padding: 0 14px; font-size: 14px; }
.sw-btn--lg { min-height: 48px; padding: 0 20px; font-size: 16px; }

/* A mouse can hit a smaller target than a thumb, so the default relaxes to the
   desktop height. Small and large do not move: small is already at the floor
   and large is a deliberate one-per-screen size. */
@media (hover: hover) and (pointer: fine) {
  .sw-btn { min-height: 40px; font-size: 15px; }
  /* Restated, because the line above is later in the file at the same weight
     and would otherwise drag both of them to 40. A toolbar built out of small
     buttons came out 40 tall beside 36 wide icons, which is where this was
     found. */
  .sw-btn--sm { min-height: 36px; }
  .sw-btn--lg { min-height: 48px; }
}

.sw-btn:hover { background: var(--sw-fill-2, #DCDAD4); text-decoration: none; }
.sw-btn:active { opacity: 0.8; }
.sw-btn:focus-visible {
  outline: none; box-shadow: var(--sw-focus-shadow);
}
.sw-btn:disabled,
.sw-btn[aria-disabled="true"] { opacity: 0.45; cursor: default; }
.sw-btn:disabled:hover { background: var(--sw-fill, #E6E4DF); }

/* Kinds. Exactly one --primary per region: an orange button next to another
   orange button means neither one is the answer. */
.sw-btn--primary {
  background: var(--sw-orange, #F38A3F);
  color: #fff;
  /* White on orange is 2.5:1, the system's one known contrast exception, so
     the primary label carries the extra weight. */
  font-weight: 600;
}
.sw-btn--primary:hover { background: var(--sw-orange-dark, #C66518); }
.sw-btn--primary:disabled:hover { background: var(--sw-orange, #F38A3F); }

.sw-btn--ghost { background: transparent; color: var(--sw-ink-2, #5F6575); }
.sw-btn--ghost:hover { background: var(--sw-fill, #E6E4DF); color: var(--sw-ink, #373B4D); }
.sw-btn--ghost:disabled:hover { background: transparent; }

/* Words are ink, never red: the clay wash on hover carries the meaning. */
.sw-btn--danger { background: transparent; color: var(--sw-ink, #373B4D); }
.sw-btn--danger:hover { background: var(--sw-clay-soft, #FFE9E6); }
.sw-btn--danger:disabled:hover { background: transparent; }

/* Icon-only: square, same scale, and it needs an aria-label to be usable with
   a screen reader on. */
.sw-btn--icon { width: 44px; padding: 0; }
.sw-btn--icon.sw-btn--sm { width: 36px; }
.sw-btn--icon.sw-btn--lg { width: 48px; }
@media (hover: hover) and (pointer: fine) {
  .sw-btn--icon { width: 40px; }
  .sw-btn--icon.sw-btn--sm { width: 36px; }
  .sw-btn--icon.sw-btn--lg { width: 48px; }
}

/* A glyph inside a button is drawn like every other glyph in the product: a
   thin outline at the row's size, in the button's own ink. Stated here, the
   way .sw-rail and .sw-search already do it, so no page has to re-declare
   width, stroke and caps on every icon it puts in a button. */
.sw-btn svg { flex: none; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.sw-btn--sm svg { width: 16px; height: 16px; }

/* Icons are DRAWN, never filled. Every part that can hold one gets the same
   hairline weight and round ends, so a glyph dropped into a chip, a menu row
   or a tag arrives as a line drawing instead of a black blob. An icon that is
   meant to be solid (a pinned star, a play triangle) says so with its own
   fill attribute and this leaves it alone. */
:is(.sw-window, .sw-tag, .sw-menu, .sw-pop, .sw-stepper, .sw-file, .sw-state) svg:not([fill]):not(.sw-btn *) {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  width: 15px;
  height: 15px;
}

/* ── Family fills: the only colored buttons that exist ──────────────────────
   Orange is the app's one action color, so a colored button is NOT a way to
   make something look important. It is what you reach for when the region's
   orange is already spent (an active sun chip, or the primary button belongs
   to something else) AND the action's meaning is that family's meaning:

     --moss   settling money that is now real: Record deposit, Mark paid
     --tide   putting work or money in motion: Send to..., Match, Sync now
     --dusk   proposing: Send estimate, Create automation
     --danger.is-armed  the second tap of a destructive confirm

   A filled family button uses the family's INK shade with white type, never
   the solid: the solids are dots, bars and chart marks, and white on them
   fails contrast. Hover deepens toward --sw-ink, so no family needs a second
   token. Amber has no filled button and never will: it has no ink of its own
   (mustard text is banned), so an amber fill would render as orange and lie
   about being the primary action. */
.sw-btn--moss,
.sw-btn--tide,
.sw-btn--dusk,
.sw-btn--danger.is-armed {
  color: #fff;
  font-weight: 600;
}
.sw-btn--moss { background: var(--sw-moss-ink, #04765E); }
.sw-btn--tide { background: var(--sw-tide-ink, #026A76); }
.sw-btn--dusk { background: var(--sw-dusk-ink, #624DA4); }
.sw-btn--danger.is-armed { background: var(--sw-clay-ink, #993A31); }

.sw-btn--moss:hover { background: color-mix(in oklab, var(--sw-moss-ink, #04765E) 86%, var(--sw-ink, #373B4D)); }
.sw-btn--tide:hover { background: color-mix(in oklab, var(--sw-tide-ink, #026A76) 86%, var(--sw-ink, #373B4D)); }
.sw-btn--dusk:hover { background: color-mix(in oklab, var(--sw-dusk-ink, #624DA4) 86%, var(--sw-ink, #373B4D)); }
.sw-btn--danger.is-armed:hover { background: color-mix(in oklab, var(--sw-clay-ink, #993A31) 86%, var(--sw-ink, #373B4D)); }

/* Disabled must not fall back to the neutral fill and look like a different
   button, so each family holds its own color at .45. */
.sw-btn--moss:disabled:hover { background: var(--sw-moss-ink, #04765E); }
.sw-btn--tide:disabled:hover { background: var(--sw-tide-ink, #026A76); }
.sw-btn--dusk:disabled:hover { background: var(--sw-dusk-ink, #624DA4); }
.sw-btn--danger.is-armed:disabled:hover { background: var(--sw-clay-ink, #993A31); }

/* Two states a button can be IN, as against a kind of button it IS.
   .is-on is latched: a starred project, a toggle left switched on, a
   formatting mark that applies here. It reads as held down and it never takes
   a fill, so a latched button can never be mistaken for the screen's one
   orange action. .is-live is happening right this second (a mic recording):
   the single case where a button that is not the primary may go solid orange,
   because missing it costs you the recording. */
.sw-btn:is(.is-on, .is-active, .is-current) { background: var(--sw-fill-2, #DCDAD4); color: var(--sw-ink, #373B4D); font-weight: 600; }
.sw-btn:is(.is-on, .is-active, .is-current):hover { background: var(--sw-fill-2, #DCDAD4); }
/* .is-picked is one of several options somebody chose (the estimate's Pick
   buttons): the faint sage chosen wash with ink words, so a pick reads as
   chosen and never as the screen's one solid orange action (Levi,
   2026-09-28; sage since 2026-10-01). Words are ink or
   white everywhere; the colour lives only in the wash (Levi, 2026-09-29). */
.sw-btn.is-picked, .sw-btn.is-picked:hover { background: var(--sw-chosen-soft, #EDF4E6); color: var(--sw-ink, #373B4D); font-weight: 600; }
.sw-btn.is-live { position: relative; background: var(--sw-orange, #F38A3F); color: #fff; }
.sw-btn.is-live:hover { background: var(--sw-orange-dark, #C66518); }
/* Nothing pulses (Levi, 2026-10-01): the orange fill alone says it is live. */

/* .is-success is DONE, the one moment after a press lands for real (booking
   filed, ACCEPTANCE 211, Levi's locked tick). The button keeps its width, goes
   settled green, a pale wash sweeps through it and a tick writes itself where
   the words were. The caller puts <svg class="sw-btn-check"> inside it. */
.sw-btn.is-success { position: relative; overflow: hidden; pointer-events: none; background: var(--sw-moss-ink, #04765E); color: #fff; transition: background 0.28s ease; }
.sw-btn.is-success > :not(.sw-btn-check) { opacity: 0; transition: opacity 0.16s ease; }
.sw-btn.is-success::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.22) 50%, transparent 100%);
  transform: translateX(-100%); animation: sw-success-sweep 0.55s ease-out forwards;
}
@keyframes sw-success-sweep { from { transform: translateX(-100%); } to { transform: translateX(120%); } }
.sw-btn-check {
  position: absolute; top: 50%; left: 50%; width: 22px; height: 22px; pointer-events: none;
  transform: translate(-50%, -50%) scale(0.6); opacity: 0;
  transition: opacity 0.18s ease, transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.sw-btn-check path {
  fill: none; stroke: #fff; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 22; stroke-dashoffset: 22;
  transition: stroke-dashoffset 0.32s cubic-bezier(0.55, 0.06, 0.4, 0.95) 0.22s;
}
.sw-btn.is-success .sw-btn-check { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.sw-btn.is-success .sw-btn-check path { stroke-dashoffset: 0; }

/* A hidden button is gone. .sw-btn sets a display of its own, which beats the
   browser's [hidden] rule, so a button a screen hides stayed on screen. */
.sw-btn[hidden] { display: none; }

/* Capsule is earned, not decorative: chips, toggles, avatars, and the one hero
   action at the bottom of a sheet or a phone page. */
.sw-btn--pill { border-radius: var(--sw-radius-pill, 999px); }

/* Full width is allowed on a phone for the one main action, and nowhere else.
   Zero specificity so a caller's own width always wins. */
.sw-btn--block { width: 100%; }

/* A button whose label is somebody else's words: a search they typed, the name
   of the next result, a customer. The words are cut with an ellipsis rather
   than pushing the rest of the row out through the edge. A button only needs
   this when what it says is not written by us, so it is asked for, never given.
   The caller still says how wide the button may get; this only decides what
   happens when the words do not fit. */
.sw-btn--tight { min-width: 0; }
.sw-btn--tight > span { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .sw-btn { transition: background 120ms ease, color 120ms ease; }
}

/* One action joined to its chooser (Add, and which microphone; Add, and for
   whom). It is ONE control, so it is one rounded rect with a seam down it,
   never two buttons parked next to each other. Focus sinks the half you are
   on, so nothing spills across the seam. */
.sw-split { display: inline-flex; align-items: stretch; border-radius: var(--sw-radius-button, 14px); overflow: hidden; }
.sw-split > .sw-btn { border-radius: 0; }
.sw-split > .sw-btn + .sw-btn { box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.35); }
.sw-split > .sw-btn[aria-expanded="true"] svg { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  .sw-btn.is-success::before { animation: none; }
}

/* ═══ The state tag (.sw-state) ═══════════════════════════════════════════
   Locked 2026-09-19 by Levi, after eight rounds of options: a state is a
   capsule washed in its family at 40% with the ordinary ink words on top, one
   notch bolder. The color is the background, never the letters, which is what
   stopped it reading like every other AI-built dashboard.

   One size (28px, the dense-row size), one weight, six meanings, and that is
   the whole vocabulary:

     .sw-state--sun    waiting on somebody: needs approval, unsigned
     .sw-state--amber  open, queued, scheduled
     .sw-state--clay   wrong or late: overdue, failed, declined
     .sw-state--moss   settled: paid, approved, done
     .sw-state--tide   in motion: on the way, syncing, matched
     .sw-state--dusk   proposed: estimate sent, automation planned

   Never hand-roll a colored pill; use this. Never put family ink on the words,
   and never wash two families on one tag. */
.sw-state {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  /* Levi, 2026-09-19: a tag is a footnote beside a name, not a second name.
     Small enough that the name still wins the row. */
  gap: 5px;
  min-height: 19px;
  padding: 0 7px;
  border: 0;
  border-radius: var(--sw-radius-pill, 999px);
  background: var(--sw-fill, #E6E4DF);
  color: var(--sw-ink, #373B4D);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -.005em;
  white-space: nowrap;
}
.sw-state--sun   { background: var(--sw-sun-wash, #F8CDAD); }
.sw-state--amber { background: var(--sw-amber-wash, #F6DFA9); }
.sw-state--clay  { background: var(--sw-clay-wash, #E8C1B5); }
.sw-state--moss  { background: var(--sw-moss-wash, #AEE4D3); }
.sw-state--tide  { background: var(--sw-tide-wash, #98D8DA); }
.sw-state--dusk  { background: var(--sw-dusk-wash, #D0C8F0); }

/* A state is a label, not a control. When one IS pressable (a filter chip),
   it answers like everything else. */
button.sw-state, a.sw-state { cursor: pointer; transition: opacity 120ms ease, transform 120ms ease; }
button.sw-state:active, a.sw-state:active { opacity: .8; transform: scale(.98); }
button.sw-state:focus-visible, a.sw-state:focus-visible {
  outline: none; box-shadow: var(--sw-focus-shadow);
}
@media (prefers-reduced-motion: reduce) {
  button.sw-state, a.sw-state { transition: none; }
}

/* ═══ The dot (.sw-dot) and the identity tag (.sw-tag) ═══════════════════════
   A dot is the smallest thing that can carry a colour: WHICH tag, WHICH
   project, WHICH status. It is not a count (that is .sw-count) and not a state
   you can read (that is .sw-state). The colour arrives as --sw-dot or from a
   family class, and the words beside it stay ink, always.
   A tag is that dot plus a name: no box, no fill, no pill. It says which one,
   where a state tag says what is happening to it. */
.sw-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--sw-dot, var(--sw-muted, #7B8190)); }
.sw-dot--sun   { background: var(--sw-sun, #F38A3F); }
.sw-dot--amber { background: var(--sw-amber, #EDB635); }
.sw-dot--clay  { background: var(--sw-clay, #CA6C53); }
.sw-dot--moss  { background: var(--sw-moss, #3AC39E); }
.sw-dot--tide  { background: var(--sw-tide, #03A6B0); }
.sw-dot--dusk  { background: var(--sw-dusk, #8F7EE6); }
/* Nothing chosen yet: the same disc, drawn hollow. */
.sw-dot--hollow { background: none; box-shadow: inset 0 0 0 1.5px var(--sw-border, #E5DED3); }
/* The unread mark that rides the corner of a bell or a tab. */
.sw-dot--badge { position: absolute; top: 7px; right: 7px; background: var(--sw-orange, #F38A3F); box-shadow: 0 0 0 2px var(--sw-card-lift, #FFFDF9); }
.sw-btn:has(.sw-dot--badge), .sw-rail a:has(.sw-dot--badge), .sw-rail button:has(.sw-dot--badge) { position: relative; }

.sw-tag {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%;
  appearance: none; border: 0; background: none; padding: 0;
  font: inherit; font-size: 13px; color: var(--sw-ink-2, #5F6575);
}
.sw-tag > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
button.sw-tag, a.sw-tag, .sw-tag[role="button"] { cursor: pointer; }
.sw-tag:is(.is-on, .is-active, .is-current) { color: var(--sw-ink, #373B4D); font-weight: 600; }
.sw-tag:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
/* The same tag inside a quiet pill, for the line under a name where several
   of them sit together and need edges to tell them apart (Levi, 2026-09-19). */
.sw-tag--pill { padding: 3px 10px; border-radius: var(--sw-radius-pill, 999px); background: var(--sw-tag-fill, rgba(186, 166, 138, 0.16)); font-size: 12px; font-weight: 500; }
/* When a date IS the state: ink words on the softest orange (Levi, 2026-09-19,
   the Tasks due pill). The words stay black, as everywhere else. */
.sw-state--due { background: var(--sw-orange-soft, #FFEBDE); }

/* ═══ The card (.sw-card) ════════════════════════════════════════════════
   Locked 2026-09-19: a card is the warm card tone, the 14 corner, and a soft
   lift off the page. No hairline, because the shadow already separates it, and
   a line plus a shadow is two answers to one question. The lift is deliberately
   wide and faint so a list of ten cards still reads as a list, not a pile. */
.sw-card {
  background: var(--sw-card, #FCFAF6);
  border: 0;
  border-radius: var(--sw-radius-card, 14px);
  box-shadow: var(--sw-elev-card, 0 2px 6px rgba(55, 59, 77, 0.08), 0 12px 32px rgba(55, 59, 77, 0.06));
  padding: var(--sw-space-md, 16px);
}
.sw-card--flat { box-shadow: none; border: 1px solid var(--sw-border, #E5DED3); }
.sw-card--lift { background: var(--sw-card-lift, #FFFDF9); }
/* Edge to edge: a month grid, a list or a PDF that must run to the corners of
   the thing holding it. A page may not zero a Kit part's padding itself. */
.sw-card.sw-flush, .sw-dialog.sw-flush, .sw-well.sw-flush { padding: 0; }

/* ═══ The note (.sw-note) ════════════════════════════════════════════════
   The block that says one important thing: a deposit due, a decline, a
   confirmation. Locked 2026-09-19: it is washed exactly like the state tag it
   belongs to, so a note and a tag about the same thing are the same color, and
   the words stay ink. One note per screen; two is a screen with no hierarchy.
   Families carry the same six meanings as .sw-state. */
.sw-note {
  border-radius: var(--sw-radius-card, 14px);
  padding: var(--sw-space-sm, 12px) var(--sw-space-md, 16px);
  background: var(--sw-fill, #E6E4DF);
  color: var(--sw-ink, #373B4D);
  font-size: 14px;
  line-height: 1.45;
}
.sw-note > b, .sw-note > strong { display: block; font-weight: 600; margin-bottom: 2px; }
.sw-note--sun   { background: var(--sw-sun-wash, #F8CDAD); }
.sw-note--amber { background: var(--sw-amber-wash, #F6DFA9); }
.sw-note--clay  { background: var(--sw-clay-wash, #E8C1B5); }
.sw-note--moss  { background: var(--sw-moss-wash, #AEE4D3); }
.sw-note--tide  { background: var(--sw-tide-wash, #98D8DA); }
.sw-note--dusk  { background: var(--sw-dusk-wash, #D0C8F0); }

/* The quiet line under a field, a card or an action that explains it. Small
   print: muted, never washed, never centred, never a heading. .sw-note is the
   one washed thing on a screen; this is the opposite of that. */
.sw-hint { margin: 0; font-size: 13px; line-height: 1.5; color: var(--sw-muted, #7B8190); }
/* The words a search matched. */
.sw-mark { padding: 0 1px; border-radius: 3px; background: var(--sw-sun-wash, #F8CDAD); color: inherit; }

/* ═══ The switch (.sw-switch) ════════════════════════════════════════════
   Locked 2026-09-19: on is GREEN, not orange. On means a setting is live,
   which is the moss family's meaning, and it leaves the screen's one orange
   free for the action the person came to take. Flipping it saves immediately,
   so nothing under it is ever a Save button.

   Markup: <button class="sw-switch" role="switch" aria-checked="true"></button>
   Add .is-on (or aria-checked="true") for the on state. */
.sw-switch {
  appearance: none;
  position: relative;
  flex: none;
  width: 50px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: var(--sw-radius-pill, 999px);
  background: var(--sw-fill-2, #DCDAD4);
  cursor: pointer;
  transition: background 120ms cubic-bezier(0.22, 1, 0.36, 1);
}
.sw-switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(55, 59, 77, 0.2);
  transition: transform 120ms cubic-bezier(0.22, 1, 0.36, 1);
}
.sw-switch:is(.is-on, .is-active, .is-current), .sw-switch[aria-checked="true"], .sw-switch[aria-pressed="true"],
[aria-pressed="true"] > .sw-switch, [aria-checked="true"] > .sw-switch { background: var(--sw-moss-ink, #04765E); }
.sw-switch:is(.is-on, .is-active, .is-current)::after, .sw-switch[aria-checked="true"]::after, .sw-switch[aria-pressed="true"]::after,
[aria-pressed="true"] > .sw-switch::after, [aria-checked="true"] > .sw-switch::after { transform: translateX(20px); }
.sw-switch:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
.sw-switch:disabled { opacity: .45; cursor: default; }
@media (prefers-reduced-motion: reduce) {
  .sw-switch, .sw-switch::after { transition: none; }
}

/* ═══ Days of the week (.sw-days / .sw-day) ══════════════════════════════
   Picked 2026-09-29 (Availability, version 1). The days a shop takes bookings
   are seven small switches in a row, so ON carries the switch's meaning: the
   moss wash behind ink words, the state tag's recipe, never orange. Off is
   bare ground in the second ink. The weight never changes, so the row never
   shifts as days flip.

   Markup: <div class="sw-days" role="group" aria-label="Open days">
             <button class="sw-day is-on" aria-pressed="true">Mon</button> ...
           </div> */
.sw-days { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.sw-day {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: 48px;
  min-height: 36px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--sw-radius-pill, 999px);
  background: transparent;
  color: var(--sw-ink-2, #5F6575);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  transition: background-color 120ms cubic-bezier(0.22, 1, 0.36, 1), color 120ms cubic-bezier(0.22, 1, 0.36, 1), opacity 120ms ease;
}
.sw-day:hover { background: var(--sw-fill, #E6E4DF); color: var(--sw-ink, #373B4D); }
.sw-day:active { opacity: .8; }
.sw-day:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
.sw-day:disabled { opacity: .45; cursor: default; }
.sw-day:is(.is-on, .is-active, .is-current), .sw-day[aria-pressed="true"] {
  background: var(--sw-moss-wash, #AEE4D3);
  color: var(--sw-ink, #373B4D);
}
@media (pointer: coarse), (max-width: 600px) { .sw-day { min-height: 40px; } }
@media (prefers-reduced-motion: reduce) { .sw-day { transition: none; } }

/* ═══ Something somebody added (.sw-token) ═══════════════════════════════
   Picked 2026-09-29 (Availability, version 1). A value a person typed or
   picked into a list (a start time, a closed date) is a quiet pill with ink
   words and a small cross that takes it back off. The cross is a real button
   and it says what it removes.

   Markup: <span class="sw-token"><span>8:00 AM</span>
             <button class="sw-token-x" aria-label="Remove 8:00 AM"><svg/></button>
           </span> */
.sw-token {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  box-sizing: border-box;
  min-height: 32px;
  padding: 0 4px 0 12px;
  border-radius: var(--sw-radius-pill, 999px);
  background: var(--sw-tag-fill, rgba(186, 166, 138, 0.16));
  color: var(--sw-ink, #373B4D);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sw-token-x {
  appearance: none;
  -webkit-appearance: none;
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--sw-ink-2, #5F6575);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, opacity 120ms ease;
}
.sw-token-x svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.sw-token-x:hover { background: var(--sw-soft-rule, rgba(186, 166, 138, 0.34)); color: var(--sw-ink, #373B4D); }
.sw-token-x:active { opacity: .8; }
.sw-token-x:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
@media (pointer: coarse), (max-width: 600px) {
  .sw-token { min-height: 36px; }
  .sw-token-x { width: 28px; height: 28px; }
}
@media (prefers-reduced-motion: reduce) { .sw-token-x { transition: none; } }

/* ═══ Tabs and filters (.sw-tabs / .sw-filters) ══════════════════════════
   Locked 2026-09-19. Two different jobs, two different looks, so nobody has to
   guess which one they are touching:

     .sw-tabs     change what the page is ABOUT (Estimates / Invoices), an
                  underline under the current one
     .sw-filters  narrow the SAME list (Today / This week), a pill track with
                  the chosen one solid orange

   Never use a pill track for sections, and never underline a filter. */
.sw-tabs {
  display: flex;
  gap: var(--sw-space-lg, 20px);
  border-bottom: 1px solid var(--sw-border, #E5DED3);
}
.sw-tabs > * {
  position: relative;
  appearance: none;
  border: 0;
  background: 0;
  padding: 8px 0 10px;
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  color: var(--sw-muted, #7B8190);
  cursor: pointer;
}
.sw-tabs > :is(.is-on, .is-active, .is-current) { color: var(--sw-ink, #373B4D); font-weight: 600; }
.sw-tabs > :is(.is-on, .is-active, .is-current)::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  border-radius: 2px;
  background: var(--sw-orange, #F38A3F);
}
.sw-tabs > *:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }

.sw-filters {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border-radius: var(--sw-radius-pill, 999px);
  background: var(--sw-fill, #E6E4DF);
}
.sw-filters > * {
  appearance: none;
  border: 0;
  background: 0;
  padding: 7px 14px;
  border-radius: var(--sw-radius-pill, 999px);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--sw-ink-2, #5F6575);
  cursor: pointer;
}
.sw-filters > :is(.is-on, .is-active, .is-current), .sw-filters > .is-active { background: var(--sw-orange, #F38A3F); color: #fff; font-weight: 600; }
/* Six options will not fit one line inside a 240px dropdown. */
.sw-filters--wrap { flex-wrap: wrap; }
.sw-filters > *:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }

/* ═══ Counts (.sw-count) ═════════════════════════════════════════════════
   The unread number on a tab, a rail item, the app icon. Locked 2026-09-19:
   ALWAYS orange, everywhere, so a number never has to be decoded. A count of
   zero is not drawn at all. */
.sw-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--sw-radius-pill, 999px);
  background: var(--sw-orange, #F38A3F);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  font-style: normal;
  font-variant-numeric: tabular-nums;
}

/* ═══ People (.sw-avatar) ════════════════════════════════════════════════
   Initials in a circle, tinted by WHO the person is. The tint is identity
   color: it comes from a stable hash of the name or id, never from a status,
   so the same person is the same color on every screen and a long list has
   rhythm instead of a column of grey coins.

   Six tints, the six family washes cycled in this order: tide, dusk, moss,
   amber, clay, sun. The words stay ink on every one. A person with no id falls
   back to the plain grey circle.

   Status never lands on the circle. If a person is late or unpaid, that is a
   state tag next to their name, not a red avatar.
   Sizes: 26 dense rows, 36 default, 44 a page head. */
.sw-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--sw-fill, #E6E4DF);
  color: var(--sw-ink, #373B4D);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .01em;
  text-transform: uppercase;
}
.sw-avatar--sm { width: 26px; height: 26px; font-size: 11px; }
.sw-avatar--lg { width: 44px; height: 44px; font-size: 15px; }
.sw-avatar--tide  { background: var(--sw-tide-wash, #98D8DA); }
.sw-avatar--dusk  { background: var(--sw-dusk-wash, #D0C8F0); }
.sw-avatar--moss  { background: var(--sw-moss-wash, #AEE4D3); }
.sw-avatar--amber { background: var(--sw-amber-wash, #F6DFA9); }
.sw-avatar--clay  { background: var(--sw-clay-wash, #E8C1B5); }
.sw-avatar--sun   { background: var(--sw-sun-wash, #F8CDAD); }
/* A person who already has a steady colour elsewhere in the product (their
   colour on the schedule) brings it with them, as --sw-avatar-tint. */
.sw-avatar[style*="--sw-avatar-tint"] {
  background: color-mix(in srgb, var(--sw-avatar-tint) 16%, transparent);
  color: var(--sw-ink, #373B4D);
}

/* Several people on one thing: the faces overlap, each ringed in the page so
   the overlap reads, and the last one carries the count of the rest. The ring
   and the overlap are the stack's business, never the face's. */
.sw-avatars { display: inline-flex; align-items: center; }
.sw-avatars > .sw-avatar { margin-left: -8px; box-shadow: 0 0 0 2px var(--sw-canvas, #F8F5EF); }
.sw-avatars > .sw-avatar:first-child { margin-left: 0; }
.sw-avatars > .sw-avatar--more { background: var(--sw-fill, #E6E4DF); color: var(--sw-ink-2, #5F6575); }

/* ═══ Saved (.sw-saved) ══════════════════════════════════════════════════
   Nothing has a Save button, so a row says it kept the change itself. Locked
   2026-09-19: the word sits at the END OF THE ROW you just changed, not in a
   pill over the page, so you can see exactly which setting saved. It fades in,
   holds, and fades out; the row never moves, because the space is always
   reserved. */
.sw-saved {
  margin-left: auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--sw-ink-2, #5F6575);
  opacity: 0;
  transition: opacity 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.sw-saved:is(.is-on, .is-active, .is-current) { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .sw-saved { transition: none; } }

/* ═══ Menu (.sw-menu) ════════════════════════════════════════════════════
   .sw-pop is the panel itself and nothing else: the card, the corner, the
   float shadow, the padding and the house scrollbar. What goes inside is the
   caller's business, so a panel holding a search box, a month grid or a grid
   of colours is still one part.
   .sw-menu IS a .sw-pop whose children are rows: the list that opens off a
   three-dot button, rows big enough for a work glove, anything destructive
   alone under a divider. The row look lands only on things that really are
   rows, so a heading or a hint inside a menu is not silently turned into a
   fake clickable line. */
.sw-pop, .sw-menu {
  min-width: 220px;
  padding: 6px;
  border-radius: var(--sw-radius-card, 14px);
  background: var(--sw-card, #FCFAF6);
  box-shadow: var(--sw-elev-float, 0 12px 32px rgba(55, 59, 77, 0.16), 0 2px 6px rgba(55, 59, 77, 0.08));
  scrollbar-width: thin;
  scrollbar-color: var(--sw-border, #E5DED3) transparent;
}
/* Rows sit 4px apart, the same breath the left menu keeps (picked 2026-09-22):
   a row lifting under the pointer throws its light 7px, and with the rows
   touching that light ran straight into the sunk row beside it. */
.sw-menu { display: flex; flex-direction: column; gap: 4px; }
/* A menu's rows set inside a panel (a status list above a note field): the
   rows keep their look, the panel is the one floating card. */
.sw-menu.sw-flush { min-width: 0; padding: 0; border-radius: 0; background: none; box-shadow: none; }
.sw-pop ::-webkit-scrollbar, .sw-menu ::-webkit-scrollbar { width: 8px; height: 8px; }
.sw-pop ::-webkit-scrollbar-thumb, .sw-menu ::-webkit-scrollbar-thumb { border-radius: 4px; background: var(--sw-border, #E5DED3); }
.sw-pop ::-webkit-scrollbar-track, .sw-menu ::-webkit-scrollbar-track { background: transparent; }
/* A switch set in a menu (a section's rules) is a switch, not a row, so the
   row look skips it. :where keeps the selector's weight what it was. */
.sw-menu :is(a, button, [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"]):not(:where(.sw-switch)) {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  appearance: none;
  border: 0;
  background: 0;
  padding: 11px 12px;
  border-radius: 10px;
  font: inherit;
  font-size: 15px;
  color: var(--sw-ink, #373B4D);
  text-align: left;
  cursor: pointer;
}
.sw-menu :is(a, button, [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"]):not(:where(.sw-switch)):hover { background: var(--sw-fill, #E6E4DF); }
.sw-menu .is-danger { color: var(--sw-ink, #373B4D); }
.sw-pop hr, .sw-menu hr { border: 0; border-top: 1px solid var(--sw-border, #E5DED3); margin: 6px 8px; }
/* The one that is chosen. Eight menus in the product mark their current pick,
   and without this a person cannot see which microphone, sort or status they
   are already on. The tick leads the row, so the words stay lined up whether
   or not anything is chosen. */
.sw-menu :is(.is-on, .is-active, .is-current):not(:where(.sw-switch)) { background: var(--sw-fill, #E6E4DF); font-weight: 600; }
.sw-menu-tick { flex: none; width: 16px; height: 16px; opacity: 0; }
.sw-menu :is(.is-on, .is-active, .is-current) .sw-menu-tick { opacity: 1; }
/* A heading or a note inside a panel is not a row. */
.sw-menu > .sw-label, .sw-menu > .sw-heading, .sw-menu > .sw-hint, .sw-menu > .sw-kicker { padding: 6px 12px 2px; }

/* ═══ Search (.sw-search) ════════════════════════════════════════════════
   A field like any other field, with the glass on the left, so it lines up
   with the buttons beside it. Never a grey capsule. */
.sw-search {
  display: flex;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--sw-border, #E5DED3);
  border-radius: var(--sw-radius-input, 14px);
  background: var(--sw-card-lift, #FFFDF9);
  color: var(--sw-ink, #373B4D);
}
.sw-search > input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: 0;
  font: inherit;
  font-size: 16px;
  color: inherit;
}
.sw-search > input:focus { outline: 0; }
.sw-search > input::placeholder { color: var(--sw-muted, #7B8190); }
.sw-search > svg { flex: none; width: 18px; height: 18px; color: var(--sw-muted, #7B8190); }
.sw-search:focus-within { border-color: transparent; box-shadow: var(--sw-focus-shadow); }

/* ═══ Progress (.sw-bar / .sw-steps) ═════════════════════════════════════
   A goal is one thin bar with its number above it, never inside. A form or a
   job in stages is a row of equal segments. Waiting for a first read draws
   nothing at all: no shape, no spinner, no shimmer. */
.sw-bar { width: 100%; height: 8px; border-radius: 4px; background: var(--sw-fill, #E6E4DF); overflow: hidden; }
.sw-bar > * { display: block; height: 100%; border-radius: 4px; background: var(--sw-orange, #F38A3F); transition: width 200ms cubic-bezier(0.22, 1, 0.36, 1); }
.sw-steps { display: flex; gap: 4px; width: 100%; }
.sw-steps > * { flex: 1; height: 4px; border-radius: 2px; background: var(--sw-fill, #E6E4DF); }
.sw-steps > :is(.is-on, .is-active, .is-current) { background: var(--sw-orange, #F38A3F); }
/* Waiting draws NOTHING (Levi, 2026-09-22, LOCKED). There is no skeleton part
   any more: a page with no answer yet is simply flat, everywhere, including
   the inbox while messages come in. `.sw-skeleton` survives only so that any
   markup still carrying the class stays invisible instead of turning back into
   a bar. Put aria-busy on the region and say nothing else. */
.sw-skeleton { display: none; }

/* Live sound: bars that move with the microphone. The part owns the colour,
   the widths and the resting height; only the per-sample transform is written
   by the page, because only the page knows the level. */
.sw-meter { display: flex; align-items: center; gap: 2px; width: 100%; height: 22px; }
.sw-meter > * { flex: 1 1 auto; min-width: 2px; height: 100%; border-radius: 2px; background: var(--sw-orange, #F38A3F); transform: scaleY(0.16); transform-origin: center; transition: transform 90ms linear; }
/* Scrubbing through a recording. .sw-bar is progress you cannot touch; this
   is the one you can drag. */
.sw-range { appearance: none; width: 100%; height: 18px; background: none; cursor: pointer; }
.sw-range::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: var(--sw-fill, #E6E4DF); }
.sw-range::-webkit-slider-thumb { appearance: none; margin-top: -5px; width: 14px; height: 14px; border: 0; border-radius: 50%; background: var(--sw-orange, #F38A3F); }
.sw-range::-moz-range-track { height: 4px; border-radius: 2px; background: var(--sw-fill, #E6E4DF); }
.sw-range::-moz-range-thumb { width: 14px; height: 14px; border: 0; border-radius: 50%; background: var(--sw-orange, #F38A3F); }
.sw-range:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
/* Still working, and nobody knows for how long. A skeleton is a shape waiting
   to be filled; this says the job is running. */
.sw-working { display: inline-flex; align-items: center; gap: 4px; }
.sw-working > * { width: 5px; height: 5px; border-radius: 50%; background: var(--sw-muted, #7B8190); animation: sw-working 1.2s ease-in-out infinite; }
.sw-working > *:nth-child(2) { animation-delay: 0.15s; }
.sw-working > *:nth-child(3) { animation-delay: 0.3s; }
@keyframes sw-working { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .sw-working > * { animation: none; opacity: 0.6; }
  .sw-meter > * { transition: none; }
}

/* ═══ Big numbers (.sw-stat) ═════════════════════════════════════════════
   Size makes a number the hero, never color. Even-width digits so a column of
   them lines up without anyone eyeballing it. */
.sw-stat { display: flex; flex-direction: column; gap: 4px; }
.sw-stat > .sw-stat-n { font-size: 28px; font-weight: 600; letter-spacing: -.015em; font-variant-numeric: tabular-nums; }
.sw-stat > .sw-stat-l { font-size: 13px; color: var(--sw-ink-2, #5F6575); }

/* ═══ Glass (.sw-glass) ══════════════════════════════════════════════════
   The look Levi keeps pointing at: the corner location pill, the bell, and the
   floating dock. It is four things at once, and it only works with all four:

     1. a warm translucent gradient, lighter at the top
     2. a heavy blur WITH saturation, which is why the page underneath glows
        through warm instead of going grey
     3. a warm hairline
     4. a soft lift plus a white inner highlight along the top edge, the lit rim

   Where glass is allowed: chrome that FLOATS over scrolling content, so the
   page moving underneath is what you are seeing through it. The corner pill,
   the bell, the dock, a sticky toolbar, a floating action bar, a search bar
   pinned over a list.

   Where it is not: content. A card, a row, a table, a sheet's body and any
   surface that simply sits on the page use the flat warm card, because glass
   over nothing is just a muddy grey box, and glass on everything makes the
   whole screen look smeared. One or two pieces of glass per screen.

   Reduced transparency (the phone's setting, and any browser without backdrop
   support) falls back to the opaque warm card, which is what the native app
   already does through SWFrostedStyle. */
.sw-glass {
  border: 1px solid var(--sw-glass-edge, rgba(229, 222, 211, 0.85));
  background: var(--sw-glass-bg, linear-gradient(180deg, rgba(255, 254, 250, 0.62), rgba(252, 249, 243, 0.42)));
  box-shadow: var(--sw-glass-shadow, 0 10px 26px rgba(55, 59, 77, 0.12), 0 2px 8px rgba(55, 59, 77, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.72));
  backdrop-filter: var(--sw-glass-blur, blur(22px) saturate(190%));
  -webkit-backdrop-filter: var(--sw-glass-blur, blur(22px) saturate(190%));
}
/* A big floating bar (the dock) carries the deeper lift. */
.sw-glass--bar {
  box-shadow: var(--sw-glass-shadow-lg, 0 26px 64px rgba(55, 59, 77, 0.2), 0 6px 18px rgba(55, 59, 77, 0.07), inset 0 1px 0 rgba(255, 255, 255, 0.76));
}
/* Glass is a nicety, never a requirement: when the browser cannot blur, or the
   person asked for less transparency, it becomes the plain warm card so the
   text on it never floats over live pixels. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .sw-glass { background: var(--sw-card, #FCFAF6); }
}
@media (prefers-reduced-transparency: reduce) {
  .sw-glass { background: var(--sw-card, #FCFAF6); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ═══ Tier tags (.sw-tier) ═══════════════════════════════════════════════
   A customer's standing: Bronze, Silver, Gold, Platinum, Diamond. This is the
   one tag that is NOT one of the six meanings, because it says WHICH, not what
   state something is in, and the colors are the metals themselves. Identity
   color, like a technician's lane on the schedule.

   Same shape as a state tag but a step quieter (12px in a 26px capsule), so a
   tier never out-shouts the thing a person has to act on. Each ink clears
   4.5:1 on its own wash. */
.sw-tier {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  min-height: 26px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--sw-radius-pill, 999px);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
}
.sw-tier--bronze   { background: #F7E2D2; color: var(--sw-ink, #373B4D); }
.sw-tier--silver   { background: #ECEDF1; color: var(--sw-ink, #373B4D); }
.sw-tier--gold     { background: var(--sw-amber-soft, #FFEED1); color: var(--sw-ink, #373B4D); }
.sw-tier--platinum { background: #E8EDF6; color: var(--sw-ink, #373B4D); }
.sw-tier--diamond  { background: #DEF3FB; color: var(--sw-ink, #373B4D); }
/* On a profile the tag is a button that opens the tier popover. */
button.sw-tier { appearance: none; cursor: pointer; }
button.sw-tier:hover { filter: brightness(0.97); }
button.sw-tier:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }

/* ═══ Layers: the two ways something opens (locked 2026-09-19) ════════════
   There is no side drawer any more. A record opens one of two ways, and which
   one is decided by how much WORK is inside it, not by how big the record is:

     .sw-dialog   a card in the middle of the screen, page dimmed behind it.
                  For one decision or a short form: confirm something, pick a
                  window, edit a couple of fields, collect a payment.
                  Rule of thumb: it fits without scrolling, and you leave it
                  in under a minute.

     .sw-page     a full page with a back link at the top left. For a record
                  you WORK in: an estimate with line items, an invoice, a
                  customer profile, a job. It gets the whole screen, and the
                  back link returns you to the list where you were.

   A dialog never contains a second dialog; if you need a second step, the
   first one becomes a page. */
.sw-dialog {
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  overflow: auto;
  padding: var(--sw-space-lg, 20px);
  border-radius: var(--sw-radius-sheet, 20px);
  background: var(--sw-card, #FCFAF6);
  box-shadow: var(--sw-elev-float, 0 12px 32px rgba(55, 59, 77, 0.16), 0 2px 6px rgba(55, 59, 77, 0.08));
}
.sw-dialog-title { margin: 0 0 4px; font-size: 17px; font-weight: 600; }
.sw-dialog-sub { margin: 0 0 16px; font-size: 14px; color: var(--sw-ink-2, #5F6575); }
/* Actions sit bottom right, the safe one nearest the hand. */
.sw-dialog-acts { display: flex; gap: 8px; justify-content: flex-end; }
/* The dim behind any layer: light, with the page blurred. */
.sw-dim {
  position: fixed;
  inset: 0;
  background: var(--sw-scrim, rgba(55, 59, 77, 0.15));
  backdrop-filter: blur(var(--sw-scrim-blur, 5px));
  -webkit-backdrop-filter: blur(var(--sw-scrim-blur, 5px));
}
/* Where a dialog sits when its page did not say. The card is portaled to the
   end of <body> with no position, so unplaced it was a static block UNDER the
   dim: the page blurred, the card never showed, the screen looked frozen
   (2026-09-28). Dialog.tsx adds these two only when the caller passes no
   class of its own. Centered by inset and auto margins, never a transform, so
   a fixed child inside still measures against the screen. Above every page
   layer (the highest is 12000), under the toasts and the address menus. */
.sw-dim--center { z-index: 20000; }
.sw-dialog--center {
  position: fixed;
  inset: 0;
  z-index: 20001;
  height: fit-content;
  margin: auto;
}
/* Room between the parts of a card nobody placed (Levi, 2026-10-01). The card
   carried padding and nothing else, so whatever a screen put inside it sat
   edge to edge: a header on a step bar on a question, a field on the buttons
   under it. Every part now stands 12px off the one above, and the buttons 16px
   off whatever they act on. The title keeps its own tight 4px to the line
   under it. A card a page placed with its own class spaces itself, as before. */
.sw-dialog--center > * + * { margin-top: 12px; }
.sw-dialog--center > .sw-dialog-title + .sw-dialog-sub { margin-top: 0; }
/* The buttons stand off whatever they act on in EVERY card, placed or not: a
   page that places its own card still got them flush under its last field.
   The same for a title with no line under it: the first field sat on it. */
.sw-dialog > * + .sw-dialog-acts { margin-top: 16px; }
.sw-dialog > .sw-dialog-title + :not(.sw-dialog-sub):not(.sw-dialog-acts) { margin-top: 12px; }
/* Room on a computer (Levi, 2026-10-07, feedback 397: "it feels so cramped
   inside of it ... take full advantage of the full-size desktop screen"). A
   card nobody placed gets deeper padding and more air between its parts once
   the window is a computer's, and a card that holds a FORM (fields, a picker,
   a calendar) asks for .sw-dialog--wide. A one-line confirm stays the narrow
   card: wide, its sentence would run the width of the screen. A phone keeps
   the tight card, where every pixel of width is already spent. */
.sw-dialog--wide { width: min(760px, calc(100vw - 32px)); }
@media (min-width: 900px) {
  .sw-dialog--center { padding: var(--sw-space-xxl, 32px); }
  .sw-dialog--center > * + * { margin-top: var(--sw-space-md, 16px); }
  .sw-dialog--center > .sw-dialog-title + .sw-dialog-sub { margin-top: 0; }
  .sw-dialog--center > * + .sw-dialog-acts { margin-top: var(--sw-space-xl, 24px); }
}
/* The back link at the top of a full page. Always the first thing on it. */
.sw-back {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  appearance: none;
  border: 0;
  background: 0;
  padding: 6px 0;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--sw-ink-2, #5F6575);
  cursor: pointer;
}
.sw-back:hover { color: var(--sw-ink, #373B4D); }

/* ═══ Line items (.sw-items) ═════════════════════════════════════════════
   The rows an estimate or an invoice is built from: what it is, one line of
   plain English under it, the price on the right. A discount is a negative
   price on its own row. */
.sw-items { width: 100%; border-radius: var(--sw-radius-card, 14px); background: var(--sw-card-lift, #FFFDF9); overflow: hidden; }
.sw-item { display: flex; gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--sw-border, #E5DED3); }
.sw-item:last-child { border-bottom: 0; }
/* A row a screen hides (a discount line that is not there yet) is gone, and the
   row above it becomes the last one. Same reason as .sw-btn[hidden]. */
.sw-item[hidden] { display: none; }
.sw-item:has(+ .sw-item[hidden]:last-child) { border-bottom: 0; }
.sw-item-name { font-weight: 500; }
.sw-item-note { margin-top: 2px; font-size: 13px; color: var(--sw-muted, #7B8190); }
.sw-item-price { margin-left: auto; align-self: center; font-weight: 600; font-variant-numeric: tabular-nums; }

/* ═══ Totals (.sw-total) ═════════════════════════════════════════════════
   Subtotal, discount, tax, total, paid, balance. Locked 2026-09-19: the WORDS
   are always ink, including "Balance due". A number is never red; if money is
   owed, the state tag beside it says so. Only the total and the balance are
   ruled off and set bigger; everything else is a quiet row. */
.sw-total { display: flex; flex-direction: column; gap: 6px; width: 100%; padding: 12px 14px; }
.sw-total-row { display: flex; font-size: 14px; color: var(--sw-ink-2, #5F6575); }
.sw-total-row > :last-child { margin-left: auto; font-variant-numeric: tabular-nums; }
.sw-total-row--big {
  padding-top: 8px;
  border-top: 1px solid var(--sw-border, #E5DED3);
  font-size: 20px;
  font-weight: 600;
  color: var(--sw-ink, #373B4D);
}

/* ═══ Dates and arrival windows (.sw-cal / .sw-window) ═══════════════════
   A month grid with a dot under any day that already has work, then the three
   arrival windows as plain choices. The chosen day and the chosen window are
   the screen's one orange. */
.sw-cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; width: 100%; text-align: center; font-size: 14px; }
.sw-cal-dow { font-size: 11px; font-weight: 500; color: var(--sw-muted, #7B8190); padding-bottom: 4px; }
.sw-cal-day { position: relative; min-height: 36px; display: grid; place-items: center; border: 0; background: 0; font: inherit; border-radius: 10px; cursor: pointer; color: var(--sw-ink, #373B4D); }
.sw-cal-day:hover { background: var(--sw-fill, #E6E4DF); }
.sw-cal-day.is-off { color: var(--sw-muted, #7B8190); }
.sw-cal-day:is(.is-on, .is-active, .is-current) { background: var(--sw-orange, #F38A3F); color: #fff; font-weight: 600; }
.sw-cal-day.has-work::after { content: ""; position: absolute; left: 50%; bottom: 5px; transform: translateX(-50%); width: 4px; height: 4px; border-radius: 50%; background: var(--sw-tide, #03A6B0); }
.sw-cal-day:is(.is-on, .is-active, .is-current).has-work::after { background: #fff; }
/* A span of days (.sw-cal--range, picked 2026-09-30 for Booking activity): the
   two ends are the chosen orange day, the days between wash in sun, and the
   wash runs unbroken across a week, rounding off at each end of a row. The
   page names the squares: is-start, is-end, is-in, is-row-start, is-row-end. */
.sw-cal--range { column-gap: 0; row-gap: 2px; }
.sw-cal--range .sw-cal-day { isolation: isolate; }
.sw-cal--range .sw-cal-day.is-in, .sw-cal--range .sw-cal-day.is-in:hover { background: var(--sw-sun-wash, #F8CDAD); border-radius: 0; }
.sw-cal--range .sw-cal-day.is-in.is-row-start { border-radius: 10px 0 0 10px; }
.sw-cal--range .sw-cal-day.is-in.is-row-end { border-radius: 0 10px 10px 0; }
.sw-cal--range .sw-cal-day:is(.is-start, .is-end) { color: #fff; font-weight: 600; }
.sw-cal--range .sw-cal-day:is(.is-start, .is-end)::before { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: 10px; background: var(--sw-orange, #F38A3F); }
.sw-cal--range .sw-cal-day.is-start:not(.is-end):not(.is-row-end) { background: linear-gradient(to right, transparent 50%, var(--sw-sun-wash, #F8CDAD) 50%); }
.sw-cal--range .sw-cal-day.is-end:not(.is-start):not(.is-row-start) { background: linear-gradient(to left, transparent 50%, var(--sw-sun-wash, #F8CDAD) 50%); }
.sw-windows { display: flex; gap: 8px; flex-wrap: wrap; }
.sw-window { appearance: none; display: inline-flex; align-items: center; gap: 7px; min-height: 40px; padding: 0 14px; border-radius: var(--sw-radius-pill, 999px); border: 1px solid var(--sw-border, #E5DED3); background: var(--sw-card-lift, #FFFDF9); font: inherit; font-size: 14px; font-weight: 500; color: var(--sw-ink, #373B4D); cursor: pointer; }
.sw-window:is(.is-on, .is-active, .is-current) { background: var(--sw-orange, #F38A3F); border-color: var(--sw-orange, #F38A3F); color: #fff; font-weight: 600; }
/* Nothing picked yet: the chip is the same shape, saying the name of the
   thing rather than a value, so it reads as a prompt and not as an answer. */
.sw-window.is-quiet, .sw-state.is-quiet, .sw-btn.is-quiet { color: var(--sw-muted, #7B8190); font-weight: 400; }

/* ═══ Photos and files (.sw-thumb / .sw-file) ════════════════════════════
   A photo reads as a photo: a square you can see. Anything else is a line you
   can tap. The last square is always the one that adds another. */
.sw-thumbs { display: flex; gap: 8px; flex-wrap: wrap; }
.sw-thumb { position: relative; width: 64px; height: 64px; border-radius: 12px; background: var(--sw-fill, #E6E4DF); background-size: cover; background-position: center; border: 0; padding: 0; cursor: pointer; }
.sw-thumb--add { display: grid; place-items: center; color: var(--sw-ink-2, #5F6575); font-size: 22px; }
.sw-thumb-x { position: absolute; top: 4px; right: 4px; width: 18px; height: 18px; border-radius: 50%; border: 0; background: rgba(55, 59, 77, .55); color: #fff; font-size: 11px; line-height: 1; cursor: pointer; }
/* A set of colours with one chosen. Not filters (they would all go orange and
   the colour, which IS the meaning here, would be gone) and not photos. */
.sw-swatches { display: flex; flex-wrap: wrap; gap: 8px; }
.sw-swatch { appearance: none; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%; background: var(--sw-swatch, var(--sw-fill, #E6E4DF)); cursor: pointer; }
.sw-swatch:is(.is-on, .is-active, .is-current) { box-shadow: 0 0 0 2px var(--sw-card-lift, #FFFDF9), 0 0 0 4px var(--sw-ink, #373B4D); }
.sw-swatch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--sw-card-lift, #FFFDF9), 0 0 0 4px var(--sw-ink, #373B4D); } /* a small disc keeps its colour: the ink ring, never sunk */
.sw-file { display: inline-flex; align-items: center; gap: 8px; min-height: 40px; padding: 0 12px; border-radius: 12px; border: 1px solid var(--sw-border, #E5DED3); background: var(--sw-card-lift, #FFFDF9); font-size: 14px; }
.sw-file-size { color: var(--sw-muted, #7B8190); }
/* The same file, listed rather than chipped: full width, the name, what it is
   under it, and a quiet way to take it off. */
.sw-file--row { display: flex; width: 100%; box-sizing: border-box; }
.sw-file-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.sw-file-meta { font-size: 13px; color: var(--sw-muted, #7B8190); }
.sw-file-x { margin-left: auto; flex: none; appearance: none; border: 0; background: none; padding: 0 2px; font: inherit; color: var(--sw-muted, #7B8190); cursor: pointer; }
.sw-file-x:hover { color: var(--sw-ink, #373B4D); }
.sw-file.is-uploading { opacity: 0.6; }
/* A file row with a square at the front (Levi, 2026-10-05, feedback #340): a
   photo shows its own picture, anything else its kind in two to four letters,
   so a list of attachments reads at a glance. A row you can open lightens
   under the pointer and sinks on focus, the way every other row does. */
.sw-file--lead { min-height: 56px; padding: 8px 12px 8px 8px; gap: 12px; }
.sw-file-mark { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 10px; overflow: hidden; background: var(--sw-fill, #E6E4DF); color: var(--sw-ink-2, #5F6575); font-size: 11px; font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase; }
.sw-file-mark img { display: block; width: 100%; height: 100%; object-fit: cover; }
.sw-file--row[role="button"] { cursor: pointer; transition: background-color 150ms ease; }
.sw-file--row[role="button"]:hover { background: var(--sw-hover-soft, #EFEDE8); }
.sw-file--row[role="button"]:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }

/* ═══ Picking several at once (.sw-bulk) ═════════════════════════════════
   A checkbox on each row, and a glass bar that floats in only while something
   is picked. It says how many, then the actions, then Clear. */
.sw-bulk { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: var(--sw-radius-pill, 999px); }
.sw-bulk-n { font-weight: 600; }
.sw-check { appearance: none; -webkit-appearance: none; width: 22px; height: 22px; padding: 0; flex: none; display: inline-grid; place-items: center; border: 1.5px solid var(--sw-border, #E5DED3); border-radius: 6px; background: var(--sw-card-lift, #FFFDF9); color: #fff; font-size: 13px; cursor: pointer; }
.sw-check:is(.is-on, .is-active, .is-current) { background: var(--sw-moss-ink, #04765E); border-color: var(--sw-moss-ink, #04765E); }
/* padding 0 above is load-bearing: a <button> keeps the browser's 6px side
   padding otherwise, and the 17 to 22px tick then sits in an 8px content box
   and hangs off to the right of its square (the calendar chips, 2026-09-28). */
/* THE WRITTEN MARK (Levi, locked 2026-09-19, from the alignment study).
   Finishing something is a MARK BEING WRITTEN, never a box being filled. The
   square lives on ::before so it can open outward and fade on its own clock;
   the green tick then writes itself tip to tail in the space it left,
   spilling past the top-right corner the way a pen overshoots a printed
   square, and running backwards on undo. The filled green box above stays for
   picking rows to act on, which is a different job. */
.sw-check--mark {
  position: relative;
  width: 26px; height: 26px; border: 0; border-radius: 8px;
  background: none; box-shadow: none;
  transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
}
.sw-check--mark::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  border: 1px solid var(--sw-check-line, rgba(186, 166, 138, 0.45));
  transition: opacity 180ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1), border-color 200ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .sw-check--mark:not(:disabled):hover::before { border-color: var(--sw-tick, #2F8F79); }
}
.sw-check--mark svg {
  position: relative; width: 22px; height: 22px; overflow: visible;
  fill: none; stroke: var(--sw-tick, #2F8F79); stroke-width: 3.2;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 18; stroke-dashoffset: 18;
  /* 2026-09-23 Levi's pick: the tick sits centered in the square, no overshoot. */
  transform: none;
  transition: stroke-dashoffset 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* The square opens out of the way first; the mark starts once it has gone. */
.sw-check--mark:is(.is-on, .is-active, .is-current)::before { opacity: 0; transform: scale(1.14); }
.sw-check--mark:is(.is-on, .is-active, .is-current) { background: none; }
.sw-check--mark:is(.is-on, .is-active, .is-current) svg { stroke-dashoffset: 0; }
/* One size down, for a subtask under the task it belongs to. */
.sw-check--sm { width: 20px; height: 20px; border-radius: 6px; }
.sw-check--sm svg { width: 17px; height: 17px; stroke-width: 3; transform: none; }
/* A mark you can only read. It must not promise a click it will not answer. */
.sw-check.is-readonly, .sw-check[aria-disabled="true"] { cursor: default; }
/* The real form control, for the places where something reads .checked. */
input[type="checkbox"].sw-check { appearance: none; margin: 0; }
input[type="checkbox"].sw-check:checked { background: var(--sw-moss-ink, #04765E); border-color: var(--sw-moss-ink, #04765E); }
@media (prefers-reduced-motion: reduce) { .sw-check--mark svg { transition: none; } }

/* ═══ Help (.sw-help / .sw-tip) ══════════════════════════════════════════
   A small question mark beside a label that a person may not know the words
   for. Never a second line under the label: the explanation only appears when
   somebody asks for it. */
.sw-help { display: inline-grid; place-items: center; width: 18px; height: 18px; border: 0; border-radius: 50%; background: var(--sw-fill, #E6E4DF); color: var(--sw-ink-2, #5F6575); font-size: 11px; font-weight: 700; cursor: pointer; }
.sw-tip { max-width: 260px; padding: 8px 11px; border-radius: 10px; background: var(--sw-ink, #373B4D); color: #fff; font-size: 13px; line-height: 1.4; }

/* ═══ Fold (.sw-fold) ════════════════════════════════════════════════════
   Answers first, details folded (Levi, 2026-09-29, Communication KPIs). One
   topic per line: its name on the left, its short answer on the right in the
   quieter ink, a chevron at the end. The line opens in place to show what is
   behind the answer, and a group opens one at a time, so a page stays quiet
   until somebody asks. Hairlines between the lines, never a card per line. */
.sw-folds { width: 100%; border-top: 1px solid var(--sw-border, #E5DED3); }
.sw-fold { border-bottom: 1px solid var(--sw-border, #E5DED3); }
.sw-fold-head {
  appearance: none; -webkit-appearance: none; box-sizing: border-box;
  display: flex; align-items: center; gap: 16px; width: 100%; min-height: 56px; margin: 4px 0; padding: 0 12px;
  border: 0; border-radius: 12px; background: none; color: var(--sw-ink, #373B4D); font: inherit; text-align: left; cursor: pointer;
}
@media (hover: hover) and (pointer: fine) { .sw-fold-head:hover { background: var(--sw-card, #FCFAF6); } }
.sw-fold-head:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
.sw-fold-name { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 500; }
.sw-fold-answer { flex: 0 1 auto; min-width: 0; font-size: 14px; color: var(--sw-ink-2, #5F6575); font-variant-numeric: tabular-nums; text-align: right; }
.sw-fold-answer .sw-dot { display: inline-block; margin: 0 6px 1px 0; vertical-align: middle; }
.sw-fold-head > svg { flex: none; width: 16px; height: 16px; fill: none; stroke: var(--sw-ink-2, #5F6575); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; transition: transform 200ms var(--sw-ease, cubic-bezier(0.22, 1, 0.36, 1)); }
.sw-fold.is-open > .sw-fold-head > svg { transform: rotate(90deg); }
.sw-fold-body { padding: 4px 12px 28px; animation: sw-fold-in 200ms var(--sw-ease, cubic-bezier(0.22, 1, 0.36, 1)) both; }
@keyframes sw-fold-in { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) {
  .sw-fold-head > svg { transition: none; }
  .sw-fold-body { animation: none; }
}

/* The numbers every left menu reads. Change one here and the admin rails, the
   inbox rail, settings, Price Book, Tasks and the tech dashboard all move
   together: each of those files uses these variables instead of its own
   values, so the menu look lives in exactly one place. */
:root {
  --sw-rail-h: 40px;
  --sw-rail-pad: 7px 12px;
  --sw-rail-gap: 10px;
  --sw-rail-radius: 14px; /* rounded square, the Sunwave Tasks shape. Never a full pill (Levi, 2026-09-19) */
  --sw-rail-size: 14px;
  --sw-rail-weight: 500;
  --sw-rail-weight-on: 600;
  --sw-rail-icon: 18px;
  --sw-rail-title-size: 11px;
  --sw-rail-title-weight: 600;
  /* Room under the words for the chosen row's orange line (the line is 2px of
     it). 3px, was 5: the line sat too far below the word (Levi, 2026-10-01). */
  --sw-rail-line-gap: 3px;
}

/* ═══ Left menu (.sw-rail) ═══════════════════════════════════════════════
   One look for every left menu in the product (locked 2026-09-19, "one
   capsule, no coins"): admin rails, the inbox rail, settings, Price Book,
   Tasks and the tech dashboard. Flat rows, a bare glyph in the SAME dark ink
   as the words, and one solid orange capsule under the chosen row. No icon
   coin, no peach-with-orange-words, no underline, no grey selection.
   The live rails still carry their own class names; this is the spec they are
   all built to, and what a new menu should use directly. */
.sw-rail { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.sw-rail-title {
  padding: 10px 11px 4px;
  color: var(--sw-muted, #7B8190);
  font-size: var(--sw-rail-title-size, 11px);
  font-weight: var(--sw-rail-title-weight, 600);
}
.sw-rail > a, .sw-rail > button, .sw-rail > [role="button"],
.sw-rail-sub > a, .sw-rail-sub > button, .sw-rail-sub > [role="button"],
.sw-rail-row > :first-child {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--sw-rail-h, 40px);
  padding: var(--sw-rail-pad, 7px 12px);
  appearance: none;
  border: 0;
  background: none;
  border-radius: var(--sw-rail-radius, 14px);
  font: inherit;
  font-size: var(--sw-rail-size, 14px);
  font-weight: var(--sw-rail-weight, 500);
  color: var(--sw-ink, #373B4D);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background 140ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* The glyph never changes color: dark ink at rest, dark ink on the orange. */
.sw-rail > :is(a, button, [role="button"]) svg,
.sw-rail-sub > :is(a, button, [role="button"]) svg,
.sw-rail-row > :first-child svg { flex: none; width: var(--sw-rail-icon, 18px); height: var(--sw-rail-icon, 18px); fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.sw-rail > a:hover, .sw-rail > button:hover, .sw-rail > [role="button"]:hover,
.sw-rail-sub > a:hover, .sw-rail-sub > button:hover, .sw-rail-sub > [role="button"]:hover,
.sw-rail-row > :first-child:hover { background: var(--sw-hover-soft, #EFEDE8); }
.sw-rail > .is-active, .sw-rail-sub > .is-active, .sw-rail-row > .is-active:first-child { background: var(--sw-orange, #F38A3F); font-weight: var(--sw-rail-weight-on, 600); }
.sw-rail > a:focus-visible, .sw-rail > button:focus-visible, .sw-rail > [role="button"]:focus-visible,
.sw-rail-sub > a:focus-visible, .sw-rail-sub > button:focus-visible, .sw-rail-sub > [role="button"]:focus-visible,
.sw-rail-row > :first-child:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
/* The count rides at the end of the row, ink words, hidden at zero. */
.sw-rail > * > .sw-rail-count, .sw-rail > .sw-rail-count,
.sw-rail-sub > * > .sw-rail-count, .sw-rail-sub > .sw-rail-count,
.sw-rail-row > :first-child > .sw-rail-count { margin-left: auto; padding: 1px 7px; border-radius: var(--sw-radius-pill, 999px); background: var(--sw-card-lift, #FFFDF9); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.sw-rail > .is-active .sw-rail-count, .sw-rail-sub > .is-active .sw-rail-count, .sw-rail-row > .is-active .sw-rail-count { background: rgba(255, 255, 255, 0.55); }
/* A count that is UNREAD rather than a total: three texts nobody has read, not
   four hundred services in a folder. The warm gold wash, the words still ink.
   (Levi's pick, 2026-09-21, carried in from the inbox's own menu, where it was
   a page rule painting the badge. It belongs to the part.) */
.sw-rail > * > .sw-rail-count--unread, .sw-rail > .is-active .sw-rail-count--unread,
.sw-rail-sub > * > .sw-rail-count--unread, .sw-rail-sub > .is-active .sw-rail-count--unread,
.sw-rail-row > :first-child > .sw-rail-count--unread {
  background: var(--sw-orange-soft, #FFEBDE);
  color: var(--sw-ink, #373B4D);
}
/* A second level indents behind a hairline; same rows, one step smaller. */
.sw-rail-sub { display: flex; flex-direction: column; gap: 4px; margin-left: 11px; padding-left: 14px; border-left: 1px solid var(--sw-border, #E5DED3); }
.sw-rail-sub > a, .sw-rail-sub > button, .sw-rail-sub > [role="button"] {
  min-height: 34px;
  font-size: 13px;
  /* The glyph steps down with the words, the way the old nested rail did
     (06-horizon-shell.css, .rail-list--nested .rail-icon svg). */
  --sw-rail-icon: 16px;
}
/* A row with its own action riding at the end of it: the ⋯ that edits a tag,
   a project's menu. Two real buttons side by side, because a button may not
   sit inside a button. The action floats over the row's end and shows under
   the pointer or the keyboard; the row keeps room for it at all times, so
   the words and the count never move when it arrives (Levi, 2026-09-19, the
   Tasks tag rows). */
.sw-rail-row { position: relative; min-width: 0; }
.sw-rail-row > :first-child { padding-right: 44px; }
.sw-rail-act { position: absolute; right: 2px; top: 50%; display: flex; margin: 0; transform: translateY(-50%); opacity: 0; transition: opacity 140ms ease; }
.sw-rail-row:hover > .sw-rail-act, .sw-rail-row:focus-within > .sw-rail-act, .sw-rail-act:has([aria-expanded="true"]) { opacity: 1; }
@media (hover: none) { .sw-rail-act { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .sw-rail-act { transition: none; } }
/* A title that carries its own "+": the word on the left, the button at the
   end, on the title's own line. */
.sw-rail-title { display: flex; align-items: center; gap: 8px; min-height: 22px; }
.sw-rail-title > .sw-btn { margin: -8px 0 -8px auto; }
/* A name somebody typed (a project, a tag) is theirs, so it wraps onto a
   second line instead of being cut off; a word we wrote stays on one. */
.sw-rail-wrap > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)) { flex: 1 1 auto; min-width: 0; white-space: normal; }
.sw-rail-clamp { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; line-height: 1.3; }

/* ═══ Parts that used to be drawings ═════════════════════════════════════
   Until 2026-09-19 the Kit page DREW these with its own private classes, so
   no screen could point at them and changing the Kit changed nothing. They
   are real parts now: the Kit page renders these classes, and so does any
   screen converted to the Kit. */

/* A labelled type-in box. Same height as a button, so a row lines up. */
.sw-field { display: flex; flex-direction: column; gap: 6px; width: 100%; min-width: 0; }
.sw-field > label, .sw-label { font-size: 13px; font-weight: 500; color: var(--sw-ink-2, #5F6575); }
.sw-input, .sw-select, .sw-textarea {
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--sw-border, #E5DED3);
  border-radius: var(--sw-radius-input, 14px);
  background: var(--sw-card-lift, #FFFDF9);
  color: var(--sw-ink, #373B4D);
  font: inherit;
  font-size: 16px;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
@media (hover: hover) and (pointer: fine) { .sw-input, .sw-select { min-height: 40px; } }
.sw-input::placeholder, .sw-textarea::placeholder { color: var(--sw-muted, #7B8190); }
.sw-input:focus, .sw-select:focus, .sw-textarea:focus {
  outline: none;
  border-color: transparent;
  box-shadow: var(--sw-focus-shadow);
}
.sw-textarea { padding: 12px 14px; line-height: 1.45; resize: none; }
.sw-input--num { text-align: right; font-variant-numeric: tabular-nums; }
/* A typing line with no box: an inline row editor, a subtask, a name being
   renamed in place. The words must not move when the field appears, so there
   is no padding and no corner, only a thin orange line under them while they
   are being typed. */
.sw-input--bare { min-height: 0; padding: 0; border: 0; border-radius: 0; background: none; }
.sw-input--bare:focus { border: 0; box-shadow: inset 0 -1.5px 0 var(--sw-orange, #F38A3F); }
/* Wrong, and it needs fixing before this can be saved. */
.sw-input.is-bad, .sw-select.is-bad, .sw-textarea.is-bad { border-color: var(--sw-clay-ink, #993A31); }
.sw-input.is-bad:focus, .sw-select.is-bad:focus, .sw-textarea.is-bad:focus { box-shadow: 0 0 0 1px var(--sw-clay-ink, #993A31); }
/* Missing, and the button was pressed: the field it is waiting on, in amber
   (the "waiting on you" family), with one ink line under it saying what to
   do. Nothing failed, so this is never clay. Levi's pick, 2026-10-07 (the
   self-checkout's "What could we have done better?"), the web twin of the
   app's SWPointAt. */
.sw-input.is-missing, .sw-select.is-missing, .sw-textarea.is-missing { border-color: var(--sw-amber, #EDB635); box-shadow: 0 0 0 1px var(--sw-amber, #EDB635); }
.sw-missing { margin: 0; font-size: 14px; font-weight: 500; color: var(--sw-ink, #373B4D); }
/* Minus, the number, plus. */
.sw-stepper { display: inline-flex; align-items: center; gap: 2px; padding: 2px; border-radius: var(--sw-radius-pill, 999px); background: var(--sw-fill, #E6E4DF); }
.sw-stepper > button { appearance: none; width: 28px; height: 28px; display: grid; place-items: center; border: 0; border-radius: 50%; background: none; font: inherit; font-size: 15px; color: var(--sw-ink, #373B4D); cursor: pointer; }
.sw-stepper > button:hover { background: var(--sw-card-lift, #FFFDF9); }
.sw-stepper > button:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
.sw-stepper > span { min-width: 26px; text-align: center; font-weight: 600; font-variant-numeric: tabular-nums; }
/* A dropdown is a field with a chevron, never the browser's grey box. */
/* Room on the right for the chevron, so a dropdown sized to its words never
   has the arrow sitting over its last letters. */
.sw-select { padding-right: 38px; }
.sw-select-wrap { position: relative; width: 100%; }
.sw-select-wrap::after {
  content: ""; position: absolute; right: 16px; top: 50%; width: 8px; height: 8px; margin-top: -6px;
  border-right: 1.5px solid var(--sw-ink-2, #5F6575); border-bottom: 1.5px solid var(--sw-ink-2, #5F6575);
  transform: rotate(45deg); pointer-events: none;
}
/* THE QUIET DROPDOWN (Levi, 2026-09-29, Communication KPIs). In a dense table
   of records the choice reads as plain words, and the field draws itself only
   when its row is pointed at or it has focus. The room it takes never changes,
   so nothing in the row moves when the box arrives. Pointer screens only: with
   no hover to ask, a phone keeps every dropdown a field at the 16px floor. */
@media (hover: hover) and (pointer: fine) {
  .sw-select--quiet { min-height: 36px; padding-left: 10px; font-size: 14px; border-color: transparent; background-color: transparent; }
  .sw-select-wrap--quiet::after { opacity: .4; transition: opacity 120ms ease; }
  :is(tr, .sw-row):hover .sw-select--quiet:not(:focus) { border-color: var(--sw-border, #E5DED3); }
  :is(tr, .sw-row):hover .sw-select-wrap--quiet::after,
  .sw-select-wrap--quiet:focus-within::after { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .sw-select-wrap--quiet::after { transition: none; } }

/* A radio, the round twin of .sw-check. */
.sw-radio { width: 22px; height: 22px; flex: none; display: inline-grid; place-items: center; border: 1.5px solid var(--sw-border, #E5DED3); border-radius: 50%; background: var(--sw-card-lift, #FFFDF9); cursor: pointer; }
.sw-radio:is(.is-on, .is-active, .is-current) { border-color: var(--sw-moss-ink, #04765E); }
.sw-radio:is(.is-on, .is-active, .is-current)::after { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--sw-moss-ink, #04765E); }

/* A setting: the words on the left, the control at the end of the row. */
.sw-setting { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 44px; }
.sw-setting > .sw-switch, .sw-setting > .sw-saved, .sw-setting > .sw-count, .sw-setting > .sw-btn { margin-left: auto; }

/* A list and its rows. One surface, hairline rows, the value on the right. */
.sw-list { width: 100%; border-radius: var(--sw-radius-card, 14px); background: var(--sw-card-lift, #FFFDF9); overflow: hidden; }
.sw-row {
  display: flex; align-items: center; gap: 12px; width: 100%; box-sizing: border-box;
  padding: 12px 14px; border: 0; border-bottom: 1px solid var(--sw-border, #E5DED3);
  background: none; color: inherit; font: inherit; text-align: left; text-decoration: none;
}
.sw-row:last-child { border-bottom: 0; }
a.sw-row, button.sw-row, .sw-row[role="button"], .sw-row[tabindex] { cursor: pointer; }
@media (hover: hover) and (pointer: fine) {
  a.sw-row:hover, button.sw-row:hover, .sw-row[role="button"]:hover, .sw-row[tabindex]:hover { background: var(--sw-card, #FCFAF6); }
}
.sw-row:focus-visible { outline: none; box-shadow: var(--sw-focus-shadow); }
.sw-row-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.sw-row-title { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sw-row-sub { font-size: 13px; color: var(--sw-muted, #7B8190); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sw-row-end { margin-left: auto; flex: none; font-weight: 500; font-variant-numeric: tabular-nums; }
/* One density step down, for rows that have to fit several to a cell: a task
   on a day in the month grid, a line on a board card. */
.sw-row--dense { padding: 4px 7px; gap: 6px; border-bottom: 0; border-radius: 8px; font-size: 11px; }
/* The fixed leading column of a row: a time, a date, a number. Even-width
   figures, so a column of them lines up without anyone eyeballing it. */
.sw-row-lead { flex: none; color: var(--sw-ink-2, #5F6575); font-variant-numeric: tabular-nums; }
/* Finished: struck through and faded, never a different colour. */
.sw-row-title.is-done { color: var(--sw-muted, #7B8190); text-decoration: line-through; }
/* Running right now (a recording playing), which is not the same as picked
   for an action. */
.sw-row.is-live { background: var(--sw-chosen-soft, #EDF4E6); }

/* Moving something: the hole it left, the place it would land, and the line
   between the two rows it would sit between. The hole is written here rather
   than only in the soft layer, because a drag on a phone needs a slot too. */
.sw-row-hole { border: 0; border-radius: 10px; background: var(--sw-fill, #E6E4DF); }
.sw-drop { background: var(--sw-chosen-soft, #EDF4E6); }
.sw-seam { height: 2px; border-radius: 1px; background: var(--sw-orange, #F38A3F); }
.sw-seam--v { width: 2px; height: auto; align-self: stretch; }

/* A control that only shows up under the pointer, and never moves the row by
   arriving: the space is always reserved, only the opacity changes. */
.sw-reveal { opacity: 0; transition: opacity 140ms ease; }
:hover > .sw-reveal, :focus-within > .sw-reveal, .sw-reveal:focus-visible { opacity: 1; }
@media (hover: none) { .sw-reveal { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .sw-reveal { transition: none; } }

/* The name of the page, or of the record you are looking at. It has to work
   three ways off one class without the words moving a pixel: as a heading, as
   a click-to-rename button, and as the field it turns into. */
.sw-title { margin: 0; font-size: var(--sw-text-page, 28px); font-weight: var(--sw-weight-page, 700); letter-spacing: -0.02em; color: var(--sw-ink, #373B4D); }
button.sw-title { appearance: none; border: 0; background: none; padding: 0; font-family: inherit; text-align: left; cursor: text; }
input.sw-title, textarea.sw-title { width: 100%; appearance: none; border: 0; background: none; padding: 0; resize: none; font-family: inherit; }
input.sw-title:focus, textarea.sw-title:focus, button.sw-title:focus-visible { outline: none; box-shadow: inset 0 -1.5px 0 var(--sw-orange, #F38A3F); }
/* The small word above or beside a title. */
.sw-kicker { margin: 0; font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--sw-muted, #7B8190); }

/* A group heading says the state once, so no row under it repeats it. */
.sw-heading { display: flex; align-items: baseline; gap: 6px; width: 100%; margin: 0; padding-bottom: 6px; font-size: 13px; font-weight: 600; color: var(--sw-ink, #373B4D); }
.sw-heading > em { font-style: normal; font-weight: 400; color: var(--sw-muted, #7B8190); }
/* A divider with the words IN it: the hairline runs out to both edges through
   the label, where .sw-heading rules underneath instead. */
.sw-rule { display: flex; align-items: center; gap: 10px; width: 100%; margin: 0; font-size: 12px; font-weight: 600; color: var(--sw-muted, #7B8190); }
.sw-rule::before, .sw-rule::after { content: ""; flex: 1; height: 1px; background: var(--sw-border, #E5DED3); }
.sw-rule::before { flex: 0 0 14px; }

/* A table: lines between rows and nothing else. Computers only. */
.sw-table { width: 100%; border-collapse: collapse; border-radius: var(--sw-radius-card, 14px); overflow: hidden; background: var(--sw-card-lift, #FFFDF9); font-size: 14px; }
.sw-table th { padding: 10px 12px; border-bottom: 1px solid var(--sw-border, #E5DED3); text-align: left; font-size: 13px; font-weight: 600; color: var(--sw-ink-2, #5F6575); }
.sw-table td { padding: 11px 12px; border-bottom: 1px solid var(--sw-border, #E5DED3); }
.sw-table tr:last-child td { border-bottom: 0; }
.sw-table .is-num { text-align: right; font-variant-numeric: tabular-nums; }

/* Nothing here: one sentence, no button, no picture. */
.sw-empty { width: 100%; padding: 22px 10px; text-align: center; color: var(--sw-ink-2, #5F6575); }

/* Something went wrong: a dark bar at the bottom, and it holds the Undo. */
.sw-toast { display: flex; align-items: center; gap: 12px; width: 100%; box-sizing: border-box; padding: 12px 15px; border-radius: var(--sw-radius-card, 14px); background: var(--sw-ink, #373B4D); color: #fff; font-size: 14px; font-weight: 500; }
/* THE SHELL'S OWN TOAST HOST. Every page loads this sheet, so a React screen
   that is not the admin dashboard can still say something out loud. It sits at
   the top, clear of the phone dock, and above everything: a message drawn under
   a full-screen cover is a message nobody sees. The bar itself is the Kit's
   .sw-toast, unchanged. */
.sw-toast-host {
  position: fixed;
  top: calc(16px + env(safe-area-inset-top, 0px));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  z-index: 2147483200;
  pointer-events: none;
  width: min(420px, calc(100vw - 32px));
}
.sw-toast-host > .sw-toast { pointer-events: auto; align-items: flex-start; box-shadow: var(--sw-shadow-3, 0 18px 38px rgba(55,59,77,.18)); animation: swToastIn .28s cubic-bezier(.34,1.4,.64,1); }
.sw-toast.is-leaving { animation: swToastOut .22s ease-in forwards; }
@keyframes swToastIn { from { opacity: 0; transform: translateY(-12px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes swToastOut { to { opacity: 0; transform: translateY(-12px) scale(.98); } }
.sw-toast-icon { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-weight: 800; font-size: .78rem; background: rgba(255, 255, 255, .16); color: #fff; }
.sw-toast-body { flex: 1; min-width: 0; line-height: 1.4; }
.sw-toast-body strong { display: block; font-weight: 600; }
.sw-toast-body p { margin: 2px 0 0; font-size: 13px; opacity: .82; }
.sw-toast-close { appearance: none; border: 0; background: none; padding: 0 2px; margin-left: 0; align-self: flex-start; color: #fff; opacity: .75; font-size: 1.1rem; line-height: 1; cursor: pointer; }
.sw-toast-close:hover { opacity: 1; }
.sw-toast--success .sw-toast-icon { background: var(--sw-moss, #3AC39E); }
.sw-toast--error .sw-toast-icon { background: var(--sw-clay, #CA6C53); }
.sw-toast--warn .sw-toast-icon { background: var(--sw-amber, #EDB635); color: var(--sw-ink, #373B4D); }
@media (prefers-reduced-motion: reduce) {
  .sw-toast-host > .sw-toast, .sw-toast.is-leaving { animation: none; }
}
.sw-toast > button, .sw-toast > a { margin-left: auto; appearance: none; border: 0; background: none; padding: 0; font: inherit; font-weight: 600; color: #fff; text-decoration: none; cursor: pointer; }

/* A block inside a card. Flat surfaces draw it as a quieter fill. */
.sw-well { padding: 12px 14px; border-radius: 12px; background: var(--sw-canvas, #F8F5EF); }

/* A big number that is the thing on screen gets its own tile. */
.sw-stat--tile { padding: 14px 16px; border-radius: var(--sw-radius-card, 14px); background: var(--sw-card-lift, #FFFDF9); }

/* The same number at the end of a list row: one size step down. */
.sw-stat--row .sw-stat-n { font-size: 17px; }

/* A view switch (.sw-segment): two or three ways to look at the SAME thing
   (Computer / Phone and app, List / Board, Month to Date / Today / Other).
   It narrows nothing, so it never takes the orange.
   LIQUID INK (Levi, 2026-09-23). A pressed-in grainy track, and the chosen
   side is an ink drop that floats on its own shadow. The drop is two pieces:
   the front (::after) races to the new side and the back (::before) springs
   after it, so it stretches like ink and settles. Every side is the same
   width, so the drop is always one fraction of the bar and the words never
   change weight (a bolder word is a wider word and the bar would reflow).
   One look on every surface, computer and phone.
   WORDS ALWAYS FIT (Levi, 2026-09-24): the bar is never narrower than its
   longest words. Every side is as wide as the widest label plus its padding,
   so the bar grows to fit them; a page may make it wider, never narrower. */
.sw-segment {
  --seg-n: 2; --seg-i: 0;
  position: relative; isolation: isolate;
  display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  min-width: max-content; box-sizing: border-box;
  padding: 5px; border-radius: var(--sw-radius-pill, 999px);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .42 0 0 0 0 .35 0 0 0 0 .25 0 0 0 .14 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"),
    var(--sw-soft-page, #F5F0E7);
  box-shadow: var(--sw-soft-down);
}
.sw-segment:has(> :nth-child(3)) { --seg-n: 3; }
.sw-segment:has(> :nth-child(4)) { --seg-n: 4; }
.sw-segment:has(> :nth-child(5)) { --seg-n: 5; }
.sw-segment:has(> :nth-child(6)) { --seg-n: 6; }
.sw-segment:has(> :nth-child(2):is(.is-on, .is-active, .is-current)) { --seg-i: 1; }
.sw-segment:has(> :nth-child(3):is(.is-on, .is-active, .is-current)) { --seg-i: 2; }
.sw-segment:has(> :nth-child(4):is(.is-on, .is-active, .is-current)) { --seg-i: 3; }
.sw-segment:has(> :nth-child(5):is(.is-on, .is-active, .is-current)) { --seg-i: 4; }
.sw-segment:has(> :nth-child(6):is(.is-on, .is-active, .is-current)) { --seg-i: 5; }
.sw-segment::before, .sw-segment::after {
  content: ""; position: absolute; z-index: 0; top: 5px; bottom: 5px; left: 5px;
  width: calc((100% - 10px) / var(--seg-n));
  border-radius: var(--sw-radius-pill, 999px);
  background: var(--sw-ink, #373B4D);
  transform: translateX(calc(100% * var(--seg-i)));
  pointer-events: none;
}
.sw-segment::before {
  box-shadow: 0 6px 8px rgba(55, 59, 77, 0.22);
  transition: transform 660ms cubic-bezier(0.3, 1.28, 0.45, 1);
}
.sw-segment::after {
  box-shadow: 0 6px 8px rgba(55, 59, 77, 0.32), 0 1px 2px rgba(55, 59, 77, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  transition: transform 300ms cubic-bezier(0.5, 0, 0.2, 1);
}
.sw-segment:not(:has(> :is(.is-on, .is-active, .is-current)))::before,
.sw-segment:not(:has(> :is(.is-on, .is-active, .is-current)))::after { opacity: 0; }
.sw-segment > * {
  appearance: none; position: relative; z-index: 1; flex: 1 1 0; border: 0; background: none; box-shadow: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 36px;
  padding: 7px 16px; border-radius: var(--sw-radius-pill, 999px);
  font: inherit; font-size: 14px; font-weight: 500; color: var(--sw-ink-2, #5F6575); white-space: nowrap;
  transition: color 200ms var(--sw-soft-ease, cubic-bezier(0.25, 0.8, 0.3, 1));
}
.sw-segment > :is(.is-on, .is-active, .is-current) {
  background: none; color: var(--sw-card-lift, #FFFDF9); font-weight: 500;
  animation: sw-seg-pop 460ms cubic-bezier(0.3, 1.28, 0.45, 1);
}
@media (hover: hover) and (pointer: fine) {
  .sw-segment > *:hover:not(:is(.is-on, .is-active, .is-current)) { color: var(--sw-ink, #373B4D); }
}
.sw-segment > *:focus-visible { outline: none; box-shadow: none; text-decoration: underline; text-underline-offset: 4px; }
@keyframes sw-seg-pop { 0% { transform: scale(0.9); } 60% { transform: scale(1.05); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .sw-segment::before, .sw-segment::after { transition: none; }
  .sw-segment > * { animation: none !important; }
}

/* A sticky bar above a list that scrolls under it. Flat surfaces draw it as
   glass (add .sw-glass); a soft surface sinks the page under it instead. */
.sw-sticky { position: sticky; top: var(--sticky-top, 16px); z-index: 5; }
/* The one sticky bar that must NOT be see-through: column letters with task
   chips scrolling under them read straight through a transparent header. */
.sw-sticky--solid { background: var(--sw-canvas, #F8F5EF); }

/* There for a screen reader, not on screen. Parks a real checkbox behind a
   face or a swatch so the picker stays keyboard operable. */
.sw-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ═══ SOFT SURFACE LAYER (Levi, locked 2026-09-19) ═══════════════════════
   The same parts as above, drawn soft. ONE rule: a button and a filter chip
   stand a little off the page at rest (--sw-soft-out; Levi, 2026-10-01, the
   "Warm ink shadow" pick), and everything else is flat until it is touched.
   A real pointer lifts it, a press sinks it, and the chosen thing stays sunk.
   Nothing you cannot act on is ever indented. Meaning never changed: every
   color still means what it meant.

   WHERE IT APPLIES: any shell whose <body> carries .sw-soft, on a computer
   (761px and up). The phone shell (.admin-mobile), the iOS app (.swtasks-app)
   and anything inside .sw-flat keep the flat look above; the app has not
   adopted soft yet.

   THIS IS THE ONLY PLACE SOFT IS WRITTEN. The recipe (light, shade, the four
   shadow layers) lives in design/tokens.json as --sw-soft-*. A page file never
   restyles a .sw-* part and never carries its own copy of the shadows; if a
   page looks wrong, the fix goes here, and every page gets it.

   Each rule below names the pick it came from. Parts with no pick (state
   tags, notes, section tabs, toast, saved, empty, tier tags, family buttons,
   calendar, glass chrome) are deliberately not in this block. */
@media (min-width: 761px) {

  /* SECONDARY BUTTON (picked: all soft; 2026-10-01: out at rest). No fill at
     all. It stands a little off the page, a pointer lifts it a hair further,
     a press sinks it. Size, the 14 corner and the ink label are unchanged. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *) {
    background-color: transparent;
    box-shadow: var(--sw-soft-out);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease), border-color 200ms var(--sw-soft-ease), background-color 200ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *):hover:not(:disabled):not([aria-disabled="true"]) {
      background-color: transparent;
      box-shadow: var(--sw-soft-up);
      transform: translateY(-2px);
    }
  }
  /* THE QUIET BUTTON is words on nothing, so it lies flat until it is pointed at. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--ghost:not(.sw-flat *) {
    box-shadow: var(--sw-soft-flat);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *):active:not(:disabled):not([aria-disabled="true"]) {
    opacity: 1;
    transform: translateY(2px) scale(.985);
    box-shadow: var(--sw-soft-down);
    transition-duration: 120ms, 150ms, 120ms, 120ms, 120ms;
  }

  /* PRIMARY BUTTON (picked: all soft). The one orange button, out at rest like
     every button, lifts under the pointer, sinks on press. Still one per
     region, still the only filled button. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--primary:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--primary:not(.sw-flat *):hover:not(:disabled) {
    background-color: var(--sw-orange, #F38A3F);
    color: #fff;
    box-shadow: var(--sw-soft-out);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--primary:not(.sw-flat *):hover:not(:active):not(:disabled):not([aria-disabled="true"]) {
      box-shadow: var(--sw-soft-up);
    }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--primary:not(.sw-flat *):active:not(:disabled) {
    box-shadow: var(--sw-soft-down);
  }

  /* The family buttons were not part of any pick: they keep their fill. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--moss:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--moss:not(.sw-flat *):hover:not(:disabled) {
    background-color: var(--sw-moss-ink, #04765E);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--tide:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--tide:not(.sw-flat *):hover:not(:disabled) {
    background-color: var(--sw-tide-ink, #026A76);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--dusk:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--dusk:not(.sw-flat *):hover:not(:disabled) {
    background-color: var(--sw-dusk-ink, #624DA4);
  }

  /* A FILLED BUTTON SINKS IN ITS OWN COLOR (picked 2026-10-01, "Own light and
     shade"). The white light and the warm ink shade are mixed for the cream
     page; laid inside an orange or a green button they read as a white smear
     over a brown stain. So each filled button names its fill, and its sunk
     shadow is mixed from that fill: a darker shade of it and a lighter tint of
     it. The throw is --sw-soft-down's own (design/tokens.json), so a filled
     press is exactly as deep as any other: change the two together. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--primary:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn.is-live:not(.sw-flat *) { --sw-btn-fill: var(--sw-orange, #F38A3F); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--moss:not(.sw-flat *) { --sw-btn-fill: var(--sw-moss-ink, #04765E); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--tide:not(.sw-flat *) { --sw-btn-fill: var(--sw-tide-ink, #026A76); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--dusk:not(.sw-flat *) { --sw-btn-fill: var(--sw-dusk-ink, #624DA4); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--danger.is-armed:not(.sw-flat *) { --sw-btn-fill: var(--sw-clay-ink, #993A31); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-btn--primary, .sw-btn--moss, .sw-btn--tide, .sw-btn--dusk, .sw-btn--danger.is-armed, .sw-btn.is-live):not(.sw-flat *) {
    --sw-btn-now: var(--sw-btn-fill);
    --sw-btn-sink: 0 0 0 var(--sw-soft-lo0), 0 0 0 var(--sw-soft-hi0), inset 2px 4px 7px color-mix(in oklab, var(--sw-btn-now) 56%, #1A0F06), inset -2px -2px 4px color-mix(in oklab, var(--sw-btn-now) 58%, #fff);
  }
  /* UNDER THE POINTER A FILLED BUTTON GOES A STEP DARKER (picked 2026-10-01).
     It used to lose its fill here: the plain button's hover clears the
     background and outweighed every fill, so white words were left on the bare
     page. The step is a big one, because a small one cannot be seen on the
     dark fills, and the sunk shadow is mixed from the darker fill so a press
     still reads as deep. */
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-btn--primary, .sw-btn--moss, .sw-btn--tide, .sw-btn--dusk, .sw-btn--danger.is-armed, .sw-btn.is-live):not(.sw-flat *):hover:not(:disabled):not([aria-disabled="true"]) {
      --sw-btn-now: color-mix(in oklab, var(--sw-btn-fill) 76%, #1A0F06);
      background-color: var(--sw-btn-now);
    }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-btn--primary, .sw-btn--moss, .sw-btn--tide, .sw-btn--dusk, .sw-btn--danger.is-armed, .sw-btn.is-live):not(.sw-flat *):active:not(:disabled):not([aria-disabled="true"]),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-btn--primary, .sw-btn--moss, .sw-btn--tide, .sw-btn--dusk, .sw-btn--danger.is-armed, .sw-btn.is-live):not(.sw-flat *):focus-visible {
    box-shadow: var(--sw-btn-sink);
  }
  /* The same for a ticked box, which is filled moss and can still be pressed. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check:is(.is-on, .is-active, .is-current):not(.sw-flat *):active {
    box-shadow: 0 0 0 var(--sw-soft-lo0), 0 0 0 var(--sw-soft-hi0), inset 1px 1px 2px color-mix(in oklab, var(--sw-moss-ink, #04765E) 56%, #1A0F06), inset -1px -1px 1px color-mix(in oklab, var(--sw-moss-ink, #04765E) 58%, #fff);
  }

  /* DESTRUCTIVE CONFIRM (picked: outline, then fill). A red hairline at rest,
     filling solid on the armed second tap. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--danger:not(.sw-flat *) {
    border: 1px solid color-mix(in oklab, var(--sw-clay-ink, #993A31) 55%, transparent);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--danger.is-armed:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn--danger.is-armed:not(.sw-flat *):hover:not(:disabled) {
    background-color: var(--sw-clay-ink, #993A31);
    border-color: var(--sw-clay-ink, #993A31);
    color: #fff;
  }

  /* DISABLED (picked: faded and flat). A sunk disabled control reads as pressed,
     the opposite of what it means. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *):disabled,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *)[aria-disabled="true"],
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *)[aria-disabled="true"]:hover:not(:disabled):not(:active) {
    opacity: .45;
    box-shadow: var(--sw-soft-flat);
    transform: none;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *):disabled:not(.sw-btn--primary):not(.sw-btn--moss):not(.sw-btn--tide):not(.sw-btn--dusk):not(.is-armed) {
    background-color: transparent;
  }

  /* FOCUS (picked 2026-09-19: sinks instead of a ring; 2026-09-22: no orange ring
     anywhere, so the base layer above sinks too and this only restates it). An ink
     ring comes back at the foot of this block, where a shadow cannot be seen. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:not(.sw-flat *):focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-switch:not(.sw-flat *):focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) > *:focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row:not(.sw-flat *):focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(a, button, [role="button"]):focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(a, button, [role="button"]):focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child:focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check:not(.sw-flat *):focus-visible,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-radio:not(.sw-flat *):focus-visible {
    outline: none;
    box-shadow: var(--sw-soft-down);
  }

  /* FIELD (picked: all soft). A hairline at rest; the line disappears as the field
     sinks on focus. The caret, the placeholder and the label are unchanged. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-input:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-select:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-textarea:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-search:not(.sw-flat *) {
    background-color: transparent;
    border: 1px solid var(--sw-soft-hair);
    box-shadow: var(--sw-soft-flat);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease), border-color 200ms var(--sw-soft-ease), background-color 200ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-input:not(.sw-flat *):focus,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-select:not(.sw-flat *):focus,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-textarea:not(.sw-flat *):focus,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-search:not(.sw-flat *):focus-within {
    outline: none;
    border-color: transparent;
    box-shadow: var(--sw-soft-down);
    transition-duration: 120ms, 150ms, 120ms, 120ms, 120ms;
  }
  /* THE SEARCH BOX (picked 2026-10-01) pops up off the page under the pointer,
     the way a row does, and still sinks the moment you type in it. */
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-search:not(.sw-flat *):hover:not(:focus-within) {
      border-color: transparent;
      box-shadow: var(--sw-soft-up);
      transform: translateY(-2px);
    }
  }

  /* CHECKBOX (picked: the second mix). A hollow rounded square on a hairline,
     and it fills moss when done. Moss still means settled, everywhere. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-radio:not(.sw-flat *) {
    background-color: transparent;
    border: 1px solid var(--sw-soft-hair);
    box-shadow: var(--sw-soft-flat);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease), border-color 200ms var(--sw-soft-ease), background-color 200ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check:not(.sw-flat *) {
    border-radius: 7px;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check:not(.sw-flat *):active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-radio:not(.sw-flat *):active {
    box-shadow: var(--sw-soft-down-sm);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check:is(.is-on, .is-active, .is-current):not(.sw-flat *) {
    background-color: var(--sw-moss-ink, #04765E);
    border-color: var(--sw-moss-ink, #04765E);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-radio:is(.is-on, .is-active, .is-current):not(.sw-flat *) {
    border-color: var(--sw-moss-ink, #04765E);
  }

  /* FILTERS (picked: all soft; 2026-10-01: out at rest). Each filter stands a
     little off the page like a button. The chosen one is an orange outline
     with INK words, sunk; it carries no count, because the chosen filter
     already says it. The gap leaves room for each one's shadow. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) {
    background-color: transparent;
    padding: 0;
    gap: 12px;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) > * {
    background-color: transparent;
    border: 1px solid transparent;
    color: var(--sw-ink-2, #5F6575);
    box-shadow: var(--sw-soft-out);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease), border-color 200ms var(--sw-soft-ease), background-color 200ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) > *:hover:not(:is(.is-on, .is-active, .is-current)):not(.is-active):not(:active) {
      box-shadow: var(--sw-soft-up);
      color: var(--sw-ink, #373B4D);
      transform: translateY(-2px);
    }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) > *:active {
    transform: translateY(2px) scale(.99);
    box-shadow: var(--sw-soft-down);
    transition-duration: 120ms, 150ms, 120ms, 120ms, 120ms;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) > :is(.is-on, .is-active, .is-current),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) > .is-active {
    background-color: transparent;
    border-color: var(--sw-orange, #F38A3F);
    color: var(--sw-ink, #373B4D);
    font-weight: 600;
    box-shadow: var(--sw-soft-down);
    transform: translateY(2px);
  }
  /* The count collapses to nothing rather than being switched off, so it can
     animate away (a display flip cannot) and still takes no room when gone. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) .sw-count {
    max-width: 0;
    padding: 0;
    margin: 0;
    min-width: 0;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
  }

  /* COUNT (picked: all soft). The number in ink, no pill. Still never drawn at zero. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-count:not(.sw-flat *) {
    min-width: 0;
    height: auto;
    padding: 0;
    border-radius: 0;
    background-color: transparent;
    color: var(--sw-ink, #373B4D);
  }

  /* PERSON (picked: the second mix). The six washes by identity, in a rounded square. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-avatar:not(.sw-flat *) {
    border-radius: 12px;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-avatar--sm:not(.sw-flat *) {
    border-radius: 9px;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-avatar--lg:not(.sw-flat *) {
    border-radius: 14px;
  }
  /* One exception (picked 2026-10-07): a face in a round bubble of its own,
     the caller beside the dock, stays a circle. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-avatar--round:not(.sw-flat *) {
    border-radius: 50%;
  }

  /* MENU ITEM (picked: all soft, with the icon moving WITH the words). Flat until
     chosen, then sunk, with the press motion below and an orange line under the
     words. The weight never changes. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child {
    background-color: transparent;
    box-shadow: var(--sw-soft-flat);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease), border-color 200ms var(--sw-soft-ease), background-color 200ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a:hover:not(.is-active):not(:active),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]):hover:not(.is-active):not(:active),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]):hover:not(.is-active):not(:active),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child:hover:not(.is-active):not(:active) {
      background-color: transparent;
      box-shadow: var(--sw-soft-up);
    }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a:active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]):active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]):active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child:active {
    box-shadow: var(--sw-soft-down);
    transition-duration: 120ms, 150ms, 120ms, 120ms, 120ms;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > .is-active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > .is-active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > .is-active:first-child {
    background-color: transparent;
    font-weight: var(--sw-rail-weight, 500);
    box-shadow: var(--sw-soft-down);
    transform: translateY(1px) scale(.985);
  }

  /* THE PRESS: DOWN AND OVER (Levi, 2026-09-19; the Sunwave Tasks motion, copied
     exactly). The chosen row drops 1px and tightens a hair as it sinks, the icon
     slides 3px right, and the words slide 5px right, a touch further than the
     icon, so the label leans into the dent. Then the orange line draws itself
     under the words from left to right.

     THE WORDS, NOT THE GLYPH. Every rail wraps its icon in a plain <span> (the
     comms, users, profile, settings, KPI and booking menus all do), so a bare
     `> span` also caught the icon: it drew a second orange line under the glyph
     and, because these rules also hand the span `overflow: hidden`, the 3px the
     icon slides on the chosen row was clipped straight off its right edge. The
     label is the only child span with no svg in it, which is what picks it out
     without the Kit having to know any page's class name. The Kit's own colour
     dot (.sw-dot, a bare span, the Tasks project rows lead with one) is named
     and left out too, or it grew a second line and lost its right edge. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > * svg,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > * > .sw-dot,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child svg,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child > .sw-dot {
    transition: transform 260ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)) {
    position: relative;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-bottom: var(--sw-rail-line-gap, 3px);
    margin-bottom: calc(-1 * var(--sw-rail-line-gap, 3px));
    transition: transform 240ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > .is-active svg,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > .is-active > .sw-dot,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > .is-active svg,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > .is-active:first-child svg,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > .is-active:first-child > .sw-dot {
    transform: translateX(3px);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > .is-active > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > .is-active > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > .is-active:first-child > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)) {
    transform: translateX(5px);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after {
    content: "";
    position: absolute;
    left: 0;
    right: 100%;
    bottom: 0;
    height: 2px;
    border-radius: 2px;
    background: var(--sw-orange, #F38A3F);
    transition: right 300ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > .is-active > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > .is-active > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > .is-active:first-child > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after {
    right: 0;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) .sw-rail-count,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > .is-active .sw-rail-count,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) .sw-rail-count {
    padding: 0;
    background-color: transparent;
    color: var(--sw-muted, #7B8190);
  }
  /* Except an unread count, which keeps its gold: a total goes quiet here, but
     something waiting for you is the reason the row is worth looking at. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) .sw-rail-count--unread,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > .is-active .sw-rail-count--unread,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) .sw-rail-count--unread {
    padding: 1px 7px;
    background-color: var(--sw-orange-soft, #FFEBDE);
    color: var(--sw-ink, #373B4D);
  }

  /* A NAME SOMEBODY TYPED wraps onto two lines (Levi, 2026-09-19, the Tasks
     project and tag rows), so the one-line rule above lets go of it and the
     row grows to hold both lines with the orange line clear underneath. The
     two-line cut is the inner .sw-rail-clamp, so the orange line's room
     never shows a sliver of a third line. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) .sw-rail-wrap > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)) {
    flex: 1 1 auto; white-space: normal; text-overflow: clip;
    padding-bottom: 6px; margin-bottom: -6px;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) .sw-rail-wrap { min-height: 46px; padding-top: 9px; padding-bottom: 9px; }

  /* The work area beside the menu sits faintly recessed (picked: the faint one). */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-recess:not(.sw-flat *) {
    box-shadow: var(--sw-soft-recess);
  }

  /* SECTION HEADING (picked: all soft). Sentence case at 12/600 with a hairline rule. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-heading:not(.sw-flat *) {
    padding-bottom: 7px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--sw-soft-rule);
    font-size: 12px;
    font-weight: 600;
  }

  /* SWITCH (picked: all soft, knob raised). Moss when on; the knob has a light lift
     whose shadow stays INSIDE the track, so nothing spills over the top edge.
     2026-10-01 (Switch version 3 "Shade and light", then version 1 "Soft fade"):
     on sinks too, a green shade top left and a light on the bottom right lip, and
     the track fades to green while the knob crosses, one 260ms move. On lists
     the same four shadow layers as off, so the dent eases from warm to green. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-switch:not(.sw-flat *) {
    overflow: hidden;
    transition: background-color 260ms var(--sw-soft-ease), box-shadow 260ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-switch:not(.sw-flat *):not(:is(.is-on, .is-active, .is-current)):not([aria-checked="true"]) {
    background-color: transparent;
    box-shadow: var(--sw-soft-down-sm);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-switch:not(.sw-flat *):is(.is-on, .is-active, .is-current, [aria-checked="true"]):not(:focus-visible) {
    box-shadow: 0 0 0 var(--sw-soft-lo0), 0 0 0 var(--sw-soft-hi0), inset 2px 2px 5px rgba(0, 46, 36, .55), inset -2px -2px 4px rgba(255, 255, 255, .28);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-switch:not(.sw-flat *)::after {
    box-shadow: 1px 1px 2px var(--sw-soft-lo);
    transition: transform 260ms var(--sw-soft-ease);
  }

  /* DAYS OF THE WEEK (picked 2026-09-29, Availability version 1). Flat at rest,
     the pointer lifts one, a press sinks it. An open day keeps its moss wash,
     because the wash is the answer, not a hover. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-day:not(.sw-flat *) {
    box-shadow: var(--sw-soft-flat);
    transition: background-color 120ms var(--sw-soft-ease), color 120ms var(--sw-soft-ease), box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-day:not(.sw-flat *):hover:not(:disabled) {
      background-color: transparent;
      box-shadow: var(--sw-soft-up);
    }
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-day:not(.sw-flat *):is(.is-on, .is-active, .is-current, [aria-pressed="true"]):hover:not(:disabled) {
      background-color: var(--sw-moss-wash, #AEE4D3);
    }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-day:not(.sw-flat *):active:not(:disabled) {
    opacity: 1;
    transform: translateY(1px) scale(.98);
    box-shadow: var(--sw-soft-down);
    transition-duration: 120ms, 120ms, 120ms, 150ms;
  }

  /* LIST ROW and TABLE ROW (picked: all soft). Flat, no card behind the list and
     no hairline between rows; the one under the pointer lifts, a press sinks. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-list:not(.sw-flat *) {
    background-color: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    overflow: visible;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row:not(.sw-flat *) {
    border-bottom-color: transparent;
    border-radius: 12px;
    box-shadow: var(--sw-soft-flat);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease), border-color 200ms var(--sw-soft-ease), background-color 200ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) a.sw-row:not(.sw-flat *):hover,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) button.sw-row:not(.sw-flat *):hover,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row:not(.sw-flat *)[role="button"]:hover,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row:not(.sw-flat *)[tabindex]:hover {
      background-color: transparent;
      box-shadow: var(--sw-soft-up);
    }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) a.sw-row:not(.sw-flat *):active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) button.sw-row:not(.sw-flat *):active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row:not(.sw-flat *)[role="button"]:active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row:not(.sw-flat *)[tabindex]:active {
    transform: translateY(1px) scale(.995);
    box-shadow: var(--sw-soft-down);
    transition-duration: 120ms, 150ms, 120ms, 120ms, 120ms;
  }

  /* PICKING SEVERAL (picked: the ones you pick sink and stay sunk). */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row.is-picked:not(.sw-flat *) {
    box-shadow: var(--sw-soft-down);
  }

  /* DRAGGING (picked: lift and sunk hole). The row lifts on the big shadow and the
     slot it left is a sunk hole, never a dashed outline. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row.is-dragging:not(.sw-flat *) {
    box-shadow: var(--sw-soft-up-lg);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row-hole:not(.sw-flat *) {
    border-radius: 12px;
    box-shadow: var(--sw-soft-down);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) {
    background-color: transparent;
    border-radius: 0;
    overflow: visible;
    /* Holds the row's lift (below) behind every cell's words. */
    isolation: isolate;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) th {
    border-bottom-color: var(--sw-soft-rule);
    padding: 7px 12px;
  }

  /* DENSITY (picked: compact only on tables). */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) td {
    position: relative;
    border-bottom-color: transparent;
    padding: 7px 12px;
    transition: border-color 200ms var(--sw-soft-ease), background-color 200ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  /* THE ROW UNDER THE POINTER (Levi, 2026-10-01): one continuous bar with round
     ends, the lift a list row gets. A table row cannot hold a shadow or a
     corner of its own, and a shadow on each cell breaks at every column. So
     each cell draws its stretch of the ONE bar: a layer wider than the cell
     carries the bar's shadow and is cut to the cell's own column, so the
     stretches meet with no seam. Only the first and last cells keep their
     outer end, and that end is round. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) td::before {
    content: "";
    position: absolute;
    inset: 0 -24px;
    z-index: -1;
    box-shadow: var(--sw-soft-flat);
    clip-path: inset(-24px 24px);
    pointer-events: none;
    transition: box-shadow 260ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) td:first-child::before {
    left: 0;
    border-radius: 12px 0 0 12px;
    clip-path: inset(-24px 24px -24px -24px);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) td:last-child::before {
    right: 0;
    border-radius: 0 12px 12px 0;
    clip-path: inset(-24px -24px -24px 24px);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) td:first-child:last-child::before {
    border-radius: 12px;
    clip-path: none;
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) tbody tr:hover td::before,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) tr:not(:first-child):hover td::before {
      box-shadow: var(--sw-soft-up);
    }
  }

  /* ONE WHITE (Levi, 2026-10-06, feedback #366). On a computer the staff pages
     had two near-whites side by side: the card, and a notch paler "lift" under
     the tiles, chips, picture boxes and fields sitting on it. Every lift fill
     now takes the card's own tone, so a card and what is inside it are one
     white and the hairline is what draws a box. Phones and the apps keep the
     lift (a control there still sits on a lifted surface). */
  body.sw-soft.admin-body:not(.admin-mobile) {
    --sw-card-lift: var(--sw-card, #FCFAF6);
  }

  /* RECORD CARD (picked: permanently raised, a sunk well inside). You read a card
     rather than press it, so it stays raised. A block INSIDE it is a sunk well,
     the one honest indent: never a second raised card inside the first. */
  /* Levi, 2026-09-19: a card is the SAME tone as the page. Depth alone says it
     is a card, so a paler panel floating on the canvas is a second signal for
     the same thing. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-card:not(.sw-flat *):not(.sw-card--flat),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-card--lift:not(.sw-flat *) {
    background-color: transparent;
    box-shadow: var(--sw-soft-up-lg);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-well:not(.sw-flat *) {
    background-color: transparent;
    box-shadow: var(--sw-soft-down);
  }

  /* DIALOG (picked: flat). Levi, 2026-09-22: a card in the middle of the
     screen sits over the blurred dim, and that dim already lifts it. The soft
     light and shade on its two sides (the raised-big shadow) read as a white
     bar down the left and a dark bar down the right, so a dialog carries no
     shadow at all: the same surface, no fill, no hairline, flat. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-dialog:not(.sw-flat *) {
    background-color: var(--sw-canvas, #F8F5EF);
    border: 0;
    box-shadow: none;
  }

  /* STAT TILE (picked: the mix). A raised tile, because the number IS the thing. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-stat--tile:not(.sw-flat *) {
    background-color: transparent;
    box-shadow: var(--sw-soft-up-lg);
  }

  /* MENU / POPOVER (picked 2026-09-22: the same tone). Levi: "make the card
     not so white." A soft page is one tone and depth is drawn on it, never a
     second fill, and the menu was the one part still painted a notch lighter.
     Now it is the page's own tone, lifted by the warm shadow alone. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-menu:not(.sw-flat *) {
    background-color: var(--sw-soft-page, #F5F0E7);
    box-shadow: var(--sw-soft-float);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-menu.sw-flush:not(.sw-flat *) {
    background-color: transparent;
    box-shadow: none;
  }

  /* PROGRESS (picked: sunk slots). A track is a real slot with a real thing
     moving along it, so it stays sunk.
     WAITING has no look left: 2026-09-22 it stopped being drawn at all. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-bar:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-steps:not(.sw-flat *) > i:not(:is(.is-on, .is-active, .is-current)) {
    background-color: transparent;
    box-shadow: var(--sw-soft-down-sm);
  }

  /* STICKY HEADER (picked: the page sinks under it). Nothing on the bar itself;
     the work slides under a soft lip. Floating chrome (dock, bell) stays glass. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-sticky:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-sticky.sw-glass:not(.sw-flat *) {
    background: transparent;
    border-color: transparent;
    box-shadow: var(--sw-soft-lip);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-radius: 0;
  }

  /* The one orange in a multi-pick is spent on the count of how many. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-bulk-n:not(.sw-flat *) {
    padding: 1px 8px;
    border-radius: var(--sw-radius-pill, 999px);
    background-color: var(--sw-orange, #F38A3F);
    color: #fff;
  }

  /* ── The parts added for the Tasks conversion, drawn soft ─────────────────
     Same one rule as everything above: a pointer lifts it, a press sinks it,
     and the chosen thing stays sunk. */

  /* LATCHED (a starred project, a formatting mark that applies here): it
     stays pressed. No fill, because a fill is how the one orange action is
     told apart from everything else. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:is(.is-on, .is-active, .is-current):not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn:is(.is-on, .is-active, .is-current):not(.sw-flat *):hover:not(:disabled) {
    background-color: transparent;
    box-shadow: var(--sw-soft-down);
    transform: none;
  }
  /* PICKED keeps its sun wash and ink words, sunk like a choice. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn.is-picked:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn.is-picked:not(.sw-flat *):hover:not(:disabled) {
    background-color: var(--sw-chosen-soft, #EDF4E6);
    box-shadow: var(--sw-soft-down);
    transform: none;
  }
  /* RECORDING RIGHT NOW keeps its orange, like the family fills. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn.is-live:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn.is-live:not(.sw-flat *):hover:not(:disabled) {
    background-color: var(--sw-orange, #F38A3F);
    color: #fff;
  }
  /* DONE keeps its settled green over the primary's orange. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-btn.is-success:not(.sw-flat *) {
    background-color: var(--sw-moss-ink, #04765E);
    color: #fff;
    box-shadow: var(--sw-soft-flat);
  }
  /* ONE ACTION AND ITS CHOOSER take one shared shape, never two: the pair
     stands out and lifts as one button, and neither half moves on its own. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-split:not(.sw-flat *) {
    box-shadow: var(--sw-soft-out);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-split:not(.sw-flat *) > .sw-btn,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-split:not(.sw-flat *) > .sw-btn:hover:not(:active):not(:disabled):not([aria-disabled="true"]) { box-shadow: none; transform: none; }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-split:not(.sw-flat *) > .sw-btn + .sw-btn,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-split:not(.sw-flat *) > .sw-btn + .sw-btn:hover:not(:active):not(:disabled):not([aria-disabled="true"]) { box-shadow: inset 1px 0 0 var(--sw-soft-hair); }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-split:not(.sw-flat *):hover { box-shadow: var(--sw-soft-up); transform: translateY(-2px); }
  }

  /* THE IDENTITY TAG. A label has no depth; a tag you can press behaves like
     every other pressable thing, and the chosen one stays sunk. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(button, a, [role="button"]).sw-tag:not(.sw-flat *) {
    padding: 3px 9px;
    border-radius: var(--sw-radius-pill, 999px);
    box-shadow: var(--sw-soft-flat);
    transition: box-shadow 260ms var(--sw-soft-ease), color 200ms var(--sw-soft-ease);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(button, a, [role="button"]).sw-tag:not(.sw-flat *):hover:not(:is(.is-on, .is-active, .is-current)) { box-shadow: var(--sw-soft-up); }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(button, a, [role="button"]).sw-tag:is(.is-on, .is-active, .is-current):not(.sw-flat *) { box-shadow: var(--sw-soft-down); }

  /* PANELS are the same tone as the menu, on the same warm shadow. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-pop:not(.sw-flat *) {
    background-color: var(--sw-soft-page, #F5F0E7);
    box-shadow: var(--sw-soft-float);
  }
  /* The chosen row in a menu sinks, exactly like a picked list row. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-menu :is(.is-on, .is-active, .is-current):not(.sw-flat *):not(:where(.sw-switch)) {
    background-color: transparent;
    box-shadow: var(--sw-soft-down);
  }

  /* THE WRITTEN MARK on the soft surface: the square is the hairline the
     other checks wear, and the tick is the only thing that arrives. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check--mark:not(.sw-flat *),
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-check--mark:is(.is-on, .is-active, .is-current):not(.sw-flat *) {
    background-color: transparent;
    border-color: transparent;
    box-shadow: var(--sw-soft-flat), inset 0 0 0 1px var(--sw-soft-hair);
  }

  /* A BARE TYPING LINE draws its orange underline instead of sinking, so the
     words never shift sideways as it opens. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-input--bare:not(.sw-flat *) {
    border: 0;
    box-shadow: none;
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-input--bare:not(.sw-flat *):focus {
    box-shadow: inset 0 -1.5px 0 var(--sw-orange, #F38A3F);
  }
  /* Wrong keeps its clay hairline over the soft shadow. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-input, .sw-select, .sw-textarea).is-bad:not(.sw-flat *) {
    border-color: color-mix(in oklab, var(--sw-clay-ink, #993A31) 55%, transparent);
  }

  /* MINUS AND PLUS: the track is a sunk slot, the two buttons lift. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-stepper:not(.sw-flat *) {
    background-color: transparent;
    box-shadow: var(--sw-soft-down-sm);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-stepper:not(.sw-flat *) > button:hover { background-color: transparent; box-shadow: var(--sw-soft-up); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-stepper:not(.sw-flat *) > button:active { box-shadow: var(--sw-soft-down); }

  /* A COMPACT ROW behaves like a row: flat, lifting under the pointer. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row--dense:not(.sw-flat *) { border-bottom: 0; }
  /* NOW PLAYING is a warm stamp, not a dent, so it cannot read as picked. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-row.is-live:not(.sw-flat *) {
    background-color: var(--sw-chosen-soft, #EDF4E6);
    box-shadow: var(--sw-soft-flat);
  }
  /* WHERE IT WOULD LAND: the hole and the target are sunk slots. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-row-hole, .sw-drop):not(.sw-flat *) {
    background-color: transparent;
    box-shadow: var(--sw-soft-down-sm);
  }

  /* A COLOUR YOU PICKED (picked 2026-09-22: the ring, never sunk). Sinking a
     28px disc pours the soft shade and light across the whole colour, and the
     one you chose came out as the blurriest, palest dot in the grid. The
     colour IS the answer here, so the chosen one keeps its full colour and
     wears the Kit's ink ring on a gap of the panel's own tone. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-swatch:is(.is-on, .is-active, .is-current):not(.sw-flat *) {
    box-shadow: 0 0 0 2px var(--sw-soft-page, #F5F0E7), 0 0 0 4px var(--sw-ink, #373B4D);
  }

  /* LIVE SOUND and THE SCRUBBER sit in sunk slots, like every other track. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-range:not(.sw-flat *)::-webkit-slider-runnable-track { background-color: transparent; box-shadow: var(--sw-soft-down-sm); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-range:not(.sw-flat *)::-moz-range-track { background-color: transparent; box-shadow: var(--sw-soft-down-sm); }

  /* A NAME BEING RENAMED IN PLACE: hovering says it can be, focus sinks. */
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) button.sw-title:not(.sw-flat *):hover { box-shadow: var(--sw-soft-up); border-radius: 8px; }
  }

  /* THE SOLID STICKY BAR must stay opaque: it is opaque for a reason. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-sticky--solid:not(.sw-flat *) {
    background: var(--sw-soft-page, #F5F0E7);
    box-shadow: var(--sw-soft-lip);
  }

  /* A LABELLED DIVIDER uses the same rule colour as a section heading. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rule:not(.sw-flat *)::before,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rule:not(.sw-flat *)::after { background: var(--sw-soft-rule); }

  /* FOLD (Levi, 2026-09-29). The lines between topics are the soft rule; the
     line under the pointer lifts like a row and a press sinks it. An open fold
     is not held down: the body showing under it already says it is open. */
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-folds:not(.sw-flat *) { border-top-color: var(--sw-soft-rule); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-fold:not(.sw-flat *) { border-bottom-color: var(--sw-soft-rule); }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-fold-head:not(.sw-flat *) {
    background-color: transparent;
    box-shadow: var(--sw-soft-flat);
    transition: box-shadow 260ms var(--sw-soft-ease), transform 200ms var(--sw-soft-ease);
  }
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-fold-head:not(.sw-flat *):hover {
      background-color: transparent;
      box-shadow: var(--sw-soft-up);
    }
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-fold-head:not(.sw-flat *):active,
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-fold-head:not(.sw-flat *):focus-visible {
    outline: none;
    box-shadow: var(--sw-soft-down);
  }
  body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-fold-head:not(.sw-flat *):active {
    transform: translateY(1px) scale(.995);
    transition-duration: 120ms, 150ms;
  }

  /* THE QUIET DROPDOWN (Levi, 2026-09-29) keeps its hairline hidden on the soft
     surface too, until its row is pointed at. Focus still sinks it. */
  @media (hover: hover) and (pointer: fine) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-select--quiet:not(.sw-flat *):not(:focus) {
      border-color: transparent;
      box-shadow: var(--sw-soft-flat);
    }
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(tr, .sw-row):hover .sw-select--quiet:not(.sw-flat *):not(:focus) {
      border-color: var(--sw-soft-hair);
    }
  }

  /* Where a shadow cannot be seen, a ring comes back, in ink, never orange. */
  @media (forced-colors: active), (prefers-contrast: more) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-btn, .sw-switch, .sw-row, .sw-check, .sw-radio, .sw-input, .sw-select, .sw-textarea, .sw-fold-head, .sw-day, .sw-token-x):focus-visible,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-search:focus-within,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters > *:focus-visible,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail :is(a, button):focus-visible {
      outline: 2px solid var(--sw-ink, #373B4D);
      outline-offset: 2px;
    }
  }
  @media (prefers-reduced-motion: reduce) {
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-btn, .sw-row, .sw-input, .sw-select, .sw-textarea, .sw-search, .sw-check, .sw-radio, .sw-day, .sw-filters > *, .sw-rail a, .sw-rail button, .sw-rail svg, .sw-rail span, .sw-table td, .sw-fold-head) { transition: none; }
    /* The line above only reaches a part sitting flat. Each soft part's motion
       is set by a heavier selector, so every one is repeated here, word for
       word, to weigh the same and win by coming last. A pseudo-element with
       its own motion gets its own line (:is() cannot hold one). */
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(.sw-btn, .sw-split, .sw-input, .sw-select, .sw-textarea, .sw-search, .sw-check, .sw-radio, .sw-switch, .sw-day, .sw-row, .sw-fold-head):not(.sw-flat *),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-switch:not(.sw-flat *)::after,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-filters:not(.sw-flat *) > *,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) td,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-table:not(.sw-flat *) td::before,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) :is(button, a, [role="button"]).sw-tag:not(.sw-flat *),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > * svg,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > * > .sw-dot,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child svg,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child > .sw-dot,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg)),
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) a > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-sub:not(.sw-flat *) > :is(button, [role="button"]) > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after,
    body.sw-soft:not(.admin-mobile):not(.swtasks-app) .sw-rail-row:not(.sw-flat *) > :first-child > span:not(.sw-rail-count):not(.sw-dot):not(:has(svg))::after { transition: none; }
  }
}
