/* Olowo shared component styles
 *
 * Consumes tokens from variables.css and the loaded theme only. Spacing and
 * radius read public_html/theme/spacing.css values with literal fallbacks so
 * the scale stays single-sourced without requiring that file on every page.
 *
 * Class convention: .ol-<component>[__element][--modifier]
 * Mobile-first. Tablet and desktop refinements live at the bottom of the file.
 */

/* ---------------------------------------------------------------- Focus */

.ol-btn:focus-visible,
.ol-tabbar__tab:focus-visible,
.ol-card__disclosure:focus-visible,
.ol-link:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* ---------------------------------------------------------------- Button */

.ol-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2, 0.5rem);
  border: 1px solid transparent;
  border-radius: var(--radius-button, 9999px);
  font-family: inherit;
  font-weight: 600;
  line-height: 1.2;
  /* A button label is one line. Breaking "Add contact" across two lines makes
     the pill taller than every other control and reads as two labels. */
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--motion-fast) var(--motion-ease),
    color var(--motion-fast) var(--motion-ease),
    border-color var(--motion-fast) var(--motion-ease),
    opacity var(--motion-fast) var(--motion-ease);
}

.ol-btn--small {
  padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
  font-size: 0.75rem;
}
.ol-btn--medium {
  padding: var(--space-3, 0.75rem) var(--space-5, 1.25rem);
  font-size: 0.8125rem;
}
.ol-btn--large {
  padding: var(--space-4, 1rem) var(--space-6, 1.5rem);
  font-size: 0.9375rem;
}

.ol-btn--block {
  display: flex;
  width: 100%;
}

.ol-btn--primary {
  background: var(--color-action-primary);
  color: var(--color-action-on-primary);
}
.ol-btn--primary:hover:not(:disabled) {
  filter: brightness(1.08);
}

.ol-btn--secondary {
  background: var(--color-action-secondary);
  color: var(--color-action-on-secondary);
  border-color: var(--color-border-subtle);
}
.ol-btn--secondary:hover:not(:disabled) {
  background: var(--color-surface-sunken);
}

.ol-btn--danger {
  background: var(--tone-danger-bg);
  color: var(--tone-danger-fg);
}
.ol-btn--danger:hover:not(:disabled) {
  filter: brightness(0.96);
}

.ol-btn--ghost {
  background: transparent;
  color: var(--color-text-secondary);
}
.ol-btn--ghost:hover:not(:disabled) {
  background: var(--color-surface-sunken);
}

.ol-btn--success {
  background: var(--color-status-success);
  color: var(--color-action-on-primary);
}
.ol-btn--success:hover:not(:disabled) {
  filter: brightness(1.06);
}

/* Outlined counterpart to --success. Used for the Cancel half of the
   standard two-button footer (see .ol-actions). */
.ol-btn--success-outline {
  background: transparent;
  color: var(--color-status-success);
  border-color: var(--color-status-success);
}
.ol-btn--success-outline:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-status-success) 8%, transparent);
}

.ol-btn:disabled,
.ol-btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.ol-btn__spinner {
  width: 0.875em;
  height: 0.875em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--radius-full, 9999px);
  animation: ol-spin 0.7s linear infinite;
}

@keyframes ol-spin {
  to {
    transform: rotate(360deg);
  }
}

.ol-btn__icon {
  display: inline-flex;
  flex: none;
}

/* ----------------------------------------------------------- Profile tag */

.ol-profile-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  min-width: 0;
  max-width: 100%;
  padding: 0.12rem 0.45rem;
  border: 1px solid rgba(214, 211, 209, 0.65);
  border-radius: var(--radius-full, 9999px);
  background: #fff;
  color: #4e4336;
  font-size: 0.58rem;
  font-weight: 700;
  line-height: 1.4;
  vertical-align: middle;
  white-space: nowrap;
}

.ol-profile-tag__icon {
  display: inline-block;
  flex: none;
  width: 12px;
  height: 12px;
  background: #a8a29e;
  -webkit-mask: var(--ol-profile-tag-icon, url("/icons/ltd.png")) center /
    contain no-repeat;
  mask: var(--ol-profile-tag-icon, url("/icons/ltd.png")) center / contain
    no-repeat;
}

.ol-profile-tag__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* For profile labels placed over photography or another dark surface. */
.ol-profile-tag--glass {
  border-color: rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 2px 8px rgba(0, 0, 0, 0.12);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  backdrop-filter: blur(10px) saturate(130%);
}

.ol-profile-tag--glass .ol-profile-tag__icon {
  background: rgba(255, 255, 255, 0.88);
}

/* ---------------------------------------------------------------- Badge */

.ol-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: var(--space-1, 0.25rem) var(--space-3, 0.75rem);
  border-radius: var(--radius-full, 9999px);
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}

.ol-badge--neutral {
  background: var(--tone-neutral-bg);
  color: var(--tone-neutral-fg);
}
.ol-badge--brand {
  background: var(--tone-brand-bg);
  color: var(--tone-brand-fg);
}
.ol-badge--accent {
  background: var(--tone-accent-bg);
  color: var(--tone-accent-fg);
}
.ol-badge--success {
  background: var(--tone-success-bg);
  color: var(--tone-success-fg);
}
.ol-badge--warning {
  background: var(--tone-warning-bg);
  color: var(--tone-warning-fg);
}
.ol-badge--danger {
  background: var(--tone-danger-bg);
  color: var(--tone-danger-fg);
}
.ol-badge--royal {
  background: var(--tone-royal-bg);
  color: var(--tone-royal-fg);
}
/* Terracotta. The token already existed for status surfaces; a badge earns it
   so money movement can sit beside an amber compliance pill and still read as a
   separate kind of thing. */
.ol-badge--attention {
  background: var(--tone-attention-bg);
  color: var(--tone-attention-fg);
}

/* Team roles. One tone per role rather than reusing the status tones, so a
   role's colour never implies success or danger. */
.ol-badge--role-owner {
  background: var(--role-owner-bg);
  color: var(--role-owner-fg);
}
.ol-badge--role-admin {
  background: var(--role-admin-bg);
  color: var(--role-admin-fg);
}
.ol-badge--role-manager {
  background: var(--role-manager-bg);
  color: var(--role-manager-fg);
}
.ol-badge--role-bookkeeper {
  background: var(--role-bookkeeper-bg);
  color: var(--role-bookkeeper-fg);
}
.ol-badge--role-accountant {
  background: var(--role-accountant-bg);
  color: var(--role-accountant-fg);
}
.ol-badge--role-viewer {
  background: var(--role-viewer-bg);
  color: var(--role-viewer-fg);
}

.ol-badge--outline {
  background: transparent;
  border: 1px solid var(--color-border-subtle);
  color: var(--color-text-secondary);
}

/* Leading icon slot. Artwork badges (profile types) carry their own colour, so
   this only handles alignment and keeps the glyph from being squashed. */
.ol-badge > [aria-hidden="true"]:not(.ol-badge__dot) {
  display: inline-flex;
  align-items: center;
  flex: none;
}

.ol-badge__dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--radius-full, 9999px);
  background: currentColor;
  flex: none;
}

/* ---------------------------------------------------------------- Avatar */

.ol-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  overflow: hidden;
  border-radius: var(--radius-full, 9999px);
  background: var(--tone-brand-bg);
  color: var(--tone-brand-fg);
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
}

.ol-avatar--sm {
  width: var(--size-avatar-sm, 2rem);
  height: var(--size-avatar-sm, 2rem);
  font-size: 0.75rem;
}
.ol-avatar--md {
  width: var(--size-avatar-md, 2.5rem);
  height: var(--size-avatar-md, 2.5rem);
  font-size: 0.875rem;
}
.ol-avatar--lg {
  width: var(--size-avatar-lg, 3rem);
  height: var(--size-avatar-lg, 3rem);
  font-size: 1rem;
}

.ol-avatar__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---------------------------------------------------------------- Card */

/* Canonical application card surface. Feature cards add layout classes, but
   background, radius and elevation belong here so Projects, Teams, Tasks and
   every other page render the same object. */

.ol-card {
  box-sizing: border-box;
  background: var(--color-surface-card);
  border: 0;
  border-radius: var(--radius-card-lg, 1.25rem);
  box-shadow: var(--elevation-card);
}

.ol-card--section {
  padding: var(--space-4, 1rem);
}
.ol-card--summary {
  padding: var(--space-4, 1rem);
}
.ol-card--expandable {
  padding: var(--space-4, 1rem);
}
.ol-card--empty {
  padding: var(--space-8, 2rem) var(--space-5, 1.25rem);
  text-align: center;
}

/* Block inside a drawer. White, 20px radius, 16px padding, separated by a hairline
   border rather than elevation — the drawer is already a raised surface, so
   stacking shadows inside it made every section look like it was floating. */
.ol-card--nested {
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-card-lg, 1.25rem);
  box-shadow: none;
  padding: var(--space-4, 1rem);
}

/* Size, radius and inset come from the global hero tokens so this card and the
   dashboard balance cards stay the same object across pages. */
.ol-card--hero {
  position: relative;
  overflow: hidden;
  padding: 0;
  border: none;
  border-radius: var(--hero-card-radius, 20px);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  min-height: var(--hero-card-min-height, 9.25rem);
}

.ol-card__hero-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.85;
}

.ol-card__hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(20, 16, 12, 0.15) 0%,
    rgba(20, 16, 12, 0.72) 100%
  );
}

.ol-card__hero-body {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--space-3, 0.75rem);
  min-height: inherit;
  padding: var(--hero-card-padding, 1rem);
}

.ol-card__hero-title {
  margin: 0;
  font-size: clamp(1.0625rem, 3.6vw, 1.375rem);
  font-weight: 700;
  line-height: 1.2;
  color: var(--color-text-inverse);
  text-shadow: 0 1px 2px rgba(20, 16, 12, 0.45);
}

.ol-card__hero-subtitle {
  margin: calc(var(--space-3, 0.75rem) * -1) 0 0;
  font-size: 0.8125rem;
  color: var(--color-text-inverse);
  opacity: 0.78;
}

.ol-card__title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

.ol-card__subtitle {
  margin: var(--space-1, 0.25rem) 0 0;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

.ol-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3, 0.75rem);
}

/* The actions slot holds fixed-size controls -- normally the disclosure -- so it
   must never shrink. Without this it is a plain flex item that gives up width to
   a header whose text or chips ask for more than the card has, and the control
   inside overflows its own box: the chevrons then sit at a different offset on
   every card, some of them past the card's padding. Header content absorbs the
   squeeze instead. */
.ol-card__actions {
  flex: none;
}

.ol-card__body {
  margin-top: var(--space-3, 0.75rem);
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-card__body a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* Whole card acts as the trigger for a drawer. */
.ol-card--tappable {
  cursor: pointer;
  transition:
    transform var(--motion-default) var(--motion-ease),
    box-shadow var(--motion-default) var(--motion-ease);
}

.ol-card--tappable:hover {
  transform: translateY(-1px);
}
.ol-card--tappable:active {
  transform: translateY(0);
}

.ol-card--tappable:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.ol-card__disclosure {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  align-self: center;
  justify-content: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-surface-sunken);
  color: var(--color-text-secondary);
  line-height: 0;
  padding: 0;
  vertical-align: middle;
  cursor: pointer;
  transition:
    background-color var(--motion-default) var(--motion-ease),
    color var(--motion-default) var(--motion-ease),
    transform var(--motion-default) var(--motion-ease);
}

/* The disclosure is always the fixed circular control; the icon is a smaller
   child centred inside it. Keeping the SVG off the outer class prevents its
   viewBox from shifting the chevron within the circle. */
.ol-card__disclosure > svg {
  display: block;
  flex: none;
  width: 1rem;
  height: 1rem;
  margin: auto;
}

.ol-card__disclosure[aria-expanded="true"],
.ol-card__disclosure.is-open,
.ol-card[open] .ol-card__disclosure {
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
  transform: rotate(180deg);
}

/* ------------------------------------------------------------- Team card */

/* The Team page pairs standalone tasks with access management in one compact
   segmented surface. The switch sits between profile scope and the shared
   summary card, matching the mobile hierarchy used across the dashboard. */
/* Fills the column like the toolbar and the task cards under it, so the tasks
   view reads as one stack instead of a 345px pill floating over full-width
   content. The 345px reference frame survives as the max, which is also what
   keeps a two-tab control from stretching absurdly wide on a desktop column. */
.ol-team-switch {
  box-sizing: border-box;
  display: flex;
  align-items: stretch;
  width: 100%;
  min-height: 2.75rem;
  margin: var(--space-4, 2rem) 0;
  padding: 0.25rem;
  border: 0.5px solid var(--color-switch-track-border, #75461d);
  border-radius: 1rem;
  background: var(--color-switch-track, #fbf4ef);
}

.ol-team-switch__tab {
  box-sizing: border-box;
  display: flex;
  flex: 1 1 0;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  min-width: 0;
  min-height: 2.25rem;
  /* Tightens to 8px on a narrow phone and opens up on a wide column, so the two
     labels stay optically centred at either end instead of only one. */
  padding: 0.5rem clamp(0.5rem, 3vw, 1.25rem);
  border: 0;
  border-radius: 0.5rem;
  background: var(--color-switch-option-bg, transparent);
  color: var(--color-switch-option-fg, #8e8e8e);
  font-family: "Manrope", sans-serif;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.25rem;
  cursor: pointer;
  transition:
    background-color var(--motion-default) var(--motion-ease),
    color var(--motion-default) var(--motion-ease);
}

.ol-team-switch__tab:hover:not(.is-active) {
  color: var(--color-switch-option-fg-hover, #75461d);
}

.ol-team-switch__tab.is-active {
  border-radius: 0.8125rem;
  background: var(--color-switch-selected-bg, #75461d);
  color: var(--color-switch-selected-fg, #fff);
  font-weight: 600;
}

/* Shrinks and truncates rather than pushing the count out of the tab, so a
   longer label than "Tasks"/"Teams" can't break the control. */
.ol-team-switch__label {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}

/* A circle at one digit that grows into a pill at two or more. Fixed width plus
   padding gave the content box 4px, so any count above 9 spilled the disc. */
.ol-team-switch__count {
  box-sizing: border-box;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  min-width: 1.125rem;
  min-height: 1.125rem;
  padding: 0 0.3125rem;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-switch-count-bg, #e1e1e1);
  color: var(--color-switch-count-fg, #8e8e8e);
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.125rem;
  text-align: center;
}

.ol-team-switch__tab.is-active .ol-team-switch__count {
  background: var(--color-switch-selected-count-bg, #fbf4ef);
  color: var(--color-switch-selected-count-fg, #75461d);
}

.ol-team-switch__tab:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* --------------------------------------------------- Workspace switch */

/* One level up from the tab bar: which set of books the page describes — this
   account's own, or a workspace someone shared from /team. It sits between the
   profile header and the profile scope carousel on every signed-in page, above
   the page's own content, so it reads as navigation rather than a control:
   plain labels on the page ground, the chosen one in brown with a short rule
   under it. No track, no pill — the segmented pill is reserved for switches
   that sit inside a section (see .ol-team-switch), and stacking two pills a row
   apart made the page header look like two competing controls. */
.ol-team-switch--workspace {
  display: flex;
  align-items: center;
  gap: 1.5625rem;
  width: 100%;
  max-width: none;
  min-height: 2rem;
  margin: 0.25rem 0 1rem;
  padding: 0.25rem;
  border: 0;
  border-radius: 3.9375rem;
  background: none;
  /* More names than the column holds scroll sideways rather than wrapping into
     a second row that reads as a separate group. */
  overflow-x: auto;
  scrollbar-width: none;
}

.ol-team-switch--workspace::-webkit-scrollbar {
  display: none;
}

.ol-team-switch--workspace .ol-team-switch__tab {
  flex: 0 0 auto;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 0.25rem;
  min-height: 1.5rem;
  padding: 0;
  border-radius: 2.8125rem;
  background: none;
  color: var(--color-switch-option-fg, #8e8e8e);
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.125rem;
}

.ol-team-switch--workspace .ol-team-switch__tab.is-active {
  border-radius: 2.8125rem;
  background: none;
  color: var(--color-action-active, #75461d);
  font-weight: 700;
}

/* The rule under the chosen name. Deliberately shorter than the label and
   centred on it, so a long trading name does not drag a full-width bar across
   the row. Painted on the label rather than the button because the button
   carries the bottom padding that sets the gap. */
.ol-team-switch--workspace .ol-team-switch__label {
  position: relative;
  overflow: visible;
  padding-bottom: 0.375rem;
}

.ol-team-switch--workspace .ol-team-switch__tab.is-active .ol-team-switch__label::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 1.5rem;
  height: 0.125rem;
  transform: translateX(-50%);
  border-radius: 0.75rem;
  background: var(--color-action-active, #75461d);
}

.ol-team-hero {
  min-height: 11rem;
  box-shadow: 0 1rem 2.25rem rgba(28, 25, 23, 0.22);
}

.ol-team-hero--members .ol-card__hero-media {
  opacity: 0.85;
}

.ol-team-hero--members .ol-card__hero-scrim {
  background: linear-gradient(
    180deg,
    rgba(20, 16, 12, 0.12),
    rgba(20, 16, 12, 0.78)
  );
}

/* The stat row is its own band, not a caption under the heading, so the team
   heroes open the default card rhythm up between the title and the tiles. */
.ol-team-hero .ol-card__hero-body {
  justify-content: flex-end;
  gap: var(--space-6, 1.5rem);
}

/* Tile type runs one step larger inside the team heroes than the shared
   StatTile default: these four sit on a photo, so the figure and its label have
   to hold their own against it. The tiles carry no caption line here -- figure
   over descriptor only. The ramp stays fluid so the row still fits four tiles
   on a narrow phone. */
.ol-team-hero .ol-stat__value {
  font-size: clamp(1rem, 3.4vw, 1.5rem);
}

.ol-team-hero .ol-stat__label {
  font-size: clamp(0.6875rem, 2.2vw, 0.875rem);
}

/* These heroes lay their tiles on a grid rather than the shared wrapping flex
   row. Five tiles -- the four workflow stages plus Add Task -- cannot stay one
   row on a phone, and a wrapping flex row hands the two leftovers half a hero
   each; on a grid every tile keeps the same width whatever row it lands on.
   `auto-fit` reads the column count off the space available and collapses the
   empty tracks, so the row is five across again the moment five fit. The 5rem
   floor is the narrowest a tile may get before a column is dropped -- enough to
   hold "In Progress" on one line at the label size these heroes use. */
.ol-team-hero .ol-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 5rem), 1fr));
}

.ol-team-hero--tasks {
  background: #171612;
}

/* Both team heroes lay their four tiles out the same way: the tiles pair off
   into a 2x2 block and the action -- Add Task, Add Team Member -- stands beside
   them as one double-height tile. The four figures read as a set rather than a
   row that breaks three-then-two, and the one action in the hero is the largest
   thing in it. The action tile is placed explicitly, so the stat tiles
   auto-flow around it and keep their document order. The `:has` guard keeps the
   plain four-tile row when the viewer has no action to take. */
.ol-team-hero .ol-stats:has(.ol-stat--action) {
  grid-template-columns: 1fr 1fr 1.1fr;
}

.ol-team-hero .ol-stat--action {
  grid-column: 3;
  grid-row: 1 / span 2;
  align-items: center;
  justify-content: center;
  gap: var(--space-2, 0.5rem);
  text-align: center;
  /* The same frosted fill as the stage tiles beside it: this tile is one of
     the set, and the green belongs to the mark inside it rather than to the
     whole panel. Overrides the solid green `.ol-stat--action` default. */
  background: rgba(255, 255, 255, 0.14);
  color: var(--color-text-inverse);
}

/* The plus mark only appears on this tile -- the house Add mark at hero size,
   white on green. Elsewhere `.ol-stat__icon` stays hidden, and the disc and
   glyph now come from `.ol-add-mark`; all this rule does is unhide the wrapper
   and say how big the mark should be here. */
.ol-team-hero .ol-stat--action .ol-stat__icon {
  --add-mark-size: clamp(2rem, 9vw, 2.75rem);
  display: block;
}

.ol-team-hero .ol-stat--action .ol-stat__label {
  font-size: clamp(0.875rem, 3.4vw, 1.25rem);
  font-weight: 700;
}

/* "Add Team Member" is meant to read over two lines. The label carries a
   non-breaking space after "Add", so "Add Team" cannot split; capping the line
   box below the width of the whole phrase forces the remaining wrap point to be
   taken however wide the hero gets. */
.ol-team-hero--members .ol-stat--action .ol-stat__label {
  max-width: 9ch;
  margin-inline: auto;
}

/* ------------------------------------------------------------- Task card */

/* Canonical task-card surface shared by Team, dashboards and other task feeds.
   It owns its shell and composes the shared Badge, Button and Avatar pieces. */
.ol-team-card {
  padding: var(--space-4, 1rem);
}

/* The card's own palette. Every hex the task-card spec names lives here and
   nowhere else in this block, so retuning a tone is a one-line change and the
   sub-parts below only ever reference these. The 345px reference frame sets the
   internal rhythm; the card itself fills its column like its list siblings. */
.ol-task-card {
  --task-card-ink: #212121;
  --task-card-ink-strong: #1f1f1f;
  --task-card-ink-body: #4b4b4b;
  --task-card-ink-muted: #8e8e8e;
  --task-card-sunken: #fafafa;
  --task-card-hairline: #e1e1e1;
  --task-card-rule: #eeeeee;
  --task-card-chip: #f5f5f5;
  --task-card-danger: #ec2d30;
  --task-card-danger-soft: #fef2f2;
  --task-card-danger-line: #ffccd2;
  /* Rejection is a judgement on the work, not an alarm about the task, so it
     answers in a muted clay rather than the delete control's red. */
  --task-card-reject: #b0654c;
  --task-card-reject-soft: #fbf5f3;
  --task-card-reject-line: #ecd9d2;
  --task-card-warning: #fe9b0e;
  --task-card-warning-soft: #fff9ee;
  --task-card-warning-line: #ffeab3;
  --task-card-go: #518c15;
  --task-card-brown: #75461d;
  --task-card-brown-soft: #fbf4ef;
  --task-card-brown-line: #f1dbc7;
  --task-card-gold-soft: #fdf5ed;

  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  padding: var(--space-4, 1rem);
  border: 0;
  border-radius: var(--radius-card-lg, 1.25rem);
  background: var(--color-surface-card, #fff);
  box-shadow: var(--elevation-card);

  /* The card reflows against its own width, not the viewport, so a task feed in
     a narrow column behaves the same as one on a phone. Browsers without
     container queries keep the wide layout, which wraps rather than breaks. */
  container-type: inline-size;
  container-name: task-card;
}

/* The card a notification's ?task= link opened: a brief gold pulse separates it
   from the board around it while the reader finds the thread. */
.ol-task-card--from-link {
  animation: ol-task-card-link-pulse 1.2s ease-out 2;
  box-shadow:
    var(--elevation-card),
    0 0 0 1px var(--task-card-gold-soft);
}

/* The exact comment a notification's link opened: the same pulse around the
   reply bubble, plus a gold wash on the message itself so it reads at once
   against the rest of the thread. */
.ol-task-card__comment--from-link {
  animation: ol-task-card-link-pulse 1.2s ease-out 2;
  background: var(--task-card-gold-soft);
}

@keyframes ol-task-card-link-pulse {
  0% {
    box-shadow:
      var(--elevation-card),
      0 0 0 0 rgba(244, 175, 29, 0.55);
  }
  100% {
    box-shadow:
      var(--elevation-card),
      0 0 0 0.75rem rgba(244, 175, 29, 0);
  }
}

/* The inner wrapper owns the content rhythm while the TaskCard root owns the
   reusable surface, radius and elevation. */
.ol-task-card__inner {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

/* Priority, category, status, rule and delete share one wrapping row, so the
   status group drops to a second line exactly as the frame shows once the
   labels run out of width. */
.ol-task-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  gap: 0.625rem;
  min-width: 0;
}

/* 24px tall: a 18px line box with 2px above and below, borders inside. */
.ol-task-card__meta .ol-badge {
  box-sizing: border-box;
  gap: var(--space-1, 0.25rem);
  min-height: 1.5rem;
  padding: 0.125rem 0.375rem;
  border: 1px solid transparent;
  border-radius: 3.375rem;
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.125rem;
}

.ol-task-card__meta .ol-badge--danger {
  border-color: var(--task-card-danger-line);
  background: var(--task-card-danger-soft);
  color: var(--task-card-danger);
}

.ol-task-card__meta .ol-badge--warning {
  border-color: var(--task-card-warning-line);
  background: var(--task-card-warning-soft);
  color: var(--task-card-warning);
}

.ol-task-card__meta .ol-badge--neutral,
.ol-task-card__meta .ol-badge--outline {
  border-color: var(--task-card-hairline);
  background: var(--task-card-chip);
  color: var(--task-card-ink-muted);
}

/* High priority is the one chip carried in the brand brown rather than a
   traffic-light colour: red is reserved for urgent, amber for the middle. */
.ol-task-card__meta .ol-badge--brand {
  border-color: var(--task-card-brown-line);
  background: var(--task-card-brown-soft);
  color: var(--task-card-brown);
}

.ol-task-card__meta .ol-badge--success {
  border-color: #cfe4bd;
  background: #f4f9ef;
  color: var(--task-card-go);
}

.ol-task-card__meta-divider {
  flex: none;
  width: 1px;
  height: 1.25rem;
  border-radius: 1.75rem;
  background: var(--task-card-rule);
}

.ol-task-card__delete-form {
  display: flex;
}

/* Sits beside delete in the same 30px round-button rhythm, carried in the
   quiet chip grey — editing a task is routine, deleting one is not. */
.ol-task-card__edit {
  display: grid;
  place-items: center;
  width: 1.875rem;
  height: 1.875rem;
  padding: 0;
  border: 1px solid var(--task-card-hairline);
  border-radius: var(--radius-full, 9999px);
  background: var(--task-card-chip);
  color: var(--task-card-ink-muted);
  cursor: pointer;
}

.ol-task-card__edit:hover {
  border-color: var(--task-card-brown-line);
  color: var(--task-card-brown);
}

.ol-task-card__edit:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.ol-task-card__delete {
  display: grid;
  place-items: center;
  width: 1.875rem;
  height: 1.875rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 9999px);
  background: var(--task-card-danger-soft);
  color: var(--task-card-danger);
  cursor: pointer;
}

.ol-task-card__delete:hover {
  background: var(--task-card-danger-line);
}

.ol-task-card__delete:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* Who the task is on, as the house name-tag pill. Sits under the badge row so
   the person reads as a fact about the task rather than another category chip,
   and shrinks a step: a card is a summary, not the allocation sheet. */
.ol-task-card__people {
  gap: 0.3rem;
  margin-top: 0.7rem;
  min-width: 0;
}

.ol-task-card__people .ol-name-tag {
  padding: 0.2rem 0.55rem;
  font-size: 0.65rem;
}

/* Copy, footer and comment thread stack on a 14px rhythm. */
.ol-task-card__main {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.875rem;
  min-width: 0;
}

.ol-task-card__copy {
  display: grid;
  gap: 0.1875rem;
  min-width: 0;
}

.ol-task-card__title {
  margin: 0;
  color: var(--task-card-ink);
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Completed work still has to be readable, so the rule is the only signal the
   title carries; the text drops one step rather than fading out. */
.ol-task-card__title--done {
  color: var(--task-card-ink-body);
  text-decoration: line-through;
}

.ol-task-card__description {
  margin: 0;
  color: var(--task-card-ink-muted);
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.125rem;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-task-card__description a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* Written text on this card -- the task's own wording and every reply in the
   thread -- clamps to the same four lines. Left at full height one numbered
   brief pushes the due date, the status button and the whole thread past the
   bottom of the screen, and one long reply buries the rest of the conversation.
   Four lines is enough to tell one from another; the disclosure below each
   opens the rest in place rather than on another page. */
.ol-task-card__clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}

/* A text link rather than a button chip: it sits inside the copy block and must
   not read as another action alongside Start task in the footer below. */
.ol-task-card__more {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  gap: var(--space-1, 0.25rem);
  margin-top: 0.125rem;
  padding: 0;
  border: 0;
  background: none;
  color: var(--task-card-brown);
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  cursor: pointer;
}

.ol-task-card__more:hover {
  text-decoration: underline;
}

.ol-task-card__more:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-md, 0.5rem);
}

.ol-task-card__more-icon {
  display: inline-flex;
  flex: none;
  transition: transform var(--motion-default) var(--motion-ease);
}

.ol-task-card__more-icon > svg {
  display: block;
  width: 1rem;
  height: 1rem;
  color: currentColor;
}

/* Open: the chevron flips to point back up at the text it unfolded. */
.ol-task-card__more-icon.is-open {
  transform: rotate(180deg);
}

.ol-task-card__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.875rem;
  min-height: 3rem;
  padding: var(--space-2, 0.5rem);
  border: 0;
  border-radius: 1rem;
  background: var(--task-card-sunken);
}

/* Added on the left, due on the right: one row that says how long the task has
   been waiting next to when it is wanted. It takes the space the status button
   leaves, and drops to a row of its own when there is not enough. */
.ol-task-card__dates {
  display: flex;
  flex: 1 1 12rem;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-2, 0.5rem) 0.875rem;
  min-width: 0;
}

/* Undated tasks have nothing on the left, and the due side still owns the right
   edge — otherwise "No due date" slides over to where "Added" usually sits. */
.ol-task-card__dates > .ol-task-card__due {
  margin-left: auto;
}

.ol-task-card__added {
  display: flex;
  flex: 0 1 auto;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
  color: var(--task-card-ink-muted);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
}

.ol-task-card__added > * {
  white-space: nowrap;
}

.ol-task-card__added time {
  overflow: hidden;
  text-overflow: ellipsis;
}

.ol-task-card__due {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
  color: var(--task-card-ink-muted);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
}

/* Overdue, today and tomorrow: the dot inherits the colour with the text. */
.ol-task-card__due--urgent {
  color: var(--task-card-danger);
}

.ol-task-card__due-dot {
  flex: none;
  width: 0.1875rem;
  height: 0.1875rem;
  border-radius: var(--radius-full, 9999px);
  background: currentColor;
}

.ol-task-card__due > * {
  white-space: nowrap;
}
.ol-task-card__due time {
  overflow: hidden;
  text-overflow: ellipsis;
}
.ol-task-card__undated {
  color: var(--task-card-ink-muted);
  font-weight: 500;
}

/* 32px tall, so the 48px footer is 8px of padding either side of the pill. */
.ol-task-card__status-button.ol-btn {
  gap: var(--space-1, 0.25rem);
  min-height: 2rem;
  padding: 0.4375rem var(--space-1, 0.25rem);
  border: 0;
  border-radius: 0.625rem;
  background: var(--task-card-go);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  white-space: nowrap;
}

/* Same 32px pill as the green action, in the outline treatment that keeps
   "undo this completion" from competing with the live tasks above it. */
.ol-task-card__reopen-button.ol-btn {
  gap: var(--space-1, 0.25rem);
  min-height: 2rem;
  padding: 0.4375rem var(--space-1, 0.25rem);
  border: 1px solid var(--task-card-brown-line);
  border-radius: 0.625rem;
  background: var(--task-card-brown-soft);
  color: var(--task-card-brown);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  white-space: nowrap;
}

.ol-task-card__reopen-button.ol-btn:hover {
  background: var(--task-card-brown-line);
}

/* Review answers with two pills rather than one, so they share a row and keep
   the gap the footer already uses between the dates and the action. */
.ol-task-card__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2, 0.5rem);
}

/* The refusal, in the same 32px pill as Mark complete but in the card's muted
   clay outline: it has to be as reachable as the green one without reading as
   the expected answer, and without shouting louder than either. */
.ol-task-card__reject-button.ol-btn {
  gap: var(--space-1, 0.25rem);
  min-height: 2rem;
  padding: 0.4375rem var(--space-2, 0.5rem);
  border: 1px solid var(--task-card-reject-line);
  border-radius: 0.625rem;
  background: var(--task-card-reject-soft);
  color: var(--task-card-reject);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  white-space: nowrap;
}

.ol-task-card__reject-button.ol-btn:hover:not(:disabled) {
  background: var(--task-card-reject-line);
  filter: none;
}

/* The reason panel takes a row of its own under both pills, whatever width the
   card is: the footer is a wrapping flex row, so a 100% basis is the break. */
.ol-task-card__reject {
  display: flex;
  flex: 1 1 100%;
  flex-direction: column;
  gap: var(--space-2, 0.5rem);
  padding: var(--space-2, 0.5rem);
  border: 1px solid var(--task-card-reject-line);
  border-radius: 0.75rem;
  background: var(--task-card-reject-soft);
}

/* /team does not load the dashboard's global [x-cloak] rule, so the panel is
   hidden by its own selector until Alpine takes over the x-show. */
.ol-task-card__reject[x-cloak] {
  display: none;
}

.ol-task-card__reject-label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  color: var(--task-card-reject);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
}

/* Says the field is compulsory before the browser has to. Sentence case and a
   tinted pill rather than a solid red badge -- it is a note, not a warning. */
.ol-task-card__reject-required {
  padding: 0.0625rem 0.375rem;
  border-radius: var(--radius-full, 9999px);
  background: var(--task-card-reject-line);
  color: var(--task-card-reject);
  font-size: 0.625rem;
  font-weight: 700;
}

.ol-task-card__reject-input {
  /* No global border-box reset here, so the padding is claimed explicitly --
     and word-break matches the mention mirror's, which otherwise wraps the
     painted text one word later than the field it sits behind. */
  box-sizing: border-box;
  width: 100%;
  word-break: break-word;
  min-height: 7.5rem;
  padding: var(--space-2, 0.5rem);
  border: 1px solid var(--task-card-reject-line);
  border-radius: 0.625rem;
  background: #fff;
  color: var(--task-card-ink);
  font: inherit;
  font-size: 0.8125rem;
  line-height: 1.25rem;
  resize: vertical;
}

/* The mention mirror turns the field's own background transparent, so the white
   the reason is typed on is painted by the wrapper it inserts. */
.ol-task-card__reject .ol-mention {
  background: #fff;
  border-radius: 0.625rem;
}

.ol-task-card__reject-input:focus-visible {
  outline: 2px solid var(--task-card-reject);
  outline-offset: 1px;
}

/* The browser's own "fill this in" state, matched to the panel's clay. */
.ol-task-card__reject-input:invalid {
  border-color: var(--task-card-reject);
}

/* The picked files, listed exactly as the reply composer lists its own -- the
   panel is a comment box that happens to change the status, so it carries the
   same tray, renameable names and all. */
.ol-task-card__reject-files {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1, 0.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ol-task-card__reject-files[x-cloak] {
  display: none;
}

/* Tools sit as one row: the attachment chip on the left, the two answers on
   the right, so the panel reads left-to-right as write, attach, send. */
.ol-task-card__reject-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2, 0.5rem);
}

