/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *

















 */
/* Feature icons - both in list and as the main icon */
.feature-icon {
  font-size: 24px;
  color: #333;
}

/* Feature category icon inside the label input */
.feature-category-icon {
  position: absolute;
  right: 10px;
  top: 40%;
  transform: translateY(-50%);
  cursor: pointer;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
/* Ensure the material icon inherits the positioning */
.feature-category-icon .material-icons {
  font-size: 24px;
  color: #333;
}

/* Category list dropdown */
.category-list {
  position: absolute;
  top: calc(100% + 5px);
  right: 0;
  background: white;
  border: 1px solid #ccc;
  box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
  border-radius: 8px;
  z-index: 10;
  padding: 10px;
  width: 200px;
}

/* List of category items */
.category-list ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.category-list li {
  display: flex;
  align-items: center;
  padding: 8px;
  cursor: pointer;
  border-radius: 6px;
}

.category-list li:hover {
  background-color: #f0f0f0;
}

/* Category icons in the list */
.category-list .material-icons {
  width: 24px;
  height: 24px;
  margin-right: 8px;
  display: flex;
  align-items: center;
}

/* Delete icon styling */
.delete-feature .material-icons {
  color: #dc3545;
  font-size: 20px;
}
/* Zone Memberships Container */
#zone-memberships {
    margin-top: 20px;
  }
  
  /* Base Panel Styling */
  .zone-membership-panel {
    background-color: var(--brand-neutral-50);
    border-radius: 8px;
    padding: 12px;
  }
  
  /* Headers */
  .zone-membership-panel h3 {
    font-size: 14px;
    font-weight: 500;
    color: var(--brand-neutral-700);
    margin: 0;
  }
  
  .zone-membership-panel h4 {
    font-size: 12px;
    font-weight: 500;
    color: var(--brand-neutral-500);
    margin: 12px 0 4px 0;
  }
  
  /* Member/Zone List */
  .membership-list {
    margin-top: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  
  /* Individual Member/Zone Item */
  .membership-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background-color: white;
    padding: 8px;
    border-radius: 4px;
    border: 1px solid var(--brand-neutral-200);
  }
  
  .membership-item span {
    font-size: 14px;
    color: var(--brand-neutral-700);
  }
  
  /* Remove Button */
  .remove-membership-button {
    background: none;
    border: none;
    padding: 4px;
    cursor: pointer;
    color: #dc2626;
  }
  
  .remove-membership-button:hover {
    color: #b91c1c;
  }
  
  .remove-membership-button svg {
    width: 16px;
    height: 16px;
  }
  
  /* Suggested Section */
  .suggested-section {
    margin-top: 12px;
  }
  
  /* Add Membership Form */
  .add-membership-form {
    display: inline-block;
  }
  
  /* Add Button */
  .add-membership-button {
    display: flex;
    align-items: center;
    gap: 4px;
    background: none;
    border: none;
    padding: 4px;
    cursor: pointer;
    color: #2563eb;
    font-size: 14px;
  }
  
  .add-membership-button:hover {
    color: #1d4ed8;
  }
  
  .add-membership-button svg {
    width: 16px;
    height: 16px;
  }
  
  /* Error messages: the #zone-membership-error rule was removed with the id it
     styled — nothing rendered that element, so validation failures were never
     shown. The slot now lives in map_features/_breadcrumb, keyed per feature and
     styled with Tailwind alongside the rest of that partial. */

  /* Suggestions List */
  .suggestions-list {
    margin-top: 4px;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
.google-maps-deep-link-btn {
    position: absolute;
    left: -23px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    cursor: pointer;
  }
  
  .google-maps-deep-link-btn:hover .tooltip {
    visibility: visible;
    opacity: 1;
  }
  
  .tooltip {
    visibility: hidden;
    opacity: 0;
    background-color: #555;
    color: #fff;
    text-align: center;
    border-radius: 5px;
    padding: 5px;
    position: absolute;
    z-index: 19;
    bottom: 150%; /* Adjust to position the tooltip above the button */
    left: 320%;
    transform: translateX(-50%);
    white-space: nowrap;
    transition: opacity 0.3s;
  }
  
#layer-switcher {
    position: absolute; /* Place it on top of the map */
    bottom: 35px; /* Positioned above the Mapbox attribution */
    left: 300px;
    background-color: rgba(255, 255, 255, 0.9); /* Slightly more transparent */
    padding: 3px;
    border-radius: 8px; /* Increased border-radius for a smoother look */
    box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2); /* Softer shadow for better contrast */
    z-index: 10;
    display: flex;
    flex-direction: column;
    gap: 2px; /* Ensure a small gap between buttons */
    border: 1px solid #ddd; /* Subtle border */
  }
  
  #layer-switcher button {
    background-color: #fff;
    border: 1px solid #ccc; /* Define button border */
    padding: 5px 8px; /* Balanced padding for symmetry */
    cursor: pointer;
    text-align: center;
    width: 100%;
    font-size: 16px;
    border-radius: 4px; /* Slight rounding on buttons */
    box-shadow: none;
    transition: background-color 0.3s, border-color 0.3s;
  }
  
  #layer-switcher button:hover {
    background-color: #f0f0f0; /* Slight hover effect */
    border-color: #007bff; /* Change border on hover */
  }
  
  #layer-switcher button + button {
    margin-top: 4px; /* Space between buttons for improved symmetry */
  }
  
/* Comment input area and icons */
.comment-form {
    display: flex;
    flex-direction: column;
  }
  
  .comment-text-area {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 8px;
    outline: none;
    resize: none;
    font-size: 14px;
    background-color: #fff;
    min-height: 50px;
    margin-bottom: 10px;
  }
  
  
  .mention-suggestions {
    position: relative;
    margin-top: 5px;
  }
  
  .comment-icons {
    display: flex;
    justify-content: flex-end;
    align-items: center;
  }
  
  .upload-icon {
    cursor: pointer;
    margin-right: 10px;
  }
  
  .file-selected-name {
    font-size: 14px;
    color: #333;
    margin-right: 10px;
  }
  
  .remove-file-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    font-size: 18px;
    color: red;
  }
  
  .submit-comment-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    width: 20px;
  }
  
  .icon {
    width: 20px;
    height: 20px;
  }
  
  .remove-icon {
    color: red;
    cursor: pointer;
  }
  
  .file-upload-input {
    display: none; /* Hidden file input */
  }
  
  .mention {
    background-color: #e0f7fa;
    color: #007c91;
    font-weight: bold;
    padding: 2px 5px;
    border-radius: 5px;
    margin-right: 5px;
  }
#feature-search-bar {
    padding: 10px;
    margin-top: 15px;
    border: 1px solid #ddd;
    border-radius: 8px;
    outline: none;
    resize: none;
    font-size: 14px;
    background-color: #fff;
    min-height: 20px;
    min-width: 240px;
    margin-bottom: 10px;
}
/* ======================================================================== */
/*  Shared location filter bar — multi-select category / status / person     */
/*  popovers. Used by location_filter_controller.js + the                    */
/*  shared/filters/_filter_popover partial. Ported from the metrics          */
/*  dashboard's .md-filter* styles so every location view reads as one       */
/*  family.                                                                  */
/* ======================================================================== */
.lf-filter { position: relative; }

.lf-btn {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 500; white-space: nowrap; cursor: pointer;
  border: 1px solid var(--brand-neutral-200); border-radius: 8px; padding: 5px 10px;
  background: #fff; color: var(--brand-primary-dark);
  transition: background-color 80ms ease, border-color 80ms ease;
}
.lf-btn:hover { background: var(--brand-primary-surface); border-color: rgb(var(--brand-primary-rgb) / 0.4); }
.lf-btn.is-active { background: var(--brand-primary-surface); border-color: var(--brand-primary); }

.lf-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
  font-size: 10px; font-weight: 700; line-height: 1; background: var(--brand-primary); color: #fff;
}

.lf-pop {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 60;
  width: 250px; padding: 6px;
  background: #fff; border: 1px solid var(--brand-neutral-200); border-radius: 10px;
  box-shadow: 0 12px 28px rgb(var(--brand-primary-dark-rgb) / 0.16);
  display: flex; flex-direction: column; max-height: 360px;
}
/* Keep `.hidden` working — `display: flex` above otherwise beats Tailwind's
   `.hidden { display: none }`. */
.lf-pop.hidden { display: none; }