/* Cancel and Reject & comment travel together and keep to the right, so they
   wrap as a pair under the Upload chip on a narrow card. */
.ol-task-card__reject-answers {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  margin-left: auto;
}

/* The composer lets its Upload chip give up the label when the row is tight;
   here there is no second chip to make room for, so the chip keeps its word and
   the answers wrap below it instead. */
.ol-task-card__reject-tools .ol-task-card__chip--upload {
  flex: none;
}

.ol-task-card__reject-cancel.ol-btn {
  min-height: 2rem;
  padding: 0.4375rem var(--space-2, 0.5rem);
  border-radius: 0.625rem;
  color: var(--task-card-ink-muted);
  font-size: 0.75rem;
  font-weight: 600;
}

.ol-task-card__reject-send.ol-btn {
  gap: var(--space-1, 0.25rem);
  min-height: 2rem;
  padding: 0.4375rem 0.75rem;
  border: 0;
  border-radius: 0.625rem;
  background: var(--task-card-reject);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  white-space: nowrap;
}

/* --- Comment thread ----------------------------------------------------- */

.ol-task-card__comments {
  margin: 0;
  padding: var(--space-2, 0.5rem);
  border: 0;
  border-radius: 1rem;
  background: var(--task-card-sunken);
  color: var(--task-card-ink-strong);
}

.ol-task-card__comments summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.875rem;
  min-height: 1.5rem;
  cursor: pointer;
  list-style: none;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
}

.ol-task-card__comments summary::-webkit-details-marker {
  display: none;
}

.ol-task-card__comments summary:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-md, 0.5rem);
}

.ol-task-card__comments-label,
.ol-task-card__comments-aside {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-task-card__comments-label > svg {
  flex: none;
  color: var(--task-card-brown);
}

/* The locked chip rides in the label group, so it needs the meta row's compact
   badge metrics without inheriting that row's bordered palette. */
.ol-task-card__comments-label .ol-badge {
  flex: none;
  gap: var(--space-1, 0.25rem);
  min-height: 1.5rem;
  padding: 0.1875rem 0.375rem;
  border: 0;
  border-radius: 3.375rem;
  background: var(--task-card-rule);
  color: var(--task-card-ink-muted);
  font-size: 0.5rem;
  font-weight: 600;
  line-height: 1.125rem;
}

/* The frame draws this as a bare 24px chevron in Neutral/500 -- no circular
   chip, no fill, and the same grey whether the thread is open or shut. It is the
   one disclosure on the card that is not the shared circular control, so it owns
   its own box rather than borrowing ol-card__disclosure. */
.ol-task-card__comments-aside {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--task-card-ink-muted);
  transition: transform var(--motion-default) var(--motion-ease);
}

.ol-task-card__comments-aside > svg {
  display: block;
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  color: currentColor;
  stroke-width: 1.5;
}

/* Open: the chevron flips to point back up at its heading. Colour does not
   change -- the rotation is the whole state. */
.ol-task-card__comments[open] .ol-task-card__comments-aside {
  transform: rotate(90deg);
}

.ol-task-card__thread {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-8, 2rem);
  margin-top: var(--space-4, 1rem);
  min-width: 0;
}

/* The composer needs air below the thread; the locked notice is part of the
   same block of read-only history, so it sits closer. */
.ol-task-card__thread--locked {
  gap: var(--space-4, 1rem);
}

.ol-task-card__comment-list {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-task-card__comment {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  min-width: 0;
  padding: var(--space-2, 0.5rem);
  border: 0;
  border-radius: 0.75rem;
  background: #fff;
}

/* The thread runs at 24px avatars, below the sm primitive's 32px. */
.ol-task-card__comment .ol-avatar {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.5625rem;
}

.ol-task-card__comment-body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-4, 1rem);
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-task-card__comment-body a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* A name, a role, a timestamp and two controls never fit one phone-width row:
   the aside would not shrink, so the role pill was clipped and painted over the
   time. The head is two lines instead — who spoke, then when and what you can
   do about it — and each line wraps on its own. */
.ol-task-card__comment-head {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.25rem;
  min-width: 0;
}

.ol-task-card__comment-author {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
}

.ol-task-card__comment-author strong {
  min-width: 0;
  overflow: hidden;
  color: var(--task-card-ink);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.25rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ol-task-card__comment-author .ol-badge {
  flex: none;
  padding: 0.1875rem 0.375rem;
  border-radius: 3.375rem;
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.125rem;
}

.ol-task-card__comment-time {
  flex: none;
  color: var(--task-card-ink-strong);
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.125rem;
}

.ol-task-card__comment-text {
  margin: 0;
  color: var(--task-card-ink-body);
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.125rem;
  overflow-wrap: anywhere;
}

/* The reply and its Show more sit together as one block: .ol-task-card__comment-body
   stacks head, text and attachments on a 1rem rhythm, and the chevron belongs to
   the text rather than floating between it and the attachment tray. */
.ol-task-card__comment-copy {
  display: grid;
  gap: 0.1875rem;
  min-width: 0;
}

/* Time, the edited marker and the author's own controls read as one meta line
   under the name. It wraps rather than overflowing, so a long role or a second
   control drops to the next line instead of colliding with the timestamp. */
.ol-task-card__comment-aside {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-task-card__comment-edited {
  color: var(--task-card-ink-body);
  font-size: 0.625rem;
  font-style: italic;
  line-height: 1.125rem;
}

.ol-task-card__comment-edit {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  padding: 0.125rem 0.375rem;
  border: 0;
  border-radius: 0.375rem;
  background: var(--task-card-chip);
  color: var(--task-card-ink-strong);
  font-family: inherit;
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.125rem;
  cursor: pointer;
}

.ol-task-card__comment-edit > svg {
  flex: none;
  color: var(--task-card-brown);
  stroke-width: 1.5;
}

.ol-task-card__comment-edit:hover {
  background: var(--task-card-hairline);
}

.ol-task-card__comment-edit:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* Delete sits beside Edit as the same chip in a danger tint; its form is only
   a wrapper for the POST, so it adds no box of its own. */
.ol-task-card__comment-delete-form {
  display: inline-flex;
  margin: 0;
}

.ol-task-card__comment-delete {
  background: var(--task-card-danger-soft);
  color: var(--task-card-danger);
}

.ol-task-card__comment-delete > svg {
  color: var(--task-card-danger);
}

.ol-task-card__comment-delete:hover {
  background: var(--task-card-danger-line);
}

/* --- Attachment tray ---------------------------------------------------- */

.ol-task-card__attachments {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.625rem;
  min-width: 0;
  padding: var(--space-2, 0.5rem);
  border-radius: 1rem;
  background: var(--task-card-gold-soft);
}

.ol-task-card__attachment {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-end;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
  padding: 0.375rem var(--space-2, 0.5rem);
  border: 1.5px solid var(--task-card-hairline);
  border-radius: 0.75rem;
  background: var(--task-card-sunken);
  color: inherit;
  text-decoration: none;
}

.ol-task-card__attachment:hover {
  border-color: var(--task-card-brown);
}

.ol-task-card__attachment:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* 107px at the reference width, then tracked to the card so the crop keeps its
   proportions instead of turning into a letterbox strip on a wide card. The
   fixed height stays as the fallback where cqi is unsupported. */
.ol-task-card__attachment-preview {
  display: block;
  width: 100%;
  height: 6.6875rem;
  height: clamp(5rem, 34cqi, 9.5rem);
  border-radius: 0.4375rem;
  object-fit: cover;
  background: var(--task-card-chip);
}

.ol-task-card__attachment-preview--file {
  display: grid;
  place-items: center;
  color: var(--task-card-brown);
}

.ol-task-card__attachment-meta {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-1, 0.25rem);
  min-width: 0;
}

.ol-task-card__attachment-name {
  overflow: hidden;
  color: var(--task-card-ink-strong);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ol-task-card__attachment-size {
  flex: none;
  color: var(--task-card-ink-muted);
  font-size: 0.5rem;
  font-weight: 600;
  line-height: 1.125rem;
}

/* A file sent with a reply belongs to what was said, so its tray sits inside the
   comment: no gold panel of its own, indented under the text, and a shorter crop
   so an image reads as part of the thread rather than as a second task tray. */
.ol-task-card__comment-attachments {
  padding: 0;
  border-radius: 0.75rem;
  background: none;
  gap: var(--space-2, 0.5rem);
}

.ol-task-card__comment-attachments .ol-task-card__attachment-preview {
  height: 5rem;
  height: clamp(4rem, 22cqi, 7rem);
}

.ol-task-card__comment-attachments .ol-task-card__attachment-name {
  font-size: 0.6875rem;
}

/* Every attachment tile carries a save button: the tile opens the file for
   reading, the button writes it to disk under the name the uploader gave it.
   It sits over the top corner of the preview because an anchor cannot hold
   another anchor, so the two are siblings rather than nested. */
.ol-task-card__attachment-slot {
  position: relative;
  display: flex;
  min-width: 0;
}

.ol-task-card__attachment-slot .ol-task-card__attachment {
  flex: 1;
  min-width: 0;
}

.ol-task-card__attachment-download {
  position: absolute;
  top: 0.625rem;
  right: 0.625rem;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border: 1.5px solid var(--task-card-hairline);
  border-radius: 0.5rem;
  background: var(--task-card-sunken);
  color: var(--task-card-brown);
  text-decoration: none;
}

.ol-task-card__attachment-download:hover {
  border-color: var(--task-card-brown);
  background: var(--task-card-chip);
}

.ol-task-card__attachment-download:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* --- Reply composer ----------------------------------------------------- */

/* 297px in the frame, but the thread above it is full width, so the composer
   tracks the card rather than leaving a dead column beside itself. */
.ol-task-card__reply {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-self: stretch;
  align-items: stretch;
  gap: var(--space-6, 1.5rem);
  width: 100%;
  min-width: 0;
  min-height: 5.625rem;
  padding: var(--space-2, 0.5rem);
  border-radius: 0.75rem;
  background: #fff;
  font-family: "Manrope", sans-serif;
}

/* The extra parent selector deliberately outranks the legacy page-wide
   textarea skin, which otherwise adds a beige fill, border and 14px radius. */
.ol-task-card__reply .ol-task-card__reply-input {
  box-sizing: border-box;
  display: block;
  width: 100%;
  /* The frame draws a single placeholder line, which is too small a target to
     write a reply in. Three lines to start, then it grows with the text. */
  height: auto;
  min-height: 3.375rem;
  padding: 0;
  border: 0;
  border-radius: 0.25rem;
  background: transparent;
  color: var(--task-card-ink-body);
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.125rem;
  overflow-y: hidden;
  resize: none;
}

.ol-task-card__reply .ol-task-card__reply-input::placeholder {
  color: var(--task-card-ink-body);
}

.ol-task-card__reply .ol-task-card__reply-input:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-sm, 0.25rem);
}

/* One line, as the frame draws it: chips pack from the left and Send is pushed
   to the far edge. Nothing wraps -- the chips give up their reference widths
   first (see the narrow-card block below), because a Send button alone on a
   second row costs more height than the labels are worth. */
.ol-task-card__reply-tools {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-task-card__reply-tools .ol-task-card__send {
  margin-left: auto;
}

/* Picked files, listed between the reply box and the tools. They wrap rather
   than scroll: five is the cap, and a name that has to be read to be checked is
   worth the second row. */
.ol-task-card__reply-files {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1, 0.25rem);
  margin: calc(var(--space-6, 1.5rem) * -0.5) 0 0;
  padding: 0;
  list-style: none;
}

.ol-task-card__reply-file {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  max-width: 100%;
  min-width: 0;
  padding: 0.1875rem 0.25rem 0.1875rem 0.375rem;
  border-radius: 0.375rem;
  background: var(--task-card-chip);
  color: var(--task-card-ink-strong);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.125rem;
}

.ol-task-card__reply-file-icon {
  display: inline-flex;
  flex: none;
  color: var(--task-card-brown);
}

/* The name is a field, not a caption: the sender types the label the thread
   will carry, and the pencil says so before anyone taps it. It is also the part
   that gives ground -- the size and the remove control are fixed, so a long
   filename truncates instead of pushing them off the row. */
.ol-task-card__reply-file-name {
  min-width: 0;
  max-width: 100%;
  flex: 0 1 auto;
  padding: 0;
  border: 0;
  border-bottom: 1px solid transparent;
  background: transparent;
  color: inherit;
  font: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ol-task-card__reply-file-name:focus {
  outline: none;
  border-bottom-color: var(--color-action-active);
}

.ol-task-card__reply-file-rename {
  display: inline-flex;
  flex: none;
  color: var(--task-card-ink-muted);
}

.ol-task-card__reply-file:focus-within {
  background: var(--task-card-hairline);
}

.ol-task-card__reply-file:focus-within .ol-task-card__reply-file-rename {
  color: var(--color-action-active);
}

.ol-task-card__reply-file-size {
  flex: none;
  color: var(--task-card-ink-muted);
  font-weight: 400;
}

.ol-task-card__reply-file-remove {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--task-card-ink-muted);
  cursor: pointer;
}

.ol-task-card__reply-file-remove:hover {
  background: var(--task-card-hairline);
  color: var(--task-card-ink-strong);
}

.ol-task-card__reply-file-remove:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* Tag roster. The names used to stand permanently between the reply box and the
   tools; they now open from the Tag chip, so the composer keeps the frame's two
   rows. It opens upward: the composer is the last thing in the card, and a menu
   dropping down would open past the end of the thread. */
.ol-task-card__tag-picker {
  position: relative;
  display: inline-flex;
  flex: none;
}

.ol-task-card__tag-menu {
  position: absolute;
  left: 0;
  bottom: calc(100% + 0.375rem);
  /* Above .pf-bottom-nav (65). Task cards sit in a scrolling list, so one
     can be near the bottom of the screen when this downward menu opens; at 20
     the nav would paint over it. Not yet reproduced on a real task list. */
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 11rem;
  max-width: min(15rem, 80vw);
  max-height: 11rem;
  overflow-y: auto;
  padding: var(--space-1, 0.25rem);
  border: 1px solid var(--task-card-hairline, #e8e0d5);
  border-radius: 0.75rem;
  background: #fff;
  box-shadow: 0 -6px 16px rgba(0, 0, 0, 0.1);
}

.ol-task-card__tag-option {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 0.375rem;
  width: 100%;
  padding: 0.375rem var(--space-2, 0.5rem);
  border: 0;
  border-radius: var(--radius-md, 0.5rem);
  background: transparent;
  color: var(--task-card-ink-strong);
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.125rem;
  text-align: left;
  cursor: pointer;
}

.ol-task-card__tag-option-at {
  flex: none;
  color: var(--color-status-warning);
  font-weight: 700;
}

/* Truncation has to sit on the label: on the button it only clips, which cuts a
   long name mid-letter. */
.ol-task-card__tag-option-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ol-task-card__tag-option:hover {
  background: var(--task-card-chip);
}

.ol-task-card__tag-option:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: calc(var(--focus-ring-offset) * -1);
}

/* Held down while its menu is open, so the chip reads as the open control. */
.ol-task-card__chip--tag.is-open {
  background: var(--task-card-hairline);
}

.ol-task-card__chip {
  box-sizing: border-box;
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  width: auto;
  height: 1.5rem;
  padding: 0.1875rem var(--space-1, 0.25rem);
  border: 0;
  border-radius: 0.375rem;
  background: var(--task-card-chip);
  color: var(--task-card-ink-strong);
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.125rem;
  cursor: pointer;
}

/* The frame's reference widths, as preferred sizes rather than floors: the two
   labelled chips are the only things in the row that can give ground, so they
   shrink and truncate to keep all five controls on one line. Send and the
   icon-only reaction chips never shrink -- there is nothing in them to clip. */
.ol-task-card__chip--tag,
.ol-task-card__chip--upload {
  flex: 0 1 auto;
  min-width: 0;
}

.ol-task-card__chip--tag {
  width: 3.1875rem;
}

/* Wider than the bare label needs: the count badge is always drawn, so the
   reference width carries "Upload" plus the badge and the word only truncates
   once the row is genuinely tight. */
.ol-task-card__chip--upload {
  width: 5.625rem;
}

.ol-task-card__chip--tag > span:not(.ol-visually-hidden),
.ol-task-card__chip--upload
  > span:not(.ol-visually-hidden):not(.ol-task-card__chip-count) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* How many files the reply is carrying, on the chip that picked them — zero
   included, so the badge is a fixed part of the chip. The chip gives up its
   reference width once files are on it: the count is the whole point of the
   badge, so it is the label that shrinks, never the number. */
.ol-task-card__chip--upload.is-loaded {
  width: auto;
  background: var(--task-card-hairline);
}

.ol-task-card__chip-count {
  flex: none;
  min-width: 1rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--color-status-success, #2f6f4e);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1rem;
  text-align: center;
}

.ol-task-card__chip > svg {
  flex: none;
  color: var(--task-card-brown);
  stroke-width: 1.5;
}
.ol-task-card__chip:hover {
  background: var(--task-card-hairline);
}

.ol-task-card__chip:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.ol-task-card__reply-mention-wrap {
  position: relative;
}

.ol-task-card__mention-dropdown {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  z-index: 20;
  max-height: 10rem;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--task-card-hairline, #e8e0d5);
  border-radius: 0.5rem;
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.08);
  padding: 0.25rem 0;
}

.ol-task-card__mention-item {
  display: block;
  width: 100%;
  padding: 0.375rem 0.625rem;
  border: 0;
  background: transparent;
  color: var(--task-card-ink-strong, #3d2e1f);
  font-family: inherit;
  font-size: 0.75rem;
  line-height: 1.125rem;
  text-align: left;
  cursor: pointer;
}

.ol-task-card__mention-item:hover,
.ol-task-card__mention-item--active {
  background: var(--task-card-chip, #f5efe8);
}

.ol-task-card__mention {
  display: inline;
  padding: 0.0625rem 0.25rem;
  border-radius: 0.25rem;
  background: #f5efe8;
  color: var(--task-card-brown, #8b6914);
  font-weight: 600;
  white-space: nowrap;
}

.ol-task-card__send.ol-btn {
  box-sizing: border-box;
  flex: none;
  min-width: 4.5rem;
  gap: var(--space-1, 0.25rem);
  height: 2rem;
  padding: 0.4375rem 0.625rem;
  border: 0;
  border-radius: 0.75rem;
  background: var(--task-card-brown);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
}

/* The frame trails the send glyph after the label; Button always emits its icon
   slot first, so the order is flipped here rather than in the primitive. */
.ol-task-card__send.ol-btn .ol-btn__icon {
  order: 2;
}

.ol-task-card__send.ol-btn svg {
  stroke-width: 1.5;
}

/* --- Locked thread -------------------------------------------------------- */

/* Takes the composer's place on a completed task. Greyer and bordered so the
   thread visibly ends rather than looking like an input that failed to load. */
.ol-task-card__locked {
  box-sizing: border-box;
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  min-width: 0;
  padding: var(--space-2, 0.5rem);
  border: 1px solid #cacaca;
  border-radius: 0.75rem;
  background: var(--task-card-chip);
}

.ol-task-card__locked-icon {
  flex: none;
  display: grid;
  place-items: center;
  color: var(--task-card-ink-muted);
}

.ol-task-card__locked-copy {
  min-width: 0;
}

.ol-task-card__locked-title {
  margin: 0;
  color: var(--task-card-ink-body);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.25rem;
}

.ol-task-card__locked-text {
  margin: 0;
  color: var(--task-card-ink-muted);
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.125rem;
}

/* The five controls need 277px, and the composer gets the container less the
   thread's and its own padding (32px) -- so both labels stop fitting just under
   the 345px reference card, whose 313px container leaves exactly the frame's
   281px. Below that the two labelled chips go icon-only rather than clipping
   "Upload" to an ellipsis. The label stays in the accessibility tree; hiding it
   outright would leave the buttons unnamed. */
@container task-card (max-width: 19.5rem) {
  .ol-task-card__chip--tag,
  .ol-task-card__chip--upload {
    width: auto;
  }

  .ol-task-card__chip--tag > span:not(.ol-visually-hidden),
  .ol-task-card__chip--upload
    > span:not(.ol-visually-hidden):not(.ol-task-card__chip-count) {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* The reject panel carries three controls in that same width, not five, so
     its Upload keeps the word and the answers wrap under it if they must. */
  .ol-task-card__reject-tools .ol-task-card__chip--upload
    > span:not(.ol-visually-hidden):not(.ol-task-card__chip-count) {
    position: static;
    width: auto;
    height: auto;
    clip-path: none;
  }
}

/* The footer carries three things now — added, due, and the action — and they
   stop sharing a line well before the rest of the card feels narrow. Under
   26rem the dates keep a row of their own, added on the left and due on the
   right, and the button spans the width beneath them. */
@container task-card (max-width: 26rem) {
  .ol-task-card__footer {
    align-items: stretch;
    gap: var(--space-2, 0.5rem);
  }

  .ol-task-card__dates {
    flex: 1 1 100%;
  }

  .ol-task-card__footer > form,
  .ol-task-card__actions,
  .ol-task-card__actions > form,
  .ol-task-card__footer .ol-task-card__status-button.ol-btn,
  .ol-task-card__footer .ol-task-card__reopen-button.ol-btn,
  .ol-task-card__footer .ol-task-card__reject-button.ol-btn {
    width: 100%;
  }

  /* Reject and Mark complete are one decision with two answers, so they stay
     side by side and split the row rather than stacking. The form around the
     green pill is the flex child, so it is the one that has to share. */
  .ol-task-card__actions > form,
  .ol-task-card__footer .ol-task-card__reject-button.ol-btn {
    flex: 1 1 0;
    min-width: 0;
  }
}

/* --- Narrow card ---------------------------------------------------------- */

/* Below the 319px reference row the due date and its action no longer fit side
   by side, and the composer is narrower than its 297px reference size. Both
   become stacked blocks rather than clipped controls. */
@container task-card (max-width: 19rem) {
  .ol-task-card__footer {
    align-items: stretch;
    gap: var(--space-2, 0.5rem);
  }

  .ol-task-card__footer > form,
  .ol-task-card__actions,
  .ol-task-card__actions > form,
  .ol-task-card__footer .ol-task-card__status-button.ol-btn,
  .ol-task-card__footer .ol-task-card__reopen-button.ol-btn,
  .ol-task-card__footer .ol-task-card__reject-button.ol-btn {
    width: 100%;
  }

  /* The tray's own padding plus the tile's is most of a narrow card's width. */
  .ol-task-card__attachments {
    padding: var(--space-1, 0.25rem);
  }
  .ol-task-card__comment-body {
    gap: var(--space-2, 0.5rem);
  }
}

/* The head already stacks at every width; the narrowest cards only need the
   avatar to sit closer to what was said. */
@container task-card (max-width: 15rem) {
  .ol-task-card__comment {
    gap: var(--space-2, 0.5rem);
  }
}

/* Sits directly above the task list, so it fills the column exactly like a task
   card rather than sitting inset at the 345px reference width. Heights are
   minimums: a long assignee name or a bumped text size grows the card instead
   of spilling out of it. */
.ol-task-toolbar {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  gap: 1rem;
  width: 100%;
  min-height: 6.875rem;
  margin-top: var(--space-3, 0.75rem);
  padding: 1rem;
  border: 0;
  border-radius: 1.25rem;
  background: #fff;
}

.ol-task-toolbar__search {
  box-sizing: border-box;
  display: flex;
  flex: none;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 1.9375rem;
  padding: 0.375rem 1rem;
  border: 1px solid #e1e1e1;
  border-radius: 1rem;
  background: #fafafa;
  color: #8e8e8e;
}

.ol-task-toolbar__search svg {
  flex: none;
}

.ol-task-toolbar__search input {
  min-width: 0;
  width: 100%;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: #303030;
  font-family: "Manrope", sans-serif;
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.125rem;
}

.ol-task-toolbar__search input::placeholder {
  color: #8e8e8e;
  opacity: 1;
}

/* Four pickers, two to a row on a phone. Each one is its own label above its
   own select, so the grid stays two lines rather than eight. */
.ol-task-toolbar__filters {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
}

@media (max-width: 20rem) {
  .ol-task-toolbar__filters {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Says what is currently set is not the default, and puts it all back. Quiet:
   it is an escape hatch beside the filters, not a fifth control. */
.ol-task-toolbar__clear {
  align-self: flex-end;
  padding: 0;
  border: 0;
  background: transparent;
  color: #75461d;
  font-family: "Manrope", sans-serif;
  font-size: 0.75rem;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}

.ol-task-toolbar__filter {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-1, 0.25rem);
  min-width: 0;
  width: 100%;
  min-height: 1.9375rem;
  color: #303030;
  font-family: "Manrope", sans-serif;
  font-size: 0.8125rem;
  line-height: 1.25rem;
}

.ol-task-toolbar__filter > span {
  flex: none;
  font-weight: 600;
}

.ol-task-toolbar__filter select {
  box-sizing: border-box;
  min-width: 0;
  width: 100%;
  height: 1.9375rem;
  flex: none;
  appearance: none;
  padding: 0.3125rem 2.25rem 0.3125rem 1rem;
  border: 1px solid #e1e1e1;
  border-radius: 1rem;
  outline: 0;
  background-color: #fafafa;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='m3 6 5 5 5-5' stroke='%238E8E8E' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.875rem center;
  color: #303030;
  font-family: "Manrope", sans-serif;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.25rem;
}

.ol-task-toolbar__search:focus-within,
.ol-task-toolbar__filter select:focus-visible {
  border-color: #75461d;
  outline: 2px solid rgba(117, 70, 29, 0.12);
  outline-offset: 1px;
}
.ol-team-task-form {
  display: grid;
  gap: var(--space-4, 1rem);
}
.ol-team-task-form__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3, 0.75rem);
}

/* Member and pending-invite rows on /team share this shell so the two lists
   read as one. Sized to the design: 16px padding, 20px radius, a 40px avatar
   beside two 20px text lines, then 16px down to the chip row — 113px overall.
   Borderless; the shadow alone lifts it off the page. */

.ol-team-card .ol-card__header {
  align-items: center;
  gap: var(--space-3, 0.75rem);
}

.ol-team-card .ol-media {
  align-items: flex-start;
  gap: var(--space-3, 0.75rem);
}

/* Avatar tracks the two text lines it sits beside. */
.ol-team-card .ol-avatar {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 0.875rem;
  margin-top: 0.0625rem;
}

.ol-team-card .ol-media__title {
  font-size: 1rem;
  line-height: 1.25;
}

/* The member list is a grid, and a grid item's automatic minimum size is its
   min-content width -- so a card whose name row may not wrap widens the whole
   track past the page and the chevron is clipped off the right edge. Letting
   the items shrink hands the squeeze back to the name's truncate. */
.ol-team-page .ol-page__list > * {
  min-width: 0;
}

/* Name and role badge share one line: the name truncates, the badge keeps its
   width instead of dropping to a row of its own. */
.ol-team-card .ol-media__body > .ol-inline:first-child {
  flex-wrap: nowrap;
}

.ol-team-card .ol-media__body > .ol-inline:first-child > .ol-media__title {
  flex: 0 1 auto;
  min-width: 0;
}

.ol-team-card .ol-media__body > .ol-inline:first-child > .ol-badge {
  flex: 0 0 auto;
}

.ol-team-card .ol-media__meta {
  margin-top: 0.125rem;
  gap: var(--space-2, 0.5rem);
  font-size: 0.8125rem;
  line-height: 1.25;
  color: var(--color-text-tertiary);
}

/* Long access summaries ("Accountant & transactions · Assigned properties")
   wrap onto further lines instead of ellipsizing, so nothing spills past the
   card edge on narrow screens. The name/role row above keeps its truncate. */
.ol-team-card .ol-media__meta .ol-truncate {
  white-space: normal;
  overflow: visible;
  overflow-wrap: anywhere;
  flex: 1 1 auto;
}

.ol-team-card__state {
  font-weight: 600;
  color: var(--color-status-success);
}

.ol-team-card__state--inactive {
  color: var(--color-text-tertiary);
}
.ol-team-card__state--pending {
  color: var(--color-status-warning);
}

.ol-team-card__sep {
  color: var(--color-text-tertiary);
  opacity: 0.7;
}

/* Avatar row and chip row stack inside the header so the chips are no longer
   indented by the avatar. */
.ol-team-card__head {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

/* One straight line of profile chips, starting at the card's own left edge.
   Three assigned profiles do not fit the width left over beside the avatar, so
   the row was breaking one chip per line; given the full card width they sit
   together, and anyone with more than fits scrolls the rail sideways rather
   than growing the card. */
.ol-team-card .ol-chips {
  margin-top: var(--space-4, 1rem);
}

.ol-team-card .ol-chips--rail {
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
  /* The rail scrolls, so its own padding is what keeps the last chip clear of
     the card edge -- and a chip's focus ring needs a little room too. */
  padding-bottom: 2px;
}

.ol-team-card .ol-chips--rail::-webkit-scrollbar {
  display: none;
}

.ol-team-card .ol-chips--rail > * {
  flex: 0 0 auto;
}

/* ---------------------------------------------------------------- StatTile */

.ol-stats {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(0.25rem, 1.2vw, 0.5rem);
  align-items: stretch;
  width: 100%;
  min-width: 0;
}

.ol-stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 0.25rem);
  /* Top-aligned, not centred: when one label in a row wraps to two lines the
     row grows, and centring would then drop that tile's figure below the
     figures beside it. Aligning from the top keeps the numbers on one line. */
  justify-content: flex-start;
  flex: 1 1 0;
  min-width: 0;
  padding: clamp(0.375rem, 1.4vw, 0.875rem) clamp(0.375rem, 1.6vw, 1rem);
  border-radius: var(--radius-xl, 0.75rem);
  background: rgba(255, 255, 255, 0.14);
  color: var(--color-text-inverse);
  text-decoration: none;
  overflow-wrap: anywhere;
}

/* The action tile carries a label and nothing else, so it centres rather than
   top-aligning -- there is no figure above it to line up with. */
.ol-stat--action {
  justify-content: center;
  background: var(--color-status-success);
  color: var(--color-action-on-primary);
}

/* A tile that is also a shortcut into the list below. It is a real <button>, so
   the reset here is only about keeping it looking like its inert siblings while
   hover, focus and the selected state read as a control. */
.ol-stat--interactive {
  appearance: none;
  border: 0;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
  /* A hairline ring is the only cue a tile is tappable. Hover cannot carry
     that on a touchscreen, and the selected state only lands after the tap. */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
  transition:
    background-color 150ms ease,
    box-shadow 150ms ease;
}

.ol-stat--interactive:hover {
  background: rgba(255, 255, 255, 0.24);
}

.ol-stat--interactive:focus-visible {
  outline: 2px solid var(--color-text-inverse);
  outline-offset: 2px;
}

/* Selected: this tile's filter is the one the list below is showing. */
.ol-stat--interactive.is-active {
  background: rgba(255, 255, 255, 0.3);
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.65);
}

@media (prefers-reduced-motion: reduce) {
  .ol-stat--interactive {
    transition: none;
  }
}

.ol-stat__icon {
  display: none;
}

.ol-stat__value {
  font-size: clamp(0.8125rem, 2.6vw, 1.25rem);
  font-weight: 700;
  line-height: 1.1;
}

.ol-stat__label {
  font-size: clamp(0.5rem, 1.7vw, 0.75rem);
  font-weight: 600;
  line-height: 1.2;
}

.ol-stat__hint {
  font-size: clamp(0.4375rem, 1.5vw, 0.6875rem);
  opacity: 0.7;
  line-height: 1.2;
}

/* ---------------------------------------------------------------- SectionHeading */

.ol-section-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3, 0.75rem);
  margin: var(--space-6, 1.5rem) 0 var(--space-3, 0.75rem);
}