.lf-pop-head { display: flex; gap: 6px; padding: 2px 2px 6px; border-bottom: 1px solid var(--brand-neutral-100); }
.lf-pop-head button {
  flex: 1; font-size: 11px; font-weight: 500; color: var(--brand-primary);
  background: var(--brand-primary-surface); border: 0; border-radius: 6px; padding: 5px; cursor: pointer;
  transition: background-color 80ms ease;
}
.lf-pop-head button:hover { background: #DCF5E6; }

.lf-opts { overflow-y: auto; max-height: 244px; padding: 4px 0; }
.lf-opt {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 6px; font-size: 12px; color: var(--brand-primary-dark); cursor: pointer;
}
.lf-opt:hover { background: #F7FDF8; }
.lf-opt input { accent-color: var(--brand-primary); width: 14px; height: 14px; flex: none; }
.lf-opt-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
.lf-opt-count { flex: none; min-width: 18px; text-align: right; font-size: 10px; color: var(--brand-neutral-400); }
/* Grey-out (never hide) options that would yield zero locations given the
   other active filters — house rule, matches the sales-dashboard legend. */
.lf-opt.is-unavailable { opacity: .45; filter: grayscale(1); pointer-events: none; }

.lf-pop-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 8px 2px; margin-top: 2px; border-top: 1px solid var(--brand-neutral-100);
  font-size: 11px; font-weight: 500; color: var(--brand-neutral-500);
}
/* "Apply" — only shown in form mode (full-page pages like Farms & Fields). */
.lf-apply {
  flex: none; font-size: 11px; font-weight: 600; color: #fff;
  background: var(--brand-primary); border: 0; border-radius: 6px; padding: 4px 12px; cursor: pointer;
  transition: background-color 80ms ease;
}
.lf-apply:hover { background: var(--brand-primary-dark); }
/* Mapbox's own zoom and geolocate controls, restyled to match the map's other
   chrome (the floating pills and panels in maps/show.html.erb).

   The CONTROLS are Mapbox's, deliberately — GeolocateControl carries the
   permission prompt, the denial path, the accuracy circle and the follow-me
   state, none of which is worth rebuilding. Only the appearance is ours.

   Scoped to #map so no other Mapbox instance in the app is restyled by this. */

/* Stack the two groups with a gap; Mapbox butts them together by default. */
#map .mapboxgl-ctrl-bottom-right {
  /* Stacked ABOVE the "Ask Propel" FAB (peek.css: fixed, right 20px, bottom
     20px, ~38px tall) and clear of the notification stack below it.
     
     The FAB is the thing that does NOT move: it sits in the same place on every
     page in the app, and shifting it only here would cost that. These controls
     are the map's own, so they are the ones that give way. 84px clears the FAB
     and a single notification card; the right edge lines both of them up in one
     gutter rather than leaving two ragged columns. */
  bottom: 84px;
  right: 10px;
}

#map .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-group {
  margin: 0 0 8px 0;
  border-radius: 9999px;
  background: var(--brand-neutral-50);
  border: 1px solid var(--brand-neutral-200);
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

#map .mapboxgl-ctrl-group button {
  width: 34px;
  height: 34px;
  background-color: transparent;
}

#map .mapboxgl-ctrl-group button + button {
  /* Mapbox draws this in its own grey; match the panel hairline instead. */
  border-top: 1px solid var(--brand-neutral-200);
}

#map .mapboxgl-ctrl-group button:hover {
  background-color: var(--brand-primary-surface);
}

#map .mapboxgl-ctrl-group button:focus,
#map .mapboxgl-ctrl-group button:focus:not(:focus-visible) {
  box-shadow: none; /* Mapbox's default focus ring is a hard blue square */
}

#map .mapboxgl-ctrl-group button:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
}

/* The glyphs are background SVGs with a baked-in near-black. Tinting them to
   the palette needs a filter rather than `color`; this lands close to
   --brand-neutral-700 and keeps Mapbox's own icons rather than replacing
   the sprite. */
#map .mapboxgl-ctrl-group button .mapboxgl-ctrl-icon {
  filter: opacity(0.72);
}

#map .mapboxgl-ctrl-group button:hover .mapboxgl-ctrl-icon {
  filter: opacity(1);
}

/* Following the user: tint the button so the state is visible at a glance
   rather than only in the dot on the map. */
#map .mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active,
#map .mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background {
  background-color: var(--brand-primary-surface);
}

#map .mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon,
#map .mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon {
  filter: none;
}

/* NOT hidden — Mapbox's terms require the logo and attribution to stay visible.
   The attribution is moved to bottom-left instead (compact, so it is an ⓘ that
   expands), which is done in mapbox_map_controller#connect by turning the
   default one off and adding it on the other side. Nothing here may reintroduce
   a display:none on .mapboxgl-ctrl-attrib or .mapboxgl-ctrl-logo. */
/* The bottom-centred guide bar, and how it keeps out of the way.
 *
 * The guide (data-mapbox-map-target="drawGuide") is the pill that explains the
 * gestures for whichever mode is active — right-click undoes, Enter finishes,
 * Ctrl adds to a selection. It is decoration: pointer-events-none, z-30, and
 * anchored bottom-centre at 1.5rem.
 *
 * The visualization TIMELINE (#viz-timeline) is anchored to the same corner at
 * 0.75rem and is a real control — two drag handles for the start and end date.
 * Those handles sit 20px off the bottom and are 16px tall; the guide's pill
 * spans roughly 24px to 56px. So they overlap, and because the guide is z-30
 * against the timeline's z-20, the pill paints straight over both handles and
 * the histogram behind them.
 *
 * pointer-events-none means the clicks still reach the handles, which is why
 * this reads as a rendering bug rather than a dead control — you simply cannot
 * see what you are grabbing, or where the range currently sits. That is worse
 * than a blocked control, not better: the handle appears to be missing.
 *
 * Lifting the guide is the right half to move. The timeline is a scrubber and
 * belongs low, near the edge it measures along; the guide is a hint and does
 * not care where it sits. 6rem clears the timeline card (which is ~71px tall
 * over a 12px offset, so it ends around 83px) with room to spare.
 *
 * Keyed off `body` rather than the map wrapper on purpose: the two elements sit
 * at different depths of maps/show, so any closer common ancestor would be a
 * selector that quietly stops matching the first time that markup is nested
 * differently. `body` cannot stop being an ancestor of both.
 */
body:has(#viz-timeline:not(.hidden)) [data-mapbox-map-target="drawGuide"] {
  bottom: 6rem;
}

/* Worth keeping in mind if another bottom-anchored overlay is added: the map's
 * bottom edge is already crowded. Toasts sit at 5rem (content_for
 * :flash_position in maps/show), the zoom and locate controls at 84px on the
 * right, and the Ask Propel FAB below those. Centre-anchored additions need to
 * declare how they stack with this one. */
/* Label for the "Go to a place" search result.
 *
 * Not a bubble: Mapbox's popup chrome is stripped to nothing and the label sits
 * directly on the map beside the red marker, the way map labels normally read.
 * The white halo is what keeps red text legible over satellite imagery, which
 * is why it is a halo and not a background card.
 *
 * Applied via `new mapboxgl.Popup({ className: 'place-bubble', anchor: 'left' })`
 * in mapbox_map_controller#viewLocationFromGoogleMaps. anchor:'left' puts the
 * popup's left edge at the marker, so the label lands to its RIGHT.
 *
 * Scoped to .place-bubble — every other popup on the map keeps Mapbox defaults.
 */

/* The dot in the middle of the marker. Mapbox's default pin SVG puts a white
 * circle there; dark red reads as part of the pin instead of a hole punched
 * through it. Matched on the fill value rather than position, so if Mapbox
 * restructures its marker SVG this rule simply stops applying instead of
 * recolouring the wrong shape.
 *
 * Applied via `new mapboxgl.Marker({ className: 'place-marker' })` by BOTH the
 * place search and the long-press dropped pin — they mark the same kind of
 * thing (a spot you are considering, not one you have saved), so they should
 * not look like two different ideas. */
.place-marker svg circle[fill="#FFFFFF"],
.place-marker svg circle[fill="#ffffff"],
.place-marker svg circle[fill="white"] {
  fill: #8B1E2F; /* dark red */
}

.place-bubble .mapboxgl-popup-content {
  padding: 0;
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  /* Nothing here clips (Mapbox sets no overflow on its popup elements) — this
     is belt-and-braces for a popup stripped of its chrome, where the content is
     bare text with an outline rather than a card. */
  overflow: visible;
}

/* No card, so no tip pointing at one, and no × on a bare label. */
.place-bubble .mapboxgl-popup-tip,
.place-bubble .mapboxgl-popup-close-button {
  display: none;
}

.place-bubble__inner {
  display: flex;
  flex-direction: column;
  /* flex-end, not center: the button tucks under the RIGHT end of the name.
     The column is only as wide as the name, so this reads as right-aligned
     against the label rather than floating in the middle of it. */
  align-items: flex-end;
  gap: 0.3rem;
  overflow: visible;
  /* 2px of headroom so the topmost stroke has somewhere to live even if an
     ancestor does clip. Costs nothing visually — the label has no background. */
  padding-top: 2px;
}

.place-bubble__name {
  /* inline-block so the box hugs the text and the padding applies. The padding
     is breathing room around the outline, not a fix for the sheared tops — see
     the text-shadow note below for what that actually was. */
  display: inline-block;
  padding: 4px 5px;
  font-size: 1rem;
  font-weight: 700;
  /* 1.25 was tight enough that a stroked cap-height letter reached the top of
     its line box. 1.4 gives the outline room, and matters more now that long
     names wrap onto a second line. */
  line-height: 1.4;
  color: var(--brand-danger); /* matches the marker, which reads the same token */

  /* Wraps instead of truncating: a place name is the whole point of the label,
     so cutting it off with an ellipsis loses the information. max-width caps how
     far it can run before wrapping; break-word handles a single long token. */
  max-width: 14rem;
  white-space: normal;
  overflow-wrap: break-word;

  /* A real 1px white border, not a halo — eight zero-blur shadows, one per
     direction, so the edge is hard rather than feathered.
     
     This was -webkit-text-stroke, which kept getting its TOP shaved off. That
     was never an overflow clip: Mapbox's stylesheet has no overflow:hidden on
     any popup element (only .mapboxgl-map, which clips at the map's edges), and
     this box already carries ~7px of room above the ascenders from the padding
     and half-leading below. The clip lands exactly on the ascender line because
     Chromium computes a text fragment's ink-overflow rect without adding the
     stroke width — so the paint is cut at the glyphs' own ink bounds no matter
     how much padding the element has, which is why adding room never helped.
     text-shadow IS accounted for in that rect, so it does not get cut. */
  text-shadow:
     1px  0    0 #ffffff, -1px  0    0 #ffffff,
     0    1px  0 #ffffff,  0   -1px  0 #ffffff,
     1px  1px  0 #ffffff, -1px  1px  0 #ffffff,
     1px -1px  0 #ffffff, -1px -1px  0 #ffffff;
}

/* Still a real control, so it keeps a solid fill and a shadow to lift it off
 * the imagery — a haloed text link would be too weak to read as a button. */
/* Colours read --brand-* directly. shared/_brand_tokens emits those on :root
   for every brand-driven layout, so they resolve wherever this popup is
   rendered — no scope to opt into, and they follow BRAND like the rest. */
.place-bubble__add {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.2rem 0.55rem;
  border: 0;
  border-radius: 9999px;
  background: var(--brand-primary);
  color: #ffffff;
  font-size: 0.6875rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 1px 4px rgb(var(--brand-primary-dark-rgb) / 0.35);
  transition: background-color 0.15s ease;
}

.place-bubble__add:hover {
  background: var(--brand-primary-dark);
}

.place-bubble__add:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}
/* Collapse the map's right-hand details panel while nothing is selected.
 *
 * The panel used to sit there permanently showing "Select a Point or Polygon",
 * covering the top-right corner of the map for the entire session. It should
 * only appear once there is something to show.
 *
 * Done in CSS rather than JS because the panel's contents are driven by Turbo:
 * selecting a feature replaces the frame's HTML, so the marker class appearing
 * and disappearing is already the exact signal we need. No controller has to
 * know about the panel's visibility.
 *
 * The rule reads: hide the panel when it contains the "nothing selected"
 * placeholder AND the bulk-actions panel is not currently shown. That second
 * clause matters — during multi-select the details frame still holds the
 * placeholder, and without it the bulk-actions UI would be hidden too.
 *
 * :has() is Chrome 105+ / Safari 15.4+ / Firefox 121+. Where it is unsupported
 * the rule simply does not apply and the panel behaves as it always did, which
 * is the right way for this to fail.
 */