.ol-section-heading__text {
  min-width: 0;
}

/* For a heading inside a flex column that already sets the gap — the property
   sheet's sections — where the default margins would double the spacing. */
.ol-section-heading--flush {
  margin: 0;
}

/* The one section-heading type rule. .ol-section-title is the class to reach
   for in new markup; the feature-specific titles below and in dashboard-mock
   are aliased onto it so a section heading is the same object everywhere.
   Size and weight come from --section-title-* in variables.css — do not set
   them here. */
.ol-section-title,
.section-title,
.ol-section-heading__title {
  margin: 0;
  font-size: var(--section-title-size);
  font-weight: var(--section-title-weight);
  line-height: var(--section-title-line);
  letter-spacing: var(--section-title-tracking);
  color: var(--section-title-color);
}

.ol-section-heading__subtitle {
  margin: var(--space-1, 0.25rem) 0 0;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

/* ---------------------------------------------------------------- Dots */

/* The three-dot cluster: the accent beside a section heading and the dots
   inside a kebab menu button are the same object at different tones. Size and
   gap are custom properties so a host can retune the cluster without writing a
   rule that reaches past .ol-dots to style its spans. */

.ol-dots {
  --ol-dots-size: 0.25rem;
  --ol-dots-gap: 0.1875rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ol-dots-gap);
  flex: none;
  line-height: 0;
}

.ol-dots--column {
  flex-direction: column;
}

.ol-dots--sm {
  --ol-dots-size: 0.1875rem;
  --ol-dots-gap: 0.125rem;
}
.ol-dots--lg {
  --ol-dots-size: 0.3125rem;
}

.ol-dots__dot {
  width: var(--ol-dots-size);
  height: var(--ol-dots-size);
  border-radius: var(--radius-full, 9999px);
  background: currentColor;
  flex: none;
}

/* Brand, warning, success — the house trio, repeating past the third dot. */
.ol-dots--accent .ol-dots__dot:nth-child(3n + 1) {
  background: var(--color-action-primary);
}
.ol-dots--accent .ol-dots__dot:nth-child(3n + 2) {
  background: var(--color-status-warning);
}
.ol-dots--accent .ol-dots__dot:nth-child(3n + 3) {
  background: var(--color-status-success);
}

.ol-dots--active .ol-dots__dot {
  background: var(--color-action-active);
}
.ol-dots--muted .ol-dots__dot {
  background: var(--color-text-tertiary);
}
/* .ol-dots--current keeps the currentColor default and inherits from the host. */

/* ----------------------------------------------------------------- Count */

/* A number in a circle. `min-width: height` is what keeps a single digit round
   and lets 2+ digits stretch into a pill rather than clip, so the two must stay
   equal — change one, change the other. The disc holds one cream colour whether
   the host under it is plain or filled brown; only the ink changes. */
.ol-count {
  flex: none;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-count-surface);
  color: var(--color-count-text);
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.ol-count--md {
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  font-size: 0.75rem;
}

.ol-count--muted {
  background: var(--color-surface-muted);
  color: var(--color-text-tertiary);
}

.ol-count--accent {
  background: var(--color-action-active);
  color: var(--color-action-on-active);
}

.ol-count--inverse {
  background: var(--color-surface-card);
  color: var(--color-action-active);
}

/* ---------------------------------------------------------------- TabBar */

.ol-tabbar {
  display: flex;
  gap: var(--space-2, 0.5rem);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: var(--space-1, 0.25rem);
}

.ol-tabbar + .ol-page__list {
  margin-top: var(--space-3, 0.75rem);
}
.ol-task-toolbar + .ol-page__list {
  margin-top: var(--space-3, 0.75rem);
}

.ol-tabbar::-webkit-scrollbar {
  display: none;
}

/* 30px tall pill: 5px/12px padding around a 20px line box, borders inside.
   Padding is inset by the 1px border so the box stays exactly 30px. */
.ol-tabbar__tab {
  flex: none;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  min-height: 30px;
  padding: 4px 11px;
  border: 1px solid transparent;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-surface-card);
  color: var(--color-text-secondary);
  font-family: inherit;
  font-size: 0.8125rem;
  line-height: 20px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--motion-fast) var(--motion-ease),
    color var(--motion-fast) var(--motion-ease);
}

.ol-tabbar__tab:hover {
  background: var(--color-surface-sunken);
}

/* Loose-pill switch: no tray, so the idle pill keeps the card surface above and
   only the selected state comes from the switch tokens. */
.ol-tabbar__tab[aria-selected="true"] {
  background: var(--color-switch-selected-bg, #75461d);
  color: var(--color-switch-selected-fg, #fff);
  border-color: var(--color-switch-selected-bg, #75461d);
}

.ol-tabbar__tab[aria-selected="true"]:hover {
  background: var(--color-switch-selected-bg, #75461d);
}

/* Spacing comes from the tab's flex gap, so no margin here. The circle is the
   Count component and keeps its cream fill on a selected tab too — the pill
   turns brown around it, the disc does not. */

/* ------------------------------------------------------- Profile header */

/* The strip above the profile carousel: avatar and greeting, date and current
   weather, notification bell. Rendered by App/Views/partials/profile-header.php
   on every page that scopes by profile — so, like the carousel below, it lives
   here rather than in dashboard-mock.css, which only loads on dashboard routes.

   The neumorphic custom properties are only defined on the dashboard body, so
   every use below carries a fallback. */

/* Three equal-weight columns so the date/weather block sits on the true centre
   of the header rather than the centre of whatever space the avatar and bell
   leave over. */
.dash-header {
  /* Greeting and date/weather are both two-line stacks sharing one rhythm, so
     "Hi" sits on the date's line and the name on the weather's line. Change the
     row heights here and both columns stay in step. */
  --dash-header-row-1: 1.15rem;
  --dash-header-row-2: 1.35rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding: 0 0.15rem;
}

.dash-header-identity {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
  min-width: 0;
}

/* Two rows, "Hi" over the name, on the same rhythm as the date/weather block
   beside it. Each span sets line-height to its row height, which centres the
   single line of text in the row without needing extra wrappers. */
.dash-header-greeting {
  display: grid;
  grid-template-rows: var(--dash-header-row-1) var(--dash-header-row-2);
  justify-items: start;
  margin: 0;
  min-width: 0;
  font-size: 0.82rem;
  color: #8a7a5c;
}

.dash-header-greeting-hi {
  display: block;
  max-width: 100%;
  line-height: var(--dash-header-row-1);
  white-space: nowrap;
}

.dash-header-greeting-name {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: var(--dash-header-row-2);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--ink, #4a4744);
}

.dash-header-avatar-wrap {
  position: relative;
  flex-shrink: 0;
}

.dash-header-avatar-btn {
  position: relative;
  display: block;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
  color: inherit;
}

.dash-header-avatar,
.dash-header-avatar-img {
  display: block;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
}

.dash-header-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--gold-dark);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 600;
}

.dash-header-settings {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--gold);
  color: #fff;
  font-size: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}

.dash-header-meta {
  justify-self: center;
  display: grid;
  grid-template-rows: var(--dash-header-row-1) var(--dash-header-row-2);
  justify-items: center;
  text-align: center;
  font-size: 0.82rem;
  color: #8a7a5c;
}

.dash-header-date {
  line-height: var(--dash-header-row-1);
  white-space: nowrap;
}

.dash-header-meta strong {
  font-weight: 500;
  color: #6f5f42;
}

.dash-header-weather {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  height: var(--dash-header-row-2);
  line-height: var(--dash-header-row-2);
  font-size: 0.8rem;
  color: #8a7a5c;
  white-space: nowrap;
}

.dash-header-weather-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.dash-header-weather-place {
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 14ch;
}

.dash-header-weather-label {
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 13ch;
}

/* "Set weather" — shown until the user picks a place; opens the location sheet. */
.dash-header-weather--action {
  margin: 0 auto;
  padding: 0 0.15rem;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-action-active, #7a4a12);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

/* Fallback day/night icon shown while there is no live forecast yet. */
.dash-header-weather--placeholder {
  opacity: 0.6;
}

.dash-header-bell {
  position: relative;
  justify-self: end;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  padding: 0;
  color: var(--gold-dark);
  text-decoration: none;
  border-radius: 12px;
  background: linear-gradient(145deg, #f8f4eb, #e8e0d2);
  box-shadow:
    4px 4px 10px var(--dash-neu-shadow-dark, rgba(134, 122, 102, 0.28)),
    -3px -3px 8px var(--dash-neu-shadow-light, rgba(255, 255, 255, 0.85));
}

/* Right-hand actions: search and notification bell, stacked in the last grid
   column so the bell keeps its end-alignment. */
.dash-header-actions {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.dash-header-icon {
  position: relative;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  padding: 0;
  border: none;
  color: var(--gold-dark);
  background: linear-gradient(145deg, #f8f4eb, #e8e0d2);
  border-radius: 12px;
  cursor: pointer;
  font: inherit;
  box-shadow:
    4px 4px 10px var(--dash-neu-shadow-dark, rgba(134, 122, 102, 0.28)),
    -3px -3px 8px var(--dash-neu-shadow-light, rgba(255, 255, 255, 0.85));
}

.dash-header-icon:hover {
  color: var(--gold, #b98a2e);
}

.dash-header-icon:focus-visible,
.dash-header-search__close:focus-visible {
  outline: 2px solid rgba(117, 70, 29, 0.34);
  outline-offset: 2px;
}

.dash-header-icon__glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
}

.dash-header-icon__glyph svg {
  display: block;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

/* Search panel dropped below the header strip. */
.dash-header-search {
  margin-bottom: 1rem;
  padding: 0 0.15rem;
}

.dash-header-search__box {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.75rem;
  border-radius: 14px;
  background: linear-gradient(145deg, #f8f4eb, #e8e0d2);
  box-shadow:
    4px 4px 10px var(--dash-neu-shadow-dark, rgba(134, 122, 102, 0.28)),
    -3px -3px 8px var(--dash-neu-shadow-light, rgba(255, 255, 255, 0.85));
}

.dash-header-search__icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--gold-dark, #75461d);
}

.dash-header-search__input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  font: inherit;
  font-size: 0.9rem;
  color: var(--ink, #292524);
  outline: none;
  padding: 0;
}

.dash-header-search__close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: rgba(117, 70, 29, 0.08);
  color: var(--gold-dark, #75461d);
  cursor: pointer;
  font: inherit;
}

.dash-header-search__close:hover {
  background: rgba(117, 70, 29, 0.16);
}

.dash-header-search__close svg {
  width: 14px;
  height: 14px;
}

.dash-header-search__results {
  margin-top: 0.5rem;
  border: 1px solid var(--color-border-subtle, #f0ece3);
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 8px 22px rgba(28, 25, 23, 0.08);
  overflow: hidden;
}

.dash-header-search__results:empty {
  display: none;
}

.dash-header-search__status {
  padding: 0.9rem 1rem;
  font-size: 0.82rem;
  color: #8a7a5c;
}

.dash-header-search__group {
  padding: 0.4rem 0 0.25rem;
}

.dash-header-search__group + .dash-header-search__group {
  border-top: 1px solid #f4efe6;
}

.dash-header-search__group-title {
  padding: 0.25rem 1rem 0.15rem;
  font-size: 0.66rem;
  font-weight: 700;
  color: #a89a80;
}

.dash-header-search__result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.55rem 1rem;
  text-decoration: none;
  color: var(--ink, #292524);
  transition: background 0.15s;
}

.dash-header-search__result:hover {
  background: var(--gold-pale, #faf5e8);
}

.dash-header-search__result-main {
  min-width: 0;
}

.dash-header-search__result-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink, #292524);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dash-header-search__result-sub {
  font-size: 0.72rem;
  color: #8a7a5c;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dash-header-search__result-tag {
  flex-shrink: 0;
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--gold-dark, #75461d);
  background: rgba(233, 161, 78, 0.22);
  border: 1px solid rgba(117, 70, 29, 0.12);
  border-radius: 999px;
  padding: 0.18rem 0.5rem;
}

.dash-header-bell-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
}

.dash-header-bell-icon svg {
  display: block;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  /* Bell glyphs sit low in the viewBox — nudge up for optical center */
  transform: translateY(-1px);
}

.dash-header-bell-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--dash-neu-surface, #f3efe6);
}

@media (max-width: 380px) {
  .dash-header-weather-place {
    max-width: 9ch;
  }
}

@media (max-width: 520px) {
  .dash-header-weather-label {
    display: none;
  }
}

/* ------------------------------------------------------ Console switcher */

/* The link that takes an admin to the admin console. Sits at the left of the
   header's action row next to search and the bell, so it borrows the same
   cream-on-gold tile language those already use. The label is what makes it
   readable; a lone shield next to a magnifier would be guesswork. */
.dash-header-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 14px 0 11px;
  border-radius: 999px;
  background: linear-gradient(145deg, #f8f4eb, #e8e0d2);
  color: var(--gold-dark, #75461d);
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
  transition: background .15s ease, color .15s ease;
}

.dash-header-switch__icon {
  display: flex;
  align-items: center;
  line-height: 0;
}

.dash-header-switch:hover {
  background: linear-gradient(145deg, #fbf8f1, #efe7d8);
  color: var(--gold, #b98a2e);
}

.dash-header-switch:focus-visible {
  outline: 2px solid rgba(117, 70, 29, 0.34);
  outline-offset: 2px;
}

@media (max-width: 900px) {
  /* Narrow: the weather line is already compressed, so the label is dropped
     before the signed-in name is pushed off the strip. Icon plus tooltip
     still identifies it. */
  .dash-header-switch__label {
    display: none;
  }

  .dash-header-switch {
    padding: 0 11px;
    gap: 0;
  }
}

/* ------------------------------------------------------ Profile carousel */

/* Horizontal scope picker: an icon tile per tax profile. Used by the dashboard
   (as links that reload) and the team page (as buttons driving Alpine state),
   so the rules live here rather than in dashboard-mock.css, which only loads
   on dashboard routes. */

.profile-carousel-card {
  padding: 1rem 0.85rem 0.75rem;
}

/* Four tiles fill the track width exactly; the rest scroll in. Spacing lives in
   the tile's own padding rather than a flex gap, so the 25% basis stays honest
   however many tiles there are. */
.profile-carousel-track {
  display: flex;
  gap: 0;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -ms-overflow-style: none;
  scrollbar-width: none;
  padding: 0.15rem 0 0.35rem;
}

.profile-carousel-track::-webkit-scrollbar {
  display: none;
}

.profile-carousel-item {
  flex: 0 0 25%;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  text-decoration: none;
  color: inherit;
  min-width: 0;
  padding-inline: 0.3rem;
  box-sizing: border-box;
}

.profile-carousel-icon {
  position: relative;
  width: 38px;
  height: 38px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  opacity: 0.72;
  transition:
    opacity 0.2s,
    transform 0.2s;
}

/* Two artworks per tile: outline at rest, filled when selected. Both are in
   the DOM and cross-fade, so switching profiles never flashes a blank icon. */
.profile-carousel-icon-img {
  width: 34px;
  height: 34px;
  object-fit: contain;
  transition: opacity 0.2s ease;
}

.profile-carousel-icon-img.is-active {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  opacity: 0;
}

.profile-carousel-item.active .profile-carousel-icon-img.is-rest {
  opacity: 0;
}
.profile-carousel-item.active .profile-carousel-icon-img.is-active {
  opacity: 1;
}

.profile-carousel-item.active .profile-carousel-icon {
  opacity: 1;
  transform: scale(1.04);
}

.profile-carousel-label {
  font-size: 0.66rem;
  color: var(--ink-muted);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}

.profile-carousel-item.active .profile-carousel-label {
  color: var(--ink);
  font-weight: 600;
}

/* Action tile ("Add profile"): never selected, so the filled artwork is the
   hover/focus state instead of the active state. */
.profile-carousel-item--action .profile-carousel-icon {
  opacity: 0.6;
}

.profile-carousel-item--action:hover .profile-carousel-icon,
.profile-carousel-item--action:focus-visible .profile-carousel-icon {
  opacity: 1;
  transform: scale(1.04);
}

.profile-carousel-item--action:hover .profile-carousel-icon-img.is-rest,
.profile-carousel-item--action:focus-visible
  .profile-carousel-icon-img.is-rest {
  opacity: 0;
}

.profile-carousel-item--action:hover .profile-carousel-icon-img.is-active,
.profile-carousel-item--action:focus-visible
  .profile-carousel-icon-img.is-active {
  opacity: 1;
}

.carousel-dots {
  display: flex;
  justify-content: center;
  gap: 0.35rem;
  margin-top: 0.55rem;
}

.carousel-dot {
  position: relative;
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #d4cdc0;
  cursor: pointer;
  transition:
    width var(--motion-default) var(--motion-ease),
    background var(--motion-default) var(--motion-ease);
}

/* Comfortable tap target without changing the painted dot. */
.carousel-dot::after {
  content: "";
  position: absolute;
  inset: -0.5rem -0.25rem;
}

.carousel-dot:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.carousel-dot.active {
  background: #e67e22;
  width: 14px;
  border-radius: 999px;
}

/* Button variant: the team page drives Alpine state instead of navigating, so
   the items are buttons and need a reset the anchor version never required. */
.ol-profile-carousel {
  margin: 0 0 var(--space-2, 0.5rem);
  padding-left: 0;
  padding-right: 0;
}

.ol-profile-carousel .profile-carousel-item {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  padding: 0 0.3rem;
  font: inherit;
  cursor: pointer;
}

.ol-profile-carousel .profile-carousel-item:focus-visible {
  outline: var(--focus-ring-width, 2px) solid
    var(--focus-ring-color, currentColor);
  outline-offset: var(--focus-ring-offset, 2px);
  border-radius: var(--radius-lg, 0.5rem);
}

/* ------------------------------------------------------------- Top band */

/* The strip the signed-in pages open with: profile header, workspace switch
   and the profile carousel below them. It sits straight on the cream ground
   (no white fill or hairline edge); --ol-topband-bg / --ol-topband-edge bring
   them back for a page that wants them. It is two elements
   rather than one because the carousel is rendered by each page's view while
   the header and switch come from layouts/main.php -- they abut with no margin
   between them, so the fill reads as a single block.

   Both bleed sideways by however much .pf-page is padding by, which puts them
   edge to edge on a phone. The bleed is a variable because that padding drops
   to 0.75rem on a narrow screen (layouts/main.php, !important) -- bleeding the
   full 1.5rem there would push the band past the page and give the phone a
   horizontal scroll. box-sizing is set for the same reason: the app has no
   global border-box reset. */
.ol-topband,
.ol-topband__tail {
  --ol-topband-bleed: 1.5rem;
  box-sizing: border-box;
  background: var(--ol-topband-bg, transparent);
  border-bottom: 1px solid var(--ol-topband-edge, transparent);
  margin-left: calc(-1 * var(--ol-topband-bleed));
  margin-right: calc(-1 * var(--ol-topband-bleed));
  padding-left: var(--ol-topband-bleed);
  padding-right: var(--ol-topband-bleed);
}

/* The band owns the top of the page, so the page's own top padding (set inline
   by layouts/main.php and again with !important in its narrow-screen block)
   gives way to the band's -- otherwise a cream strip is left above the white. */
.pf-page:has(> .ol-topband) {
  padding-top: 0 !important;
}

.ol-topband {
  margin-top: 0;
  /* Air between the band and the page that follows it. Only whichever element
     ends the band carries it, so the two halves stay flush against each other. */
  margin-bottom: var(--ol-topband-gap, 1.25rem);
  padding-top: 1.25rem;
  padding-bottom: 0.25rem;
}

/* The switch's own bottom margin would otherwise escape the band and leave the
   hairline floating below the tabs. */
.ol-topband > :last-child {
  margin-bottom: 0;
}

.ol-topband__tail {
  padding-bottom: 0.75rem;
  margin-bottom: var(--ol-topband-gap, 1.25rem);
}

/* The carousel continues the band, so it takes over both the hairline and the
   gap below -- the header half then runs straight into it. */
.pf-page:has(.ol-topband__tail) .ol-topband {
  border-bottom-color: transparent;
  margin-bottom: 0;
}

@media (max-width: 768px) {
  .ol-topband,
  .ol-topband__tail {
    --ol-topband-bleed: 0.75rem;
  }
}

/* ---------------------------------------------------------------- EmptyState */

.ol-empty__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size-avatar-lg, 3rem);
  height: var(--size-avatar-lg, 3rem);
  margin: 0 auto;
  border-radius: var(--radius-full, 9999px);
  background: var(--tone-brand-bg);
  color: var(--tone-brand-fg);
}

.ol-empty__title {
  margin: var(--space-4, 1rem) 0 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

.ol-empty__description {
  margin: var(--space-1, 0.25rem) auto 0;
  max-width: 26rem;
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-empty__description a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

.ol-empty__actions {
  margin-top: var(--space-5, 1.25rem);
}

/* ---------------------------------------------------------------- Shared primitives */

.ol-stack {
  display: grid;
  gap: var(--space-3, 0.75rem);
}
.ol-stack--tight {
  gap: var(--space-2, 0.5rem);
}

.ol-inline {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  flex-wrap: wrap;
  min-width: 0;
}

.ol-truncate {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ol-meta-label {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--color-text-tertiary);
}

.ol-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-action-primary);
  text-decoration: none;
  cursor: pointer;
}

.ol-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------- Input */

.ol-input {
  display: grid;
  gap: var(--space-2, 0.5rem);
}

/* Grid children default to min-width:auto, which means they refuse to shrink
   below their content. In a narrow column that made the label and the field
   render wider than the .ol-input around them and spill out of whatever card
   they sat in — the control itself already carries min-width:0, so the overflow
   came from the two wrappers, not the input. */
.ol-input > * {
  min-width: 0;
}

.ol-input__label {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--color-text-tertiary);
}

.ol-input__required {
  margin-left: 0.125rem;
  color: var(--color-status-danger);
}

.ol-input__field {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  padding: 0 var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-card);
  transition:
    border-color var(--motion-fast) var(--motion-ease),
    box-shadow var(--motion-fast) var(--motion-ease);
}

.ol-input__field:focus-within {
  border-color: var(--focus-ring-color);
}

@supports (background: color-mix(in srgb, red 50%, transparent)) {
  .ol-input__field:focus-within {
    box-shadow: 0 0 0 var(--focus-ring-width)
      color-mix(in srgb, var(--focus-ring-color) 30%, transparent);
  }
}

.ol-input__field--invalid {
  border-color: var(--color-status-danger);
}

/* Doubled-up selector on purpose. Legacy sheets style bare `input[type=...]`,
   which outranks a single class — this keeps the control transparent so the
   .ol-input__field wrapper owns the border, radius and background. */
.ol-input__field .ol-input__control,
.ol-input__control {
  flex: 1;
  min-width: 0;
  padding: var(--space-3, 0.75rem) 0;
  border: none;
  border-radius: 0;
  background: transparent;
  color: var(--color-text-primary);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 400;
}

.ol-input__control:focus {
  outline: none;
}

.ol-input__control::placeholder {
  color: var(--color-text-tertiary);
}

.ol-input__control:read-only {
  color: var(--color-text-secondary);
}

/* Autofill. The UA paints its own background on the <input> only, which leaves
   a hard-edged block sitting inside the field's padding. Repaint it with the
   field colour and park the transition so Chrome never animates back to it. */
.ol-input__control:-webkit-autofill,
.ol-input__control:-webkit-autofill:hover,
.ol-input__control:-webkit-autofill:focus,
.ol-input__control:autofill {
  -webkit-text-fill-color: var(--color-text-primary);
  box-shadow: 0 0 0 100vmax var(--color-surface-card) inset;
  caret-color: var(--color-text-primary);
  transition:
    background-color 0s 600000s,
    color 0s 600000s;
}

.ol-input--disabled .ol-input__control:-webkit-autofill {
  box-shadow: 0 0 0 100vmax var(--color-surface-sunken) inset;
}

/* Mention highlight. A textarea cannot colour part of its own value, so
   /js/mention-highlight.js wraps an opted-in field and paints its text on a
   mirror sitting behind it: the textarea keeps the caret and the typing, the
   mirror keeps the colour. Both boxes must therefore wrap text identically —
   the mirror's font, padding and border widths are copied off the textarea at
   runtime, and only what cannot be copied lives here. */
.ol-mention {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

.ol-mention__backdrop {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.ol-mention__mirror {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: break-word;
}

/* The field itself goes see-through, or its own glyphs would sit in front of
   the mirror's. The caret keeps the ink the field had, restored in JS. */
.ol-mention > .ol-mention__input {
  position: relative;
  z-index: 1;
  /* Block, not the inline-block a textarea is by default: on a baseline the
     field leaves the descender gap under it inside the wrapper, which makes the
     backdrop a few pixels taller than the field. The backdrop would then run out
     of scroll before the field does, and once the box has scrolled the mirror's
     text stops those pixels short of the caret — the caret reads as sitting
     above its own line when you go back into a long task to edit it. */
  display: block;
  width: 100%;
  background: transparent;
  color: transparent;
  /* Height is free — the wrapper grows with it. Width is not: dragged wider
     than the wrapper, the field would wrap its text where the mirror does not. */
  resize: vertical;
}

/* Selection paints in the field's layer, above the mirror, so it stays a
   translucent wash rather than a block that hides the selected words. */
.ol-mention > .ol-mention__input::selection {
  background: rgba(82, 163, 116, 0.28);
}

/* Green, and thickened with a stroke rather than font-weight: a heavier weight
   is wider, and the mirror's letters would then walk away from the textarea's
   caret by a few pixels per mention — enough to wrap a line in a different
   place. The stroke leaves every glyph advance untouched. */
.ol-mention__tag {
  background: none;
  color: var(--color-mention-text);
  font-weight: inherit;
  -webkit-text-stroke: 0.05em currentColor;
}

/* The same mention once it is written text on the page — a task's wording, a
   reply in the thread. Nothing has to line up with a caret here, so this one
   carries a real bold weight. */
.ol-mention-text {
  color: var(--color-mention-text);
  font-weight: 700;
}

.ol-input--disabled {
  opacity: 0.6;
}

.ol-input--disabled .ol-input__field {
  background: var(--color-surface-sunken);
}

.ol-input__affix {
  flex: none;
  display: inline-flex;
  align-items: center;
  color: var(--color-text-tertiary);
  font-size: 0.8125rem;
}

.ol-input__reveal {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  margin-right: calc(var(--space-2, 0.5rem) * -1);
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 9999px);
  background: none;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition:
    color var(--motion-fast) var(--motion-ease),
    background-color var(--motion-fast) var(--motion-ease);
}

.ol-input__reveal:hover {
  background: var(--color-surface-sunken);
  color: var(--color-text-secondary);
}

.ol-input__reveal:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.ol-input__help,
.ol-input__error {
  margin: 0;
  font-size: 0.6875rem;
}

.ol-input__help {
  color: var(--color-text-tertiary);
}

.ol-input__error {
  color: var(--color-status-danger);
  font-weight: 600;
}

/* Select needs its own affordance since the native arrow is hidden by the
   transparent control background in some engines. */
.ol-input__field select.ol-input__control {
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    right 0.5rem center,
    right 0.25rem center;
  background-size:
    0.25rem 0.25rem,
    0.25rem 0.25rem;
  background-repeat: no-repeat;
  padding-right: var(--space-4, 1rem);
}

/* ---------------------------------------------------------------- MediaRow */

/* Avatar or icon, a text block, and an optional trailing slot. Used by any
   list of people, accounts or records. */
.ol-media {
  display: flex;
  align-items: center;
  gap: var(--space-3, 0.75rem);
  min-width: 0;
}

.ol-media__body {
  min-width: 0;
  flex: 1;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-media__body a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

.ol-media__title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

.ol-media__meta {
  margin: var(--space-1, 0.25rem) 0 0;
  font-size: 0.75rem;
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-media__trailing {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  flex: none;
}

/* ---------------------------------------------------------------- Chips */

.ol-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2, 0.5rem);
  margin-top: var(--space-2, 0.5rem);
}

.ol-chips--flush {
  margin-top: 0;
}

/* ----------------------------------------------------------- Add mark */

/* The house mark for "Add": a white plus on a solid green disc. Every Add
   affordance wears the same one — the Add Task tile in the Tasks hero, the
   "+ Add" that opens the member picker on a task — so a plus on green always
   means "this makes a new one", wherever it turns up. A call site sets the
   size through --add-mark-size and nothing else; the colours are fixed here
   on purpose, because the point of the mark is that it never varies. */
.ol-add-mark {
  display: inline-grid;
  place-items: center;
  flex: none;
  box-sizing: border-box;
  width: var(--add-mark-size, 1.25rem);
  aspect-ratio: 1;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-status-success);
  color: var(--color-action-on-primary, #fff);
}

/* The glyph is sized off the disc rather than off its own width/height
   attributes, so one --add-mark-size governs the whole mark. */
.ol-add-mark > svg {
  width: 60%;
  height: 60%;
}

/* House style for a chip on a light surface: white pill, hairline border, and
   the leading glyph picked out in the brand accent so it reads before the
   label does. Applies to every chip row — team, invites, assigned profiles.
   Artwork icons (profile types) are <img>, so the colour leaves them alone. */
.ol-chips .ol-badge--outline {
  gap: var(--space-2, 0.5rem);
  padding: var(--space-1, 0.25rem) var(--space-3, 0.75rem);
  background: var(--color-surface-card);
  border-color: var(--color-border-subtle);
  color: var(--color-text-primary);
  font-size: 0.75rem;
  font-weight: 500;
}

.ol-chips .ol-badge--outline > [aria-hidden="true"] {
  display: inline-flex;
  color: var(--color-status-warning);
}

/* ------------------------------------------------------------ Name tags */

/* The house name tag: one person, or one label, as a rounded pill. Written
   once here rather than per screen — the categories on the add-contact sheet
   and the people on a task's Views and comments are the same kind of object,
   and a tag that looks different in two places reads as two different things.
   House rule: a switched-on tag a reader can press is a solid brown fill with
   a white label, never an outline tint. A tag that merely states who is on
   something (--static) takes the softer Upload pair instead, as does a
   multi-select chooser that opts into --soft — see below. */

.ol-name-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

.ol-name-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid var(--color-border-subtle, #e7e5e4);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
  background: var(--color-surface-card, #fff);
  color: var(--color-text-secondary, #57534e);
  font-family: inherit;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1.3;
  cursor: pointer;
  transition:
    background 0.15s ease,
    color 0.15s ease,
    border-color 0.15s ease;
}

.ol-name-tag > svg {
  flex: none;
}

.ol-name-tag:hover {
  border-color: var(--color-action-active, #75461d);
}

.ol-name-tag.is-on {
  background: var(--color-action-active, #75461d);
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

.ol-name-tag:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--color-action-active-soft, #f1ece6);
}

/* A tag that states a fact rather than offering a choice — nothing to press. */
.ol-name-tag--static {
  cursor: default;
}

/* A tagged member is a fact, not a live choice, so it takes the softer Upload
   treatment — brown label on the pale brown ground — rather than the solid
   brown fill.

   --soft asks for that same pair on a tag that *is* pressable, for a chooser
   whose ticked tags are read as a quiet list rather than as the one thing on
   screen wanting attention — the roles on the add-contact sheet, where several
   tags are on at once. Both share the declarations below so the on-state has a
   single source of truth: change the tokens in tokens.css and every tag in the
   app follows. */
.ol-name-tag--static.is-on,
.ol-name-tag--static.is-on:hover,
.ol-name-tag--soft.is-on,
.ol-name-tag--soft.is-on:hover {
  background: var(--color-action-active-soft, #f1ece6);
  border-color: transparent;
  color: var(--color-action-active, #75461d);
}

/* On the pale ground the white-alpha disc of the cross disappears, so it takes
   the card surface instead and deepens on hover. */
.ol-name-tag--static.is-on .ol-name-tag__remove,
.ol-name-tag--soft.is-on .ol-name-tag__remove {
  background: var(--color-surface-card, #fff);
}

.ol-name-tag--static.is-on .ol-name-tag__remove:hover,
.ol-name-tag--soft.is-on .ol-name-tag__remove:hover {
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-primary, #fff);
}

/* The cross that takes someone back off the list. It lives inside the pill so
   the tag stays one object at a glance rather than a label beside a button. */
.ol-name-tag__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  margin-right: -0.25rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.22);
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.ol-name-tag:not(.is-on) .ol-name-tag__remove {
  background: var(--color-surface-sunken, #f1ece6);
}

.ol-name-tag__remove:hover {
  background: rgba(255, 255, 255, 0.38);
}

.ol-name-tag:not(.is-on) .ol-name-tag__remove:hover {
  background: var(--color-action-active-soft, #f1ece6);
}

.ol-name-tag__remove:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 1px;
}

/* The + that opens the picker: dashed, so it reads as a slot still to fill
   rather than as somebody already on the list. The plus itself is the house
   Add mark, sized down to sit beside a 0.7rem label without crowding it. */
.ol-name-tag--add {
  --add-mark-size: 1.05rem;
  border-style: dashed;
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-active, #75461d);
}

.ol-name-tag--add:hover {
  background: var(--color-action-active-soft, #f1ece6);
}

/* -------------------------------------------------------- Editable section */

/* View/edit pair inside the team access drawer: a heading with a chevron, a
   read-only summary, and the form that opens *beneath* it — the summary stays
   put so what is currently set reads above the controls that change it. */

.ol-edit-section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3, 0.75rem);
  margin-bottom: var(--space-3, 0.75rem);
}

.ol-edit-section__head .ol-meta-label {
  margin: 0;
}

.ol-edit-section__toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-surface-sunken);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    transform var(--motion-default) var(--motion-ease),
    color var(--motion-fast) var(--motion-ease),
    background var(--motion-fast) var(--motion-ease);
}

/* Open is the "on" state: the circle fills with the house active brown and the
   chevron flips to the on-active foreground. */
.ol-edit-section__toggle.is-open {
  transform: rotate(180deg);
  color: var(--color-action-on-active);
  background: var(--color-action-active);
}

.ol-edit-section__toggle:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* Summary chips read as labels, not status pills — same weight and fill as the
   ones on the team card so a profile looks identical in both places. */
.ol-edit-section__summary .ol-badge--outline {
  gap: var(--space-2, 0.5rem);
  background: var(--color-surface-card);
  color: var(--color-text-primary);
  font-size: 0.75rem;
  font-weight: 500;
}

/* The form takes the summary's place rather than sitting under it, so there is
   nothing above it to rule off from -- the heading is its own separation. */
.ol-edit-section__form {
  margin-top: var(--space-1, 0.25rem);
}

/* Global convention: a panel or form footer is a two-button row —
   outlined "Cancel" on the left, filled "Save changes" on the right, equal
   width. Use .ol-actions with Button variants success-outline + success. */
.ol-actions,
.ol-edit-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3, 0.75rem);
  margin-top: var(--space-1, 0.25rem);
}

.ol-actions .ol-btn,
.ol-edit-actions .ol-btn {
  width: 100%;
}

.ol-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: var(--space-2, 0.5rem) var(--space-4, 1rem);
}

/* ------------------------------------------------- Inline edit (Form A / B) */

/* Form A — the value is the input. Drawn as the line it replaces, not as a
   field: no border, no fill, inherited type. The pencil is the only thing
   saying it can be typed over, and it picks up the action colour once the row
   has focus, exactly as a task's attachment name does. */

.ol-inline-field {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
  padding: var(--space-2, 0.5rem) 0;
}

.ol-inline-field__icon {
  display: inline-flex;
  flex: none;
  color: var(--color-text-secondary);
}

/* A visible label, for pages where the value alone does not say what it is:
   "Europe/London" needs one, an email next to an envelope does not. Fixed
   width so a column of them lines its values up. */
.ol-inline-field__caption {
  flex: none;
  width: 9rem;
  max-width: 45%;
  color: var(--color-text-secondary);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.01em;
}

@media (max-width: 26rem) {
  .ol-inline-field__caption {
    width: 7rem;
  }
}

/* The control. Sized and coloured as body text so a value reads the same
   whether or not the reader may change it. */
.ol-inline-field__input {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  margin: 0;
  padding: 0.15rem 0;
  border: 0;
  border-bottom: 1px solid transparent;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  color: var(--color-text-primary);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 400;
}

.ol-inline-field__input--select {
  appearance: none;
  cursor: pointer;
}

.ol-inline-field__input::placeholder {
  color: var(--color-text-tertiary);
}

/* Focus underlines rather than boxes: a box here would reintroduce the field
   chrome the whole pattern exists to remove. */
.ol-inline-field__input:focus,
.ol-inline-field__input:focus-visible {
  outline: none;
  border-bottom-color: var(--color-action-active);
  background: transparent;
  box-shadow: none;
}

.ol-inline-field__value {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--color-text-primary);
  font-size: 0.875rem;
}

.ol-inline-field__pencil {
  display: inline-flex;
  flex: none;
  color: var(--color-text-tertiary);
  transition: color var(--motion-fast) var(--motion-ease);
}

.ol-inline-field:focus-within .ol-inline-field__pencil {
  color: var(--color-action-active);
}

/* Stacked inline fields sit on hairlines, so a block of them reads as a list
   of values rather than a form. */
.ol-inline-fields {
  display: grid;
  min-width: 0;
}

.ol-inline-fields > * + * {
  border-top: 1px solid var(--color-border-subtle);
}

/* The settings page still draws its own cards and rules, so a block of inline
   fields there gets the hairline above and below that its old form-grid had. */
.settings-inline-fields {
  margin-bottom: var(--space-3, 0.75rem);
  padding-top: var(--space-1, 0.25rem);
  border-top: 1px solid var(--color-border-subtle);
  border-bottom: 1px solid var(--color-border-subtle);
}

/* Form B — a line that becomes its own form. The line and the fields are the
   two faces of one row and never appear together, so the value is written
   once in either state. */

.ol-inline-row {
  min-width: 0;
}

.ol-inline-row__line {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
  padding: var(--space-2, 0.5rem) 0;
}

.ol-inline-row__value {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--color-text-primary);
  font-size: 0.875rem;
}

.ol-inline-row__edit {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-surface-sunken);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    color var(--motion-fast) var(--motion-ease),
    background var(--motion-fast) var(--motion-ease);
}

.ol-inline-row__edit:hover {
  color: var(--color-action-on-active);
  background: var(--color-action-active);
}

.ol-inline-row__edit:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.ol-inline-row__form {
  display: grid;
  gap: var(--space-3, 0.75rem);
  padding: var(--space-2, 0.5rem) 0 var(--space-3, 0.75rem);
}

/* A label and its control, where the control is not a single Input. */
.ol-field-block {
  display: grid;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-field-block > .ol-input__label {
  margin: 0;
}

/* Dial code and number are one value in two controls, so they sit on one line
   under one label: the code only as wide as it needs to be, the number taking
   the rest. They stack on the narrowest phones, where a code wide enough to
   read leaves the number no room. */
.ol-phone-pair {
  display: grid;
  grid-template-columns: minmax(0, 8.5rem) minmax(0, 1fr);
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-phone-pair > * {
  min-width: 0;
}

@media (max-width: 22rem) {
  .ol-phone-pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* -------------------------------------------------------------- Avatar picker */

.ol-avatar-picker {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3, 0.75rem);
}

.ol-avatar-picker__body {
  display: grid;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-avatar-picker__hint {
  margin: 0;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

.ol-avatar-picker__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2, 0.5rem);
}

.ol-avatar-picker__option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size-avatar-sm, 2rem);
  height: var(--size-avatar-sm, 2rem);
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-full, 9999px);
  font: inherit;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    border-color var(--motion-fast) var(--motion-ease),
    transform var(--motion-fast) var(--motion-ease);
}

.ol-avatar-picker__option:hover {
  transform: scale(1.06);
}

.ol-avatar-picker__option.is-active {
  border-color: var(--color-action-selected, #4e3927);
}

.ol-avatar-picker__option:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.ol-avatar-picker__option--brand {
  background: var(--tone-brand-bg);
  color: var(--tone-brand-fg);
}
.ol-avatar-picker__option--accent {
  background: var(--tone-accent-bg);
  color: var(--tone-accent-fg);
}
.ol-avatar-picker__option--success {
  background: var(--tone-success-bg);
  color: var(--tone-success-fg);
}
.ol-avatar-picker__option--warning {
  background: var(--tone-warning-bg);
  color: var(--tone-warning-fg);
}
.ol-avatar-picker__option--royal {
  background: var(--tone-royal-bg);
  color: var(--tone-royal-fg);
}
.ol-avatar-picker__option--neutral {
  background: var(--tone-neutral-bg);
  color: var(--tone-neutral-fg);
}

.ol-link--action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-action-primary);
}

.ol-link--danger {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-action-danger);
}