.js-details-panel:has(.details-placeholder):not(:has(#bulk-actions-panel:not(.hidden))) {
  display: none;
}
/* ── Feature chip vocabulary (shared) ───────────────────────────────────────
   The dashboard family's chip language for a map feature — status, category,
   responsible, level, counts, last-visit — plus the editable (`.fchip`) variant
   used where those values can be reassigned inline (the maps/show quick-view
   panel today; the locations-list / metrics rows next).

   The read-only `.ll-*` chips are lifted verbatim out of the locations-list
   table's scoped <style> so the exact same pixels render everywhere. Keep this
   file the single source — do not re-declare these in a view <style> block.
   Table LAYOUT (.ll-table / .ll-row / .ll-sticky / .ll-peek / .ll-actions)
   stays with the locations table; it's context-specific, not chip vocabulary. */

/* ── Read-only display chips ─────────────────────────────────────────────── */
.ll-muted { color:#C4CBD4; font-size:13px; }

.ll-badge { flex:none; font-size:10px; font-weight:600; line-height:1; padding:3px 6px;
  border-radius:5px; background:#ECFDF3; color:#15803D; border:1px solid #D1FADF; }
.ll-badge.is-top { background:#EEF2FF; color:#4338CA; border-color:#E0E7FF; }

/* Counts and measurements (feature area, child counts) — tabular numerals so a
   column of these lines up. Declared here rather than leaning on `num-tabular`,
   which is only defined in the design_review layout and is a no-op in the app. */
.ll-count { flex:none; font-size:11px; color:var(--brand-neutral-500); background:var(--brand-neutral-100);
  border-radius:999px; padding:1px 8px; white-space:nowrap;
  font-variant-numeric:tabular-nums; }

.ll-pill { flex:none; font-size:10px; font-weight:500; padding:2px 6px; border-radius:5px; line-height:1.3; }
.ll-pill-warn { background:#FFFBEB; color:#B45309; border:1px solid #FDE68A; }
.ll-pill-dist { background:#EFF6FF; color:#1D4ED8; border:1px solid #DBEAFE; }

.ll-status { display:inline-flex; align-items:center; max-width:100%; font-size:11px;
  font-weight:600; padding:2px 9px; border-radius:999px; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
.ll-status .ll-dot { width:6px; height:6px; border-radius:50%; margin-right:6px; flex:none; }

.ll-cat { display:inline-flex; align-items:center; gap:7px; min-width:0; max-width:100%; }
.ll-cat-ic { font-size:15px; line-height:1; flex:none; }
.ll-cat-name { font-size:13px; color:var(--brand-neutral-700); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.ll-avatar { display:inline-flex; align-items:center; justify-content:center; width:26px;
  height:26px; border-radius:999px; background:var(--brand-primary); color:#fff; font-size:11px; font-weight:600; flex:none; }
.ll-avatar.is-creator { background:var(--brand-neutral-400); }

.ll-visit { display:inline-flex; align-items:center; gap:7px; font-size:12px; color:var(--brand-neutral-500); white-space:nowrap; }
.ll-visit .ll-dot { width:7px; height:7px; border-radius:50%; background:var(--brand-neutral-300); flex:none; }
.ll-visit.is-fresh { color:var(--brand-primary-dark); }
.ll-visit.is-fresh .ll-dot { background:var(--brand-primary); }

/* Kebab + menu — the standard secondary-actions affordance. */
.ll-kebab { padding:5px; border-radius:6px; color:var(--brand-neutral-400); background:none; border:0;
  cursor:pointer; display:inline-flex; transition:background-color 80ms ease, color 80ms ease; }
.ll-kebab svg { width:16px; height:16px; }
.ll-kebab:hover { color:var(--brand-primary-dark); background:var(--brand-primary-surface); }
.ll-menu-item { display:flex; align-items:center; gap:9px; width:100%; text-align:left;
  padding:7px 12px; font-size:13px; color:var(--brand-primary-dark); background:none; border:0; cursor:pointer; text-decoration:none; }
.ll-menu-item:hover { background:var(--brand-primary-surface); }
.ll-menu-item.is-danger { color:var(--brand-danger); }
.ll-menu-item.is-danger:hover { background:#FCE4E8; }
.ll-menu-ic { width:15px; height:15px; flex:none; color:var(--brand-primary); }
.ll-menu-item.is-danger .ll-menu-ic { color:var(--brand-danger); }

/* ── Editable chip (`.fchip`) ────────────────────────────────────────────────
   Reads like the display chips above but is a button that opens a reassign
   dropdown. The white surface + stone border + chevron are the "you can change
   this" affordance; the inner dot / icon / avatar mirror the read-only chips so
   the row still looks native to the dashboard family. */
.fchips { display:flex; flex-wrap:wrap; align-items:center; gap:8px; }
/* Fixed height (not padding) so every chip matches regardless of whether its
   content is an emoji icon, a colour dot, an avatar, or plain text — an emoji
   category icon is intrinsically taller than a status dot, which is exactly why
   a padding-sized chip row ends up uneven. */
.fchip { display:inline-flex; align-items:center; gap:6px; max-width:100%;
  height:32px; box-sizing:border-box; padding:0 10px; font-size:12px; font-weight:600;
  line-height:1; border-radius:999px; background:#fff; border:1px solid var(--brand-neutral-200);
  color:var(--brand-primary-dark); cursor:pointer; overflow:hidden;
  transition:background-color 80ms ease, border-color 80ms ease; }
.fchip:hover { background:var(--brand-primary-surface); border-color:rgb(var(--brand-primary-rgb) / .45); }
.fchip:focus-visible { outline:2px solid var(--brand-primary); outline-offset:1px; }
.fchip--muted { color:var(--brand-neutral-500); font-weight:500; }
.fchip-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fchip-chev { width:12px; height:12px; opacity:.5; flex:none; }
.fchip-ic { font-size:14px; line-height:1; flex:none; }
.fchip-dot { width:8px; height:8px; border-radius:50%; flex:none; }
.fchip-av { display:inline-flex; align-items:center; justify-content:center; width:18px; height:18px;
  border-radius:999px; background:var(--brand-primary); color:#fff; font-size:9px; font-weight:700; flex:none; }
.fchip-av.is-creator { background:var(--brand-neutral-400); }

/* ── "Finish setting up" checklist ────────────────────────────────────────
   Shown in the details panel for a location that was just drawn (see
   map_features/_panel_detail and new_feature_setup_controller). Plain CSS
   rather than Tailwind utilities because the states are toggled by class from
   JS, and a keyframe animation has no utility. */

/* Ring pulse on whichever field the checklist just pointed at. Drawn with
   box-shadow so it costs no layout and cannot shift the panel while it runs. */
.setup-pointing {
  /* Two beats over 2.6s rather than one quick fade. A single 1.5s pulse was
     easy to miss entirely if the scroll was still settling when it started. */
  animation: setup-point 1.3s ease-out 2;
  border-radius: 6px;
  position: relative;
  z-index: 1; /* the ring is drawn outside the box; keep it above its siblings */
}

/* Amber, not the brand green. The green ring sat on green-tinted chips and on a
   green-focus input, so the thing being pointed at looked much like everything
   around it. Amber belongs to nothing else on this panel, which is the whole
   job — and it holds a solid tint between beats instead of fading to nothing. */
@keyframes setup-point {
  0%   { box-shadow: 0 0 0 0 rgb(var(--brand-warning-rgb) / 0.85); background-color: rgb(var(--brand-warning-rgb) / 0.18); }
  50%  { box-shadow: 0 0 0 7px rgb(var(--brand-warning-rgb) / 0);  background-color: rgb(var(--brand-warning-rgb) / 0.10); }
  100% { box-shadow: 0 0 0 0 rgb(var(--brand-warning-rgb) / 0);    background-color: rgb(var(--brand-warning-rgb) / 0.18); }
}

/* A finished item recedes rather than disappearing — the list keeps its shape,
   so ticking one off never makes the others jump under the cursor. */
.setup-item.is-done .setup-item-text {
  text-decoration: line-through;
  opacity: 0.55;
}

@media (prefers-reduced-motion: reduce) {
  .setup-pointing {
    animation: none;
    box-shadow: 0 0 0 3px rgb(var(--brand-warning-rgb) / 0.9);
    background-color: rgb(var(--brand-warning-rgb) / 0.18);
  }
}

/* ── "Finish setting up" checklist ────────────────────────────────────────
   The box beside the details panel (maps/show.html.erb) and the ring pulse it
   puts on whichever field you jump to. Plain CSS rather than Tailwind: the
   states are toggled by class from JS, and keyframes have no utility. */

/* Ring pulse on whichever field the checklist just pointed at. box-shadow so it
   costs no layout and cannot shift the panel while it runs.

   Amber, not the brand green: the green ring sat on green-tinted chips beside a
   green-focus input, so the thing being pointed at looked like everything
   around it. Amber belongs to nothing else on this panel. */
.setup-pointing {
  animation: setup-point 1.3s ease-out 2;
  border-radius: 6px;
  position: relative;
  z-index: 1; /* the ring is drawn outside the box; keep it above its siblings */
}

@keyframes setup-point {
  0%   { box-shadow: 0 0 0 0 rgb(var(--brand-warning-rgb) / 0.85); background-color: rgb(var(--brand-warning-rgb) / 0.18); }
  50%  { box-shadow: 0 0 0 7px rgb(var(--brand-warning-rgb) / 0);  background-color: rgb(var(--brand-warning-rgb) / 0.10); }
  100% { box-shadow: 0 0 0 0 rgb(var(--brand-warning-rgb) / 0);    background-color: rgb(var(--brand-warning-rgb) / 0.18); }
}

/* A finished item recedes rather than disappearing — the list keeps its shape,
   so ticking one off never makes the others jump under the cursor. */
.setup-item.is-done .setup-item-text {
  text-decoration: line-through;
  opacity: 0.55;
}

/* Each tick lands with a small pop, so crossing an item off is visible in
   itself and not only in the count above. */
.setup-item [data-setup-tick]:not(.hidden) {
  animation: setup-tick 240ms cubic-bezier(0.34, 1.56, 0.64, 1) 1;
}

@keyframes setup-tick {
  0%   { transform: scale(0.2); opacity: 0; }
  100% { transform: scale(1);   opacity: 1; }
}

/* ── Finished ─────────────────────────────────────────────────────────────
   Completion is a state the box settles INTO, not an event it announces. The
   list collapses away and a drawn checkmark takes its place, so what is left is
   a short confirmation rather than four struck-through lines and a flash. */

.setup-complete [data-setup-list],
.setup-complete [data-setup-subtitle] {
  max-height: 0;
  opacity: 0;
  margin-top: 0;
  margin-bottom: 0;
  padding-top: 0;
  padding-bottom: 0;
  overflow: hidden;
  transition: max-height 320ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 180ms ease-out,
              padding 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* The resting state needs the same transition, or collapsing is animated and
   coming back is not. max-height is a ceiling, not a measurement — it only has
   to exceed the real height for the transition to run. */
[data-setup-list],
[data-setup-subtitle] {
  max-height: 12rem;
  opacity: 1;
  transition: max-height 320ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 200ms ease-in 80ms,
              padding 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* One icon at a time: the clipboard while there is work left, the check once
   there is not. Specificity beats Tailwind's .hidden (0,1,0) without !important. */
.setup-complete .setup-icon-todo { display: none; }
.setup-complete .setup-icon-done { display: block; }

/* Finished, the heading IS the whole message — so it centres, grows, and takes
   the green it earns. Left-aligned beside a dismiss button it read as a label
   on a form; centred with the check it reads as a result. */
.setup-complete .setup-head {
  align-items: center;
  justify-content: center;
  padding-top: 0.75rem;
  padding-bottom: 0.75rem;
  gap: 0.5rem;
}

.setup-complete .setup-head-text {
  flex: 0 1 auto;
  text-align: center;
}

.setup-complete .setup-title {
  color: var(--brand-primary);
  font-size: 13px;
  line-height: 1.2;
}

/* No dismiss once it is done: there is nothing left to dismiss, and the button
   sitting off to one side is what stopped the message from centring. The box
   goes on its own at the next selection. */
.setup-complete .setup-dismiss { display: none; }

/* The checkmark draws itself. 30 comfortably exceeds the path length, so the
   dash starts fully offset (invisible) and resolves to nothing. */
.setup-complete .setup-check path {
  stroke-dasharray: 30;
  stroke-dashoffset: 30;
  animation: setup-draw 400ms ease-out 140ms forwards;
}

@keyframes setup-draw {
  to { stroke-dashoffset: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .setup-pointing {
    animation: none;
    box-shadow: 0 0 0 3px rgb(var(--brand-warning-rgb) / 0.9);
    background-color: rgb(var(--brand-warning-rgb) / 0.18);
  }
  .setup-item [data-setup-tick]:not(.hidden) { animation: none; }
  .setup-complete .setup-check path { stroke-dashoffset: 0; animation: none; }
  [data-setup-list],
  [data-setup-subtitle],
  .setup-complete [data-setup-list],
  .setup-complete [data-setup-subtitle] { transition: none; }
}
/* ── Turbo Frame loading feedback ────────────────────────────────────────────
   Turbo sets the `busy` attribute on a <turbo-frame> while it's fetching. We use
   it to show an immediate spinner so a click never feels dead while the server
   renders — no JS, no per-link wiring. Covers the two frames that felt slow:
   the global `modal` frame (share/delete/merge/… modals) and the maps/show
   feature-details panel (loaded via turboFrame.src on feature click). */
@keyframes tf-spin { to { transform: rotate(360deg); } }

/* Global modal frame — a dimmed backdrop + centered spinner the instant a
   modal-targeting link is clicked, before the modal HTML arrives. position:fixed
   so it shows even though the empty frame has zero size. */
turbo-frame#modal[busy]::after {
  content: ""; position: fixed; inset: 0; z-index: 60;
  background: rgba(15, 42, 36, 0.25); backdrop-filter: blur(1px);
}
turbo-frame#modal[busy]::before {
  content: ""; position: fixed; top: 50%; left: 50%; z-index: 61;
  width: 34px; height: 34px; margin: -17px 0 0 -17px; border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.5); border-top-color: #fff;
  animation: tf-spin 0.7s linear infinite;
}

/* maps/show feature-details panel — dim the panel + spinner near the top while
   the clicked feature's details load (old content stays until the swap). */
turbo-frame#feature-details-frame { display: block; position: relative; }
/* …but never out-specify Tailwind's `.hidden`. mapbox_map_controller toggles
   `hidden` on this frame to swap the panel between the feature details and the
   multi-select (bulk actions) panel — without this rule the element+id selector
   above (specificity 0,1,1) beats `.hidden` (0,1,0) and both panels render
   stacked in the same card. */
turbo-frame#feature-details-frame.hidden { display: none; }
turbo-frame#feature-details-frame[busy]::after {
  content: ""; position: absolute; inset: 0; z-index: 20;
  background: rgba(255, 255, 255, 0.6);
}
turbo-frame#feature-details-frame[busy]::before {
  content: ""; position: absolute; top: 40px; left: 50%; z-index: 21;
  width: 26px; height: 26px; margin-left: -13px; border-radius: 50%;
  border: 3px solid var(--brand-neutral-200); border-top-color: var(--brand-primary);
  animation: tf-spin 0.7s linear infinite;
}

/* Metrics-dashboard table — EVERY interaction (sort, filter, window, roll-up,
   group-by, column toggle, load-more) re-navigates this frame, and the server
   takes ~300–900ms on a large team. Without feedback the stale table just sits
   there and the click reads as dead, so:
     ::after  — veil the stale table so it visibly recedes (z-index clears the
                sticky headers at 32 and the column ⋮ popover at 40)
     ::before — a fixed indeterminate bar pinned to the top of the VIEWPORT.
                Deliberately not centered-in-frame like the panel spinner above:
                the table is far taller than the viewport and has its own inner
                scroller, so an absolutely-positioned spinner is usually parked
                off-screen. A fixed bar is visible wherever you're scrolled. */
@keyframes tf-bar {
  0%   { transform: translateX(-100%) scaleX(0.35); }
  50%  { transform: translateX(0%)    scaleX(0.55); }
  100% { transform: translateX(100%)  scaleX(0.35); }
}
turbo-frame#metrics_table { display: block; position: relative; }
turbo-frame#metrics_table[busy]::after {
  content: ""; position: absolute; inset: 0; z-index: 50;
  background: rgba(255, 255, 255, 0.55);
}
turbo-frame#metrics_table[busy]::before {
  content: ""; position: fixed; top: 0; left: 0; right: 0; z-index: 9999;
  height: 3px; background: var(--brand-primary); transform-origin: 0 50%;
  animation: tf-bar 1.1s ease-in-out infinite;
}

/* ── Frames driven by a Stimulus controller assigning frame.src ─────────────
   These all had the same problem the three frames above used to have: the
   controller swaps `frame.src`, Turbo leaves the OLD content in place while it
   fetches, and the click reads as dead — long enough that people click again.
   Measured 130–180ms each on a heavy team, more on a slow connection.

   Shape is chosen by frame geometry, per the house rule:
     tall + internally scrolling → veil + FIXED top bar (a centred spinner
                                   would be parked off-screen)
     panel                       → veil + spinner near its top
     short / empty               → centred spinner (with a min-height when the
                                   frame is empty at rest and has no box) */

/* Locations list (map_features#index). Every filter, sort, view-mode and page
   click re-navigates this frame (map_features_index_controller#navigate,
   location_filter_controller), and a bulk delete now does too — the frame
   re-fetches itself so the pager, the totals and the rows flowing up from the
   next page all come from the server.

   Unlike the other frames here, this one does NOT draw its spinner in CSS.
   The list already has a first-load spinner (the "Loading locations…" card the
   page shell paints while the lazy frame fetches), and a re-fetch should look
   like a first load — so map_features/_list_loading is rendered a second time
   inside the frame as a hidden overlay, and the rule below just reveals it.
   Same markup, same svg, no second spinner to keep in sync.

   Centred overlay rather than the veil + fixed top bar used by #metrics_table
   above: that bar exists because the metrics table is far taller than the
   viewport, so an absolutely-positioned overlay's contents end up parked
   off-screen. This frame is bounded to the viewport (its container is
   h-[calc(100dvh-3.25rem)], or max-h-[74vh] when embedded) and the table
   scrolls INSIDE it, so the frame's centre is always where the user is looking.

   NOTE: deliberately does NOT set `display` — same trap as #app-peek-panel
   below. map_features/index declares this frame with
   `class="flex flex-col flex-1 min-h-0"`, and it is the middle link in the
   chain that gives the table its own scroller:
     .h-dvh.overflow-hidden container → THIS FRAME → .ll-card → .ll-scroll
   An element+id selector (1,0,1) beats Tailwind's `.flex` (0,1,0), so a
   `display: block` here severed that chain: .ll-card's `flex:1; min-height:0`
   went inert, it fell back to height:auto, .ll-scroll never got a bounded
   height to scroll inside, and the full 25-row page overflowed the
   overflow-hidden container — clipped, with no scrollbar anywhere and the
   footer and pagination unreachable. `position: relative` is all the overlay
   needs and it doesn't disturb the flex layout.
   (turbo_frame_display_guard_test enforces the no-`display` part.) */
turbo-frame#feature-list-frame { position: relative; }
/* The overlay ships with Tailwind's `hidden` on it, so it is invisible even if
   this stylesheet never loads. This selector is (1,2,1) and beats `.hidden`
   (0,1,0), so it still shows while the frame is fetching. */
turbo-frame#feature-list-frame[busy] .ll-loading-overlay {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  position: absolute; inset: 0; z-index: 50;
  background: rgba(255, 255, 255, 0.75);
}

/* App-wide peek slide-over (peek_controller). Re-opening a peek keeps the
   PREVIOUS peek's content on screen while the new one loads, which reads as
   the panel having opened on the wrong record. Veil it so the stale content
   visibly recedes.

   NOTE: deliberately does NOT set `display` or `min-height` here. peek.css has
   `.peek-panel > turbo-frame { flex: 1; min-height: 0; display: flex;
   flex-direction: column }` at specificity 0,1,1 — an id selector (1,0,1)
   would beat it and collapse the panel's flex column, which is what makes the
   sticky top bar and the scrolling body work. `position: relative` is all the
   veil needs, and it doesn't disturb the flex layout. The frame already fills
   the panel via `flex: 1`, so it always has a box to draw into. */
turbo-frame#app-peek-panel { position: relative; }
turbo-frame#app-peek-panel[busy]::after {
  content: ""; position: absolute; inset: 0; z-index: 20;
  background: rgba(255, 255, 255, 0.65);
}
turbo-frame#app-peek-panel[busy]::before {
  content: ""; position: absolute; top: 56px; left: 50%; z-index: 21;
  width: 26px; height: 26px; margin-left: -13px; border-radius: 50%;
  border: 3px solid var(--brand-neutral-200); border-top-color: var(--brand-primary);
  animation: tf-spin 0.7s linear infinite;
}

/* Settings modal body (region_preview_controller and the settings nav). A
   panel, so veil + spinner near the top rather than a viewport-fixed bar. */
turbo-frame#settings_content { display: block; position: relative; }
turbo-frame#settings_content[busy] { min-height: 140px; }
turbo-frame#settings_content[busy]::after {
  content: ""; position: absolute; inset: 0; z-index: 20;
  background: rgba(255, 255, 255, 0.65);
}
turbo-frame#settings_content[busy]::before {
  content: ""; position: absolute; top: 48px; left: 50%; z-index: 21;
  width: 26px; height: 26px; margin-left: -13px; border-radius: 50%;
  border: 3px solid var(--brand-neutral-200); border-top-color: var(--brand-primary);
  animation: tf-spin 0.7s linear infinite;
}

/* Saved-views dropdown (saved_views_controller). Short frame inside a small
   menu — a centred spinner is the whole feedback; no veil needed at this size.
   The frame itself stays visible; its parent `.saved-views-menu` is what
   toggles open/closed. */
turbo-frame#saved-views-list { display: block; position: relative; }
turbo-frame#saved-views-list[busy] { min-height: 56px; }
turbo-frame#saved-views-list[busy]::after {
  content: ""; position: absolute; inset: 0; z-index: 20;
  background: rgba(255, 255, 255, 0.7);
}
turbo-frame#saved-views-list[busy]::before {
  content: ""; position: absolute; top: 50%; left: 50%; z-index: 21;
  width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: 50%;
  border: 2px solid var(--brand-neutral-200); border-top-color: var(--brand-primary);
  animation: tf-spin 0.7s linear infinite;
}

/* Inline bulk-organise panel (bulk_organise_controller), in the maps/show
   sidebar. This one is EMPTY at rest — `turbo_frame_tag "bulk-organise-frame"`
   with no body — so there's no box to position a spinner inside until content
   arrives. Give it a height only while busy. */
turbo-frame#bulk-organise-frame[busy] {
  display: block; position: relative; min-height: 64px;
}
turbo-frame#bulk-organise-frame[busy]::before {
  content: ""; position: absolute; top: 50%; left: 50%; z-index: 21;
  width: 22px; height: 22px; margin: -11px 0 0 -11px; border-radius: 50%;
  border: 2px solid var(--brand-neutral-200); border-top-color: var(--brand-primary);
  animation: tf-spin 0.7s linear infinite;
}

/* Link popover panel (bulk_link_controller), in the Locations-list bulk bar.
   Same shape as #bulk-organise-frame above: EMPTY at rest (the toolbar renders
   a bare frame; openLinkPop assigns src on open), so it needs a box only while
   busy. Covers both the first open and the server-side candidate-search
   refetches (?q=), which would otherwise read as dead typing. */
turbo-frame#bulk-link-frame[busy] {
  display: block; position: relative; min-height: 64px;
}
turbo-frame#bulk-link-frame[busy]::before {
  content: ""; position: absolute; top: 50%; left: 50%; z-index: 21;
  width: 22px; height: 22px; margin: -11px 0 0 -11px; border-radius: 50%;
  border: 2px solid var(--brand-neutral-200); border-top-color: var(--brand-primary);
  animation: tf-spin 0.7s linear infinite;
}