/* Upload and Remove share one .ol-inline row but each needs its own <form>. */
.ol-avatar-picker__form {
  display: inline-flex;
  margin: 0;
}

/* The upload trigger is a <label> driving a visually hidden file input. */
label.ol-link--action {
  cursor: pointer;
}

.ol-avatar-picker__form
  input[type="file"]:focus-visible
  + label.ol-link--action {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* ------------------------------------------------------------ Assignment row */

/* Named resource with its access level: dot, name, role badge. */
.ol-assign-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 0.75rem);
  padding: var(--space-3, 0.75rem);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-sunken);
}

.ol-assign-row__dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-status-warning);
}

.ol-assign-row__name {
  flex: 1;
  min-width: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

/* ------------------------------------------------------------- Policy row */

.ol-policy-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3, 0.75rem);
  padding: var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--motion-ease);
}

.ol-policy-row.is-on {
  border-color: var(--color-status-success);
}

.ol-policy-row__box {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  margin-top: 0.0625rem;
  accent-color: var(--color-status-success);
}

.ol-policy-row__body {
  display: grid;
  gap: 0.125rem;
  min-width: 0;
}

.ol-policy-row__title {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

.ol-policy-row__meta {
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

/* ---------------------------------------------------------------- Checklist */

.ol-checklist {
  display: grid;
  gap: var(--space-3, 0.75rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ol-checklist__item {
  display: flex;
  gap: var(--space-2, 0.5rem);
  align-items: flex-start;
}

.ol-checklist__mark {
  flex: none;
  margin-top: 0.0625rem;
  color: var(--color-status-success);
}

.ol-checklist__item--denied .ol-checklist__mark {
  color: var(--color-text-tertiary);
}

.ol-checklist__item--denied .ol-checklist__title {
  color: var(--color-text-tertiary);
}

.ol-checklist__title {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

.ol-checklist__description {
  margin: 0.0625rem 0 0;
  font-size: 0.6875rem;
  color: var(--color-text-tertiary);
}

/* ---------------------------------------------------------------- DetailRow */

.ol-detail {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  min-width: 0;
}

.ol-detail__icon {
  flex: none;
  color: var(--color-text-tertiary);
  display: inline-flex;
}

.ol-detail__value {
  font-size: 0.8125rem;
  color: var(--color-text-primary);
}

.ol-detail__value--muted {
  color: var(--color-text-secondary);
}

/* ---------------------------------------------------------------- Page layout */

.ol-page {
  width: 100%;
  max-width: 32rem;
  margin-inline: auto;
  padding-inline: var(--space-3, 0.75rem);
  padding-bottom: var(--space-24, 6rem);
  overflow-x: hidden;
}

.ol-page__list {
  display: grid;
  gap: var(--space-3, 0.75rem);
}

.ol-page__notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2, 0.5rem);
  margin-bottom: var(--space-3, 0.75rem);
  padding: var(--space-3, 0.75rem);
  border: 1px dashed var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  font-size: 0.75rem;
  color: var(--color-text-secondary);
}

/* ---------------------------------------------------------------- Drawer */

.ol-drawer {
  position: fixed;
  inset: 0;
  z-index: 1300;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.ol-drawer__scrim {
  position: absolute;
  inset: 0;
  background: rgba(28, 22, 16, 0.45);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

/* The dialog family's shell. Every .modal-box in the app sits in one of these,
   so the two overlay systems share this geometry instead of each keeping a copy.
   These dismiss by clicking the backdrop through Alpine's @click.self, which
   only fires when the click lands on the element itself — so the scrim is
   painted here rather than layered over it as a .ol-drawer__scrim child, which
   would swallow the click. */
.ol-drawer--modal {
  background: rgba(28, 25, 23, 0.35);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

/* The panel itself never scrolls: the grabber and header stay put at the top of
   the sheet while .ol-drawer__body takes the overflow. */
.ol-drawer__panel {
  /* Room for the bottom nav, which is fixed, opaque and — because .pf-page is
     a stacking context — paints over this sheet whatever z-index the drawer
     carries. A sheet that reached the bottom edge hid its own last row, which
     on a form is the Save button: unreadable, untappable, and on a sheet short
     enough not to scroll, unreachable. Zero where the page has no nav, so a
     guest screen's sheet still docks flush. Every variant that sets its own
     max-height subtracts this too, or it just grows back down behind the nav. */
  --ol-drawer-nav-gap: 0px;
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 40rem;
  max-height: calc(88vh - var(--ol-drawer-nav-gap));
  max-height: calc(88dvh - var(--ol-drawer-nav-gap));
  margin-bottom: var(--ol-drawer-nav-gap);
  overflow: hidden;
  padding: 0 var(--space-4, 1rem)
    max(var(--space-4, 1rem), env(safe-area-inset-bottom));
  border-radius: var(--radius-2xl, 1.25rem) var(--radius-2xl, 1.25rem) 0 0;
  background: var(--color-surface-card, #ffffff);
  box-shadow: 0 -0.75rem 2.5rem rgba(28, 22, 16, 0.28);
}

body:has(> .pf-bottom-nav) .ol-drawer__panel {
  --ol-drawer-nav-gap: calc(
    var(--pf-bottom-nav-h, 4.1rem) + env(safe-area-inset-bottom, 0px)
  );
}

.ol-drawer__handle {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  width: 100%;
  padding: var(--space-2, 0.5rem) 0 var(--space-1, 0.25rem);
  border: 0;
  background: none;
  color: var(--color-text-secondary);
  cursor: pointer;
}

.ol-drawer__handle::before {
  content: "";
  width: 2.5rem;
  height: 0.25rem;
  border-radius: 999px;
  background: var(--color-border, rgba(28, 22, 16, 0.15));
}

.ol-drawer__close {
  position: absolute;
  top: var(--space-3, 0.75rem);
  right: var(--space-3, 0.75rem);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 9999px);
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
}

.ol-drawer__close:hover {
  background: var(--color-surface-sunken);
}

.ol-drawer__header {
  flex: none;
  padding-bottom: var(--space-3, 0.75rem);
  text-align: center;
}

/* Centred title with an action pinned right. The heading keeps the full width
   so the title stays optically centred regardless of the action's size. */
.ol-drawer__header--has-actions {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ol-drawer__header--has-actions .ol-drawer__heading {
  flex: 1;
  min-width: 0;
}

.ol-drawer__actions {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
}

.ol-drawer__footer {
  border-top: 1px solid var(--color-border-subtle);
  margin-top: var(--space-2, 0.5rem);
  padding-top: var(--space-4, 1rem);
}

.ol-drawer__footer .ol-btn {
  width: 100%;
}

.ol-drawer__title {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--color-text-primary);
}

.ol-drawer__subtitle {
  margin: 0.125rem 0 0;
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}

/* The centred title plus an absolutely positioned action only coexist while
   there is room for both. Below that the header becomes a plain left/right
   row, otherwise "Revoke access" sits on top of "Team access profile". */
@media (max-width: 30rem) {
  .ol-drawer__header--has-actions {
    justify-content: space-between;
    gap: var(--space-3, 0.75rem);
    text-align: left;
  }

  .ol-drawer__header--has-actions .ol-drawer__heading {
    flex: 1 1 auto;
    text-align: left;
  }

  .ol-drawer__header--has-actions .ol-drawer__actions {
    position: static;
    flex: none;
  }
}

.ol-drawer__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Never sideways. The sticky footer below is deliberately full bleed —
     negative side margins that cancel the panel's padding — but where the
     vertical scrollbar is a classic one it takes its width out of this
     element's scrollport, so the right bleed lands outside it and the sheet
     grows a horizontal scrollbar. There is nothing in a sheet that should
     ever scroll sideways, so the axis is simply closed.
     (`clip` is not used: paired with `overflow-y: auto` it computes back to
     `hidden`, so it would only read as if it did something different.) */
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding-bottom: var(--space-2, 0.5rem);
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-drawer__body a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

.ol-drawer__slide {
  transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
}
.ol-drawer__panel--closed {
  transform: translateY(100%);
}
.ol-drawer__panel--opened {
  transform: translateY(0);
}

.ol-drawer__fade {
  transition: opacity 0.3s ease;
}
.ol-drawer__scrim--hidden {
  opacity: 0;
}
.ol-drawer__scrim--shown {
  opacity: 1;
}

.ol-scroll-lock {
  overflow: hidden;
}

/* Notice sheet (/js/notice-sheet.js) — olNotice(), the bottom-sheet stand-in
   for window.alert(). .ol-drawer is display:flex, which defeats [hidden], so
   the pair is spelled out. Above the select sheet's 1400: a notice can fire
   from a form that sits inside another sheet. */
.ol-notice[hidden] {
  display: none;
}

.ol-notice {
  z-index: 1500;
}

.ol-notice__panel {
  text-align: center;
}

.ol-notice__title {
  margin: var(--space-2, 0.5rem) 0 0;
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--color-text-primary);
}

.ol-notice__message {
  margin: var(--space-2, 0.5rem) 0 var(--space-5, 1.25rem);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}

.ol-notice__ok {
  box-sizing: border-box;
  width: 100%;
  min-height: 2.75rem;
}

/* Full allocation workflow used by standalone Team tasks. It is a Drawer
   variant, so the same focus, motion and surface rules still apply globally —
   a bottom sheet at every width like the rest, only taller, because the
   allocation form is long. */
.ol-drawer--task-allocation .ol-drawer__panel {
  width: min(100%, 36rem);
  height: min(calc(94dvh - var(--ol-drawer-nav-gap)), 58rem);
  max-height: calc(94dvh - var(--ol-drawer-nav-gap));
  padding: 0;
}
/* Same centred header as every other sheet — the grabber and down chevron sit
   above it, so nothing is pinned to the corners and no room is reserved for a
   close button. Only the rule under it is particular to this sheet. */
.ol-drawer--task-allocation .ol-drawer__header {
  padding: var(--space-2, 0.5rem) var(--space-4, 1rem) var(--space-3, 0.75rem);
  border-bottom: 1px solid var(--color-border-subtle);
}
.ol-drawer--task-allocation .ol-drawer__title::before {
  content: "✓";
  display: inline-grid;
  place-items: center;
  width: 1.1rem;
  height: 1.1rem;
  margin-right: var(--space-2, 0.5rem);
  border: 1.5px solid var(--color-status-success);
  border-radius: 0.2rem;
  color: var(--color-status-success);
  font-size: 0.75rem;
  vertical-align: 0.05rem;
}
.ol-drawer--task-allocation .ol-drawer__body {
  padding: 0;
}

/* Editing a reply borrows the task sheet whole, and only differs where the
   content does: one field, so the sheet is as tall as it needs to be, and no
   ✓ mark on the title because a reply is not a task. */
/* Compound selector so it also outranks the small-screen height set on
   .ol-drawer--task-allocation further down. */
.ol-drawer--task-allocation.ol-drawer--comment-edit .ol-drawer__panel {
  height: auto;
  max-height: calc(88dvh - var(--ol-drawer-nav-gap));
}
.ol-drawer--comment-edit .ol-drawer__title::before {
  content: none;
}

.ol-task-allocation {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}
.ol-task-allocation__content {
  display: grid;
  /* minmax(0, 1fr), not the implicit auto track: an auto column grows to the
     widest unbreakable line inside it — a long attachment name, the nowrap
     upload hint — and pushed the whole sheet off the right of a phone. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4, 1rem);
  padding: var(--space-4, 1rem);
}
.ol-task-allocation__field-label {
  display: block;
  margin-bottom: var(--space-2, 0.5rem);
  color: var(--color-text-secondary);
  font-size: 0.75rem;
  font-weight: 700;
}
.ol-task-allocation__field-hint {
  color: var(--color-text-tertiary);
  font-weight: 500;
}
.ol-task-allocation__tag-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2, 0.5rem);
}
.ol-task-allocation__tag-grid .ol-btn {
  justify-content: flex-start;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--radius-full, 9999px);
  color: var(--color-action-active);
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The label is a flex item, so the truncation has to happen on it: on the button
   it only clips, which cuts a long name mid-letter. */
.ol-task-allocation__tag-grid .ol-btn > span:not(.ol-btn__icon) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ol-task-allocation__tag-grid .ol-btn__icon {
  color: var(--color-status-warning);
}

/* ----------------------------------------------------------- Task composer */

/* The task box and its toolbar are one control: the field gives up its bottom
   edge and the bar picks it up, so @ and Upload sit inside the box the text is
   typed into. That is two sections the sheet no longer spends before the
   members list, which is the part of the screen that has to be reachable. */
.ol-task-composer {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}
.ol-task-composer .ol-input__field {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  /* Transparent rather than removed: the box keeps its height, so focusing the
     field cannot nudge the toolbar by a pixel. */
  border-bottom-color: transparent;
}
/* The composer draws the focus edge around both halves at once. The field's own
   ring would stop at the seam and cut the box in two. */
.ol-task-composer .ol-input__field:focus-within {
  box-shadow: none;
}
.ol-task-composer:focus-within .ol-input__field {
  border-color: var(--focus-ring-color);
  border-bottom-color: transparent;
}
.ol-task-composer:focus-within .ol-task-allocation__upload {
  border-color: var(--focus-ring-color);
}

/* The upload block is the bottom half of the composer box here, not a card of
   its own — same white, no top edge, and the corners the field gave up. */
.ol-task-composer .ol-task-allocation__upload {
  padding: 0 0 var(--space-1, 0.25rem);
  border-top: 0;
  border-radius: 0 0 var(--radius-xl, 0.75rem) var(--radius-xl, 0.75rem);
  background: var(--color-surface-card);
}
.ol-task-composer__bar {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
}
/* The bar has the padding, so the file rows below it are indented to match
   rather than starting at the border. */
.ol-task-composer .ol-task-allocation__upload > :not(.ol-task-composer__bar) {
  margin-right: var(--space-3, 0.75rem);
  margin-left: var(--space-3, 0.75rem);
}
.ol-task-composer .ol-task-allocation__upload-row {
  min-width: 0;
  flex: 1 1 auto;
  gap: var(--space-2, 0.5rem);
}
.ol-task-composer .ol-task-allocation__field-hint {
  min-width: 0;
  overflow: hidden;
  font-size: 0.6875rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Opens the same list typing an @ opens, so it is drawn as the character it
   writes rather than as a second, competing control. */
.ol-task-composer__at {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 9999px);
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--motion-ease),
    color var(--motion-fast) var(--motion-ease);
}
.ol-task-composer__at:hover,
.ol-task-composer__at:focus-visible {
  background: var(--color-action-active-soft);
  color: var(--color-action-active);
}
.ol-task-composer__hint {
  margin: var(--space-2, 0.5rem) 0 0;
  color: var(--color-text-tertiary);
  font-size: 0.6875rem;
}

.ol-task-allocation__upload {
  display: block;
  padding: var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-sunken);
}
.ol-task-allocation__upload input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.ol-task-allocation__upload-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 0.75rem);
  cursor: pointer;
}
.ol-task-allocation__upload-notice {
  margin: var(--space-2, 0.5rem) 0 0;
  color: var(--color-status-danger);
  font-size: 0.75rem;
}
.ol-task-allocation__file-list {
  display: grid;
  /* Same reason as the content grid: a long file name must ellipsize inside
     its row, not widen the row past the sheet. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2, 0.5rem);
  margin: var(--space-3, 0.75rem) 0 0;
  padding: 0;
  list-style: none;
}
.ol-task-allocation__file {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg, 0.5rem);
  background: var(--color-surface);
}
.ol-task-allocation__file-icon {
  display: inline-flex;
  flex: none;
  color: var(--color-text-secondary);
}
.ol-task-allocation__file-name {
  min-width: 0;
  flex: 1 1 auto;
  padding: 0.15rem 0;
  border: 0;
  background: transparent;
  color: var(--color-text-primary);
  font: inherit;
  font-size: 0.8125rem;
}
.ol-task-allocation__file-name:focus {
  outline: none;
  border-bottom: 1px solid var(--color-action-active);
}
/* A just-picked file reads as a field rather than a caption, so the name being
   editable is visible before anyone taps it. The pencil is the affordance; it
   picks up the action colour once the row has focus. */
.ol-task-allocation__file--editable {
  background: var(--color-surface-card);
}
.ol-task-allocation__file--editable:focus-within {
  border-color: var(--color-action-active);
}
.ol-task-allocation__file-rename {
  display: inline-flex;
  flex: none;
  color: var(--color-text-tertiary);
}
.ol-task-allocation__file--editable:focus-within
  .ol-task-allocation__file-rename {
  color: var(--color-action-active);
}
/* Files the task already holds, listed above the picker in the Edit sheet.
   A row marked for removal is struck through and dimmed rather than hidden, so
   the choice stays visible — and reversible — until the sheet is saved. */
.ol-task-allocation__file-list--stored {
  margin-top: var(--space-2, 0.5rem);
}
.ol-task-allocation__file-link {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  color: var(--color-text-primary);
  font-size: 0.8125rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ol-task-allocation__file-remove {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  color: var(--color-text-tertiary);
  font-size: 0.75rem;
  cursor: pointer;
}
.ol-task-allocation__file.is-removed {
  border-style: dashed;
  opacity: 0.55;
}
.ol-task-allocation__file.is-removed .ol-task-allocation__file-link {
  text-decoration: line-through;
}
.ol-task-allocation__file.is-removed .ol-task-allocation__file-remove {
  color: var(--color-status-danger);
}
/* The app's upload affordance, wherever a file is attached to something: the
   task composer, a compliance obligation, a property's filing cabinet. One
   rule rather than one per feature, so "Upload" is the same brown pill on
   every screen. .ol-upload-btn is the general form; the task-allocation
   selector is the original name kept so the composer's markup still matches. */
.ol-upload-btn,
.ol-task-allocation__upload-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  padding: 0.45rem 0.75rem;
  border: 0;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-action-active-soft);
  color: var(--color-action-active);
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
}
.ol-upload-btn:hover:not(:disabled) {
  background: var(--color-action-active);
  color: var(--color-action-on-primary, #fff);
}
.ol-upload-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
/* How many files are picked, on the pill that picked them — inside it, not
   beside it, so the control reads as one thing. Green disc, white number, the
   same badge the task card's reply chip carries. Always drawn, zero included,
   so picking a file changes the number rather than the shape of the row. */
.ol-upload-count {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  min-width: 1rem;
  height: 1rem;
  margin-left: var(--space-1, 0.25rem);
  padding: 0 0.25rem;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-status-success, #2f6f4e);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1rem;
}
/* The pill flips to white-on-brown on hover; the badge keeps its own colours. */
.ol-upload-btn:hover:not(:disabled) .ol-upload-count {
  background: var(--color-surface-card, #fff);
  color: var(--color-action-active);
}

.ol-upload-btn svg {
  flex-shrink: 0;
}

.ol-task-allocation__members {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.ol-task-allocation__members legend {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  margin-bottom: var(--space-2, 0.5rem);
  color: var(--color-text-primary);
  font-size: 0.75rem;
  font-weight: 700;
}
.ol-task-allocation__member-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
}
.ol-task-allocation__member-actions button {
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-text-secondary);
  font: inherit;
  font-size: 0.6875rem;
  cursor: pointer;
}
.ol-task-allocation__member-actions button:first-child {
  color: var(--color-status-success);
}
.ol-task-allocation__member-actions i {
  width: 1px;
  height: 0.75rem;
  background: var(--color-border-subtle);
}
.ol-task-allocation__members > p {
  margin: var(--space-2, 0.5rem) 0 0;
  color: var(--color-text-tertiary);
  font-size: 0.6875rem;
}
.ol-task-allocation__members-hint {
  /* margin: 0 0 var(--space-2, 2rem); */
  padding: 10px;
  color: var(--color-text-tertiary);
  font-size: 0.6875rem;
  line-height: 1.7;
}
.ol-task-card__view-only {
  margin: var(--space-2, 0.5rem) 0 0;
  padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
  border: 1px dashed var(--color-border-subtle);
  border-radius: var(--radius-md, 0.5rem);
  color: var(--color-text-tertiary);
  font-size: 0.75rem;
}
.ol-task-allocation__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3, 0.75rem);
}
.ol-task-allocation__footer {
  position: sticky;
  bottom: 0;
  z-index: 2;
  margin-top: auto;
  padding: var(--space-3, 0.75rem) var(--space-4, 1rem)
    max(var(--space-3, 0.75rem), env(safe-area-inset-bottom));
  border-top: 1px solid var(--color-border-subtle);
  background: var(--color-surface-card);
}

@media (max-width: 36rem) {
  .ol-drawer--task-allocation .ol-drawer__panel {
    width: 100%;
    height: calc(96dvh - var(--ol-drawer-nav-gap));
    max-height: calc(96dvh - var(--ol-drawer-nav-gap));
  }
}

/* ------------------------------------------------------- @ mention picker */

/* The team, rising from the bottom of the screen the moment a composer starts
   tagging someone — WhatsApp's list, in the place a thumb already is. It lives
   on <body> and above the drawer's own layer, because the composer that opened
   it is usually inside a drawer; /js/mention-picker.js moves it clear of the
   on-screen keyboard while it is open. */
.ol-mention-picker {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1400;
  display: flex;
  justify-content: center;
  /* Only the sheet takes taps: the rest of the strip is the page underneath. */
  pointer-events: none;
}
.ol-mention-picker[hidden] {
  display: none;
}
.ol-mention-picker__sheet {
  display: flex;
  max-width: 40rem;
  /* The half stop, same as .modal-box--half — the @ list is one of the three
     sheet heights the app uses, not a fourth. */
  max-height: 55vh;
  max-height: 55dvh;
  flex: 1 1 40rem;
  flex-direction: column;
  padding: var(--space-3, 0.75rem) 0
    max(var(--space-2, 0.5rem), env(safe-area-inset-bottom));
  border-radius: var(--radius-2xl, 1.25rem) var(--radius-2xl, 1.25rem) 0 0;
  background: var(--color-surface-card, #ffffff);
  box-shadow: 0 -0.75rem 2rem rgba(28, 22, 16, 0.22);
  pointer-events: auto;
  animation: ol-mention-rise 0.18s cubic-bezier(0.32, 0.72, 0, 1);
}
@keyframes ol-mention-rise {
  from {
    transform: translateY(0.75rem);
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ol-mention-picker__sheet {
    animation: none;
  }
}
.ol-mention-picker__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2, 0.5rem);
  margin: 0;
  padding: 0 var(--space-4, 1rem) var(--space-2, 0.5rem);
  border-bottom: 1px solid var(--color-border-subtle);
  color: var(--color-text-tertiary);
  font-size: 0.6875rem;
  font-weight: 700;
}
/* What has been typed after the @ so far, echoed so a narrowed list says why
   it is short. */
.ol-mention-picker__query {
  color: var(--color-action-active);
  letter-spacing: 0;
  text-transform: none;
}
/* The chooser's own query box. In mention mode the composer is the query, so
   this is hidden and the list narrows as the name is typed up there instead. */
.ol-mention-picker__search {
  margin: var(--space-2, 0.5rem) var(--space-4, 1rem) 0;
  padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-input, 0.75rem);
  background: var(--color-surface-card, #ffffff);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: 0.875rem;
}

.ol-mention-picker__search:focus-visible {
  outline: none;
  border-color: var(--color-action-active);
  box-shadow: 0 0 0 3px var(--color-action-active-soft);
}

/* A miss keeps the chooser open, so it has to say why the list is empty. */
.ol-mention-picker__empty {
  padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
  color: var(--color-text-tertiary);
  font-size: 0.8125rem;
}

.ol-mention-picker__list {
  min-height: 0;
  flex: 1 1 auto;
  margin: 0;
  padding: var(--space-1, 0.25rem) 0 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}
.ol-mention-picker__option {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  padding: var(--space-2, 0.5rem) var(--space-4, 1rem);
  cursor: pointer;
}
/* The row the arrow keys are on, and the row a finger is on. The house solid
   brown is saved for a name that has actually been picked — by then the list
   is gone — so highlighting here is the soft tint. */
.ol-mention-picker__option.is-active,
.ol-mention-picker__option:hover {
  background: var(--color-action-active-soft);
}
.ol-mention-picker__avatar {
  width: 2rem;
  height: 2rem;
  flex: none;
  border-radius: var(--radius-full, 9999px);
  object-fit: cover;
}
.ol-mention-picker__avatar--initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-action-active-soft);
  color: var(--color-action-active);
  font-size: 0.6875rem;
  font-weight: 700;
}
.ol-mention-picker__name {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  color: var(--color-text-primary);
  font-size: 0.875rem;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ol-mention-picker__role {
  flex: none;
  color: var(--color-text-tertiary);
  font-size: 0.6875rem;
}
/* ------------------------------------------------- Sectioned form (invite, etc.) */

/* The house layout for a form built out of numbered .ol-form-section steps.
   `.ol-invite` is the original name and stays for the team invite sheet;
   `.ol-form-stack` is the neutral one to reach for on any other such form. */
.ol-invite,
.ol-form-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-3, 0.75rem);
}