/* Same guard as #feature-details-frame above: the element+id selectors here are
   specificity 0,1,1 and would beat Tailwind's `.hidden` (0,1,0) if anything
   ever toggles these frames that way. Nothing does today — this keeps it that
   way by construction rather than by luck. */
turbo-frame#feature-list-frame.hidden,
turbo-frame#app-peek-panel.hidden,
turbo-frame#settings_content.hidden,
turbo-frame#saved-views-list.hidden,
turbo-frame#bulk-organise-frame.hidden,
turbo-frame#bulk-link-frame.hidden { display: none; }
/* Active-visit home widget (app/views/pages/home/widgets/_active_card.html.erb).
   Each .av-card is an inline-size container, so a card lays itself out from its
   OWN width: a wide (full-width widget) card puts the "Captured so far" panel to
   the RIGHT of the details in a hero row; a narrow (half-width widget) card stacks
   the panel BELOW the details and renders the legend as a wrapping horizontal row.
   All values are --pm-* tokens (see pm_tokens.css). No raw hex, no raw px, no
   left-accent bar, no gradient. */

.av-frame { container-type: inline-size; }

/* ── Card shell ─────────────────────────────────────────────────────────── */
.av-card {
  background: var(--pm-surface);
  border: 1px solid color-mix(in srgb, var(--pm-primary) 22%, transparent);
  border-radius: var(--pm-radius-xl);
  box-shadow: var(--pm-shadow-card);
  padding: var(--pm-space-m);
  transition: box-shadow var(--pm-duration-fast) var(--pm-ease-standard);
}
.av-card:hover { box-shadow: var(--pm-shadow-m); }
/* Stale card — whole-card warm border tint (no left bar). */
.av-card.stale { border-color: color-mix(in srgb, var(--pm-warning) 32%, transparent); }

/* Compact (default): details, then panel stacked below. */
.av-body    { display: flex; flex-direction: column; gap: var(--pm-space-m); }
.av-details { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }

/* ── Status row ─────────────────────────────────────────────────────────── */
.av-status-row { display: flex; align-items: center; gap: var(--pm-space-s); margin-bottom: var(--pm-space-s); }
.av-eyebrow {
  display: inline-flex; align-items: center; gap: var(--pm-space-s);
  font: var(--pm-label-s); text-transform: uppercase; letter-spacing: .06em;
  color: var(--pm-fg-2); white-space: nowrap;
}
.av-dot {
  width: var(--pm-space-s); height: var(--pm-space-s); border-radius: var(--pm-radius-pill);
  background: var(--pm-success); flex-shrink: 0;
  animation: av-pulse 1.8s ease-in-out infinite;
}
@keyframes av-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
.av-pill {
  margin-left: auto; display: inline-flex; align-items: center; gap: var(--pm-space-xs);
  padding: var(--pm-space-xxs) var(--pm-space-s);
  font: var(--pm-pill); text-transform: uppercase; letter-spacing: .05em;
  background: var(--pm-surface-warning); color: var(--pm-warning-dark);
  border-radius: var(--pm-radius-pill); white-space: nowrap;
}
.av-pill .fas { font-size: 10px; }

/* ── Title ──────────────────────────────────────────────────────────────── */
.av-title {
  font: var(--pm-headline-s); font-size: 1.0625rem; /* 17px compact; 18px in hero */
  color: var(--pm-fg-1); margin: 0; min-width: 0;
  /* Clamp to 2 lines so a long feature name/purpose truncates cleanly instead of
     growing the card unbounded. */
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.av-title .av-dash { color: var(--pm-fg-2); font-weight: 600; }
/* Purpose recedes so the feature name dominates the title. */
.av-title .av-purpose { font-weight: 600; color: var(--pm-fg-2); }

/* ── Meta line ──────────────────────────────────────────────────────────── */
.av-meta {
  font: var(--pm-body-s); color: var(--pm-fg-2);
  margin: var(--pm-space-xs) 0 0;
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--pm-space-xxs) var(--pm-space-s);
}
.av-meta .av-pin { color: var(--pm-accent-critical); font-size: 12px; flex-shrink: 0; }
.av-meta .av-sep { color: var(--pm-fg-3); }
.av-meta .av-place { overflow: hidden; text-overflow: ellipsis; max-width: 12rem; white-space: nowrap; }
.av-meta .av-time { color: var(--pm-fg-1); font-weight: 600; } /* the one anchor */

/* ── Stale nudge — inline warm text, no colored surface ─────────────────── */
.av-nudge {
  display: flex; align-items: flex-start; gap: var(--pm-space-xs);
  margin-top: var(--pm-space-ms);
  font: var(--pm-body-s); line-height: 1.4; color: var(--pm-warning-dark);
}
.av-nudge .av-nudge-icon { font-size: 12px; line-height: 1.4; flex-shrink: 0; }

/* ── Actions — CTAs anchored to the bottom of the details column ─────────── */
.av-actions {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--pm-space-s);
  margin-top: auto; padding-top: var(--pm-space-m);
}
.av-btn {
  min-height: 3rem; display: inline-flex; align-items: center; justify-content: center;
  gap: var(--pm-space-s); font-size: 14px; font-weight: 600;
  border-radius: var(--pm-radius-pill); border: 1px solid transparent; cursor: pointer;
  transition: box-shadow var(--pm-duration-fast) var(--pm-ease-standard),
              background var(--pm-duration-fast) var(--pm-ease-standard),
              transform var(--pm-duration-fast) var(--pm-ease-standard);
}
.av-btn:focus-visible { outline: none; box-shadow: var(--pm-focus-ring); }
.av-btn .fas, .av-btn .far { font-size: 12px; }

/* Compact (default): the two primary pills share the row and fill the column
   side-by-side instead of stacking left-aligned. Reset to natural width in hero. */
.av-continue    { flex: 1 1 0; }
.av-finish-form { flex: 1 1 0; }
.av-finish      { width: 100%; }

/* Continue — primary GO, the only fill weight. */
.av-continue {
  padding: 0 var(--pm-space-l);
  background: var(--pm-primary); color: var(--pm-on-inverse); box-shadow: var(--pm-shadow-s);
}
.av-continue:hover { box-shadow: var(--pm-shadow-m); }
.av-continue:focus-visible { box-shadow: var(--pm-shadow-s), var(--pm-focus-ring); }
.av-arrow { transition: transform var(--pm-duration-fast) var(--pm-ease-standard); }
.av-continue:hover .av-arrow { transform: translateX(2px); }

/* Finish — END, outlined coral (lighter than Continue). */
.av-finish {
  padding: 0 var(--pm-space-l);
  background: transparent; color: var(--pm-accent-critical);
  border-color: var(--pm-accent-critical);
}
.av-finish:hover { background: color-mix(in srgb, var(--pm-accent-critical) 8%, transparent); }
/* Compact (default): flex sizes the two primary pills, so trim their inner
   padding (overrides the space-l shorthand above) to keep labels on one line.
   Hero restores the roomy padding. */
.av-continue, .av-finish { padding-left: var(--pm-space-s); padding-right: var(--pm-space-s); }

/* button_to wraps each action in a <form>; the FORM is the flex item, so the
   auto-margin that shoves Discard to the far edge must live on the form. */
.av-finish-form { margin: 0; }
.av-discard-form { margin: 0 0 0 auto; }

/* Discard — destructive, tertiary/quiet, pushed to the far edge. */
.av-discard {
  min-height: auto; padding: 0 var(--pm-space-xs);
  background: transparent; color: var(--pm-fg-3); font-weight: 500;
  box-shadow: none;
}
.av-discard:hover { color: var(--pm-accent-critical); }

/* ── "Captured so far" panel ────────────────────────────────────────────── */
.av-panel {
  background: var(--pm-surface-variant); border-radius: var(--pm-radius-l);
  padding: var(--pm-space-m); display: flex; flex-direction: column;
}
.av-panel-h {
  font: var(--pm-eyebrow); text-transform: uppercase; letter-spacing: .06em;
  color: var(--pm-fg-3); margin: 0 0 var(--pm-space-s);
}
.av-panel-empty { font: var(--pm-body-s); color: var(--pm-fg-3); margin: 0; }
/* Zero-observation stand-in — short muted inline line, NO grey sub-surface/rail.
   Sits at the top beside the details in hero (align-self), below in compact. */
.av-empty-note {
  margin: 0; font: var(--pm-body-s); color: var(--pm-fg-3);
  align-self: flex-start; flex: 0 0 auto;
}

/* Legend — compact default: wrapping horizontal row of "● Label N" units. */
.av-legend {
  display: flex; flex-direction: row; flex-wrap: wrap;
  gap: var(--pm-space-s) var(--pm-space-m);
}
.av-leg { display: flex; align-items: center; gap: var(--pm-space-xs); font: var(--pm-body-s); color: var(--pm-fg-2); }
.av-leg-dot { width: 7px; height: 7px; border-radius: var(--pm-radius-pill); flex-shrink: 0; }
.av-leg-label { white-space: nowrap; }
.av-leg-count { margin-left: var(--pm-space-xxs); font-weight: 700; color: var(--pm-fg-1); }

/* Total — sum of buckets, honest. */
.av-total {
  display: flex; align-items: center;
  margin-top: var(--pm-space-s); padding-top: var(--pm-space-s);
  border-top: 1px solid var(--pm-border);
  font: var(--pm-eyebrow);
}
.av-total-label { text-transform: uppercase; letter-spacing: .05em; color: var(--pm-fg-3); }
.av-total-count { margin-left: auto; font-size: 12px; color: var(--pm-fg-1); }

@media (prefers-reduced-motion: reduce) {
  .av-dot { animation: none; opacity: 1; }
  .av-arrow { transition: none; }
  .av-continue:hover .av-arrow { transform: none; }
}

/* ── Wide (hero) — panel BESIDE the details, roomier padding + lift ──────── */
@container (min-width: 34rem) {
  .av-card       { padding: var(--pm-space-l); box-shadow: var(--pm-shadow-m); }
  .av-card:hover { box-shadow: var(--pm-shadow-l); }

  .av-body    { flex-direction: row; align-items: flex-start; gap: var(--pm-space-l); }
  /* Panel is only as tall as its content — card height is driven by the details
     column, so a stale/low-observation card shows no dead grey rail below the
     legend. Pinned to the top beside the details. */
  .av-panel   { flex: 0 0 13.5rem; align-self: flex-start; }
  .av-title   { font-size: 1.125rem; }

  /* Hero has room — primary pills return to natural width + roomy padding. */
  .av-continue    { flex: 0 0 auto; }
  .av-finish-form { flex: 0 0 auto; }
  .av-finish      { width: auto; }
  .av-continue, .av-finish { padding-left: var(--pm-space-l); padding-right: var(--pm-space-l); }

  /* Legend returns to a vertical "summary table" with right-aligned counts. */
  .av-legend      { flex-direction: column; gap: var(--pm-space-ms); }
  .av-leg         { gap: var(--pm-space-s); }
  .av-leg-count   { margin-left: auto; }
}
/* Audio Briefings home widget (app/views/pages/home/widgets/_briefings.html.erb).
   Forward-looking: a player hero that plays the soonest upcoming visit's briefing,
   then one row per upcoming visit. The .bw-frame is an inline-size container, so
   the widget lays itself out from its OWN width — a wide (full-width) widget shows
   the "Next up" kicker, the row meta line, and the full transcript label; a narrow
   (half-width) widget drops those for a denser single-line read.

   All values are --pm-* tokens (see pm_tokens.css) except small icon/element
   sizing in px, matching home_active_widget.css. No raw hex, no left-accent bar,
   no gradient. */

.bw-frame { container-type: inline-size; }

/* ── Player hero ─────────────────────────────────────────────────────────── */
.bw-player { display: flex; align-items: center; gap: var(--pm-space-ms); }

.bw-play {
  flex-shrink: 0; width: 44px; height: 44px; border: none; cursor: pointer;
  border-radius: var(--pm-radius-pill);
  background: var(--pm-primary); color: var(--pm-fg-on-primary);
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: var(--pm-shadow-m);
  transition: transform var(--pm-duration-fast) var(--pm-ease-standard);
}
.bw-play:hover { transform: scale(1.04); }
.bw-play:focus-visible { outline: none; box-shadow: var(--pm-focus-ring), var(--pm-shadow-m); }
.bw-play .fas { font-size: 16px; }