.ol-invite__actions,
.ol-form-stack__actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  /* If the pair genuinely cannot fit side by side, they stack — the row
     wraps before a label does. */
  flex-wrap: wrap;
  gap: var(--space-2, 0.5rem);
  margin-top: var(--space-2, 0.5rem);
  padding: var(--space-3, 0.75rem) 0;
  border-top: 1px solid var(--color-border-subtle);
  background: var(--color-surface-page, #fdfbf7);
}

/* Inside a bottom sheet the row is the sheet's footer, so it has to be full
   bleed: the panel's own side padding is cancelled out, and the body's bottom
   padding with it, or the last line of the form stays visible under the bar. */
.ol-drawer__body .ol-invite__actions,
.ol-drawer__body .ol-form-stack__actions {
  bottom: calc(-1 * var(--space-2, 0.5rem));
  margin-right: calc(-1 * var(--space-4, 1rem));
  margin-bottom: calc(-1 * var(--space-2, 0.5rem));
  margin-left: calc(-1 * var(--space-4, 1rem));
  padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
  background: var(--color-surface-card, #ffffff);
}

/* The two buttons share the width evenly on a phone: a lone ghost "Cancel"
   floating beside the brown pill reads as a stray label rather than a pair.
   They share what is left over rather than a hard half each (`1 1 auto`, not
   `1 1 0`): a label is never broken across lines, so a button that cannot fit
   its own text in half the row has to be allowed to take more. */
@media (max-width: 26rem) {
  .ol-invite__actions > .ol-btn,
  .ol-form-stack__actions > .ol-btn {
    flex: 1 1 auto;
    min-width: min-content;
  }
}

/* Numbered, collapsible step. */
.ol-form-section {
  border-top: 1px solid var(--color-border-subtle);
  padding-top: var(--space-3, 0.75rem);
}

.ol-form-section:first-of-type {
  border-top: 0;
  padding-top: 0;
}

.ol-form-section__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2, 0.5rem);
  width: 100%;
  padding: var(--space-1, 0.25rem) 0;
  border: 0;
  background: none;
  cursor: pointer;
  text-align: left;
}

.ol-form-section__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--color-action-primary);
}

.ol-form-section__icon {
  display: inline-flex;
  opacity: 0.9;
}

.ol-form-section__chevron {
  display: inline-flex;
  color: var(--color-text-tertiary);
  transition: transform var(--motion-default) var(--motion-ease);
}

.ol-form-section__chevron.is-open {
  transform: rotate(180deg);
}

.ol-form-section__hint {
  margin: var(--space-1, 0.25rem) 0 0;
  font-size: 0.75rem;
  color: var(--color-text-secondary);
}

.ol-form-section__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2, 0.5rem);
  padding-top: var(--space-3, 0.75rem);
  overflow-wrap: anywhere;
  word-break: break-word;
}

.ol-form-section__body a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

.ol-form-section__note {
  margin: var(--space-1, 0.25rem) 0 0;
  font-size: 0.6875rem;
  font-style: italic;
  color: var(--color-text-tertiary);
  text-align: center;
}

.ol-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: var(--space-2, 0.5rem);
}

/* The add/edit contact form — one <form>, three tab panels behind the rail.
   Hosted both in a bottom sheet and full page, so everything here is layout
   only; the rail, fields and choice rows are the global components. */
.ol-contact-form,
.ol-contact-form * {
  /* The sectioned components are full width and padded, and the app has no
     global border-box rule to lean on. */
  box-sizing: border-box;
}

.ol-contact-form__panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3, 0.75rem);
  padding-top: var(--space-2, 0.5rem);
}

/* A narrow control in front of a wide one: the title before the name, the
   dial code before the phone number. Both tracks are minmax(…, 0) — a grid
   track's default minimum is its content, and a <select> reports its widest
   option ("+971 United Arab Emirates") as that minimum, which is how a sheet
   this narrow ends up scrolling sideways. */
.ol-contact-form__split {
  display: grid;
  grid-template-columns: minmax(0, 0.4fr) minmax(0, 1fr);
  gap: var(--space-2, 0.5rem);
  align-items: start;
}

.ol-contact-form__split > * {
  min-width: 0;
}

/* The dial code needs more room than a title does: the same 9.25rem cap the
   app-wide phone capture uses, so the code stays legible and the country name
   ellipses — the code is the half that has to be readable. */
.ol-contact-form__split--phone {
  grid-template-columns: minmax(0, 9.25rem) minmax(0, 1fr);
}

/* Same reason, one level down: the field box and the control inside it must
   both be allowed to be narrower than the text they hold. */
.ol-contact-form .ol-input,
.ol-contact-form .ol-input__field {
  min-width: 0;
  max-width: 100%;
}

.ol-contact-form .ol-input__control {
  min-width: 0;
  max-width: 100%;
  text-overflow: ellipsis;
}

/* Bank details, revealed by the role above it. Indented and ruled so it reads
   as a consequence of ticking that box rather than a step of its own. */
.ol-contact-form__sub {
  display: flex;
  flex-direction: column;
  gap: var(--space-2, 0.5rem);
  margin-left: var(--space-3, 0.75rem);
  padding-left: var(--space-3, 0.75rem);
  border-left: 2px solid var(--color-action-primary, #75461d);
}

/* Selectable row with an icon, copy and a checkbox. */
.ol-choice {
  display: flex;
  align-items: center;
  gap: var(--space-3, 0.75rem);
  padding: var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-card);
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--motion-ease),
    border-color var(--motion-fast) var(--motion-ease);
}

/* One selected look for every tickable row in the app — team access, invite
   profiles, task members. The tick box is what says "chosen"; the row backs it
   up with the house brown the rest of the app uses for a selected thing. */
.ol-choice:has(.ol-checkbox:checked) {
  border-color: var(--color-action-selected);
  background: var(--color-action-active-soft);
}

/* Opt-in "Selected" pill (ChoiceRow's selectedLabel). It only exists on a
   ticked row, so it reads as the state of that row rather than as a success
   message about the form. Green matches the Select all / Clear pair above the
   list, which is where the reader's eye comes from. */
.ol-choice__flag {
  display: none;
  flex: none;
  margin-left: auto;
  padding: var(--space-1, 0.25rem) var(--space-3, 0.75rem);
  border-radius: var(--radius-full, 9999px);
  background: var(--color-status-success);
  /* White outright: the fill is the status green in every theme, so the ink
     cannot follow a theme's on-primary, which goes dark in the dark themes. */
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}

.ol-choice:has(.ol-checkbox:checked) .ol-choice__flag {
  display: inline-flex;
  align-items: center;
}

.ol-choice__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-lg, 0.625rem);
  background: var(--tone-brand-bg);
  color: var(--tone-brand-fg);
}

/* Brand artwork rather than a stroked glyph — the PNGs carry their own colour,
   so the tile drops the tint and sits on a plain surface like the carousel. */
.ol-choice__icon--art {
  background: var(--color-surface, #fff);
  border: 1px solid var(--color-border-subtle, rgba(0, 0, 0, 0.06));
}

.ol-choice__icon--art img {
  display: block;
  width: 20px;
  height: 20px;
  object-fit: contain;
}

.ol-choice__body {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
  flex: 1 1 auto;
}
.ol-choice__media {
  display: inline-flex;
  align-items: center;
  flex: none;
}
.ol-choice__title {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text-primary);
}
.ol-choice__meta {
  font-size: 0.6875rem;
  color: var(--color-text-tertiary);
}

/* Global convention: tick boxes sit on the LEFT of their row, before the
   icon and copy. Applies to every .ol-checkbox in a flex row (.ol-choice and
   any future row component). Toggle switches (.ol-switch) stay on the right. */
.ol-checkbox {
  order: -1;
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: 0.375rem;
  background: var(--color-surface-card);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--motion-ease),
    border-color var(--motion-fast) var(--motion-ease);
}

/* A ticked box fills with the house brown, the one colour every selected
   control in the app already uses. */
.ol-checkbox:checked {
  border-color: var(--color-action-selected);
  background: var(--color-action-selected);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  background-size: 0.75rem;
  background-position: center;
  background-repeat: no-repeat;
}

.ol-checkbox:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* Resource row with a None / Viewer / Manager choice. */
.ol-assign {
  display: flex;
  flex-direction: column;
  gap: var(--space-2, 0.5rem);
  padding: var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-card);
}

.ol-assign__title {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

.ol-assign__dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  background: var(--color-border-subtle);
  transition: background var(--motion-fast) var(--motion-ease);
}

.ol-assign__dot.is-on {
  background: var(--color-status-success);
}

/* Options share the row evenly and wrap together when they stop fitting. A
   fixed 3-column grid stranded the 4th option on the accounts control (None /
   Viewer / Editor / Manager) as a lone full-width row. */
.ol-segmented {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2, 0.5rem);
}

.ol-segmented__option {
  flex: 1 1 4rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1, 0.25rem);
  min-width: 0;
  padding: var(--space-2, 0.5rem) var(--space-1, 0.25rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg, 0.625rem);
  background: var(--color-surface-card);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--motion-ease),
    color var(--motion-fast) var(--motion-ease);
}

/* Selected fills brown like every other switch. It used to be an outlined tint,
   which read as a weaker "on" than the tabs and section pills next to it. */
.ol-segmented__option.is-active {
  background: var(--color-switch-selected-bg, #75461d);
  border-color: var(--color-switch-selected-bg, #75461d);
  color: var(--color-switch-selected-fg, #fff);
  font-weight: 600;
}

/* "None" revokes access rather than granting a level, so its selected state
   reads as neutral grey instead of the brown fill used by Viewer/Manager. This
   is the one deliberate exception to the convention: the option means "off". */
.ol-segmented__option[data-level="none"].is-active {
  background: var(--color-surface-muted);
  border-color: var(--color-border-subtle);
  color: var(--color-text-secondary);
}

.ol-segmented__option[data-level="none"].is-active .ol-segmented__check {
  background: var(--color-text-tertiary);
  color: var(--color-surface-card, #fff);
}

/* On a brown option the tick inverts — cream square, brown glyph — so it stays
   legible instead of disappearing into the fill. */
.ol-segmented__option.is-active .ol-segmented__check {
  background: var(--color-switch-selected-count-bg, #fbf4ef);
  color: var(--color-switch-selected-count-fg, #75461d);
}

.ol-segmented__check {
  box-sizing: border-box;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 0.875rem;
  height: 0.875rem;
  border-radius: 0.25rem;
  background: var(--color-action-active);
  color: var(--color-text-on-action, #fff);
}

.ol-segmented__check > svg {
  width: 0.6875rem;
  height: 0.6875rem;
}

/* Role picker: a select that can show a description per option. */
.ol-listbox {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 0.25rem);
}

.ol-listbox__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2, 0.5rem);
  width: 100%;
  padding: var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-card);
  font-size: 0.875rem;
  color: var(--color-text-primary);
  cursor: pointer;
}

.ol-listbox__trigger:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.ol-listbox__chevron {
  display: inline-flex;
  color: var(--color-text-tertiary);
  transition: transform var(--motion-default) var(--motion-ease);
}

.ol-listbox__chevron.is-open {
  transform: rotate(180deg);
}

.ol-listbox__panel {
  position: absolute;
  top: calc(100% + 0.25rem);
  left: 0;
  right: 0;
  z-index: 5;
  max-height: 15rem;
  overflow-y: auto;
  padding: var(--space-1, 0.25rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-card);
  box-shadow: var(--elevation-raised);
}

.ol-listbox__option {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  width: 100%;
  padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
  border: 0;
  border-radius: var(--radius-lg, 0.625rem);
  background: none;
  text-align: left;
  cursor: pointer;
}

.ol-listbox__option:hover,
.ol-listbox__option.is-selected {
  background: var(--color-surface-sunken);
}

.ol-listbox__body {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  flex: 1 1 auto;
  min-width: 0;
}
.ol-listbox__title {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--color-text-primary);
}
.ol-listbox__meta {
  font-size: 0.6875rem;
  color: var(--color-text-tertiary);
}
.ol-listbox__check {
  display: inline-flex;
  color: var(--color-status-success);
}

.ol-policy {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3, 0.75rem);
  padding: var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-sunken);
}

.ol-policy__value {
  margin: 0.125rem 0 0;
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--color-action-primary);
}

/* Permission row with a switch. */
.ol-toggle-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 0.75rem);
  padding: var(--space-3, 0.75rem);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-xl, 0.75rem);
  background: var(--color-surface-card);
  cursor: pointer;
}

.ol-toggle-row.is-off {
  opacity: 0.55;
}

.ol-toggle-row__body {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  flex: 1 1 auto;
  min-width: 0;
}
.ol-toggle-row__title {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text-primary);
}
.ol-toggle-row__meta {
  font-size: 0.6875rem;
  color: var(--color-text-tertiary);
}

.ol-switch {
  position: relative;
  flex: none;
  width: 2.5rem;
  height: 1.375rem;
  margin: 0;
  border: 0;
  border-radius: 999px;
  background: var(--color-surface-muted);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease);
}

.ol-switch::after {
  content: "";
  position: absolute;
  top: 0.1875rem;
  left: 0.1875rem;
  width: 1rem;
  height: 1rem;
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(28, 22, 16, 0.3);
  transition: transform var(--motion-default) var(--motion-ease);
}

.ol-switch:checked {
  background: var(--color-toggle-on);
}
.ol-switch:checked::after {
  transform: translateX(1.125rem);
}

.ol-switch:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* ---------------------------------------------------------------- Responsive */

/* Narrow phones: tighten the task toolbar and let stat tiles wrap. (The team
   heroes sit this out -- their grid drops columns on its own.) */
@media (max-width: 22rem) {
  .ol-task-toolbar {
    min-height: auto;
    padding: 0.75rem;
  }
  .ol-stat {
    flex: 1 1 calc(50% - 0.25rem);
  }
}

@media (min-width: 768px) {
  .ol-page {
    max-width: 40rem;
    padding-inline: var(--space-4, 1rem);
  }
  .ol-card--hero {
    min-height: var(--hero-card-min-height-md, 11rem);
  }
  .ol-card__hero-body {
    padding: var(--hero-card-padding-md, 1.25rem);
  }
  /* No font-size bump for .ol-section-heading__title here: a section heading is
     one size at every breakpoint, set by --section-title-size. */
  .profile-carousel-icon {
    width: 52px;
    height: 52px;
  }
  .profile-carousel-icon-img {
    width: 46px;
    height: 46px;
  }
  .profile-carousel-label {
    font-size: 0.72rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ol-btn,
  .ol-card--tappable,
  .ol-card__disclosure,
  .ol-form-section__chevron,
  .ol-listbox__chevron,
  .ol-switch::after,
  .ol-drawer__slide,
  .ol-drawer__fade,
  .ol-tabbar__tab {
    transition: none;
  }
  .ol-btn__spinner {
    animation: none;
  }
}

/* The collapsing details panel shared by the activity, deadline and project
   cards. Here rather than in dashboard-mock.css because activity-card.php
   renders on /transactions too. */
.dash-expand-panel {
  overflow: hidden;
  transition: opacity 0.22s ease;
}

/* ── Item card ────────────────────────────────────────────────────────────

   The expandable record card the dashboard deadlines introduced, promoted here
   so it is not a dashboard object any more: a count circle, a badge row, a
   title over a due line, a chevron, and a collapsing panel that ends in a pair
   of actions. The property sheet renders its compliance obligations with it.

   .ol-item-card is the name to reach for in new markup; .dash-deadline-card is
   aliased onto it so the dashboard's existing markup keeps working, exactly as
   .dash-section__title is aliased onto .ol-section-title above.

   The card carries no surface of its own — pair it with .ol-card, which owns
   the background, radius and elevation for every card in the app. */

.ol-item-card,
.dash-deadline-card {
  overflow: hidden;
  transition:
    box-shadow 0.2s ease,
    border-color 0.2s ease;
}

.ol-item-card:hover,
.dash-deadline-card:hover {
  border-color: rgba(214, 211, 209, 0.95);
  box-shadow: 0 4px 14px rgba(28, 25, 23, 0.04);
}

.ol-item-card.is-expanded,
.dash-deadline-card.is-expanded {
  border-color: rgba(233, 161, 78, 0.35);
  box-shadow: 0 6px 18px rgba(28, 25, 23, 0.05);
}

.ol-item-card__header,
.dash-deadline-card__header {
  width: 100%;
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 1rem 1.05rem;
  border: none;
  background: transparent;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

/* Card headers are full-row toggles — skip global button press scale */
.ol-item-card__header:active,
.dash-deadline-card__header:active {
  transform: none;
}

.ol-item-card__lead,
.dash-deadline-card__lead {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  flex: 1;
  min-width: 0;
}

.ol-item-card__circle,
.dash-deadline-card__circle {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  border: 2px solid #d8c6b6;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.84rem;
  font-weight: 800;
  color: #292524;
  flex-shrink: 0;
}

/* The circle also carries a glyph on surfaces that count nothing — a
   compliance obligation has a document type, not a number of days, when it has
   no date to count down to. */
.ol-item-card__circle--glyph {
  border-style: solid;
  border-width: 2px;
}

.ol-item-card__copy,
.dash-deadline-card__copy {
  flex: 1;
  min-width: 0;
}

.ol-item-card__title-row,
.dash-deadline-card__title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

.ol-item-card__badge,
.dash-deadline-card__badge {
  display: inline-flex;
  padding: 0.12rem 0.45rem;
  border-radius: 999px;
  font-size: 0.66rem;
  font-weight: 800;
}

.ol-item-card__badge--urgent,
.dash-deadline-card__badge--urgent {
  background: rgba(196, 91, 75, 0.1);
  color: var(--color-status-attention);
}

.ol-item-card__badge--done,
.dash-deadline-card__badge--done {
  background: #ecfdf5;
  color: #047857;
}

.ol-item-card__tags,
.dash-deadline-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.45rem;
}

.ol-item-card__title,
.dash-deadline-card__title {
  font-size: 0.92rem;
  font-weight: 800;
  color: #292524;
  line-height: 1.25;
}

.ol-item-card.is-resolved .ol-item-card__title,
.dash-deadline-card.is-resolved .dash-deadline-card__title {
  color: #78716c;
  text-decoration: line-through;
  font-weight: 700;
}

.ol-item-card.is-resolved,
.dash-deadline-card.is-resolved {
  background: rgba(255, 255, 255, 0.6);
  border-color: rgba(214, 211, 209, 0.45);
  opacity: 0.88;
}

.ol-item-card.is-resolved .ol-item-card__circle,
.dash-deadline-card.is-resolved .dash-deadline-card__circle {
  border-color: #6ee7b7;
  color: #047857;
}

/* Dimmed rather than struck through: an obligation that does not apply to this
   property is not something you finished, it is something you never owed. */
.ol-item-card.is-muted {
  opacity: 0.55;
}

.ol-item-card__due,
.dash-deadline-card__due {
  margin: 0.35rem 0 0;
  font-size: 0.72rem;
  font-weight: 700;
  color: #78716c;
}

.ol-item-card__chevron,
.dash-deadline-card__chevron {
  flex-shrink: 0;
  color: #78716c;
  transition: transform 0.2s ease;
}

.ol-item-card__chevron.is-open,
.dash-deadline-card__chevron.is-open {
  transform: rotate(180deg);
}

.ol-item-card__details,
.dash-deadline-card__details {
  padding: 0 1.05rem 1rem;
  border-top: 1px solid rgba(214, 211, 209, 0.45);
  margin-top: 0;
}

/* ── Fact grid ────────────────────────────────────────────────────────────

   Label over value, two to a row: the house format for a block of facts, used
   inside an item card's detail panel and by the property sheet's own panels.
   .ol-fact-row remains the one-line variant for a short label/value pair. */

.ol-fact-grid,
.dash-detail-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 1rem;
  padding-top: 0.85rem;
}

/* Facts that stand on their own panel rather than inside a card's detail
   drawer have nothing above them to clear. */
.ol-fact-grid--flush {
  padding-top: 0;
}

/* A value too long to share a line — a full address, a row of profile chips —
   takes the whole width instead of being squeezed into half of it. */
.ol-fact-grid__item--wide {
  grid-column: 1 / -1;
}

.ol-fact-grid__item,
.dash-detail-grid__item {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  text-align: left;
  min-width: 0;
}

.ol-fact-grid__label,
.dash-detail-grid__label {
  font-size: 0.69rem;
  font-weight: 700;
  color: #78716c;
  line-height: 1.2;
}

.ol-fact-grid__value,
.dash-detail-grid__value {
  font-size: 0.78rem;
  font-weight: 800;
  color: #2f2a25;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/* ── Card actions ─────────────────────────────────────────────────────────

   The button pair that closes an expanded card. Equal width by default, so two
   actions read as a choice rather than a primary with an afterthought. */

.ol-card-actions,
.dash-card-actions {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 0.85rem;
}

.ol-card-actions__btn,
.dash-card-actions__btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  min-height: 2.5rem;
  padding: 0.55rem 0.75rem;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 800;
  line-height: 1.1;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent;
  transition:
    background 0.15s ease,
    border-color 0.15s ease,
    color 0.15s ease;
}

.ol-card-actions__btn--secondary,
.dash-card-actions__btn--secondary {
  background: #fff;
  border-color: rgba(214, 211, 209, 0.85);
  color: #4e4336;
  box-shadow: 0 1px 3px rgba(28, 25, 23, 0.04);
}

.ol-card-actions__btn--secondary:hover,
.dash-card-actions__btn--secondary:hover {
  background: #fafaf9;
}

.ol-card-actions__btn--primary,
.dash-card-actions__btn--primary {
  background: #e9a14e;
  border-color: rgba(233, 161, 78, 0.25);
  color: #fff;
  box-shadow: 0 2px 8px rgba(233, 161, 78, 0.22);
}

.ol-card-actions__btn--primary:hover,
.dash-card-actions__btn--primary:hover {
  background: #d69040;
}

.ol-card-actions__btn--muted,
.dash-card-actions__btn--muted {
  background: #e7e5e4;
  border-color: #d6d3d1;
  color: #44403c;
}

.ol-card-actions__btn--muted:hover,
.dash-card-actions__btn--muted:hover {
  background: #d6d3d1;
}

/* The green "commit" action — completing a project's whole step list. Reads in
   the house green rather than the gold used for softer primaries. */
.ol-card-actions__btn--go,
.dash-card-actions__btn--go {
  background: #56971b;
  border-color: #56971b;
  color: #fff;
}

.ol-card-actions__btn--go:hover,
.dash-card-actions__btn--go:hover {
  background: #497f16;
}

.ol-card-actions__btn svg {
  flex: none;
}

/* A card that is itself a link rather than a disclosure — an uploaded document
   opens, it does not expand. Same object, no toggle: the chevron points the
   way out instead of down. */
.ol-item-card--link {
  display: block;
  text-decoration: none;
  color: inherit;
}

.ol-item-card--link .ol-item-card__header {
  cursor: pointer;
}

.ol-item-card--link:hover .ol-item-card__title {
  text-decoration: underline;
}

/* ------------------------------------------------------- Activity feed

   The day-grouped transaction rows, their expanded field pickers and the
   bulk-select ticks. Rendered by App/Views/partials/activity-feed.php, which
   the dashboard and /transactions both include — so, like the profile header
   above, it lives here rather than in dashboard-mock.css, which only loads on
   dashboard routes.

   The dashboard additionally strips the panel from around this section; that
   rule is dashboard framing and stays in dashboard-mock.css. */
.dash-activity-groups {
  display: flex;
  flex-direction: column;
  gap: 1.05rem;
}

.dash-activity-group__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0 0.15rem 0.5rem;
}

.dash-activity-group__day {
  font-size: 0.82rem;
  font-weight: 800;
  color: #4a4744;
  letter-spacing: -0.01em;
}

.dash-activity-group__balance {
  font-size: 0.76rem;
  font-weight: 800;
  color: #75461d;
  white-space: nowrap;
}

.dash-activity-list {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

/* ── Activity page (/transactions) ──
 *
 * The same cards as the dashboard block, as a full page. Bulk-select (the
 * day's "select all" and the per-card ticks) is hidden for the client — the
 * markup survives so the bulk-bar JS keeps working — and one line unhides it. */
.dash-activity--page {
  margin-bottom: 1.25rem;
}

.dash-activity-group__select {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
  align-self: center;
  font-size: 0.72rem;
  font-weight: 600;
  color: #6b6760;
  cursor: pointer;
  white-space: nowrap;
}

/* The header is a full-width button, so the select tick has to share the row
   with it rather than sit inside it — a tick inside the button would toggle the
   card open on every tap. */
.dash-activity-card--selectable {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
}

.dash-activity-card--selectable .dash-activity-card__header {
  width: auto;
  flex: 1;
  min-width: 0;
  padding-left: 0.4rem;
}

/* The label opens at the arrow disc's top edge and is exactly as tall as the
   disc, so centring inside it puts the tick on the disc's centre line whatever
   the tick's own size or border. box-sizing is pinned because Tailwind's
   preflight makes everything border-box, which would fold the padding into the
   height and pull the tick back up. */
.dash-activity-card__select {
  position: relative;
  display: inline-flex;
  align-items: center;
  box-sizing: content-box;
  height: var(--activity-arrow-size);
  padding: calc(var(--activity-row-pad-y) + var(--activity-arrow-nudge)) 0
    var(--activity-row-pad-y) 0.9rem;
  cursor: pointer;
}

/* Bulk-select is hidden for the client. The markup stays (the bulk-bar JS
   bindings on the page read these same elements), but the ticks themselves —
   the day's "select all" and each card's tick — must not be visible. Declared
   after the two definitions above so it wins the cascade, and scoped under
   .dash-activity--page so the one-line unhide is equivalent. */
.dash-activity--page .dash-activity-group__select,
.dash-activity--page .dash-activity-card__select {
  display: none;
}

.dash-activity-card {
  /* One vertical rhythm for the row. The header's padding, the arrow disc and
     the bulk-select tick all derive from these, so the tick and the disc share
     a centre line instead of being lined up by eye. */
  --activity-row-pad-y: 0.9rem;
  --activity-arrow-size: 2.5rem;
  --activity-arrow-nudge: 0.1rem;
  overflow: hidden;
  transition:
    box-shadow 0.2s ease,
    border-color 0.2s ease;
}

.dash-activity-card:hover {
  border-color: var(--color-border-subtle, #f5f5f5);
  box-shadow: 0 4px 14px rgba(28, 25, 23, 0.04);
}

.dash-activity-card.is-expanded {
  border-color: rgba(233, 161, 78, 0.35);
  box-shadow: 0 6px 18px rgba(28, 25, 23, 0.05);
}

.dash-activity-card__header {
  width: 100%;
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: var(--activity-row-pad-y) 1rem;
  border: none;
  background: transparent;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

/* Full-row toggle — skip the global button press scale */
.dash-activity-card__header:active {
  transform: none;
}

/* Directional arrow in a tinted round badge — green for money in, red for money
   out — so the direction reads before the amount does. */
.dash-activity-card__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--activity-arrow-size);
  height: var(--activity-arrow-size);
  border-radius: 50%;
  margin-top: var(--activity-arrow-nudge);
}

.dash-activity-card__arrow.is-credit {
  color: #1e7a4a;
  background: rgba(30, 122, 74, 0.1);
}

.dash-activity-card__arrow.is-debit {
  color: #c0503b;
  background: rgba(192, 80, 59, 0.1);
}

.dash-activity-card__title-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
}

.dash-activity-card__clip {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  padding: 0.1rem 0.35rem;
  border-radius: 999px;
  border: 1px solid rgba(233, 161, 78, 0.4);
  background: rgba(233, 161, 78, 0.12);
  color: #75461d;
  font-size: 0.64rem;
  font-weight: 800;
  flex-shrink: 0;
}

.dash-activity-card__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.dash-activity-card__body a {
  overflow-wrap: anywhere;
  word-break: break-all;
}

.dash-activity-card__title {
  font-size: 0.92rem;
  font-weight: 800;
  color: #292524;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-activity-card__subtitle {
  font-size: 0.74rem;
  font-weight: 700;
  color: #a8a29e;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-activity-card__bank {
  font-size: 0.74rem;
  font-weight: 600;
  color: #78716c;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* An uncategorised row is unfinished work, so its subtitle steps out of the
   muted grey and asks to be dealt with — in the same brand terracotta the
   Category field below it uses, so the two reds on one card match. */
.dash-activity-card__subtitle.is-uncategorised {
  color: var(--color-status-attention, #c45b4b);
  font-weight: 800;
}

.dash-activity-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.4rem;
}

.dash-activity-card__amount {
  font-size: 0.88rem;
  font-weight: 800;
  white-space: nowrap;
  flex-shrink: 0;
  margin-top: 0.15rem;
}

.dash-activity-card__amount.is-credit {
  color: #1e7a4a;
}

.dash-activity-card__amount.is-debit {
  color: #c0503b;
}

/* Opens the edit sheet rather than an inline panel, so it does not rotate —
   there is nothing below it to point at. It still goes solid brown while its
   sheet is up, the house "active" state. */
.dash-activity-card__edit {
  color: #78716c;
  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.dash-activity-card__edit.is-open {
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

/* The card's fields sit in a bottom sheet now. The grid brings its own white
   card framing, which reads as a box inside a box once it is in a sheet, so it
   is flattened onto the sheet's own ground. */
.dash-activity-sheet .dash-activity-fields {
  padding: 0;
  border: 0;
  background: transparent;
}

.dash-activity-sheet .dash-activity-extras {
  margin-top: 0.75rem;
}

.dash-activity-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  padding: 0.55rem;
  border: 1px solid var(--color-border-subtle, #f5f5f5);
  border-radius: 14px;
  background: #fff;
}

.dash-activity-field {
  min-width: 0;
  border-radius: 12px;
  background: #f5f5f5;
}

/* An open picker takes the full width of the grid so its list has room. */
.dash-activity-field.is-open {
  grid-column: 1 / -1;
  background: #fafafa;
  border: 1px solid var(--color-border-subtle, #f5f5f5);
}

.dash-activity-field--static {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.5rem 0.6rem;
}

/* Free-text field (Reference): reads like a static field until tapped, so the
   grid keeps one rhythm. box-sizing is set here because there is no global
   border-box reset — without it the padded input overflows its cell. */
.dash-activity-field--text {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.6rem;
  cursor: text;
}

.dash-activity-field__input {
  width: 100%;
  box-sizing: border-box;
  padding: 0;
  border: 0;
  border-bottom: 1px solid transparent;
  background: transparent;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 800;
  color: #2f2a25;
  line-height: 1.25;
  /* Bank references run long and an input cannot wrap, so an unfocused one
     ends in an ellipsis like the static values beside it rather than cutting
     off mid-character. Focusing scrolls the full value back into view. */
  text-overflow: ellipsis;
}

.dash-activity-field__input::placeholder {
  color: #78716c;
  font-weight: 700;
}

.dash-activity-field__input:focus {
  outline: none;
  border-bottom-color: var(--color-action-active, #75461d);
}

/* The pencil takes the chevron's slot so an editable text field carries a
   control of its own instead of reading as static text. */
.dash-activity-field__edit {
  position: relative;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #fff;
  color: #78716c;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background 0.15s ease,
    color 0.15s ease;
}

/* 24px is a comfortable target for a mouse and a miss for a thumb. The disc
   stays its drawn size and grows an invisible 44px one underneath, so the
   grid rhythm is unchanged. */
@media (pointer: coarse) {
  .dash-activity-field__edit::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

.dash-activity-field__edit:active {
  transform: none;
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

.dash-activity-field--text:focus-within .dash-activity-field__edit {
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

.dash-activity-field__head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.6rem;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

.dash-activity-field__head:active {
  transform: none;
}

.dash-activity-field__labelling {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.dash-activity-field.is-open .dash-activity-field__labelling {
  flex-direction: row;
  align-items: center;
  gap: 0.45rem;
}

.dash-activity-field__label {
  font-size: 0.65rem;
  font-weight: 700;
  color: #a8a29e;
  line-height: 1.2;
}

.dash-activity-field__row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.dash-activity-field__value {
  font-size: 0.78rem;
  font-weight: 800;
  color: #2f2a25;
  line-height: 1.25;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-activity-field__value.is-empty {
  color: #78716c;
  font-weight: 700;
}

/* An empty Category is not just blank, it is work outstanding, so it names
   itself in the brand terracotta instead of the muted grey. */
.dash-activity-field__value.is-uncategorised {
  color: var(--color-status-attention, #c45b4b);
  font-weight: 800;
}

/* Open pickers move the current value into a pill beside the label. */
.dash-activity-field__pill {
  display: inline-flex;
  padding: 0.25rem 0.55rem;
  border-radius: 8px;
  background: var(--color-action-active-soft, #f1ece6);
  color: var(--color-action-active, #75461d);
  font-size: 0.68rem;
  font-weight: 800;
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-activity-field__chevron {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: #fff;
  border: 0;
  color: #78716c;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition:
    transform 0.2s ease,
    background 0.15s ease,
    color 0.15s ease;
}

/* Open reads as the "on" state, same as every other chevron in the app. */
.dash-activity-field__chevron.is-open {
  transform: rotate(180deg);
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

/* The attachments head holds three separate controls, so it can't be one
   button like the other fields. */
.dash-activity-field__head--split {
  align-items: flex-end;
}

.dash-activity-field__toggle {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

.dash-activity-field__toggle:active {
  transform: none;
}

.dash-activity-field__upload {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.22rem 0.45rem;
  border: 0;
  border-radius: 8px;
  background: rgba(233, 161, 78, 0.22);
  color: #75461d;
  font-size: 0.65rem;
  font-weight: 800;
  white-space: nowrap;
  flex-shrink: 0;
  cursor: pointer;
  font-family: inherit;
}

.dash-activity-field__upload:disabled {
  opacity: 0.6;
  cursor: wait;
}

.dash-activity-field__file {
  display: none;
}

button.dash-activity-field__chevron {
  padding: 0;
  cursor: pointer;
}

button.dash-activity-field__chevron:active {
  transform: none;
}

button.dash-activity-file__action {
  border: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
}

button.dash-activity-file__action:active {
  transform: none;
}

/* ── Field pickers ── */
.dash-activity-picker {
  overflow: hidden;
  padding: 0 0.5rem 0.5rem;
}

.dash-activity-picker__empty {
  margin: 0;
  padding: 0.75rem 0.35rem;
  font-size: 0.74rem;
  font-weight: 600;
  color: #78716c;
  text-align: center;
}

.dash-activity-picker__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0.15rem 0.5rem;
}

.dash-activity-picker__bar-btn {
  border: 0;
  background: transparent;
  padding: 0;
  font-size: 0.74rem;
  font-weight: 700;
  color: #57534e;
  cursor: pointer;
}

.dash-activity-picker__bar-btn.is-primary {
  color: #75461d;
  font-weight: 800;
}

/* The Category picker's own foot bar. Its picks leave the panel open, so the
   way out lives here; the note beside it is the only place a user is told a
   second tick splits the amount. Padded top as well as bottom — the top-of-
   panel bar above sits against the field head and needs neither. */
.dash-activity-picker__bar--foot {
  gap: 0.75rem;
  padding: 0.55rem 0.15rem 0.15rem;
}

.dash-activity-picker__note {
  flex: 1;
  min-width: 0;
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.35;
  color: #78716c;
}

.dash-activity-picker__search {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.55rem;
  margin-bottom: 0.5rem;
  border: 1px solid var(--color-border-subtle, #f5f5f5);
  border-radius: 10px;
  background: #fff;
  color: #a8a29e;
}

.dash-activity-picker__search input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  font-size: 0.76rem;
  font-weight: 600;
  color: #2f2a25;
}

.dash-activity-picker__search input::placeholder {
  color: #a8a29e;
  font-weight: 600;
}

/* The line that says why the contact list is short, and the way out of it.
   A filtered list that does not admit it is filtered reads as a missing
   contact, and a mapping that has the wrong people behind it would otherwise
   leave the row unfileable. */
.dash-activity-picker__hint {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0 0 0.4rem;
  padding: 0 0.2rem;
  font-size: 0.68rem;
  font-weight: 700;
  color: #a8a29e;
}

.dash-activity-picker__linkish {
  border: 0;
  background: transparent;
  padding: 0;
  font-size: 0.68rem;
  font-weight: 800;
  color: #75461d;
  text-decoration: underline;
  cursor: pointer;
  flex-shrink: 0;
}

.dash-activity-picker__add {
  border: 0;
  border-radius: 7px;
  padding: 0.18rem 0.45rem;
  background: rgba(233, 161, 78, 0.22);
  color: #75461d;
  font-size: 0.66rem;
  font-weight: 800;
  cursor: pointer;
}

/* The contacts picker takes the house Add pill instead (.ol-name-tag--add),
   so a plus on green means "this makes a new one" here as it does on a task.
   Only the metrics are local: the search row is a tight flex line, and the
   pill must not stretch or outgrow the input beside it. */
.dash-activity-picker__add-tag {
  --add-mark-size: 0.95rem;
  flex: none;
  padding: 0.2rem 0.5rem 0.2rem 0.35rem;
  font-size: 0.66rem;
  font-weight: 800;
}

/* Menu surface: a bordered card of hairline-separated rows, as in the Figma
   Teams sheet and the prototype. The ds .ods-menu drops dividers in favour of
   padding gaps, but these lists run long enough that the separators earn their
   place. */
.dash-activity-options {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  border-radius: var(--ds-radius-md, 12px);
  background: var(--color-surface-card, #fff);
  /* No inner scroller. A capped, self-scrolling list swallows the drag that
     starts on it: the page only moved once the list had run out, which reads
     as a page stuck behind the picker. The list lays out at its full height
     and the page is the only thing that scrolls, so one drag anywhere on the
     card moves the card. scrollFieldIntoView() already handles a panel taller
     than the safe band by showing it from its top. */
}

.dash-activity-option {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  border-top: 1px solid var(--color-border-hairline, #f5f5f5);
}

.dash-activity-option:first-child {
  border-top: 0;
}

.dash-activity-option:hover {
  background: var(--color-action-active-soft, #f1ece6);
}

/* Selected row reads as a box, the same shape the global settings pickers use:
   an accent ring over a soft accent fill. The ring is an inset shadow rather
   than a border so the row keeps its height and the list stops jumping as the
   selection moves; the hairline separators either side are dropped so the
   rounded corners are not cut across. */
.dash-activity-option.is-selected {
  position: relative;
  z-index: 1;
  background: var(--color-action-active-soft, #f1ece6);
  border-top-color: transparent;
  border-radius: var(--ds-radius-sm, 0.5rem);
  box-shadow: inset 0 0 0 1.5px var(--color-action-active, #75461d);
}

.dash-activity-option.is-selected + .dash-activity-option {
  border-top-color: transparent;
}

/* A subcategory sits directly under the parent it belongs to, and says so by
   indenting rather than by repeating the parent's name on every row. The rule
   down the left is the only thing tying a run of children to the row above, so
   it runs the full height of the row and stops at the tick, not the label —
   the option's own padding is what pushes it in. */
.dash-activity-option.is-child .dash-activity-option__pick {
  padding-left: 1.75rem;
}

.dash-activity-option.is-child {
  position: relative;
}

.dash-activity-option.is-child::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0.95rem;
  width: 1px;
  background: var(--color-border-hairline, #f5f5f5);
}

/* The ring on a selected row would otherwise be drawn over by the rule. */
.dash-activity-option.is-child.is-selected::before {
  display: none;
}

.dash-activity-option__pick {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.6rem 0.65rem;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

.dash-activity-option__pick:active {
  transform: none;
}

/* Tick geometry from the design system (.ods-tick in css/ds/form.css): 1.25rem
   box in the leading slot, per the app-wide convention.

   Square with soft corners and an accent outline, matching the tick used in
   global settings rather than the round radio this list used to show. Selection
   fills the box and reveals a white check.

   Shared by every tick in the feed — picker rows, the Activity page's row and
   day selects, and the extras flags — so one shape covers them all. */
.dash-activity-option__radio,
.dash-activity-tick {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--ds-radius-xs, 0.375rem);
  border: 1.5px solid var(--color-action-active, #75461d);
  background: var(--color-surface-card, #fff);
  color: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

/* The tick's checkbox is visually hidden; the label's own tick renders it. */
.dash-activity-card__select input,
.dash-activity-group__select input,
.dash-activity-extras__flag input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.dash-activity-card__select input:checked + .dash-activity-tick,
.dash-activity-group__select input:checked + .dash-activity-tick,
.dash-activity-extras__flag input:checked + .dash-activity-tick,
.ol-autocat__row input:checked + .dash-activity-tick,
.ol-autocat__field input:checked + .dash-activity-tick {
  background: var(--color-action-active, #75461d);
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

.dash-activity-card__select input:focus-visible + .dash-activity-tick,
.dash-activity-group__select input:focus-visible + .dash-activity-tick,
.dash-activity-extras__flag input:focus-visible + .dash-activity-tick,
.ol-autocat__row input:focus-visible + .dash-activity-tick,
.ol-autocat__field input:focus-visible + .dash-activity-tick {
  box-shadow: 0 0 0 3px var(--color-action-active-soft, #f1ece6);
}

.dash-activity-option__pick:hover .dash-activity-option__radio {
  border-color: var(--color-action-active, #75461d);
}

.dash-activity-option__pick:focus-visible .dash-activity-option__radio {
  outline: none;
  border-color: var(--color-action-active, #75461d);
  box-shadow: 0 0 0 3px var(--color-action-active-soft, #f1ece6);
}

.dash-activity-option.is-selected .dash-activity-option__radio {
  background: var(--color-action-active, #75461d);
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

.dash-activity-option__label {
  font-size: 0.78rem;
  font-weight: 600;
  color: #2f2a25;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-activity-option.is-selected .dash-activity-option__label {
  font-weight: 800;
}

/* Company name beside a contact's own name. Two people called the same thing
   is common enough in a contact list that the row has to disambiguate, but it
   is secondary — it gives up its width first. */
.dash-activity-option__meta {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.7rem;
  font-weight: 600;
  color: #a8a29e;
}

/* ── Category split (dash-activity-field.php) ──
   Opens under the option list once a second category is ticked: one line per
   category with an amount box, a running remainder, and a Done that stays
   disabled until the amounts add up.

   Everything here sets box-sizing explicitly. There is no global border-box
   reset in this stylesheet, and a padded full-width input inside the card's
   own padding overflows the panel without it. */
.dash-split {
  box-sizing: border-box;
  margin-top: 0.5rem;
  padding: 0.65rem 0.55rem 0.55rem;
  border-top: 1px solid var(--color-border-subtle, #f5f5f5);
}

/* The box opens under a list the user was still reading, so it says what it
   is before it says what it wants. */
.dash-split__title {
  margin: 0 0 0.2rem;
  font-size: 0.78rem;
  font-weight: 800;
  color: #2f2a25;
}

.dash-split__intro {
  margin: 0 0 0.55rem;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.4;
  color: #78716c;
}

.dash-split__intro strong {
  font-weight: 800;
  color: #2f2a25;
}

.dash-split__lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.dash-split__line {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.dash-split__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.76rem;
  font-weight: 700;
  color: #2f2a25;
}

/* The box is the wrapper, not the input: the currency symbol sits inside the
   same border so the field reads as one money control. */
.dash-split__amount {
  box-sizing: border-box;
  flex: 0 0 auto;
  width: 7.5rem;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid rgba(117, 70, 29, 0.18);
  border-radius: 10px;
  background: #fff;
}

.dash-split__amount:focus-within {
  border-color: rgba(117, 70, 29, 0.45);
}

.dash-split__symbol {
  font-size: 0.74rem;
  font-weight: 700;
  color: #a8a29e;
}

.dash-split__input {
  box-sizing: border-box;
  flex: 1;
  min-width: 0;
  width: 100%;
  border: 0;
  outline: none;
  background: transparent;
  padding: 0;
  font-size: 0.78rem;
  font-weight: 700;
  color: #2f2a25;
  text-align: right;
}

/* Only rendered when the split straddles a deductibility boundary. Follows the
   house rule: solid brown is a pressed state, not a resting one. */
.dash-split__deductible {
  box-sizing: border-box;
  flex: 0 0 auto;
  border: 1px solid rgba(117, 70, 29, 0.22);
  border-radius: 999px;
  padding: 0.2rem 0.55rem;
  background: transparent;
  font-size: 0.66rem;
  font-weight: 700;
  color: #75461d;
  cursor: pointer;
}

.dash-split__deductible.is-on {
  background: #75461d;
  border-color: #75461d;
  color: #fff;
}

.dash-split__remainder {
  margin: 0.6rem 0 0;
  font-size: 0.74rem;
  font-weight: 700;
  /* Outstanding work, in the same terracotta the card uses for Uncategorised. */
  color: #b4532a;
}

.dash-split__remainder.is-balanced {
  color: #4f7a27;
}

.dash-split__error {
  margin: 0.35rem 0 0;
  font-size: 0.72rem;
  font-weight: 600;
  color: #b4532a;
}

.dash-split__actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.6rem;
}

.dash-split__save {
  border: 0;
  border-radius: 999px;
  padding: 0.4rem 1.1rem;
  background: #75461d;
  font-size: 0.74rem;
  font-weight: 800;
  color: #fff;
  cursor: pointer;
}

/* Disabled is the normal state here until the amounts balance, so it has to
   read as "not yet", not as "broken" — hence the intro line above stating the
   rule. */
.dash-split__save:disabled {
  background: #e7e0d7;
  color: #a8a29e;
  cursor: default;
}

/* ── Add-a-contact popup (activity-contact-modal.php) ── */
.ol-contact-add__row {
  margin-top: 0.9rem;
}

/* Dial code then number, the same pair Settings uses for a phone. The select
   sizes to its content so the number keeps the room it needs. */
.ol-contact-add__phone {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}

.ol-contact-add__phone > input {
  flex: 1;
  min-width: 0;
}

/* The trigger carries the code alone — the country names live in the sheet,
   so nothing here has to be truncated. */
/* Scoped to the row so it outranks .modal-input's width: 100%, which lands
   later in the cascade (olowo-modals.css loads after this file). */
.ol-contact-add__phone .ol-contact-add__dial {
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  width: auto;
  flex: 0 0 auto;
  cursor: pointer;
  text-align: left;
}

.ol-contact-add__dial-chevron {
  display: inline-flex;
  color: #a8a29e;
}

/* ── Country-code sheet (stacked over the Add-a-contact popup) ── */
.ol-dial-sheet {
  /* Above .ol-drawer's 1300, so it sits on the popup that opened it. */
  z-index: 1400;
}

.ol-dial-sheet__list {
  margin-top: 0.6rem;
  max-height: 45vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.ol-dial-sheet__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  box-sizing: border-box;
  padding: 0.7rem 0.4rem;
  border: 0;
  border-bottom: 1px solid #f0ece7;
  background: none;
  font-size: 0.85rem;
  font-weight: 700;
  color: #1c1712;
  text-align: left;
  cursor: pointer;
}

.ol-dial-sheet__row:last-of-type {
  border-bottom: 0;
}

/* House rule: the chosen row is solid brown. */
.ol-dial-sheet__row.is-on {
  border-radius: 0.6rem;
  border-bottom-color: transparent;
  background: #75461d;
  color: #fff;
}

.ol-dial-sheet__code {
  flex: none;
  font-weight: 800;
  color: #78716c;
}

.ol-dial-sheet__row.is-on .ol-dial-sheet__code {
  color: rgba(255, 255, 255, 0.85);
}

.ol-dial-sheet__empty {
  margin: 0.9rem 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: #78716c;
  text-align: center;
}

.ol-contact-add__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.65rem;
  margin-top: 0.9rem;
}

@media (max-width: 24rem) {
  .ol-contact-add__grid {
    grid-template-columns: 1fr;
  }
}

.ol-contact-add__legend {
  margin-top: 0.9rem;
}

.ol-contact-add__cats {
  /* The pills themselves are the global .ol-name-tag — see Name tags above. */
  margin-top: 0.15rem;
}

.dash-activity-option__tools {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  padding-right: 0.5rem;
}

.dash-activity-option__tool {
  width: 1.5rem;
  height: 1.5rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: #a8a29e;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.dash-activity-option__tool:hover {
  background: var(--color-border-subtle, #f5f5f5);
  color: #57534e;
}

/* ── Extras footer ──
 *
 * The transaction settings the pickers above don't cover. The flags save on the
 * tick; the rest is one small patch form, so a save here cannot undo a pick. */
.dash-activity-extras {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 0.65rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border-hairline, #f5f5f5);
}

.dash-activity-extras__flags {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.dash-activity-extras__flag {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  cursor: pointer;
}

.dash-activity-extras__flag-text {
  display: flex;
  flex-direction: column;
  font-size: 0.72rem;
  line-height: 1.35;
  color: #8a827a;
}

.dash-activity-extras__flag-text strong {
  font-size: 0.78rem;
  font-weight: 700;
  color: #2f2a25;
}

/* ── Auto-categorisation controls (activity-card-extras.php) ──
   The two boxes that create and apply a rule, their "?" helper text, and the
   confirm bar that shows what an Apply would touch before it touches it. */
.dash-activity-extras__help {
  appearance: none;
  border: 1px solid #d9d2c9;
  background: transparent;
  color: #8a827a;
  font: inherit;
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 1;
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 50%;
  margin-left: 0.3rem;
  cursor: pointer;
  vertical-align: middle;
}

.dash-activity-extras__help:hover,
.dash-activity-extras__help:focus-visible {
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-active, #75461d);
  outline: none;
}

.dash-activity-extras__help-text {
  margin: -0.15rem 0 0 1.7rem;
  font-size: 0.7rem;
  line-height: 1.45;
  color: #8a827a;
}

.dash-activity-rules__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.7rem;
  border-radius: 10px;
  background: #faf7f3;
  border: 1px solid #ece5db;
}

.dash-activity-rules__note {
  margin: 0;
  flex: 1 1 12rem;
  min-width: 0;
  font-size: 0.7rem;
  line-height: 1.45;
  color: #6f675f;
}

.dash-activity-rules__link {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--color-action-active, #75461d);
  text-decoration: none;
}

.dash-activity-rules__link:hover {
  text-decoration: underline;
}

/* Apply stays visible while the count is still being fetched or the card has
   no category yet, but must not look pressable. */
.dash-activity-extras__btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.dash-activity-extras__form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.6rem;
}

.dash-activity-extras__field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}

.dash-activity-extras__label {
  font-size: 0.68rem;
  font-weight: 600;
  color: #a8a29e;
  letter-spacing: 0.02em;
}

.dash-activity-extras__input {
  /* The app doesn't border-box form controls globally, and without it width
     100% plus the padding and border overflows the card. */
  box-sizing: border-box;
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  border-radius: var(--ds-radius-sm, 0.5rem);
  background: var(--color-surface-card, #fff);
  font: inherit;
  font-size: 0.78rem;
  color: #2f2a25;
  resize: vertical;
}

.dash-activity-extras__input:focus {
  outline: none;
  border-color: var(--color-action-active, #75461d);
  box-shadow: 0 0 0 3px var(--color-action-active-soft, #f1ece6);
}

.dash-activity-extras__actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
}

.dash-activity-extras__btn {
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  border-radius: var(--ds-radius-sm, 0.5rem);
  background: var(--color-surface-card, #fff);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 700;
  color: #57534e;
  cursor: pointer;
}

.dash-activity-extras__btn:hover {
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-active, #75461d);
}

.dash-activity-extras__btn.is-primary {
  background: var(--color-action-active, #75461d);
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

.dash-activity-extras__btn.is-danger {
  color: #c0392b;
  border-color: #f5d6d0;
}

.dash-activity-extras__btn.is-danger:hover {
  border-color: #c0392b;
  color: #c0392b;
}

.dash-activity-extras__note {
  margin: 0;
  font-size: 0.72rem;
  color: #a8a29e;
}

/* ── Attachments ── */
.dash-activity-files {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--color-border-subtle, #f5f5f5);
  border-radius: 11px;
  background: #fff;
  overflow: hidden;
}

.dash-activity-file {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  border-top: 1px solid var(--color-border-subtle, #f5f5f5);
}

.dash-activity-file:first-child {
  border-top: 0;
}

.dash-activity-file__icon {
  color: #b47b3c;
  display: inline-flex;
  flex-shrink: 0;
}

.dash-activity-file__name {
  flex: 1;
  min-width: 0;
  font-size: 0.75rem;
  font-weight: 700;
  color: #2f2a25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-activity-file__size {
  font-size: 0.68rem;
  font-weight: 600;
  color: #a8a29e;
  white-space: nowrap;
}

.dash-activity-file__action {
  color: #a8a29e;
  display: inline-flex;
  flex-shrink: 0;
}

.dash-activity-file__action:hover {
  color: #c0503b;
}

/* ── Files inside a field panel ──
   The Invoice row holds files as well as a link to an invoice raised here, so
   the file list and its "choose existing" lane are shared with Other
   attachments rather than living in one of them. */
.dash-activity-filebox {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.dash-activity-filebox__bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 0 0.15rem;
}

.dash-activity-filebox__count {
  font-size: 0.68rem;
  font-weight: 700;
  color: #a8a29e;
}

.dash-activity-filebox__link {
  border: 0;
  background: transparent;
  padding: 0;
  font-family: inherit;
  font-size: 0.68rem;
  font-weight: 800;
  color: #75461d;
  text-decoration: underline;
  cursor: pointer;
  flex-shrink: 0;
}

.dash-activity-existing {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.45rem;
  border: 1px dashed var(--color-border-subtle, #f5f5f5);
  border-radius: 11px;
  background: #fbf9f6;
}

.dash-activity-existing__note {
  margin: 0;
  padding: 0.3rem 0.2rem;
  font-size: 0.7rem;
  font-weight: 600;
  color: #78716c;
}

/* Attaching is the point of this list, so the action reads as the same soft
   affordance as Upload rather than as a second delete. */
.dash-activity-file__attach {
  border: 0;
  border-radius: 8px;
  padding: 0.22rem 0.5rem;
  background: rgba(233, 161, 78, 0.22);
  color: #75461d;
  font-family: inherit;
  font-size: 0.65rem;
  font-weight: 800;
  white-space: nowrap;
  flex-shrink: 0;
  cursor: pointer;
}

.dash-activity-file__attach:disabled {
  opacity: 0.6;
  cursor: wait;
}

/* Divides the files at the top of the Invoice panel from the invoice records
   underneath them — two ways of answering the same question. */
.dash-activity-picker__heading {
  margin: 0.55rem 0 0.15rem;
  padding: 0.5rem 0.2rem 0;
  border-top: 1px solid var(--color-border-subtle, #f5f5f5);
  font-size: 0.65rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #a8a29e;
}

/* The Upload pill's count badge sits on the tan pill, not a brown one. */
.dash-activity-field__upload .ol-upload-count {
  margin-left: 0.1rem;
}

/* ── File names on a closed file field ──
   Each name is its own input (Form A): typed over in place, pencil beside it.
   Under the head rather than inside it, because the head's toggle is a button
   and an input cannot live in one. */
.dash-activity-filenames[hidden] {
  display: none;
}

.dash-activity-filenames {
  list-style: none;
  margin: -0.2rem 0 0;
  padding: 0 0.6rem 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;
}

.dash-activity-filename {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
}

.dash-activity-filename__icon {
  display: inline-flex;
  flex-shrink: 0;
  color: #b47b3c;
}

.dash-activity-filename__input {
  flex: 1;
  min-width: 0;
}

.dash-activity-filename:focus-within .dash-activity-field__edit {
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

/* ── File tiles inside an open file field ──
   The task attachment tile: the image itself or a clip glyph, name and size
   under it, save and delete over the corners. One file fills the row; two or
   more fall into columns. */
.dash-activity-tiles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 0.5rem;
  min-width: 0;
}

.dash-activity-tile {
  position: relative;
  display: flex;
  min-width: 0;
}

.dash-activity-tile__open {
  box-sizing: border-box;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.375rem 0.5rem;
  border: 1.5px solid #e1e1e1;
  border-radius: 0.75rem;
  background: #fff;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.dash-activity-tile__open:hover {
  border-color: var(--color-action-active, #75461d);
}

.dash-activity-tile__open:active {
  transform: none;
}

.dash-activity-tile__open:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, #75461d);
  outline-offset: var(--focus-ring-offset, 2px);
}

.dash-activity-tile__thumb {
  display: block;
  width: 100%;
  height: 6.5rem;
  border-radius: 0.4375rem;
  object-fit: cover;
  background: #f5f5f5;
}

.dash-activity-tile__thumb--file {
  display: grid;
  place-items: center;
  color: var(--color-action-active, #75461d);
}

.dash-activity-tile__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem;
  min-width: 0;
}

.dash-activity-tile__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.72rem;
  font-weight: 700;
  color: #1f1f1f;
}

.dash-activity-tile__size {
  flex: none;
  font-size: 0.6rem;
  font-weight: 600;
  color: #8e8e8e;
}

.dash-activity-tile__corner {
  position: absolute;
  top: 0.625rem;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 1.5px solid #e1e1e1;
  border-radius: 0.5rem;
  background: #fafafa;
  color: var(--color-action-active, #75461d);
  text-decoration: none;
  cursor: pointer;
}

.dash-activity-tile__corner:active {
  transform: none;
}

.dash-activity-tile__download {
  right: 0.875rem;
}

.dash-activity-tile__delete {
  left: 0.875rem;
  color: #a8a29e;
}

.dash-activity-tile__corner:hover {
  border-color: var(--color-action-active, #75461d);
}

.dash-activity-tile__delete:hover {
  border-color: #c0503b;
  color: #c0503b;
}

.dash-activity-card__clip {
  cursor: pointer;
}

.dash-activity-card__clip:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, #75461d);
  outline-offset: 2px;
}

/* ── File preview (olFilePreview, public_html/js/file-preview.js) ──
   Full screen over everything but the notice sheet: a file wants the whole
   phone, and the bottom nav under it is not reachable while it is open. */
.ol-file-preview[hidden] {
  display: none;
}

.ol-file-preview {
  position: fixed;
  inset: 0;
  z-index: 1450;
  display: flex;
  flex-direction: column;
  background: rgba(20, 16, 12, 0.97);
  color: #fff;
}

.ol-file-preview *,
.ol-file-preview *::before,
.ol-file-preview *::after {
  box-sizing: border-box;
}

.ol-file-preview__bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: calc(env(safe-area-inset-top, 0px) + 0.75rem) 1rem 0.75rem;
}

.ol-file-preview__titles {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.ol-file-preview__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.95rem;
  font-weight: 700;
}

.ol-file-preview__count {
  font-size: 0.75rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
}

.ol-file-preview__count[hidden] {
  display: none;
}

.ol-file-preview__btn {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  cursor: pointer;
  text-decoration: none;
}

.ol-file-preview__btn:hover {
  background: rgba(255, 255, 255, 0.24);
}

.ol-file-preview__stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 1rem calc(env(safe-area-inset-bottom, 0px) + 1rem);
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.ol-file-preview__image {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 0.5rem;
}

/* A PDF scrolls; its pages start at the top rather than centring. */
.ol-file-preview__pages {
  align-self: flex-start;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
}

.ol-file-preview__page {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 0.25rem;
  background: #fff;
}

.ol-file-preview__loading {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.75);
  text-align: center;
}

.ol-file-preview__fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  max-width: 20rem;
  padding: 1.5rem;
  border-radius: 1rem;
  background: var(--color-surface-card, #fff);
  color: var(--color-text-primary, #2f2a25);
  text-align: center;
}

.ol-file-preview__fallback-icon {
  color: var(--color-action-active, #75461d);
}

.ol-file-preview__fallback-name {
  margin: 0;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.ol-file-preview__fallback-note {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--color-text-secondary, #78716c);
}

.ol-file-preview__fallback-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0 1.5rem;
  border-radius: var(--radius-full, 9999px);
  background: var(--color-status-success, #2f6f4e);
  color: #fff;
  font-weight: 700;
  text-decoration: none;
}

/* Download is green wherever the viewer offers it — the same green as the
   Upload count badge. */
.ol-file-preview__fallback-btn:hover,
.ol-file-preview__download:hover {
  background: color-mix(in srgb, var(--color-status-success, #2f6f4e) 85%, #000);
}

.ol-file-preview__download {
  background: var(--color-status-success, #2f6f4e);
}

.ol-file-preview__nav {
  position: absolute;
  top: 50%;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-top: -1.375rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.9);
  color: var(--color-action-active, #75461d);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

.ol-file-preview__nav[hidden] {
  display: none;
}

.ol-file-preview__nav--prev {
  left: 0.75rem;
}

.ol-file-preview__nav--next {
  right: 0.75rem;
}

/* ── Activity pagination control ── */
.dash-activity-show-more {
  appearance: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  margin: 0.85rem auto 0;
  padding: 0.5rem 0.9rem;
  width: fit-content;
  border: 1px solid var(--color-border-subtle, #f5f5f5);
  border-radius: 999px;
  background: #fff;
  color: #4e4336;
  font-size: 0.75rem;
  font-weight: 800;
  font-family: inherit;
  text-decoration: none;
  box-shadow: 0 1px 3px rgba(28, 25, 23, 0.04);
  cursor: pointer;
}

.dash-activity-show-more:hover {
  border-color: rgba(233, 161, 78, 0.45);
  color: #75461d;
}

@media (max-width: 380px) {
  .dash-activity-fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ============================================================
   PropertyCard + the glass StatTile
   The properties list design. Values come from the design tokens
   already declared in variables.css; the few literals below are
   ones that design introduced and that have no token yet.
   ============================================================ */

/* A frosted tile over the photographic hero. Same component as the Teams
   stat tile, laid out as a row: the icon sits beside the figure because the
   tile is 71px wide and a stacked icon would leave no room for the number. */
.ol-stat--glass {
  display: grid;
  grid-template-columns: 16px 1fr;
  grid-template-rows: auto auto auto;
  column-gap: 4px;
  row-gap: 0;
  align-items: start;
  padding: 10px 6px;
  min-width: 0;
  background: linear-gradient(
    111.43deg,
    rgba(117, 70, 29, 0.47) 0.81%,
    rgba(117, 70, 29, 0.54) 100%
  );
  backdrop-filter: blur(2px);
  border-radius: 9px;
  box-shadow: none;
}

.ol-stat--glass .ol-stat__icon {
  grid-column: 1;
  grid-row: 1 / span 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 8.5px;
  background: rgba(245, 210, 171, 0.18);
  /* The design's Gold/700 (#E9A14E), not the app's --gold (#895200). They are
     different colours: --gold is tuned for text on cream and reads as almost
     black against this brown tile, where the lighter gold is what carries. */
  color: #e9a14e;
}

.ol-stat--glass .ol-stat__value {
  grid-column: 2;
  grid-row: 1;
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  font-size: 14px;
  line-height: 20px;
  color: #fff;
}

.ol-stat--glass .ol-stat__label {
  grid-column: 2;
  grid-row: 2;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 8px;
  line-height: 11px;
  color: #fff;
  text-transform: none;
  letter-spacing: 0;
}

.ol-stat--glass .ol-stat__hint {
  grid-column: 2;
  grid-row: 3;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 6px;
  line-height: 8px;
  /* Deliberately the muted grey even on a dark ground: this line is the
     qualifier, and it must not compete with the figure above it. */
  color: var(--color-text-tertiary, #8e8e8e);
}

/* The "Add new property" tile is an action, so it takes the green fill rather
   than the frosted brown — it is the only tile that does something. */
.ol-stat--glass.ol-stat--action {
  background: linear-gradient(128.08deg, #0d905a 5.25%, #096e44 95.66%);
}
.ol-stat--glass.ol-stat--action .ol-stat__label {
  font-weight: 700;
  font-size: 8px;
  line-height: 18px;
}
.ol-stat--glass.ol-stat--action .ol-stat__hint {
  color: #e1e1e1;
}
/* On the green action tile the glyph is white; on the frosted brown ones it
   stays gold. Both inherit through currentColor, so the icon never carries a
   colour of its own. */
.ol-stat--glass.ol-stat--action .ol-stat__icon {
  color: #fff;
}

/* ---------- PropertyCard ---------- */

.ol-property-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px;
  background: var(--color-surface-card, #fff);
  border-radius: 20px;
}

/* The head is the collapsed card: everything you read on the left, the one
   control that opens it on the right. align-items: center is the whole point --
   the chevron centres against the badges-plus-identity block instead of riding
   the top row, matching the global settings disclosures. */
.ol-property-card__head {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.ol-property-card__summary {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
  flex: 1 1 auto;
}

.ol-property-card__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-width: 0;
}

.ol-property-card__badges {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  min-width: 0;
  /* Badges give way; the controls beside them never do. */
  flex: 1 1 auto;
}

.ol-property-card__controls {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

/* /properties does not load the dashboard's global [x-cloak] rule, so the
   edit control is hidden by its own selector until Alpine takes over the
   x-show that ties it to the expanded state. */
.ol-property-card__controls[x-cloak],
.ol-property-card__detail[x-cloak] {
  display: none;
}

/* Hairline separating the state badges from the controls, so the row reads as
   "what this is" then "what you can do". */
.ol-property-card__rule {
  width: 1px;
  height: 20px;
  background: var(--color-border-subtle, #eee);
  border-radius: 28px;
  flex-shrink: 0;
}

.ol-property-card__edit,
.ol-property-card__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border: 0;
  cursor: pointer;
  padding: 0;
}

.ol-property-card__edit {
  width: 30px;
  height: 30px;
  border-radius: 15px;
  background: var(--tone-accent-bg, #f1f8ff);
  color: #3a70e2;
  text-decoration: none;
}
.ol-property-card__edit:hover {
  background: #e4f0ff;
}

.ol-property-card__toggle {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 12px;
  background: var(--color-surface-muted, #f5f5f5);
  color: var(--color-text-tertiary, #8e8e8e);
  transition:
    background-color 150ms ease,
    color 150ms ease,
    transform 150ms ease;
  /* Resting state points down; open flips it. One icon, two states. */
  transform: rotate(180deg);
}
.ol-property-card.is-open .ol-property-card__toggle {
  background: var(--color-action-active, #75461d);
  color: #fff;
  transform: rotate(0deg);
}
.ol-property-card__edit:focus-visible,
.ol-property-card__toggle:focus-visible {
  outline: 2px solid var(--color-action-primary, #895200);
  outline-offset: 2px;
}

.ol-property-card__identity {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  min-width: 0;
}

.ol-property-card__name {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: #212121;
}

.ol-property-card__address {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-property-card__profile {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 11px;
  padding: 0 4px;
  border: 0.5px solid var(--color-border-subtle, #eee);
  border-radius: 14px;
  font-family: "Manrope", sans-serif;
  font-weight: 500;
  font-size: 10px;
  line-height: 18px;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-property-card__detail {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ol-property-card__stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 12px;
  margin: 0;
  padding: 6px 8px;
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  border-radius: 12px;
}

.ol-property-card__stat {
  min-width: 0;
}

/* A figure that needs acting on. Only arrears uses this today: money owed
   reads differently from money expected, and the colour is the difference. */
.ol-property-card__stat.is-danger dd {
  color: var(--color-status-danger, #ba1a1a);
}

.ol-property-card__stat dt {
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-property-card__stat dd {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--color-text-primary, #1f1f1f);
  /* Figures wrap at spaces or not at all — never mid-number. */
  overflow-wrap: normal;
  word-break: keep-all;
}

/* ---------- Properties list page ---------- */

.ol-properties {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ol-properties__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.ol-properties__title {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 16px;
  line-height: 24px;
  color: var(--color-text-primary, #1f1f1f);
}

/* Three dots in the house accents — a mark, not a control. */
.ol-properties__dots {
  display: flex;
  align-items: center;
  gap: 4px;
}
.ol-properties__dots i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  display: block;
}
.ol-properties__dots i:nth-child(1) {
  background: #e9a14e;
}
.ol-properties__dots i:nth-child(2) {
  background: #518c15;
}
.ol-properties__dots i:nth-child(3) {
  background: #c45b4b;
}

.ol-properties__filters {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.ol-properties__filters::-webkit-scrollbar {
  display: none;
}

.ol-properties__controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  background: var(--color-surface-card, #fff);
  border-radius: 20px;
}

.ol-properties__profile-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.ol-properties__profile-row > label {
  flex-shrink: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-properties__field {
  width: 100%;
  padding: 8px 12px;
  background: var(--color-surface-sunken, #fafafa);
  border: 1.5px solid var(--color-border-subtle, #eee);
  border-radius: 14px;
  font-family: "Manrope", sans-serif;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-primary, #1f1f1f);
}
.ol-properties__field::placeholder {
  color: var(--color-text-tertiary, #8e8e8e);
}
.ol-properties__field:focus {
  outline: none;
  border-color: var(--color-action-primary, #895200);
}

.ol-properties__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The design is drawn at 393px. Above that the cards get room to breathe and
   the figures spread to four across rather than wrapping to two rows. */
@media (min-width: 640px) {
  .ol-property-card__stats {
    grid-template-columns: repeat(4, 1fr);
  }
}
@media (min-width: 900px) {
  .ol-properties__list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: 12px;
    align-items: start;
  }
}

/* Compact badge for dense rows — the property card carries three of these
   beside two controls at 393px, where the default size overflows. */
.ol-badge--sm {
  padding: 3px 6px;
  gap: 4px;
  font-size: 10px;
  line-height: 18px;
  border-radius: 54px;
}
.ol-badge--sm svg {
  width: 12px;
  height: 12px;
}

/* ============================================================================
   Property sheet
   ----------------------------------------------------------------------------
   The property view reads as a sheet lifted over the app rather than another
   page: rounded top corners, a title with a handle under it, then the working
   sections. It stays a real route (/properties/{id}?tab=) so a refresh, a deep
   link and the back button all still work — only the appearance is a modal.
   ========================================================================== */

.ol-property-sheet {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.3125rem; /* 21px */
  padding: 1rem 1.5rem 1.5rem;
  background: #fff;
  border-radius: 32px 32px 0 0;
  min-height: 100%;
}

/* The sheet spans the viewport, its contents do not: the design lays out a
   345px column, which would be a ribbon on a desktop window. */
.ol-property-sheet__inner {
  display: flex;
  flex-direction: column;
  /* stretch, not flex-start: flex-start sizes each child to its own content, so
     any section wider than the column ran off the screen instead of fitting it.
     Children that should hug their content opt out with align-self. */
  align-items: stretch;
  gap: 1.5rem;
  width: 100%;
  max-width: 34rem;
  min-width: 0;
}

/* Everything the sheet renders is laid out inside this column, including the
   older tab bodies written before it existed. None of them may push the page
   sideways. */
.ol-property-sheet__inner > * {
  min-width: 0;
  max-width: 100%;
}

/* Title and handle. The handle is the design's gold pill — decorative, so it
   is hidden from assistive tech rather than announced as an image. */
.ol-property-sheet__head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--color-border-hairline, #f5f5f5);
}

.ol-property-sheet__title {
  margin: 0;
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.75rem;
  color: #1f1f1f;
  text-align: center;
}

.ol-property-sheet__handle {
  width: 36px;
  height: 3px;
  border-radius: 20px;
  /* The design's Gold/700. --gold is a much darker #895200 and disappears
     against white, so the literal is deliberate. */
  background: #e9a14e;
}

/* Back pill. Named .ol-back because more than the property sheet goes back —
   the tax P&L returns to the obligations list through the same control. */
.ol-back,
.ol-property-sheet__back {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 0.25rem;
  padding: 0.5rem;
  border-radius: 39px;
  background: var(--color-count-surface, #fbf4ef);
  color: var(--color-action-active, #75461d);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  text-decoration: none;
}

.ol-back:hover,
.ol-property-sheet__back:hover {
  background: #f5e9df;
}
.ol-back svg,
.ol-property-sheet__back svg {
  width: 18px;
  height: 18px;
}

/* Identity: name and address left, the two round controls right. */
.ol-property-sheet__identity {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  width: 100%;
}

/* Type comes from the shared item-card text styles: the markup pairs these
   with .ol-item-card__title and .ol-item-card__due, so the property's address
   reads exactly as a deadline reads on the dashboard. All that is left here is
   the block spacing a card gets from its flex parent and this one does not. */
.ol-property-sheet__name {
  margin: 0;
}

.ol-property-sheet__address {
  margin: 0.25rem 0 0;
}

.ol-property-sheet__controls {
  display: flex;
  gap: 0.625rem;
  flex: none;
}

.ol-property-sheet__control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 15px;
  cursor: pointer;
}

.ol-property-sheet__control svg {
  width: 18px;
  height: 18px;
}
.ol-property-sheet__control--edit {
  background: #f1f8ff;
  color: #3a70e2;
}
.ol-property-sheet__control--delete {
  background: #fef2f2;
  color: #ec2d30;
}

/* Tab rail, sheet variant. Differs from the default .ol-tabbar in shape only —
   a grey track, tighter pills, smaller label — because the house switch
   convention allows a variant to change size, outline or shape but never what
   "on" looks like. So the selected pill still fills brown with a white label,
   the same as every other tab bar in the app. (The property mock drew a white
   pill with a green label here; matching the rest of the app won.) */
.ol-tabbar--sheet {
  gap: 0;
  padding: 4px;
  background: #f5f5f5;
  border-radius: 16px;
}

.ol-tabbar--sheet .ol-tabbar__tab {
  padding: 4px 10px;
  border: 0;
  border-radius: 13px;
  background: transparent;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.125rem;
  color: #8e8e8e;
}

/* Restated rather than inherited: the rule above sets a transparent background
   at equal specificity to the base selected rule and later in the file, so
   without this the selected pill would lose its fill. Same tokens as the
   default, so the brown is defined in one place. */
.ol-tabbar--sheet .ol-tabbar__tab[aria-selected="true"] {
  background: var(--color-switch-selected-bg, #75461d);
  color: var(--color-switch-selected-fg, #fff);
  font-weight: 600;
}

/* ── SectionCard ─────────────────────────────────────────────────────────── */

.ol-section-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  width: 100%;
  padding: 0.625rem 0.5rem;
  border-radius: 16px;
}

.ol-section-card--brand {
  background: #fafafa;
}
.ol-section-card--success {
  background: #e5f5ec;
  padding: 0.625rem;
  gap: 0.5rem;
}
.ol-section-card--plain {
  background: transparent;
  padding: 0;
}

.ol-section-card__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.875rem;
  width: 100%;
}

.ol-section-card__title {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: var(--color-action-active, #75461d);
}

.ol-section-card--success .ol-section-card__title {
  color: #518c15;
}

.ol-section-card__panel {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
  padding: 0.625rem 0.5rem;
  background: #fff;
  border-radius: 12px;
}

.ol-section-card__note {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.125rem;
  color: #8e8e8e;
}

/* ── FactRow ───────────────────────────────────────────────────────────── */

.ol-fact-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
}

.ol-fact-row--center {
  align-items: center;
}
.ol-fact-row--top {
  align-items: flex-start;
}

.ol-fact-row__label {
  flex: none;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: #8e8e8e;
}

.ol-fact-row__value {
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.25rem;
  color: #1f1f1f;
  text-align: right;
}

.ol-fact-row__empty {
  color: #8e8e8e;
  font-weight: 500;
}

/* ── Linked bank row ─────────────────────────────────────────────────────── */

.ol-bank-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  padding: 1rem;
  background: #fff;
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  border-radius: 20px;
  text-decoration: none;
}

.ol-bank-row + .ol-bank-row {
  margin-top: 0.5rem;
}

.ol-bank-row__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  padding: 8px;
  background: #fff;
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  border-radius: 12px;
  overflow: hidden;
}

.ol-bank-row__logo img {
  max-width: 100%;
  max-height: 100%;
}

.ol-bank-row__name {
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: #212121;
}

.ol-bank-row__meta {
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: #8e8e8e;
}

/* The row splits when it carries an action: the account itself stays the
   link to /accounts, and Unlink sits outside it. */
.ol-bank-row__main {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex: 1 1 auto;
  min-width: 0;
  text-decoration: none;
}

.ol-bank-row__unlink {
  flex: none;
  padding: 0.375rem 0.75rem;
  background: #fff;
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: #8e8e8e;
  cursor: pointer;
}

/* The same row as a choice in the picker sheet. It is a <button>, so it needs
   the font and the box model back, and the sheet's padding makes border-box
   the difference between fitting and overflowing. */
.ol-bank-row--choice {
  box-sizing: border-box;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* The one action under the card: dashed, quiet, and the width of the rows it
   sits beneath. An <a> and a <button> both wear it. */
.ol-bank-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  box-sizing: border-box;
  width: 100%;
  margin-top: 0.5rem;
  padding: 0.875rem 1rem;
  background: transparent;
  border: 1.5px dashed #e6e0d6;
  border-radius: 20px;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: var(--gold-dark, #75461d);
  text-decoration: none;
  cursor: pointer;
}

.ol-bank-link svg {
  flex: none;
}

/* Tax-year picker, shown only on the two tabs that report on one. */
.ol-property-sheet__year {
  margin: 0;
  align-self: flex-end;
}

.ol-property-sheet__year select {
  border: 1.5px solid #eeeeee;
  border-radius: 8px;
  padding: 0.375rem 0.75rem;
  background: #fafafa;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  color: #1f1f1f;
}

/* The design's Primary/Green/700. The shared Button's success variant is the
   app's own #52A374, so an unscoped button put two greens side by side inside
   one card. Scoped here rather than changed on Button: the rest of the app
   keeps its green, and this card stays internally consistent. */
.ol-section-card--success .ol-btn--success {
  background: #518c15;
  border-color: #518c15;
}

.ol-section-card--success .ol-btn--success:hover {
  background: #47790f;
  border-color: #47790f;
}

/* ============================================================================
   Projects list page
   ----------------------------------------------------------------------------
   The same shape as the properties list — photographic hero, title, status
   rail, one filter card, then a stack of expandable cards — because the two
   pages answer the same question about different things. Values come from the
   design tokens already declared in variables.css; the few literals below are
   ones the design introduced and that have no token yet.
   ============================================================================ */

.ol-projects {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ol-projects__flash {
  margin-bottom: 12px;
  padding: 12px 16px;
  border-radius: 14px;
  font-family: "Manrope", sans-serif;
  font-size: 12px;
  line-height: 18px;
  font-weight: 600;
}
.ol-projects__flash--success {
  background: #ecfdf3;
  color: #027a48;
}
.ol-projects__flash--error {
  background: #fdecea;
  color: #8f1d13;
}

.ol-projects__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.ol-projects__title {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 16px;
  line-height: 24px;
  color: var(--color-text-primary, #1f1f1f);
}

/* One card holding every filter that is not the status rail. */
.ol-projects__controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  background: var(--color-surface-card, #fff);
  border-radius: 20px;
}

.ol-projects__filter-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.ol-projects__filter-row > label {
  flex-shrink: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-projects__field {
  width: 100%;
  padding: 8px 12px;
  background: var(--color-surface-sunken, #fafafa);
  border: 1.5px solid var(--color-border-subtle, #eee);
  border-radius: 14px;
  font-family: "Manrope", sans-serif;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-primary, #1f1f1f);
}
.ol-projects__field::placeholder {
  color: var(--color-text-tertiary, #8e8e8e);
}
.ol-projects__field:focus {
  outline: none;
  border-color: var(--color-action-primary, #895200);
}

.ol-projects__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ---------- ProjectCard ---------- */

.ol-project-card {
  display: flex;
  flex-direction: column;
  padding: 16px;
  background: var(--color-surface-card, #fff);
  border-radius: 20px;
}

/* The whole summary is the toggle, so the target is the full width of the card
   rather than the chevron alone. It is a button, hence the reset. */
.ol-project-card__summary {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ol-project-card__summary:focus-visible {
  outline: 2px solid var(--focus-ring-color, #895200);
  outline-offset: 4px;
  border-radius: 12px;
}

.ol-project-card__top {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ol-project-card__name {
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--color-text-primary, #1f1f1f);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Finished work stays in the list, struck through: it is history you can open,
   not something to act on. */
.ol-project-card.is-finished .ol-project-card__name {
  text-decoration: line-through;
  color: var(--color-text-secondary, #524437);
}

/* The chevron is the only part of the row that moves, so it carries the state:
   pale and pointing down when shut, solid brown and pointing up when open. */
.ol-project-card__chevron {
  flex-shrink: 0;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-surface-sunken, #fafafa);
  color: var(--color-text-secondary, #524437);
  transition:
    background 0.2s ease,
    color 0.2s ease,
    transform 0.2s ease;
}
.ol-project-card.is-open .ol-project-card__chevron {
  background: var(--color-action-primary, #895200);
  color: #fff;
  transform: rotate(180deg);
}

.ol-project-card__due {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 500;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-tertiary, #8e8e8e);
}
.ol-project-card__due-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
}
/* Inside a fortnight, or already past. The same terracotta the app uses when
   something needs a decision. */
.ol-project-card__due.is-urgent {
  color: var(--color-status-attention);
  font-weight: 600;
}

.ol-project-card__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.ol-project-card__detail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}

/* The brief sits on its own tinted ground so it reads as quoted copy rather
   than another line of the card's own voice. */
.ol-project-card__description {
  margin: 0;
  padding: 12px;
  border-radius: 12px;
  background: var(--color-surface-sunken, #fafafa);
  font-family: "Manrope", sans-serif;
  font-size: 12px;
  line-height: 20px;
  color: var(--color-text-secondary, #524437);
}

.ol-project-card__money {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-family: "Manrope", sans-serif;
  font-weight: 500;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-secondary, #524437);
}
.ol-project-card__budget {
  font-weight: 600;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-project-card__meter {
  height: 4px;
  border-radius: 29px;
  background: #eeeeee;
  overflow: hidden;
}
.ol-project-card__meter-fill {
  display: block;
  height: 100%;
  border-radius: 29px;
  background: #e9a14e;
  transition: width 0.3s ease;
}
/* Overspent reads in the same terracotta the app uses for money going out. */
.ol-project-card__meter-fill.is-over {
  background: var(--color-status-attention);
}

.ol-project-card__meter-note {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 10px;
  line-height: 18px;
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-project-card__actions {
  display: flex;
  gap: 8px;
  margin-top: 6px;
}
.ol-project-card__actions .ol-btn {
  border-radius: 14px;
  min-height: 42px;
}

/* The design's Primary/Green/700. The shared Button's success variant is the
   app's own #52A374, so an unscoped pair put two greens side by side inside one
   card. Scoped here, as the property sheet does it: the rest of the app keeps
   its green and this card stays internally consistent. */
.ol-project-card .ol-btn--success {
  background: #518c15;
  border-color: #518c15;
}
.ol-project-card .ol-btn--success:hover {
  background: #47790f;
  border-color: #47790f;
}
.ol-project-card .ol-btn--success-outline {
  color: #518c15;
  border-color: #518c15;
}
.ol-project-card .ol-btn--success-outline:hover {
  background: color-mix(in srgb, #518c15 8%, transparent);
}

/* The design is drawn at 393px. Above that the cards get room to breathe, and
   past 900px the list becomes a grid rather than one very long column. */
@media (min-width: 900px) {
  .ol-projects__list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: 12px;
    align-items: start;
  }

  .ol-projects__controls {
    display: grid;
    grid-template-columns: minmax(14rem, 1.4fr) repeat(3, minmax(11rem, 1fr));
    align-items: center;
    gap: 12px;
  }
}

/* ── Property edit sheet ─────────────────────────────────────────────────────
   The form is only layout: every control is an Input and every group is a
   SectionCard, so the fields look the same here as anywhere else they appear. */

.ol-edit-form {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  width: 100%;
  min-width: 0;
}

/* Two fields on one line, as the design pairs them, dropping to one only when
   the pair genuinely will not fit.

   auto-fit rather than a media query on purpose: this grid lives inside a column
   capped at 34rem, so its width has little to do with the viewport's. A
   viewport-keyed breakpoint stacked these on a phone even though the column had
   room for both, and would have kept them side by side in a narrow column on a
   wide screen. The track size decides instead. */
.ol-edit-form__pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.5rem;
  width: 100%;
  min-width: 0;
}

.ol-edit-form__pair > * {
  min-width: 0;
}

/* Cancel and Save share the row evenly, as drawn. */
.ol-edit-form__actions {
  display: flex;
  gap: 0.5rem;
  width: 100%;
  margin-top: 0.5rem;
}

.ol-edit-form__actions > * {
  flex: 1 1 0;
  min-width: 0;
}
.ol-edit-form__actions .ol-btn {
  width: 100%;
}

.ol-edit-form__errors {
  padding: 0.75rem 1rem;
  border: 1px solid #ffccd2;
  border-radius: 12px;
  background: #fef2f2;
  color: #ec2d30;
  font-size: 0.8125rem;
}

.ol-edit-form__errors p {
  margin: 0;
}
.ol-edit-form__errors p + p {
  margin-top: 0.35rem;
}

/* ---------- Tax obligations list ---------- */

/* The app has no global border-box reset, so anything here that is both full
   width and padded — this page's own boxes, and the shared SectionCards it
   nests inside a card — would otherwise sit wider than its parent. Scoped to
   the tax page rather than made global, which is a change for another day. */
.ol-tax,
.ol-tax *,
.ol-tax *::before,
.ol-tax *::after {
  box-sizing: border-box;
}

.ol-tax {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Four figures, two across. Separate cards rather than one panel: each is a
   different question, and a shared border would imply they add up. */
.ol-tax__summary {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.ol-tax__figure {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 14px 16px;
  background: var(--color-surface-card, #fff);
  border-radius: 16px;
}

.ol-tax__figure-label {
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.125rem;
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-tax__figure-value {
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.5rem;
  color: var(--color-text-primary, #1f1f1f);
  /* Figures wrap at spaces or not at all — never mid-number. */
  overflow-wrap: normal;
  word-break: keep-all;
}

.ol-tax__figure-value.is-income {
  color: var(--color-status-success, #1e7a4a);
}
.ol-tax__figure-value.is-expense {
  color: var(--color-status-danger, #ba1a1a);
}

.ol-tax__figure-hint {
  font-size: 0.625rem;
  font-weight: 500;
  line-height: 0.9375rem;
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-tax__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.ol-tax__title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.5rem;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-tax__controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  background: var(--color-surface-card, #fff);
  border-radius: 20px;
}

.ol-tax__filter-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.ol-tax__filter-row > label {
  flex-shrink: 0;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-tax__field {
  width: 100%;
  padding: 8px 12px;
  background: var(--color-surface-sunken, #fafafa);
  border: 1.5px solid var(--color-border-subtle, #eee);
  border-radius: 14px;
  font-family: inherit;
  font-size: 0.75rem;
  line-height: 1.125rem;
  color: var(--color-text-primary, #1f1f1f);
}
.ol-tax__field::placeholder {
  color: var(--color-text-tertiary, #8e8e8e);
}
.ol-tax__field:focus {
  outline: none;
  border-color: var(--color-action-primary, #895200);
}

.ol-tax__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ---------- TaxObligationCard ---------- */

.ol-tax-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  background: var(--color-surface-card, #fff);
  border-radius: 20px;
}

.ol-tax-card__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  min-width: 0;
}

.ol-tax-card__heading {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}

.ol-tax-card__title {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.25rem;
  color: var(--color-text-primary, #1f1f1f);
}

/* The chevron matches PropertyCard's exactly: one resting shape, the state
   carried by the card rather than by the icon. */
.ol-tax-card__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 12px;
  background: var(--color-surface-muted, #f5f5f5);
  color: var(--color-text-tertiary, #8e8e8e);
  cursor: pointer;
  transform: rotate(180deg);
  transition:
    background-color 150ms ease,
    color 150ms ease,
    transform 150ms ease;
}
.ol-tax-card.is-open .ol-tax-card__toggle {
  background: var(--color-action-active, #75461d);
  color: #fff;
  transform: rotate(0deg);
}
.ol-tax-card__toggle:focus-visible {
  outline: 2px solid var(--color-action-primary, #895200);
  outline-offset: 2px;
}

.ol-tax-card__meta {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
}

.ol-tax-card__dates {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  min-width: 0;
}

/* "Year · 2026/27" reads as one line, so the term and its value sit inline with
   a separator rather than stacking as a definition list normally would. */
.ol-tax-card__date {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 0.75rem;
  line-height: 1.125rem;
}

.ol-tax-card__date dt {
  font-weight: 500;
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-tax-card__date dt::after {
  content: "·";
  margin-left: 6px;
}

.ol-tax-card__date dd {
  margin: 0;
  font-weight: 600;
  color: var(--color-text-primary, #1f1f1f);
}

/* A deadline only shouts when it is worth shouting about. */
.ol-tax-card__date.is-soon dd {
  color: #b26a00;
}
.ol-tax-card__date.is-overdue dt,
.ol-tax-card__date.is-overdue dd {
  color: var(--color-status-danger, #ba1a1a);
}

.ol-tax-card__amount {
  margin: 0;
  flex-shrink: 0;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.5rem;
  color: var(--color-text-primary, #1f1f1f);
  overflow-wrap: normal;
  word-break: keep-all;
}

.ol-tax-card__chips {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.ol-tax-card__detail {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Why an obligation is on hold, and what would lift it. A status the reader
   cannot act on is only half a status. */
.ol-tax-card__blocked {
  margin: 0;
  padding: 8px 10px;
  background: var(--tone-warning-bg, #fdf4e3);
  border-radius: 10px;
  font-size: 0.75rem;
  line-height: 1.125rem;
  color: #7a4f00;
}
.ol-tax-card__blocked a {
  color: inherit;
  font-weight: 600;
}

/* ---------- Forms/Returns, and the folds inside ---------- */

.ol-tax-forms {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.ol-tax-form {
  width: 100%;
  padding: 10px;
  background: #fff;
  border-radius: 12px;
}

.ol-tax-form__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}

.ol-tax-form__title {
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.125rem;
  color: var(--color-action-active, #75461d);
}

.ol-tax-form__chevron {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-text-tertiary, #8e8e8e);
  transform: rotate(180deg);
  transition: transform 150ms ease;
}
.ol-tax-form.is-open .ol-tax-form__chevron {
  transform: rotate(0deg);
}
.ol-tax-form__head:focus-visible {
  outline: 2px solid var(--color-action-primary, #895200);
  outline-offset: 2px;
  border-radius: 4px;
}

.ol-tax-form__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 10px;
}

/* The estimate is the line the reader came for. */
.ol-tax-form__row--success .ol-fact-row__value {
  color: var(--color-status-success, #1e7a4a);
}

.ol-tax-form__hmrc {
  margin-top: 2px;
}
.ol-tax-form__note {
  margin: -6px 0 0;
  font-size: 0.6875rem;
  line-height: 1rem;
  color: var(--color-text-tertiary, #8e8e8e);
}

/* The return as HMRC lays it out. Narrow screens scroll the grid rather than
   the page, so a long description never pushes the money column off-screen. */
.ol-tax-grid__scroll {
  width: 100%;
  overflow-x: auto;
  background: #fff;
  border-radius: 12px;
}

.ol-tax-grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.75rem;
  line-height: 1.125rem;
}

.ol-tax-grid th,
.ol-tax-grid td {
  padding: 7px 10px;
  text-align: left;
  font-weight: 500;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-tax-grid td {
  text-align: right;
  white-space: nowrap;
}

.ol-tax-grid thead th {
  font-weight: 600;
  color: var(--color-text-tertiary, #8e8e8e);
  border-bottom: 1px solid var(--color-border-hairline, #f5f5f5);
}
.ol-tax-grid thead th:last-child {
  text-align: right;
  color: var(--color-action-active, #75461d);
  /* "Amount (£)" is a unit, not a phrase — it should not break in two. */
  white-space: nowrap;
}

.ol-tax-grid tbody tr + tr th,
.ol-tax-grid tbody tr + tr td {
  border-top: 1px solid var(--color-border-hairline, #f5f5f5);
}

/* A component of the line above it: indented, quieter, and not separated by a
   rule, so the grouping is visible without a second heading level. */
.ol-tax-grid tr.is-sub th {
  padding-left: 22px;
  font-weight: 500;
  color: var(--color-text-tertiary, #8e8e8e);
}
.ol-tax-grid tr.is-sub th,
.ol-tax-grid tr.is-sub td {
  border-top: 0;
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-tax-grid tr.is-strong th,
.ol-tax-grid tr.is-strong td {
  font-weight: 600;
}

/* The design is drawn at 393px. Above that the summary spreads to four across
   rather than wrapping to two rows, matching the properties list. */
@media (min-width: 640px) {
  .ol-tax__summary {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ---------------------------------------------------------- Profiles page */

/* The profiles feed is drawn in the task-card language: the same 20px white
   surface, the same wrapping badge row closed by a 30px edit and delete pair,
   the same sunken footer holding one state-changing action. The palette is
   declared here rather than borrowed from .ol-task-card so retuning either
   card cannot silently retune the other. */
.ol-profile-card {
  /* Michael's frame, as tokens. Declared locally rather than borrowed from the
     page palette so retuning this card cannot silently retune anything else --
     the same reason the card this replaced kept its own set. */
  --profile-card-ink: #212121;
  --profile-card-ink-strong: #1f1f1f;
  --profile-card-ink-muted: #8e8e8e;
  --profile-card-rule: #f5f5f5;
  --profile-card-field-bg: #fafafa;
  --profile-card-field-line: #eeeeee;
  --profile-card-brown: #75461d;
  --profile-card-brown-soft: #fbf4ef;
  --profile-card-warn: #b26a00;
  --profile-card-warn-soft: #fff7e1;
  --profile-card-ok: #0c9d61;
  --profile-card-ok-soft: #f2faf6;
  --profile-card-danger: #ec2d30;
  --profile-card-danger-soft: #fef2f2;

  /* The app has no global border-box reset, so a padded full-width card
     overflows its column without this. Scoped to the card and everything in
     it rather than left to the page. */
  box-sizing: border-box;
  background: #fff;
  border: 1px solid var(--profile-card-rule);
  border-radius: 20px;
  font-family: "Manrope", system-ui, sans-serif;
  color: var(--profile-card-ink);
  overflow: hidden;
}

.ol-profile-card *,
.ol-profile-card *::before,
.ol-profile-card *::after {
  box-sizing: border-box;
}

/* The live profile is marked on its edge rather than with a second fill: the
   card is white, and tinting the whole surface would compete with the badge. */
.ol-profile-card.is-active {
  border-color: var(--profile-card-brown);
}

/* ---- Collapsed row ---- */

/* The row holds two controls: the disclosure, which is everything but the
   right edge, and the active switch sitting on that edge. The padding lives on
   the row so the disclosure can still be pressed edge to edge. */
.ol-profile-card__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px;
}

.ol-profile-card__disclose {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
  margin: -16px 0 -16px -16px;
  padding: 16px 0 16px 16px;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.ol-profile-card__disclose:focus-visible {
  outline: 2px solid var(--profile-card-brown);
  outline-offset: -2px;
}

/* ---- Active switch ---- */

/* Which profile scopes the workspace is a state, so it reads as a switch on
   the row rather than a button in the panel. On is the shared toggle green
   (--color-toggle-on); off is the same rule grey the chevron sits on. */
.ol-profile-card__switch-form {
  flex: none;
  display: flex;
}

.ol-profile-card__switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex: none;
  width: 40px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 12px;
  background: #e6e6e6;
  cursor: pointer;
  transition: background 160ms ease;
}

.ol-profile-card__switch.is-on {
  background: var(--color-toggle-on);
}

.ol-profile-card__switch:focus-visible {
  outline: 2px solid var(--profile-card-brown);
  outline-offset: 2px;
}

.ol-profile-card__switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
  transition: transform 160ms ease;
}

.ol-profile-card__switch.is-on .ol-profile-card__switch-knob {
  transform: translateX(16px);
}

@media (prefers-reduced-motion: reduce) {
  .ol-profile-card__switch,
  .ol-profile-card__switch-knob {
    transition: none;
  }
}

.ol-profile-card__tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  background: #fff;
  border: 1px solid var(--profile-card-rule);
  border-radius: 12px;
}

/* min-width: 0 so a long profile name truncates instead of pushing the
   chevron off the row. */
.ol-profile-card__copy {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}

.ol-profile-card__name-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.ol-profile-card__name {
  font-size: 13px;
  font-weight: 600;
  line-height: 18px;
  color: var(--profile-card-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* House rule: "switched on" is a solid brown fill with a white label. The
   warning and HMRC pills are tinted, never solid, so only one thing on the row
   can read as the active state. */
.ol-profile-card__pill {
  flex: none;
  padding: 1px 7px;
  border-radius: 36px;
  background: var(--profile-card-brown);
  color: #fff;
  font-size: 9px;
  font-weight: 600;
  line-height: 16px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* The active profile's tag. A static label, so it takes the soft pair rather
   than the solid fill reserved for things you can press. */
.ol-profile-card__pill--active {
  background: var(--profile-card-brown-soft);
  color: var(--profile-card-brown);
}

.ol-profile-card__pill--warn {
  background: var(--profile-card-warn-soft);
  color: var(--profile-card-warn);
}

.ol-profile-card__pill--ok {
  background: var(--profile-card-ok-soft);
  color: var(--profile-card-ok);
}

.ol-profile-card__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  color: var(--profile-card-ink-muted);
}

.ol-profile-card__dot {
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.ol-profile-card__chevron {
  display: inline-flex;
  padding: 0;
  border: 0;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 12px;
  background: var(--profile-card-rule);
  color: var(--profile-card-ink-muted);
  transition: transform 160ms ease;
}

.ol-profile-card__chevron.is-open {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .ol-profile-card__chevron {
    transition: none;
  }
}

/* ---- Expanded panel ---- */

.ol-profile-card__panel {
  padding: 0 16px 16px;
}

.ol-profile-card__form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border: 1px solid var(--profile-card-rule);
  border-radius: 12px;
}

.ol-profile-card__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ol-profile-card__label {
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  color: var(--profile-card-ink-strong);
}

.ol-profile-card__optional {
  font-weight: 400;
  color: var(--profile-card-ink-muted);
}

/* One rule for input and select so the two line up: a select left to the UA
   is a different height from a text input at the same padding. */
.ol-profile-card__input {
  width: 100%;
  min-width: 0;
  min-height: 34px;
  padding: 8px 12px;
  background: var(--profile-card-field-bg);
  border: 1.5px solid var(--profile-card-field-line);
  border-radius: 8px;
  font-family: inherit;
  font-size: 12px;
  line-height: 18px;
  color: var(--profile-card-ink-strong);
  appearance: none;
  -webkit-appearance: none;
}

.ol-profile-card__input:focus-visible {
  outline: 2px solid var(--profile-card-brown);
  outline-offset: -1px;
}

select.ol-profile-card__input {
  /* The chevron the appearance reset removed, drawn back in so a select still
     reads as a select. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238e8e8e' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 28px;
}

/* Two fields on one row — the currency pair, and town/postcode. Wraps to one
   column when the card is narrow rather than squeezing both. */
.ol-profile-card__pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.ol-profile-card__hint,
.ol-profile-card__note {
  margin: 0;
  font-size: 11px;
  line-height: 16px;
  color: var(--profile-card-ink-muted);
}

/* One pane of the card's tabbed form. Carries the form's own column rhythm,
   since the fields are its children now rather than the form's. */
.ol-profile-card__pane {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

/* Legal name + screen name share a wrapper for js/legal-name-copy.js; it
   must not cost the pane its gap between the two fields. */
.ol-profile-card__name-pair { display: contents; }

.ol-profile-card__legend {
  margin: 8px 0 0;
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  color: var(--profile-card-ink-muted);
}

/* ---- Docs tab ----
   The profile's latest files, one tappable row each, opening the shared
   file viewer. Rows sit on the field fill so they read as part of the form. */
.ol-profile-card__docs {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ol-profile-card__doc {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--profile-card-field-line);
  border-radius: 12px;
  background: var(--profile-card-field-bg);
  color: var(--profile-card-ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ol-profile-card__doc:focus-visible {
  outline: 2px solid var(--profile-card-brown);
  outline-offset: 2px;
}

.ol-profile-card__doc-icon {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: var(--profile-card-brown-soft);
  color: var(--profile-card-brown);
}

.ol-profile-card__doc-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.ol-profile-card__doc-name,
.ol-profile-card__doc-meta {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.ol-profile-card__doc-name {
  font-size: 13px;
  font-weight: 600;
  line-height: 18px;
}

.ol-profile-card__doc-meta {
  font-size: 11px;
  line-height: 16px;
  color: var(--profile-card-ink-muted);
}

/* Upload and Scan, side by side above the list. The upload pill is a label
   around a visually hidden input, so it needs its own focus ring. */
.ol-profile-card__doc-upload {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ol-profile-card__doc-upload .ol-upload-btn:focus-within {
  outline: 2px solid var(--profile-card-brown);
  outline-offset: 2px;
}

.ol-profile-card__doc-upload .ol-upload-btn.is-busy {
  opacity: 0.6;
  cursor: progress;
}

.ol-profile-card__hint--error {
  color: var(--profile-card-danger);
}

.ol-profile-card__doc-link {
  align-self: flex-start;
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  color: var(--profile-card-brown);
  text-decoration: none;
}

/* ---- Buttons ---- */

.ol-profile-card__actions,
.ol-profile-card__footer {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ol-profile-card__actions {
  margin-top: 4px;
}

/* Equal halves of the row, and padding that lets "Save changes" sit on one
   line inside its half at 393px -- at 16px it wrapped. */
.ol-profile-card__actions > .ol-profile-card__btn {
  flex: 1 1 0;
  min-width: 0;
  padding: 10px 8px;
}

/* "Make active" (or the note saying this one is) on the left, Delete at the
   far end, so the two never sit close enough to be mistaken for a pair. */
.ol-profile-card__footer {
  justify-content: space-between;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--profile-card-rule);
}

.ol-profile-card__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  padding: 10px 16px;
  border: 1.5px solid var(--profile-card-brown);
  border-radius: 16px;
  background: none;
  color: var(--profile-card-brown);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
}

.ol-profile-card__btn:hover {
  background: var(--profile-card-brown-soft);
}

.ol-profile-card__btn:focus-visible {
  outline: 2px solid var(--profile-card-brown);
  outline-offset: 2px;
}

/* Save is the house success green, the same as every other Save changes. */
.ol-profile-card__btn--primary {
  background: var(--color-action-success);
  border-color: var(--color-action-success);
  color: var(--color-action-on-success);
}

.ol-profile-card__btn--primary:hover {
  background: var(--color-action-success-hover);
  border-color: var(--color-action-success-hover);
}

.ol-profile-card__btn--danger {
  border-color: transparent;
  color: var(--profile-card-danger);
  font-size: 12px;
  min-height: 32px;
  padding: 6px 12px;
}

.ol-profile-card__btn--danger:hover {
  background: var(--profile-card-danger-soft);
}

.ol-profile-card__btn--danger:focus-visible {
  outline-color: var(--profile-card-danger);
}

.ol-profile-card__btn--quiet {
  font-size: 12px;
  min-height: 32px;
  padding: 6px 12px;
}

.ol-profile-card__footnote {
  margin: 0;
  font-size: 11px;
  line-height: 16px;
  color: var(--profile-card-ink-muted);
}

/* A save that failed puts the switch back and says so under the row. */
.ol-profile-card__switch-error {
  margin: -8px 16px 12px;
  font-size: 11px;
  line-height: 16px;
  color: var(--profile-card-danger);
}
.ol-profile-card__switch-error[hidden] {
  display: none;
}

.ol-profile-card__switch:disabled {
  cursor: progress;
}

/* A dial code beside its number: the code only needs room for "+234". */
.ol-profile-card__pair--phone {
  grid-template-columns: minmax(0, 6.5rem) minmax(0, 1fr);
}

/* Under ~20rem the paired fields have no room to sit side by side. */
@media (max-width: 26rem) {
  .ol-profile-card__pair {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The phone pair stays on one line -- a code alone on a row reads as a
     separate field. */
  .ol-profile-card__pair--phone {
    grid-template-columns: minmax(0, 5.5rem) minmax(0, 1fr);
  }
}

/* The page's own hero sits on the same photograph as Team and Properties. */
.ol-profiles__hero {
  min-height: 11rem;
  box-shadow: 0 1rem 2.25rem rgba(28, 25, 23, 0.22);
}

.ol-profiles__hero .ol-card__hero-body {
  justify-content: flex-end;
  gap: var(--space-4, 1rem);
}

/* The toolbar card and the tab bar both sit directly above the feed, so the
   feed keeps one gap below whichever of them comes last. */
.ol-profiles .ol-task-toolbar + .ol-page__list {
  margin-top: var(--space-3, 0.75rem);
}

/* --- Profile edit sheet -------------------------------------------------- */

.ol-profile-form {
  display: grid;
  gap: var(--space-4, 1rem);
}

.ol-profile-form__legend {
  margin: var(--space-1, 0.25rem) 0 0;
  color: var(--color-text-secondary, #4b4b4b);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.25rem;
}

.ol-profile-form__note {
  margin: 0;
  color: var(--color-text-tertiary, #8e8e8e);
  font-size: 0.75rem;
  line-height: 1.125rem;
}

.ol-profile-form__note a {
  color: var(--color-action-active, #75461d);
  font-weight: 600;
}

.ol-profile-form__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2, 0.5rem);
  padding-top: var(--space-2, 0.5rem);
  border-top: 1px solid var(--color-border-hairline, #f5f5f5);
}

/* ── Notifications list ──────────────────────────────────────────────────────
   The inbox, built the same way the projects list is: hero + TabBar + a column
   of cards. Everything below is layout only — the pills are Badges, the buttons
   are Buttons and the tallies are Counts, so nothing here restates a colour the
   design tokens already own. */

/* The app ships no global border-box reset, so a padded, full-width card here
   would overflow the page. Scoped to this family rather than going global. */
.ol-notifs,
.ol-notifs *,
.ol-notifs *::before,
.ol-notifs *::after {
  box-sizing: border-box;
}

.ol-notifs {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.ol-notifs__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.ol-notifs__title {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 16px;
  line-height: 24px;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-notifs__read-all {
  margin: 0;
}

.ol-notifs__list {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ── Search ── */

/* Its own card, so it reads as a control over the list rather than the first
   row of it. The field inside is the house muted-pill input. */
.ol-notifs__search {
  padding: 16px;
}

.ol-notifs__search-field {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border: 1.5px solid var(--color-border-subtle, #eee);
  border-radius: 14px;
  background: var(--color-surface-muted, #fafafa);
  cursor: text;
}

.ol-notifs__search-field:focus-within {
  border-color: var(--color-action-active, #75461d);
}

.ol-notifs__search-icon {
  display: inline-flex;
  flex: none;
  color: var(--color-text-tertiary, #8e8e8e);
}

/* type="search" sits outside test-ui-override's blanket input rules, so this
   field styles itself without having to opt out of anything. */
.ol-notifs__search-input {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-primary, #1f1f1f);
  -webkit-appearance: none;
  appearance: none;
}

.ol-notifs__search-input:focus {
  outline: none;
}

.ol-notifs__search-input::placeholder {
  color: var(--color-text-tertiary, #8e8e8e);
}

.ol-notifs__search-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

.ol-notifs__no-match {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-tertiary, #8e8e8e);
}

/* ── One day ── */

/* Search hides rows and empty headings with the `hidden` attribute, and the
   display rules below would otherwise out-rank the browser's own [hidden]
   styling and keep them on screen. Stated per selector so the attribute wins
   without reaching for !important. */
.ol-notifs__day[hidden],
.ol-notif-card[hidden] {
  display: none;
}

/* The heading carries the date so the rows under it need not repeat it. */
.ol-notifs__day {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ol-notifs__day-label {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--color-text-primary, #1f1f1f);
}

.ol-notifs__day-rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ── One notification ── */

/* A flat record: pills, headline, supporting line, profile. The surface, radius
   and elevation come from .ol-card; everything here is the stack inside it. */

.ol-notif-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
  padding: 16px;
  transition:
    box-shadow 0.15s ease,
    opacity 0.2s ease;
}

/* Unread is the house "on" brown on the leading edge and nothing else — no
   second accent, no tinted ground competing with the type pill. An inset shadow
   rather than a border, so the card keeps .ol-card's own geometry and the
   padding does not shift between read and unread. */
.ol-notif-card--unread {
  box-shadow:
    inset 3px 0 0 var(--color-action-active, #75461d),
    var(--elevation-card);
}

.ol-notif-card.is-leaving {
  opacity: 0;
}

/* ── Pill row ── */

.ol-notif-card__tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
}

/* A hairline, not a separator with opinions: it only says the control after it
   is a different kind of thing from the labels before it. */
.ol-notif-card__rule {
  flex: none;
  width: 1px;
  height: 20px;
  margin-inline: 2px;
  border-radius: 28px;
  background: var(--color-border-subtle, #eee);
}

/* ── Action bar ── */

/* Review, Archive, Delete. .ol-card-actions owns the row; a POST action has to
   be wrapped in its own form, so the form takes the flex share its button would
   otherwise have had and the three still come out equal. */
.ol-notif-card__actions {
  margin-top: 0;
}

.ol-notif-card__form {
  display: flex;
  flex: 1;
  margin: 0;
}

.ol-notif-card__form > .ol-card-actions__btn {
  width: 100%;
}

/* Delete is quiet until you reach for it — a red button on every card would
   read as the card's purpose — and it only commits to red on hover, where the
   reader has already aimed at it. */
.ol-notif-card__delete {
  color: var(--color-action-danger, #ba1a1a);
}

.ol-notif-card__delete:hover {
  background: var(--tone-danger-bg);
  border-color: var(--color-action-danger, #ba1a1a);
  color: var(--color-action-danger, #ba1a1a);
}

.ol-card-actions__btn[disabled] {
  opacity: 0.5;
  cursor: default;
}

/* An archived row is a record, not a live alert, so it sits back: the card is
   flat against the page and the copy is a step quieter. */
.ol-notif-card--archived {
  background: var(--color-surface-muted, #fafafa);
  box-shadow: none;
}

/* ── Body ── */

/* The body is the tap target, and it is a link, a submit button or plain text
   depending on what the row still has to offer. One rule dresses all three, so
   the card looks identical whichever it turns out to be. */
.ol-notif-card__body {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  text-decoration: none;
}

a.ol-notif-card__body,
button.ol-notif-card__body {
  cursor: pointer;
}

a.ol-notif-card__body:focus-visible,
button.ol-notif-card__body:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--color-action-active-soft, #f1ece6);
  border-radius: 8px;
}

.ol-notif-card__title {
  margin: 0 0 3px;
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  color: var(--color-text-primary, #212121);
  overflow-wrap: anywhere;
}

a.ol-notif-card__body:hover .ol-notif-card__title,
button.ol-notif-card__body:hover .ol-notif-card__title {
  color: var(--color-action-active, #75461d);
}

/* Two lines is enough to say what happened; the row it links to says the rest. */
.ol-notif-card__message {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
  font-weight: 600;
  font-size: 12px;
  line-height: 18px;
  color: var(--color-text-tertiary, #8e8e8e);
  overflow-wrap: anywhere;
}

/* ── Foot ── */

.ol-notif-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}

/* The house name tag, sized down for a card that already carries two pills: the
   profile is context, not another label competing with the type. */
.ol-notif-card__profile {
  gap: 4px;
  padding: 0 4px;
  border-width: 0.5px;
  border-radius: 14px;
  font-size: 10px;
  font-weight: 500;
  line-height: 18px;
  color: var(--color-text-primary, #1f1f1f);
}

/* Static: it states which profile the row belongs to, so it must not offer the
   chooser's hover. */
.ol-notif-card__profile:hover {
  border-color: var(--color-border-subtle, #e7e5e4);
}

.ol-notif-card__time {
  margin: 0;
  font-size: 10px;
  line-height: 18px;
  font-weight: 600;
  color: var(--color-text-tertiary, #8e8e8e);
}

/* ── Pager ── */

.ol-notifs__pager {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.ol-notifs__page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  padding: 6px 10px;
  border: 1px solid var(--color-border-subtle, #f5f5f5);
  border-radius: 999px;
  background: var(--color-surface-card, #fff);
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  color: var(--color-text-secondary, #524437);
}

.ol-notifs__page:hover {
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-active, #75461d);
}

/* The house switch convention: the page you are on is a solid brown fill with a
   white label, exactly like a selected tab. */
.ol-notifs__page.is-current {
  background: var(--color-action-active, #75461d);
  border-color: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
}

/* On a wide screen a day's rows sit side by side. The grid belongs to the rows,
   not the list: the date headings stay full-width so the days still read in
   order down the page. */
@media (min-width: 900px) {
  .ol-notifs__day-rows {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
    gap: 12px;
    align-items: start;
  }
}

/* ---------------------------------------------------------------------------
   Auto-categorise sheet
   ---------------------------------------------------------------------------
   The two "Auto-categorise …" checkboxes that used to sit in an activity
   card's footer now open a bottom sheet, because the choice grew: which of
   nine fields the rule copies, and which contacts hear about the payments.

   .ol-autocat lives inside .ol-drawer__panel, which already keeps itself clear
   of the fixed bottom nav via --ol-drawer-nav-gap. Nothing here pads its way
   around the nav.
   --------------------------------------------------------------------------- */

/* There is no global border-box reset, and every row below is a full-width
   padded block — without this they overflow the sheet by their own padding. */
.ol-autocat,
.ol-autocat * {
  box-sizing: border-box;
}

/* The card-footer row that opens the sheet. */
.ol-autocat-trigger {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--color-border-subtle, #f0e7db);
  border-radius: 14px;
  background: var(--color-surface, #fff);
  text-align: left;
  cursor: pointer;
}

.ol-autocat-trigger__text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  flex: 1;
  min-width: 0;
}

.ol-autocat-trigger__text strong {
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--ink, #3b3229);
}

.ol-autocat-trigger__hint {
  font-size: 0.74rem;
  color: var(--ink-faint, #9a8e80);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ol-autocat-trigger__chevron {
  display: flex;
  flex: 0 0 auto;
  color: var(--ink-faint, #9a8e80);
  transform: rotate(-90deg);
}

.ol-autocat__payee {
  margin: 0 0 0.75rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-action-active, #75461d);
}

.ol-autocat__group {
  padding: 0.75rem 0;
  border-top: 1px solid var(--color-border-subtle, #f0e7db);
}

.ol-autocat__group:first-of-type {
  border-top: 0;
  padding-top: 0;
}

.ol-autocat__legend {
  margin: 0 0 0.15rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-faint, #9a8e80);
}

.ol-autocat__legend-note {
  margin: 0 0 0.6rem;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--ink-faint, #9a8e80);
}

/* A choice row (future/previous, and the two notify channels) and a field row
   share their tick, and differ only in how much they have to say. */
.ol-autocat__row,
.ol-autocat__field {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  width: 100%;
  padding: 0.5rem 0;
  cursor: pointer;
}

.ol-autocat__row input[type="checkbox"],
.ol-autocat__field input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.ol-autocat__row-text,
.ol-autocat__field-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  flex: 1;
  min-width: 0;
}

.ol-autocat__row-text strong,
.ol-autocat__field-text strong {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink, #3b3229);
}

.ol-autocat__row-text span,
.ol-autocat__field-text span {
  font-size: 0.74rem;
  line-height: 1.4;
  color: var(--ink-faint, #9a8e80);
  /* A long note or a long property name wraps rather than pushing the tick
     out of the sheet. */
  overflow-wrap: anywhere;
}

/* A field the transaction has nothing in: still listed, so the sheet always
   reads as the same list, but it cannot be ticked. */
.ol-autocat__field.is-disabled {
  cursor: default;
  opacity: 0.45;
}

.ol-autocat__recipients {
  margin-top: 0.4rem;
}

.ol-autocat__recipient {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.65rem;
  margin-bottom: 0.4rem;
  border-radius: 12px;
  background: var(--color-surface-soft, #faf5ee);
}

.ol-autocat__recipient-text {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  flex: 1;
  min-width: 0;
}

.ol-autocat__recipient-text strong {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--ink, #3b3229);
}

.ol-autocat__recipient-text span {
  font-size: 0.72rem;
  color: var(--ink-faint, #9a8e80);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ol-autocat__remove {
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.6rem;
  border: 0;
  border-radius: 50%;
  background: transparent;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink-faint, #9a8e80);
  cursor: pointer;
}

.ol-autocat__select,
.ol-autocat__input {
  width: 100%;
  padding: 0.55rem 0.7rem;
  font-size: 0.82rem;
}

.ol-autocat__select {
  margin-bottom: 0.5rem;
}

/* The category picker an uncategorised row uses inside the sheet, set apart
   from the ticked fields above it that the row already carries. */
.ol-autocat__catpick {
  padding-top: 0.35rem;
}

.ol-autocat__catpick .ol-autocat__select {
  margin-bottom: 0.35rem;
}

.ol-autocat__manual {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.4rem;
}

.ol-autocat__add {
  justify-self: start;
  padding: 0.45rem 1rem;
  border: 0;
  border-radius: 999px;
  /* House rule: a pressable control is solid brown when it is live. */
  background: var(--color-action-active, #75461d);
  color: var(--color-action-on-active, #fff);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}

.ol-autocat__add:disabled {
  background: var(--color-surface-soft, #faf5ee);
  color: var(--ink-faint, #9a8e80);
  cursor: default;
}

.ol-autocat__error {
  margin: 0.4rem 0 0;
  font-size: 0.74rem;
  color: var(--color-danger, #b3261e);
}

.ol-autocat__footer {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--color-border-subtle, #f0e7db);
}

.ol-autocat__note {
  flex: 1;
  margin: 0;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--ink-faint, #9a8e80);
}

.ol-autocat__footer .dash-activity-extras__btn {
  flex: 0 0 auto;
}

/* ---------------------------------------------------------------- Dashboard: hidden profiles hint */

/* One quiet line under the dashboard carousel when "Show on dashboard" is
   leaving profiles out of the figures. Turns to the warning pair when every
   profile is hidden, because then the totals are nearly empty. */
.ol-dash-hidden-hint {
  box-sizing: border-box;
  margin: 8px 16px 0;
  padding: 8px 12px;
  border-radius: 12px;
  background: var(--color-action-active-soft, #f1ece6);
  color: var(--color-text-secondary, #524437);
  font-size: 12px;
  line-height: 18px;
}

.ol-dash-hidden-hint a {
  color: var(--color-action-active, #75461d);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.ol-dash-hidden-hint--all {
  background: var(--tone-warning-bg, #fff4e5);
  color: var(--tone-warning-fg, #b26a00);
}

/* ==========================================================================
   Document scanner — public_html/js/doc-scan.js
   Full screen over any sheet (the upload forms live in sheets at 1400), under
   the notice sheet (1500) so its messages can still be read. Dark like the
   file viewer it sits beside, because a photo of white paper reads best on
   dark. Chosen filter is the house solid brown.
   ========================================================================== */
.ol-scan {
  position: fixed;
  inset: 0;
  z-index: 1460;
  display: flex;
  flex-direction: column;
  background: #14100c;
  color: #fff;
  font-family: inherit;
}

.ol-scan[hidden],
.ol-scan [hidden] {
  display: none !important;
}

.ol-scan *,
.ol-scan *::before,
.ol-scan *::after {
  box-sizing: border-box;
}

.ol-scan__bar {
  display: flex;
  flex: none;
  align-items: center;
  gap: 8px;
  padding: calc(8px + env(safe-area-inset-top, 0px)) 12px 8px;
}

.ol-scan__title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 16px;
  font-weight: 700;
  text-align: center;
}

.ol-scan__bar-spacer {
  flex: none;
  width: 40px;
}

.ol-scan__icon-btn {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  cursor: pointer;
}

.ol-scan__screen {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  padding: 0 16px calc(16px + env(safe-area-inset-bottom, 0px));
}

/* ---- Start ---- */
.ol-scan__start {
  position: relative;
  overflow: hidden;
  align-items: center;
  justify-content: center;
  gap: 24px;
  text-align: center;
}

.ol-scan__lead {
  max-width: 300px;
  margin: 0;
  font-size: 14px;
  line-height: 20px;
  color: rgba(255, 255, 255, 0.75);
}

.ol-scan__start-actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-width: 300px;
}

.ol-scan__big-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 14px 16px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 9999px;
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}

.ol-scan__big-btn--primary {
  border-color: var(--color-action-active, #75461d);
  background: var(--color-action-active, #75461d);
}

/* ---- Stage: the photo or the page, as large as the screen allows ---- */
.ol-scan__stage {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: center;
  min-height: 0;
  overflow: hidden;
}

.ol-scan__frame {
  position: relative;
  flex: none;
}

.ol-scan__photo {
  display: block;
  width: 100%;
  height: 100%;
}

.ol-scan__quad {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  touch-action: none;
  cursor: crosshair;
}

.ol-scan__shade {
  fill: rgba(0, 0, 0, 0.5);
}

.ol-scan__edge {
  fill: none;
  stroke: #e0b16a;
  stroke-linejoin: round;
}

.ol-scan__handle {
  fill: rgba(255, 255, 255, 0.92);
  stroke: #e0b16a;
}

.ol-scan__handle.is-dragging {
  fill: #e0b16a;
}

.ol-scan__page {
  display: block;
  max-width: 100%;
  max-height: 100%;
  border-radius: 4px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
  object-fit: contain;
}

.ol-scan__hint {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 16px;
  text-align: center;
  color: rgba(255, 255, 255, 0.65);
}

/* ---- Tools row ---- */
.ol-scan__tools {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-around;
  gap: 4px;
  padding-top: 12px;
}

.ol-scan__tool {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 56px;
  padding: 6px 4px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.ol-scan__done {
  flex: none;
  padding: 12px 20px;
  border: 0;
  border-radius: 9999px;
  background: var(--color-action-active, #75461d);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

/* ---- Filters ---- */
.ol-scan__filters {
  display: flex;
  flex: none;
  justify-content: center;
  gap: 6px;
  padding-top: 12px;
  overflow-x: auto;
}

.ol-scan__chip {
  flex: none;
  padding: 7px 12px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 9999px;
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

.ol-scan__chip.is-active {
  border-color: var(--color-action-active, #75461d);
  background: var(--color-action-active, #75461d);
}

/* ---- Page strip ---- */
.ol-scan__strip {
  display: flex;
  flex: none;
  gap: 8px;
  padding: 12px 2px 2px;
  overflow-x: auto;
}

.ol-scan__thumb {
  position: relative;
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 72px;
  padding: 0;
  overflow: hidden;
  border: 2px solid transparent;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.08);
  cursor: pointer;
}

.ol-scan__thumb canvas {
  display: block;
  max-width: 100%;
  max-height: 100%;
}

.ol-scan__thumb.is-active {
  border-color: #e0b16a;
}

.ol-scan__thumb-num {
  position: absolute;
  right: 3px;
  bottom: 3px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 9999px;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
}

.ol-scan__thumb--add {
  flex-direction: column;
  gap: 2px;
  border: 1px dashed rgba(255, 255, 255, 0.35);
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 10px;
  font-weight: 600;
}

/* ---- Save ---- */
.ol-scan__save {
  flex: none;
  padding-top: 12px;
}

.ol-scan__name-label {
  display: block;
  margin-bottom: 4px;
  font-size: 11px;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.65);
}

.ol-scan__save-row {
  display: flex;
  gap: 8px;
}

/* Two classes, so the blanket bare-input styles in test-ui-override.css do
   not paint it as a cream pill. */
.ol-scan .ol-scan__name {
  flex: 1 1 auto;
  min-width: 0;
  height: auto;
  margin: 0;
  padding: 11px 14px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font: inherit;
  font-size: 14px;
  box-shadow: none;
}

/* ---- Busy ---- */
.ol-scan__busy {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: rgba(20, 16, 12, 0.7);
  font-size: 13px;
  font-weight: 600;
}

.ol-scan__spinner {
  width: 32px;
  height: 32px;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: #fff;
  border-radius: 50%;
  animation: ol-scan-spin 0.8s linear infinite;
}

@keyframes ol-scan-spin {
  to { transform: rotate(360deg); }
}

/* A band of light that sweeps down and back up the screen while a page is
   worked on, so the seconds a straighten or a PDF build can take read as
   "still scanning" rather than a frozen frame. */
.ol-scan__beam {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 4px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.92) 50%, transparent);
  box-shadow: 0 0 22px 6px rgba(193, 120, 54, 0.55), 0 0 60px 18px rgba(193, 120, 54, 0.22);
  animation: ol-scan-sweep 1.6s ease-in-out infinite alternate;
}

@keyframes ol-scan-sweep {
  from { transform: translateY(7vh); }
  to { transform: translateY(93vh); }
}

@media (prefers-reduced-motion: reduce) {
  .ol-scan__beam,
  .ol-scan__scanline {
    animation: none;
    top: 50%;
  }
}

/* A scan line over the page in view — the "take a photo" panel before the
   shot, and the page itself once it is in hand — so the scanner reads as
   working on the paper, not only on a blank screen afterwards. */
.ol-scan__scanline {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 3px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(224, 177, 106, 0.95) 50%, transparent);
  box-shadow: 0 0 16px 4px rgba(224, 177, 106, 0.5), 0 0 40px 12px rgba(193, 120, 54, 0.25);
  animation: ol-scan-crawl 2.2s ease-in-out infinite alternate;
}

@keyframes ol-scan-crawl {
  from { top: 6%; }
  to { top: 94%; }
}

.ol-scan__picker {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* The "Scan" trigger beside an upload control, on its own line under it. */
.ol-scan-trigger {
  margin-top: 8px;
}

/* ==========================================================================
   Document reader — public_html/js/doc-extract.js
   The strip that fills an upload sheet in from the file just picked or scanned.
   It sits under the file row: a status line while it reads, then a small cream
   card of what it found. It is a read, not a record — the fields below stay the
   source of truth, and everything here is safe to ignore or dismiss.
   ========================================================================== */
.ol-extract {
  margin-top: 0.6rem;
}

.ol-extract[hidden] {
  display: none !important;
}

.ol-extract__status {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8125rem;
  color: var(--color-text-tertiary, #847466);
}

.ol-extract__spin {
  width: 0.9rem;
  height: 0.9rem;
  flex: none;
  border: 2px solid color-mix(in srgb, var(--color-action-active, #75461d) 22%, transparent);
  border-top-color: var(--color-action-active, #75461d);
  border-radius: 50%;
  animation: ol-extract-spin 0.8s linear infinite;
}

@keyframes ol-extract-spin {
  to { transform: rotate(360deg); }
}

.ol-extract__card {
  border: 1px solid var(--color-border-hairline, #f5f5f5);
  background: var(--color-count-surface, #fbf4ef);
  border-radius: 12px;
  padding: 0.7rem 0.85rem;
}

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

.ol-extract__title {
  font-size: 0.71875rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-action-active, #75461d);
}

.ol-extract__x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--color-text-tertiary, #847466);
  cursor: pointer;
}

.ol-extract__x:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--color-text-primary, #1a1c1a);
}

.ol-extract__rows {
  margin: 0;
  display: grid;
  gap: 0.3rem;
}

.ol-extract__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.8125rem;
}

.ol-extract__row dt {
  margin: 0;
  color: var(--color-text-tertiary, #847466);
}

.ol-extract__row dd {
  margin: 0;
  color: var(--color-text-primary, #1a1c1a);
  font-weight: 600;
  text-align: right;
  overflow-wrap: anywhere;
}

.ol-extract__note {
  margin: 0.5rem 0 0;
  font-size: 0.75rem;
  color: var(--color-text-tertiary, #847466);
}