.bw-play-meta { min-width: 0; flex: 1; }
.bw-kicker {
  display: none; /* full only */
  font: var(--pm-label-s); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--pm-primary-light);
  margin: 0 0 var(--pm-space-xxs);
}
.bw-headline {
  font: var(--pm-title-m); color: var(--pm-fg-1); margin: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bw-subline {
  font: var(--pm-body-s); color: var(--pm-fg-2); margin: 2px 0 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Quiet hero — upcoming visits exist but none have audio yet. */
.bw-hero-quiet {
  display: flex; align-items: center; gap: var(--pm-space-s);
  font: var(--pm-body-m); color: var(--pm-fg-3);
}
.bw-hero-quiet .fas { color: var(--pm-primary-light); font-size: 14px; }

/* ── Scrubber ────────────────────────────────────────────────────────────── */
.bw-scrub {
  display: flex; align-items: center; gap: var(--pm-space-s);
  margin-top: var(--pm-space-ms);
}
.bw-t {
  font-family: var(--pm-font-mono); font-size: 10px; line-height: 1;
  color: var(--pm-fg-3); width: 30px; flex-shrink: 0;
}
.bw-t--end { text-align: right; }
.bw-track {
  position: relative; flex: 1; height: 5px; cursor: pointer;
  border-radius: var(--pm-radius-pill); background: var(--pm-surface-variant);
}
.bw-track:focus-visible { outline: none; box-shadow: var(--pm-focus-ring); }
.bw-fill {
  position: absolute; inset: 0 auto 0 0; width: 0%;
  border-radius: var(--pm-radius-pill); background: var(--pm-primary-light);
}
.bw-thumb {
  position: absolute; top: 50%; left: 0%; width: 12px; height: 12px;
  transform: translate(-50%, -50%); border-radius: var(--pm-radius-pill);
  background: var(--pm-surface); border: 2px solid var(--pm-primary-light);
  box-shadow: var(--pm-shadow-xs);
}

/* ── Transport ───────────────────────────────────────────────────────────── */
.bw-transport {
  display: flex; align-items: center; gap: var(--pm-space-s);
  margin-top: var(--pm-space-s);
}
.bw-tbtn {
  display: inline-flex; align-items: center; gap: var(--pm-space-xs);
  cursor: pointer; height: 30px; padding: 0 var(--pm-space-ms);
  border: 1px solid var(--pm-border); background: var(--pm-surface);
  color: var(--pm-fg-2); border-radius: var(--pm-radius-pill);
  font: var(--pm-title-s); font-size: 11px;
  transition: background var(--pm-duration-fast) var(--pm-ease-standard),
              color var(--pm-duration-fast) var(--pm-ease-standard);
}
.bw-tbtn:hover { background: var(--pm-surface-secondary); color: var(--pm-fg-1); }
.bw-tbtn:focus-visible { outline: none; box-shadow: var(--pm-focus-ring); }
.bw-tbtn .fas { font-size: 12px; }
.bw-sec {
  font-family: var(--pm-font-mono); font-size: 9px; color: var(--pm-fg-3);
  font-variant-numeric: tabular-nums;
}
.bw-speed {
  min-width: 46px; justify-content: center;
  font-variant-numeric: tabular-nums;
}
.bw-spacer { flex: 1; }
.bw-transcript {
  display: inline-flex; align-items: center; gap: var(--pm-space-xs);
  cursor: pointer; border: none; background: transparent;
  color: var(--pm-primary-light); font: var(--pm-title-s); font-size: 11px;
  padding: var(--pm-space-xs);
}
.bw-transcript:hover { text-decoration: underline; }
.bw-transcript:focus-visible { outline: none; box-shadow: var(--pm-focus-ring); border-radius: var(--pm-radius-s); }
.bw-transcript .fas { font-size: 13px; }
.bw-transcript-label { display: inline; } /* icon-only in compact */

.bw-audio { margin-top: var(--pm-space-s); }
.bw-audio audio { width: 100%; }

/* Transcript drawer — plain surface, no left bar. */
.bw-transcript-panel {
  margin-top: var(--pm-space-s); padding: var(--pm-space-ms);
  background: var(--pm-surface-secondary); border: 1px solid var(--pm-border-card);
  border-radius: var(--pm-radius-m);
  font: var(--pm-body-s); color: var(--pm-fg-2);
  max-height: 11rem; overflow-y: auto; white-space: pre-line;
}

/* ── Divider ─────────────────────────────────────────────────────────────── */
.bw-hr { height: 1px; border: 0; background: var(--pm-border-card); margin: var(--pm-space-ms) 0; }

/* ── List ────────────────────────────────────────────────────────────────── */
.bw-list-label {
  font: var(--pm-eyebrow); letter-spacing: .08em; text-transform: uppercase;
  color: var(--pm-fg-3); margin: 0 0 var(--pm-space-s);
}
.bw-rows { display: flex; flex-direction: column; gap: var(--pm-space-s); }

.bw-row {
  display: flex; align-items: center; gap: var(--pm-space-ms);
  padding: var(--pm-space-s) var(--pm-space-ms);
  border-radius: var(--pm-radius-m);
  border: 1px solid var(--pm-border-card); background: var(--pm-surface);
  text-align: left; width: 100%; cursor: pointer;
  transition: background var(--pm-duration-fast) var(--pm-ease-standard),
              border-color var(--pm-duration-fast) var(--pm-ease-standard);
}
.bw-row:hover { background: var(--pm-surface-secondary); }
.bw-row:focus-visible { outline: none; box-shadow: var(--pm-focus-ring); }

/* Current / loaded-in-player — 6% fill + 35% border + lift. No left bar. */
.bw-row.bw-current {
  background: color-mix(in srgb, var(--pm-primary) 6%, transparent);
  border-color: color-mix(in srgb, var(--pm-primary) 35%, transparent);
  box-shadow: var(--pm-shadow-card);
}

/* Preparing — recede, don't cage. */
.bw-row.bw-preparing { cursor: default; opacity: .85; }
.bw-row.bw-preparing:hover { background: var(--pm-surface); }

.bw-glyph {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: var(--pm-radius-m);
  display: inline-flex; align-items: center; justify-content: center;
}
.bw-glyph .fas { font-size: 14px; }

.bw-body { min-width: 0; flex: 1; }
.bw-name {
  display: block;
  font: var(--pm-body-m); font-weight: 600; color: var(--pm-fg-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bw-meta {
  display: block;
  font: var(--pm-body-s); color: var(--pm-fg-2); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bw-sep { color: var(--pm-fg-3); margin: 0 var(--pm-space-xs); }
.bw-prepared { color: var(--pm-primary-light); font-weight: 600; font-variant-numeric: tabular-nums; }

.bw-right {
  flex-shrink: 0; display: flex; flex-direction: column;
  align-items: flex-end; gap: var(--pm-space-xxs);
}
.bw-day {
  display: block; /* shown on non-current rows in both full and compact */
  font-family: var(--pm-font-mono); font-size: 11px; line-height: 1;
  color: var(--pm-fg-2); font-variant-numeric: tabular-nums;
}
.bw-row.bw-current .bw-day { display: none; } /* equalizer takes its place */
.bw-status {
  font: var(--pm-label-s); font-weight: 700; font-size: 8px; letter-spacing: .06em;
  text-transform: uppercase; padding: var(--pm-space-xxs) var(--pm-space-xs);
  border-radius: var(--pm-radius-pill);
  background: var(--pm-surface-warning); color: var(--pm-warning-dark);
}
.bw-status-full { display: inline; }
.bw-status-compact { display: none; }

/* Equalizer — current-row cue. */
.bw-eq { display: none; align-items: flex-end; gap: 2px; height: 12px; }
.bw-row.bw-current .bw-eq { display: inline-flex; }
.bw-eq i {
  width: 2px; background: var(--pm-primary-light); border-radius: 1px;
  transform-origin: bottom; /* static bars at rest; animated only while playing */
}
.bw-eq i:nth-child(1) { height: 5px; animation-delay: 0ms; }
.bw-eq i:nth-child(2) { height: 11px; animation-delay: 150ms; }
.bw-eq i:nth-child(3) { height: 7px; animation-delay: 300ms; }
.bw-row.bw-current.is-playing .bw-eq i { animation: bw-eq 900ms ease-in-out infinite; }
@keyframes bw-eq { 0%, 100% { transform: scaleY(.5); } 50% { transform: scaleY(1); } }

@media (prefers-reduced-motion: reduce) {
  .bw-play { transition: none; }
  .bw-play:hover { transform: none; }
  .bw-row.bw-current.is-playing .bw-eq i { animation: none; }
  .bw-eq i { transform: scaleY(1); }
}

/* ── Wide (full-width widget) — kicker, row meta line, transcript label ────── */
@container (min-width: 26rem) {
  .bw-play { width: 52px; height: 52px; }
  .bw-play .fas { font-size: 20px; }
  .bw-kicker { display: block; }
  .bw-headline { font: var(--pm-title-l); }
  .bw-scrub { margin-top: var(--pm-space-m); }
  .bw-transport { margin-top: var(--pm-space-ms); }
  .bw-hr { margin: var(--pm-space-m) 0; }
}

/* ── Narrow (half-width widget) — denser rows, day rail, icon-only transcript ─ */
@container (max-width: 25.999rem) {
  .bw-row { padding: var(--pm-space-s); }
  .bw-glyph { width: 26px; height: 26px; }
  .bw-glyph .fas { font-size: 13px; }
  .bw-name { font: var(--pm-title-s); }
  .bw-meta-full { display: none; }
  .bw-transcript-label { display: none; }
  .bw-status-full { display: none; }
  .bw-status-compact { display: inline; }
}
