/* ============================================================================
   NextShot — components
   styles/components.css

   Load order: tokens.css -> base.css -> components.css

   Every reusable primitive in the app. Built only from var(--ns-*).
   No literal hex, no literal radius, no literal shadow, no literal spacing.
   No rotation. No multi-value or percentage radii. No hard offset shadows.

   Contents
     0.  wash bridge (data-wash)
     1.  nav bar + large title
     2.  tab bar
     3.  card + grouped list
     4.  sheet + scrim
     5.  button
     6.  chip
     7.  segmented control
     8.  field
     9.  toast
     10. badge + score dot
     11. empty state
     12. photo
     13. icon
     14. lottie
   ========================================================================== */

/* ==========================================================================
   0. WASH BRIDGE
   One place where a score 0–5 becomes a fill and a legible ink colour.
   Put data-wash="N" on any element and it publishes two locals:
     --ns-w      the wash fill for step N
     --ns-w-ink  the correct numeral colour for step N
   The lo/hi split at 5 is a hard rule and holds in both themes, because the
   two label tokens invert with the ramp. Never substitute --ns-label or
   --ns-label-inverse on a wash.
   ========================================================================== */

[data-wash] {
  --ns-w: var(--ns-wash-0);
  --ns-w-ink: var(--ns-label-tertiary);
}

[data-wash="0"] { --ns-w: var(--ns-wash-0); --ns-w-ink: var(--ns-wash-label-lo); }
[data-wash="1"] { --ns-w: var(--ns-wash-1); --ns-w-ink: var(--ns-wash-label-lo); }
[data-wash="2"] { --ns-w: var(--ns-wash-2); --ns-w-ink: var(--ns-wash-label-lo); }
[data-wash="3"] { --ns-w: var(--ns-wash-3); --ns-w-ink: var(--ns-wash-label-hi); }
[data-wash="4"] { --ns-w: var(--ns-wash-4); --ns-w-ink: var(--ns-wash-label-hi); }
[data-wash="5"] { --ns-w: var(--ns-wash-5); --ns-w-ink: var(--ns-wash-label-hi); }

/* Paint helper: fill + ink + the faint containment edge a wash plate needs
   when it sits on a pale ground. Drop --edge when it sits on sunken. */
.ns-wash {
  background: var(--ns-w);
  color: var(--ns-w-ink);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-wash-edge);
}

.ns-wash--noedge {
  box-shadow: none;
}

/* ==========================================================================
   1. NAV BAR + LARGE TITLE
   Transparent at rest so the large title reads as part of the page. On
   scroll, app.js adds --scrolled: the material fades up, the hairline
   appears, and the compact title cross-fades in as .ns-largetitle fades out.

   Layout is three slots: __lead (left), __title (optically centred and
   pointer-transparent), __trail (right). Both slot wrappers are required
   even when empty, so the centre stays true.
   ========================================================================== */

.ns-navbar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--ns-z-navbar);
  height: var(--ns-navbar-total);
  padding-top: var(--ns-safe-top);
  padding-right: calc(var(--ns-gutter) + var(--ns-safe-right));
  padding-left: calc(var(--ns-gutter) + var(--ns-safe-left));
  display: flex;
  align-items: center;
  gap: var(--ns-sp-2);
  background: transparent;
  transition:
    background-color var(--ns-dur-base) var(--ns-ease-standard),
    backdrop-filter var(--ns-dur-base) var(--ns-ease-standard);
}

.ns-navbar--scrolled,
.ns-navbar--solid {
  background: var(--ns-material-regular);
  backdrop-filter: var(--ns-blur-regular);
  -webkit-backdrop-filter: var(--ns-blur-regular);
}

/* Soft scroll-edge fade under the glass bar — keeps content from colliding
   with chrome the way iOS soft edge effects do. */
.ns-navbar--scrolled::after,
.ns-navbar--solid::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: var(--ns-edge-fade-h);
  background: linear-gradient(
    to bottom,
    var(--ns-material-regular),
    transparent
  );
  pointer-events: none;
  z-index: -1;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-navbar--scrolled,
  .ns-navbar--solid {
    background: var(--ns-material-opaque);
  }
}

.ns-navbar__lead,
.ns-navbar__trail {
  display: flex;
  align-items: center;
  gap: var(--ns-sp-1);
  min-width: 0;
  flex: 0 0 auto;
}

.ns-navbar__trail {
  margin-left: auto;
}

/* Optically centred over the whole bar, independent of slot widths. */
.ns-navbar__title {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: var(--ns-safe-top);
  height: var(--ns-navbar-h);
  display: flex;
  align-items: center;
  max-width: 60%;
  font: var(--ns-type-headline);
  letter-spacing: var(--ns-tracking-headline);
  color: var(--ns-label);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--ns-dur-fast) var(--ns-ease-standard);
}

/* On a large-title screen the compact title only appears once collapsed.
   On a pushed screen (frames) add --solid to the bar and --static here. */
.ns-navbar--scrolled .ns-navbar__title,
.ns-navbar__title--static {
  opacity: 1;
}

/* Back = bare tint chevron (Apple Notes / Stoic / Depop).
   Trail actions = circular frosted glass (Craft / Hypelist / Bevel). */
.ns-navbar__back,
.ns-navbar__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ns-sp-1);
  min-width: var(--ns-hit-min);
  min-height: var(--ns-hit-min);
  padding-inline: var(--ns-sp-1);
  border: 0;
  border-radius: var(--ns-r-full);
  color: var(--ns-tint);
  font: var(--ns-type-callout);
  letter-spacing: var(--ns-tracking-callout);
  background: transparent;
  box-shadow: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  cursor: pointer;
}

.ns-navbar__action {
  width: var(--ns-hit-min);
  height: var(--ns-hit-min);
  padding: 0;
  background: var(--ns-material-thin);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-separator),
    var(--ns-shadow-1);
  transform: scale(1);
  transition:
    transform var(--ns-dur-base) var(--ns-ease-spring),
    background-color var(--ns-dur-fast) var(--ns-ease-standard),
    opacity var(--ns-dur-fast) var(--ns-ease-standard);
}

.ns-navbar__action .ns-icon {
  width: var(--ns-icon-md);
  height: var(--ns-icon-md);
  color: inherit;
  pointer-events: none;
}

.ns-navbar__action svg {
  width: 100%;
  height: 100%;
  display: block;
}

.ns-navbar__action svg[data-family="hero"]:not([data-solid="true"]) {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-navbar__action {
    background: var(--ns-fill-3);
  }
}

/* Chevron hugs the leading edge; label sits tight like UINavigationBar. */
.ns-navbar__back {
  justify-content: flex-start;
  gap: 2px;
  margin-left: calc(var(--ns-sp-2) * -1);
  padding-inline-start: var(--ns-sp-0);
  padding-inline-end: var(--ns-sp-2);
  transition: opacity var(--ns-dur-fast) var(--ns-ease-standard);
}

.ns-navbar__back .ns-icon {
  width: var(--ns-icon-sm);
  height: var(--ns-icon-sm);
  /* Optical: SF-style chevron sits slightly into the gutter. */
  margin-inline-start: -2px;
}

.ns-navbar__backLabel {
  max-width: 42vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-navbar__back:active {
  opacity: var(--ns-press-opacity);
}

.ns-navbar__action:active {
  transform: scale(var(--ns-press-scale));
  background: var(--ns-material-regular);
  transition-duration: var(--ns-dur-instant);
}

.ns-navbar__action:disabled,
.ns-navbar__action[aria-disabled="true"],
.ns-navbar__back:disabled,
.ns-navbar__back[aria-disabled="true"] {
  opacity: var(--ns-disabled-opacity);
  pointer-events: none;
}

/* Bottom hairline. An element, not a border, so it can fade independently
   of the material. Sits over the blur, which is why it is an alpha ink. */
.ns-navbar__sep {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--ns-hairline);
  background: var(--ns-separator);
  opacity: 0;
  transition: opacity var(--ns-dur-base) var(--ns-ease-standard);
  pointer-events: none;
}

.ns-navbar--scrolled .ns-navbar__sep,
.ns-navbar--solid .ns-navbar__sep {
  opacity: 1;
}

/* --- large title ---------------------------------------------------------
   Lives in the scroll flow as the first child of .ns-screen, directly under
   the compact bar. System UI display — clean, never script. */

.ns-largetitle {
  display: flex;
  flex-direction: column;
  gap: var(--ns-sp-1);
  min-height: var(--ns-navbar-large-h);
  padding-top: var(--ns-sp-2);
  padding-bottom: var(--ns-sp-4);
  transform-origin: left center;
  transition:
    opacity var(--ns-dur-fast) var(--ns-ease-standard),
    transform var(--ns-dur-base) var(--ns-ease-standard);
}

.ns-largetitle__text {
  font: var(--ns-type-display-xl);
  letter-spacing: var(--ns-tracking-display);
  color: var(--ns-label);
  font-style: normal;
}

.ns-largetitle__sub {
  font: var(--ns-type-subhead);
  letter-spacing: var(--ns-tracking-subhead);
  color: var(--ns-label-secondary);
}

/* app.js toggles this at the same scroll threshold as --scrolled. Opacity
   and a small lift only — no scale ramp, no rotation. */
.ns-largetitle--hidden {
  opacity: 0;
  transform: translate3d(0, calc(var(--ns-sp-2) * -1), 0);
}

/* ==========================================================================
   2. TAB BAR
   Fixed flush bottom bar — edge-to-edge over safe-area (native iOS dock).
   Center capture is a solid + disc slightly raised so it stays readable.
   ========================================================================== */

.ns-tabbar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--ns-z-tabbar);
  height: calc(var(--ns-tabbar-h) + var(--ns-safe-bottom));
  padding: 0 var(--ns-sp-2) var(--ns-safe-bottom);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0;
  border-radius: 0;
  overflow: visible; /* raised + clears the bar top edge */
  background: var(--ns-material-thick);
  backdrop-filter: var(--ns-blur-thick);
  -webkit-backdrop-filter: var(--ns-blur-thick);
  box-shadow: inset 0 var(--ns-hairline) 0 var(--ns-separator);
  transition: transform var(--ns-dur-slow) var(--ns-ease-spring),
              opacity var(--ns-dur-base) var(--ns-ease-standard);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-tabbar {
    background: var(--ns-material-opaque);
  }
}

.ns-tabbar--hidden {
  transform: translate3d(0, 100%, 0);
  opacity: 0;
  pointer-events: none;
}

.ns-tabbar__item {
  position: relative;
  flex: 1 1 0%;
  min-width: 0;
  height: var(--ns-tabbar-h);
  min-height: var(--ns-tabbar-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0;
  color: var(--ns-label-secondary);
  background: transparent;
  transition:
    color var(--ns-dur-fast) var(--ns-ease-standard),
    transform var(--ns-dur-base) var(--ns-ease-spring);
}

/* Ensure 44pt hit even if the visual column is narrower. */
.ns-tabbar__item::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: var(--ns-hit-min);
  transform: translateY(-50%);
}

.ns-tabbar__item:active {
  opacity: var(--ns-press-opacity);
  transform: scale(var(--ns-press-scale));
}

.ns-tabbar__item--active,
.ns-tabbar__item[aria-current="page"] {
  color: var(--ns-tint);
}

.ns-tabbar__item--active .ns-tabbar__icon,
.ns-tabbar__item[aria-current="page"] .ns-tabbar__icon {
  animation: ns-tab-select var(--ns-dur-base) var(--ns-ease-spring) both;
}

@keyframes ns-tab-select {
  0% { transform: scale(0.86); }
  60% { transform: scale(1.08); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .ns-tabbar__item--active .ns-tabbar__icon,
  .ns-tabbar__item[aria-current="page"] .ns-tabbar__icon {
    animation: none;
  }
}

.ns-tabbar__icon {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: inherit;
}

.ns-tabbar__label {
  font: var(--ns-weight-ui-medium) 10px/12px var(--ns-font-ui);
  letter-spacing: -0.01em;
  color: inherit;
  white-space: nowrap;
  font-style: normal;
}

/* --- primary capture — solid tint disc, slightly raised on the fixed bar --- */

.ns-tabbar__log {
  --ns-tab-log-size: var(--ns-tabbar-log-size);
  position: relative;
  flex: 0 0 auto;
  align-self: flex-start;
  width: var(--ns-tab-log-size);
  height: var(--ns-tab-log-size);
  margin-inline: var(--ns-sp-3);
  /* Center in the 49px band, then lift so + clears the top hairline. */
  margin-top: calc(
    (var(--ns-tabbar-h) - var(--ns-tab-log-size)) / 2 - var(--ns-tabbar-log-lift)
  );
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--ns-r-full);
  background: var(--ns-tint);
  color: var(--ns-on-tint);
  box-shadow: var(--ns-shadow-2);
  transition:
    transform var(--ns-dur-base) var(--ns-ease-spring),
    background-color var(--ns-dur-fast) var(--ns-ease-standard),
    box-shadow var(--ns-dur-base) var(--ns-ease-standard);
}

.ns-tabbar__logRing {
  display: none;
}

/* Visual disc can be ~50px; extend the tap target to at least 44pt. */
.ns-tabbar__log::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: max(var(--ns-hit-min), var(--ns-tab-log-size));
  height: max(var(--ns-hit-min), var(--ns-tab-log-size));
  transform: translate(-50%, -50%);
}

.ns-tabbar__logIcon {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: inherit;
  transition: transform var(--ns-dur-base) var(--ns-ease-spring);
}

.ns-tabbar__log:active {
  transform: scale(var(--ns-press-scale));
  background: var(--ns-tint-pressed);
  transition-duration: var(--ns-dur-instant);
}

.ns-tabbar__log:active .ns-tabbar__logIcon {
  transform: scale(0.9);
  transition-duration: var(--ns-dur-instant);
}

.ns-tabbar__log svg {
  width: 22px;
  height: 22px;
  display: block;
}

.ns-tabbar__icon svg {
  width: 24px;
  height: 24px;
  display: block;
}

/* Heroicons in the dock — paint must not inherit a parent fill:none. */
.ns-tabbar__icon svg[data-family="hero"]:not([data-solid="true"]) {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

.ns-tabbar__icon svg[data-solid="true"],
.ns-tabbar__icon svg[data-solid="true"] path {
  fill: currentColor;
  stroke: none;
}

@media (prefers-reduced-motion: reduce) {
  .ns-tabbar__item,
  .ns-tabbar__log,
  .ns-tabbar__logIcon {
    transition: none;
  }
}

/* ==========================================================================
   3. CARD + GROUPED LIST
   Card: a single object on a screen. Group: a list section of rows.
   Both step exactly one rung up the surface ladder from their ground.
   ========================================================================== */

.ns-card {
  position: relative;
  background: var(--ns-surface);
  border-radius: var(--ns-r-card);
  box-shadow: var(--ns-shadow-1);
  padding: var(--ns-sp-4);
  color: var(--ns-label);
}

@supports (corner-shape: squircle) {
  .ns-card,
  .ns-group,
  .ns-sheet,
  .ns-empty__art,
  .ns-photo {
    corner-shape: var(--ns-corner-shape);
  }
}

/* Media-first card: no padding, contents clip to the radius. */
.ns-card--flush {
  padding: var(--ns-sp-0);
  overflow: hidden;
}

/* A well rather than a plate — stats troughs, photo placeholders. */
.ns-card--sunken {
  background: var(--ns-surface-sunken);
  box-shadow: none;
}

/* Raised: sticky headers, the card being dragged, the hero stat. */
.ns-card--raised {
  box-shadow: var(--ns-shadow-2);
}

/* Selected / earned state — tint ground, not a tint border. */
.ns-card--selected {
  background: var(--ns-tint-wash);
}

/* Dashed outline for a locked or not-yet-filled slot. */
.ns-card--outline {
  background: transparent;
  box-shadow: none;
  border: var(--ns-hairline) dashed var(--ns-separator-strong);
  color: var(--ns-label-tertiary);
}

.ns-card--tight { padding: var(--ns-sp-3); }
.ns-card--roomy { padding: var(--ns-sp-5); }

/* --- group --------------------------------------------------------------- */

.ns-group {
  background: var(--ns-surface);
  border-radius: var(--ns-r-card);
  box-shadow: var(--ns-shadow-1);
  overflow: hidden;
}

/* Uppercase micro-label above a group. Sits on the screen ground, not on
   the group, so it is indented by the row padding, not the gutter. */
.ns-group__header {
  font: var(--ns-type-section);
  letter-spacing: var(--ns-tracking-section);
  text-transform: uppercase;
  margin-right: -0.14em;
  color: var(--ns-label-secondary);
  padding: var(--ns-sp-0) var(--ns-sp-4) var(--ns-sp-2);
}

.ns-group__footer {
  font: var(--ns-type-caption);
  letter-spacing: var(--ns-tracking-caption);
  color: var(--ns-label-secondary);
  padding: var(--ns-sp-2) var(--ns-sp-4) var(--ns-sp-0);
}

.ns-group__row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--ns-sp-3);
  min-height: var(--ns-hit-min);
  padding: var(--ns-sp-3) var(--ns-sp-4);
  color: var(--ns-label);
  font: var(--ns-type-body);
  letter-spacing: var(--ns-tracking-body);
  width: 100%;
  background: transparent;
  transition: background-color var(--ns-dur-fast) var(--ns-ease-standard);
}

/* Tappable rows highlight rather than scale — scaling a row inside a clipped
   group shears the separator. */
.ns-group__row--tappable:active {
  background: var(--ns-fill-4);
}

.ns-group__row--tall {
  align-items: flex-start;
  padding-block: var(--ns-sp-4);
}

/* Inset hairline between rows. Rendered as its own element so the inset is
   explicit and the last row can simply omit it. */
.ns-group__sep {
  height: var(--ns-hairline);
  background: var(--ns-separator);
  margin-left: var(--ns-sp-4);
}

/* Full-bleed variant for a section break inside a group. */
.ns-group__sep--full {
  margin-left: var(--ns-sp-0);
  background: var(--ns-separator-strong);
}

/* ==========================================================================
   4. SHEET + SCRIM
   A real iOS sheet: rounded top only, grabber, spring presentation,
   drag-to-dismiss driven by --ns-sheet-drag, upward-casting shadow.

   Presentation protocol for app.js
     1. append .ns-sheet (and .ns-scrim) into .ns-layer--sheet
     2. next frame, add .is-presented to both
     3. on drag: add .is-dragging and set --ns-sheet-drag to a positive px
     4. on release past threshold: remove .is-presented, remove after
        --ns-dur-slow
   ========================================================================== */

.ns-scrim {
  position: absolute;
  inset: 0;
  background: var(--ns-scrim);
  opacity: 0;
  transition: opacity var(--ns-dur-base) var(--ns-ease-standard);
  cursor: pointer;
}

.ns-scrim.is-presented {
  opacity: 1;
}

.ns-sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  /* 100% is the app frame, which on desktop is shorter than 100dvh. Clamping
     against both keeps a large sheet inside the frame instead of under it. */
  max-height: min(var(--ns-sheet-max-h), 100%);
  background: var(--ns-surface-sheet);
  border-radius: var(--ns-r-sheet) var(--ns-r-sheet) 0 0;
  box-shadow: var(--ns-shadow-sheet);
  color: var(--ns-label);
  transform: translate3d(0, 100%, 0);
  transition: transform var(--ns-dur-sheet) var(--ns-ease-spring);
  will-change: transform;
  touch-action: none;
}

.ns-sheet.is-presented {
  transform: translate3d(0, var(--ns-sheet-drag, 0px), 0);
}

/* iOS “scale behind” while a sheet is up — app chrome sits UNDER the scrim
   (tabbar z < sheet z). Dock slides away so it never paints over the card. */
.ns-app.is-sheet-open .ns-scroll,
.ns-app.is-sheet-open .ns-navbar {
  transform: scale(0.96);
  transform-origin: center top;
  border-radius: var(--ns-r-xl);
  transition:
    transform var(--ns-dur-sheet) var(--ns-ease-spring),
    border-radius var(--ns-dur-sheet) var(--ns-ease-spring);
  overflow: hidden;
}

.ns-app.is-sheet-open .ns-tabbar {
  transform: translate3d(0, 100%, 0);
  opacity: 0;
  pointer-events: none;
  transition:
    transform var(--ns-dur-sheet) var(--ns-ease-spring),
    opacity var(--ns-dur-base) var(--ns-ease-standard);
}

.ns-app:not(.is-sheet-open) .ns-scroll,
.ns-app:not(.is-sheet-open) .ns-navbar {
  transition:
    transform var(--ns-dur-sheet) var(--ns-ease-spring),
    border-radius var(--ns-dur-sheet) var(--ns-ease-spring);
}

.ns-app:not(.is-sheet-open) .ns-tabbar {
  transition:
    transform var(--ns-dur-sheet) var(--ns-ease-spring),
    opacity var(--ns-dur-base) var(--ns-ease-standard);
}

@media (prefers-reduced-motion: reduce) {
  .ns-app.is-sheet-open .ns-scroll,
  .ns-app.is-sheet-open .ns-navbar {
    transform: none;
    border-radius: 0;
  }

  .ns-app.is-sheet-open .ns-tabbar {
    transform: translate3d(0, 100%, 0);
    opacity: 0;
  }
}

/* While a finger is down the sheet tracks 1:1 — no transition. */
.ns-sheet.is-dragging {
  transition: none;
}

/* Detents. --medium is the detail sheet, --large the composer. */
.ns-sheet--medium {
  height: min(var(--ns-sheet-medium-h), 100%);
}

.ns-sheet--large {
  height: min(var(--ns-sheet-max-h), 100%);
}

/* Centered floating card — achievement / receipt (Deepstash / Ahead pattern). */
.ns-sheet--float {
  left: calc(var(--ns-gutter) + var(--ns-safe-left));
  right: calc(var(--ns-gutter) + var(--ns-safe-right));
  top: 50%;
  bottom: auto;
  height: auto;
  max-height: min(78dvh, calc(100% - var(--ns-safe-top) - var(--ns-safe-bottom) - var(--ns-sp-10)));
  margin: 0 auto;
  max-width: 22rem;
  border-radius: var(--ns-r-2xl);
  box-shadow: var(--ns-shadow-4);
  transform: translate3d(0, -42%, 0) scale(0.94);
  opacity: 0;
  transition:
    transform var(--ns-dur-sheet) var(--ns-ease-spring),
    opacity var(--ns-dur-base) var(--ns-ease-decel);
}

.ns-sheet--float.is-presented {
  transform: translate3d(0, -50%, 0) scale(1);
  opacity: 1;
}

.ns-sheet--float.is-dragging {
  transition: none;
}

.ns-sheet--float .ns-sheet__grabber {
  display: none;
}

@supports (corner-shape: squircle) {
  .ns-sheet--float {
    corner-shape: var(--ns-corner-shape);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ns-sheet--float,
  .ns-sheet--float.is-presented {
    transition: opacity var(--ns-dur-fast) var(--ns-ease-standard);
    transform: translate3d(0, -50%, 0);
  }
}

/* --- grabber -------------------------------------------------------------
   Its own 44pt drag surface so the whole header area is grabbable. */

.ns-sheet__grabber {
  flex: none;
  height: var(--ns-hit-min);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: var(--ns-grabber-top);
  cursor: grab;
  touch-action: none;
}

.ns-sheet__grabber::before {
  content: "";
  display: block;
  width: var(--ns-grabber-w);
  height: var(--ns-grabber-h);
  border-radius: var(--ns-r-full);
  background: var(--ns-grabber-color);
}

/* --- header --------------------------------------------------------------
   Pulled up into the grabber's 44pt drag band so the first line of content
   lands at --ns-sheet-content-top from the sheet edge, rather than stacking
   two full-height rows. */

.ns-sheet__header {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--ns-sp-3);
  min-height: var(--ns-hit-min);
  padding: var(--ns-sp-0) var(--ns-gutter) var(--ns-sp-3);
  margin-top: calc(var(--ns-sheet-content-top) - var(--ns-hit-min));
}

.ns-sheet__title {
  font: var(--ns-type-display-sm);
  letter-spacing: var(--ns-tracking-display);
  color: var(--ns-label);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-sheet__subtitle {
  font: var(--ns-type-footnote);
  letter-spacing: var(--ns-tracking-footnote);
  color: var(--ns-label-secondary);
}

/* Sheet chrome — one pattern: grabber + circular glass close + optional Done.
   Lead close is glass; trail Done is tint when enabled (Fitness / composer). */
.ns-sheet__lead,
.ns-sheet__trail {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--ns-sp-1);
  min-width: var(--ns-hit-min);
}

.ns-sheet__trail {
  margin-left: auto;
  justify-content: flex-end;
}

.ns-sheet__heading {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ns-sp-hair);
  text-align: center;
}

.ns-sheet__action {
  flex: none;
}

.ns-sheet__action.ns-btn--icon,
.ns-sheet__close,
.ns-sheet__done {
  width: var(--ns-hit-min);
  height: var(--ns-hit-min);
  padding: 0;
}

.ns-sheet__close {
  /* Circular glass close — composer + detail. */
  background: var(--ns-material-thin);
  color: var(--ns-tint);
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-separator),
    var(--ns-shadow-1);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
}

.ns-sheet__done {
  background: var(--ns-tint);
  color: var(--ns-on-tint);
  box-shadow: var(--ns-shadow-1);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.ns-sheet__done:disabled,
.ns-sheet__done[aria-disabled="true"] {
  background: var(--ns-fill-3);
  color: var(--ns-label-quaternary);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-separator);
  opacity: 1;
}

/* Legacy trailing slot (detail close before lead/trail split). */
.ns-sheet__action.ns-sheet__close,
.ns-sheet__header > .ns-sheet__close:last-child {
  margin-left: auto;
}

.ns-sheet__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: var(--ns-sp-0) var(--ns-gutter) calc(var(--ns-safe-bottom) + var(--ns-sp-6));
  /* Content inside a sheet scrolls; the sheet itself only drags from the
     grabber and header, which is why touch-action is restored here. */
  touch-action: pan-y;
}

.ns-sheet__body::-webkit-scrollbar {
  width: 0;
  display: none;
}

/* Pinned action row at the foot of a sheet (the save button). */
.ns-sheet__footer {
  flex: none;
  padding: var(--ns-sp-3) var(--ns-gutter) calc(var(--ns-safe-bottom) + var(--ns-sp-3));
  background: var(--ns-material-thick);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
  box-shadow: var(--ns-shadow-2);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-sheet__footer {
    background: var(--ns-surface-sheet);
  }
}

/* ==========================================================================
   5. BUTTON — iOS glass materials
   Frosted secondary / icon controls; solid tint only for primary CTAs
   (native filled UIButton). Pills by default.
   ========================================================================== */

.ns-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ns-sp-2);
  min-height: var(--ns-hit-min);
  min-width: var(--ns-hit-min);
  padding: var(--ns-sp-2) var(--ns-sp-5);
  border-radius: var(--ns-r-full);
  font: var(--ns-type-headline);
  letter-spacing: var(--ns-tracking-headline);
  color: var(--ns-label);
  background: var(--ns-material-thin);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-separator),
    var(--ns-shadow-1);
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  white-space: nowrap;
  transform: scale(1);
  transition:
    transform var(--ns-dur-base) var(--ns-ease-spring),
    background-color var(--ns-dur-fast) var(--ns-ease-standard),
    box-shadow var(--ns-dur-fast) var(--ns-ease-standard),
    opacity var(--ns-dur-fast) var(--ns-ease-standard);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-btn {
    background: var(--ns-fill-2);
  }
}

.ns-btn:active {
  transform: scale(var(--ns-press-scale));
  transition-duration: var(--ns-dur-instant);
  background: var(--ns-material-regular);
}

.ns-btn:focus-visible {
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-separator),
    var(--ns-focus-ring);
}

.ns-btn:disabled,
.ns-btn[aria-disabled="true"] {
  opacity: var(--ns-disabled-opacity);
  pointer-events: none;
  cursor: default;
}

.ns-btn svg {
  width: var(--ns-icon-md);
  height: var(--ns-icon-md);
  display: block;
  flex: none;
  stroke-width: var(--ns-stroke-icon);
}

/* --- primary: solid tint CTA (iOS filled UIButton — no glass, no hatch) -- */
.ns-btn--primary {
  background: var(--ns-tint);
  color: var(--ns-on-tint);
  box-shadow: var(--ns-shadow-tint);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.ns-btn--primary:active {
  background: var(--ns-tint-pressed);
  box-shadow: var(--ns-shadow-1);
}

.ns-btn--primary:focus-visible {
  box-shadow: var(--ns-shadow-tint), var(--ns-focus-ring);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-btn--primary {
    background: var(--ns-tint);
  }
}

/* --- secondary: glass + tint label --------------------------------------- */
.ns-btn--secondary {
  background: var(--ns-material-thin);
  color: var(--ns-tint);
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-separator),
    var(--ns-shadow-1);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
}

.ns-btn--secondary:active {
  background: var(--ns-tint-subtle);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-btn--secondary {
    background: var(--ns-fill-2);
  }
}

/* --- plain: tint text, glass wash on press ------------------------------- */
.ns-btn--plain {
  background: transparent;
  color: var(--ns-tint);
  padding-inline: var(--ns-sp-2);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.ns-btn--plain:active {
  transform: scale(1);
  opacity: var(--ns-press-opacity);
  background: var(--ns-tint-subtle);
  box-shadow: none;
}

/* --- icon: circular glass ------------------------------------------------ */
.ns-btn--icon {
  width: var(--ns-hit-min);
  height: var(--ns-hit-min);
  padding: var(--ns-sp-0);
  border-radius: var(--ns-r-full);
  background: var(--ns-material-thin);
  color: var(--ns-tint);
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-separator),
    var(--ns-shadow-1);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
}

.ns-btn--icon:active {
  background: var(--ns-material-regular);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-btn--icon {
    background: var(--ns-fill-3);
  }
}

/* --- stepper: bare tint chevrons for month/period prev-next --------------
   Bevel date sheets, Stoic week pill, iOS Calendar — never glass discs. */
.ns-btn--stepper {
  width: var(--ns-hit-min);
  height: var(--ns-hit-min);
  padding: 0;
  border-radius: var(--ns-r-full);
  background: transparent;
  color: var(--ns-tint);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.ns-btn--stepper:active {
  background: transparent;
  opacity: var(--ns-press-opacity);
  transform: scale(var(--ns-press-scale));
  box-shadow: none;
}

.ns-btn--stepper:focus-visible {
  box-shadow: var(--ns-focus-ring);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-btn--stepper {
    background: transparent;
  }
}

/* --- modifiers ----------------------------------------------------------- */
.ns-btn--pill  { border-radius: var(--ns-r-full); }
.ns-btn--full  { width: 100%; }
.ns-btn--small {
  min-height: var(--ns-hit-min);
  padding-inline: var(--ns-sp-4);
  font: var(--ns-type-subhead);
  letter-spacing: var(--ns-tracking-subhead);
}
.ns-btn--danger-quiet {
  color: var(--ns-label-secondary);
  background: var(--ns-material-thin);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-separator),
    var(--ns-shadow-1);
}

/* Busy state — the save spinner beat. Label stays put, opacity drops. */
.ns-btn--busy {
  pointer-events: none;
  opacity: var(--ns-press-opacity);
}

/* ==========================================================================
   6. CHIP — glass pills (Memuna / Craft filter chips)
   ========================================================================== */

.ns-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--ns-sp-1);
  padding: var(--ns-sp-2) var(--ns-sp-3);
  border-radius: var(--ns-r-full);
  background: var(--ns-material-thin);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-separator);
  color: var(--ns-label-secondary);
  font: var(--ns-type-footnote);
  letter-spacing: var(--ns-tracking-footnote);
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  transform: scale(1);
  transition:
    transform var(--ns-dur-base) var(--ns-ease-spring),
    background-color var(--ns-dur-fast) var(--ns-ease-standard),
    color var(--ns-dur-fast) var(--ns-ease-standard),
    box-shadow var(--ns-dur-fast) var(--ns-ease-standard);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-chip {
    background: var(--ns-fill-2);
  }
}

/* Invisible 44pt tap target centred on the chip. */
.ns-chip::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: var(--ns-hit-min);
  transform: translateY(-50%);
}

/* A chip that is not a control has no extended hit area and no press. */
.ns-chip--static::after {
  content: none;
}

.ns-chip:active {
  transform: scale(var(--ns-press-scale));
  transition-duration: var(--ns-dur-instant);
}

.ns-chip--static:active {
  transform: scale(1);
}

/* Selected chips match composer type tiles: tint wash + tint ring. */
.ns-chip--selected {
  background: var(--ns-tint-subtle);
  color: var(--ns-tint);
  box-shadow: inset 0 0 0 var(--ns-sp-hair) var(--ns-tint);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Score chip: the wash carries the meaning, so it overrides the fill. Pair
   with data-wash="0".."5". */
.ns-chip--wash {
  background: var(--ns-w);
  color: var(--ns-w-ink);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-wash-edge);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.ns-chip--tint {
  background: var(--ns-tint);
  color: var(--ns-on-tint);
  box-shadow: var(--ns-shadow-1);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.ns-chip svg {
  width: var(--ns-icon-sm);
  height: var(--ns-icon-sm);
  display: block;
  flex: none;
  stroke-width: var(--ns-stroke-icon);
}

/* Horizontal chip rail that scrolls without a visible bar. */
.ns-chiprail {
  display: flex;
  align-items: center;
  gap: var(--ns-sp-2);
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Bleed to the screen edge so the rail scrolls out of the gutter. */
  margin-inline: calc(var(--ns-gutter) * -1);
  padding-inline: var(--ns-gutter);
  padding-block: var(--ns-sp-1);
}

.ns-chiprail::-webkit-scrollbar {
  height: 0;
  display: none;
}

/* Wrapping variant for the detail sheet's chip cloud. */
.ns-chipwrap {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ns-sp-2);
}

/* ==========================================================================
   7. SEGMENTED CONTROL — iOS UISegmentedControl
   Frosted glass track; selected segment is a raised surface knob (not tint).
   ========================================================================== */

.ns-segmented {
  display: flex;
  align-items: stretch;
  gap: var(--ns-sp-hair);
  min-height: var(--ns-hit-min);
  padding: var(--ns-sp-1);
  border-radius: var(--ns-r-full);
  background: var(--ns-material-thin);
  backdrop-filter: var(--ns-blur-thin);
  -webkit-backdrop-filter: var(--ns-blur-thin);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-separator);
  user-select: none;
  -webkit-user-select: none;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-segmented {
    background: var(--ns-fill-2);
  }
}

.ns-segmented__item {
  flex: 1 1 0%;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--ns-sp-3);
  border-radius: var(--ns-r-full);
  background: transparent;
  color: var(--ns-label);
  font: var(--ns-type-subhead);
  letter-spacing: var(--ns-tracking-subhead);
  white-space: nowrap;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--ns-dur-fast) var(--ns-ease-standard),
    color var(--ns-dur-fast) var(--ns-ease-standard),
    box-shadow var(--ns-dur-fast) var(--ns-ease-standard),
    transform var(--ns-dur-base) var(--ns-ease-spring);
}

.ns-segmented__item:active {
  color: var(--ns-label);
  opacity: var(--ns-press-opacity);
}

.ns-segmented__item--active,
.ns-segmented__item[aria-selected="true"] {
  background: var(--ns-surface-elevated);
  color: var(--ns-label);
  font-weight: var(--ns-weight-ui-semibold);
  box-shadow: var(--ns-shadow-1);
  opacity: 1;
}

.ns-segmented__item:focus-visible {
  box-shadow: var(--ns-focus-ring);
}

.ns-segmented__item--active:focus-visible,
.ns-segmented__item[aria-selected="true"]:focus-visible {
  box-shadow: var(--ns-shadow-1), var(--ns-focus-ring);
}

/* ==========================================================================
   8. FIELD
   Caps micro-label over a trough. The ring lands on the trough, not on a
   naked underline, so keyboard focus is unmistakable.
   ========================================================================== */

.ns-field {
  display: flex;
  flex-direction: column;
  gap: var(--ns-sp-1);
  min-width: 0;
}

.ns-field__label {
  font: var(--ns-type-section);
  letter-spacing: var(--ns-tracking-section);
  text-transform: uppercase;
  margin-right: -0.14em;
  color: var(--ns-label-secondary);
}

.ns-field__input {
  width: 100%;
  min-height: var(--ns-hit-min);
  padding: var(--ns-sp-2) var(--ns-sp-3);
  border-radius: var(--ns-r-md);
  background: var(--ns-fill-4);
  border: var(--ns-hairline) solid var(--ns-separator);
  color: var(--ns-label);
  font: var(--ns-type-body);
  letter-spacing: var(--ns-tracking-body);
  transition:
    border-color var(--ns-dur-fast) var(--ns-ease-standard),
    background-color var(--ns-dur-fast) var(--ns-ease-standard),
    box-shadow var(--ns-dur-fast) var(--ns-ease-standard);
}

.ns-field__input::placeholder {
  color: var(--ns-placeholder);
  opacity: 1;
}

.ns-field__input:focus,
.ns-field__input:focus-visible {
  outline: none;
  background: var(--ns-fill-3);
  border-color: var(--ns-tint);
  box-shadow: var(--ns-focus-ring);
}

.ns-field__input:disabled {
  opacity: var(--ns-disabled-opacity);
}

/* Multi-line — notes. */
.ns-field__input--area {
  min-height: calc(var(--ns-hit-min) * 2);
  padding-block: var(--ns-sp-3);
  line-height: var(--ns-lh-body);
}

/* Notebook variant: a single hairline rule instead of a trough. Use for the
   composer's title line, where a boxed input reads too much like a form. */
.ns-field--underline .ns-field__input {
  background: transparent;
  border: 0;
  border-bottom: var(--ns-hairline) solid var(--ns-separator-strong);
  border-radius: 0;
  padding-inline: var(--ns-sp-0);
}

.ns-field--underline .ns-field__input:focus,
.ns-field--underline .ns-field__input:focus-visible {
  background: transparent;
  border-bottom-color: var(--ns-tint);
  /* Soft second hairline — ballpoint pressure under the caret */
  box-shadow: 0 2px 0 var(--ns-tint-subtle);
}

/* Large expressive input — composer title, system display weight. */
.ns-field__input--display {
  font: var(--ns-type-display-sm);
  letter-spacing: var(--ns-tracking-display);
  font-style: normal;
}

.ns-field__hint {
  font: var(--ns-type-caption);
  letter-spacing: var(--ns-tracking-caption);
  color: var(--ns-label-secondary);
}

/* Two fields side by side on the 8pt grid. */
.ns-fieldgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ns-sp-3);
}

/* ==========================================================================
   9. TOAST
   Sits above the tab bar, centred, single slot. Default is the elevated
   surface (system-correct). --tint is the celebratory streak toast.
   ========================================================================== */

.ns-toast {
  position: absolute;
  left: calc(var(--ns-gutter) + var(--ns-safe-left));
  right: calc(var(--ns-gutter) + var(--ns-safe-right));
  bottom: calc(var(--ns-tabbar-total) + var(--ns-sp-4));
  margin-inline: auto;
  max-width: var(--ns-screen-w);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--ns-sp-2);
  min-height: var(--ns-hit-min);
  padding: var(--ns-sp-2) var(--ns-sp-4);
  border-radius: var(--ns-r-full);
  background: var(--ns-material-thick);
  backdrop-filter: var(--ns-blur-thick);
  -webkit-backdrop-filter: var(--ns-blur-thick);
  box-shadow: var(--ns-shadow-3);
  color: var(--ns-label);
  font: var(--ns-type-callout);
  letter-spacing: var(--ns-tracking-callout);
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(0, var(--ns-sp-3), 0);
  transition:
    opacity var(--ns-dur-base) var(--ns-ease-standard),
    transform var(--ns-dur-base) var(--ns-ease-spring);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ns-toast {
    background: var(--ns-surface-elevated);
  }
}

.ns-toast.is-presented {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* On screens with no tab bar the toast drops to the safe area. */
.ns-toast--low {
  bottom: calc(var(--ns-safe-bottom) + var(--ns-sp-4));
}

/* Celebration: the streak message only. */
.ns-toast--tint {
  background: var(--ns-hatch), var(--ns-tint);
  color: var(--ns-on-tint);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.ns-toast__icon {
  width: var(--ns-icon-sm);
  height: var(--ns-icon-sm);
  display: block;
  flex: none;
  color: inherit;
}

.ns-toast__icon svg {
  width: 100%;
  height: 100%;
  display: block;
  stroke-width: var(--ns-stroke-icon);
}

.ns-toast__icon svg[data-family="hero"] {
  stroke-width: 1.5;
}

.ns-toast__icon svg[data-solid="true"] {
  fill: currentColor;
  stroke: none;
}

/* ==========================================================================
   10. BADGE + SCORE DOT
   Badge: a small textual marker. Score dot: the circular numeral plate that
   carries a wash. Both read data-wash when they need to encode a score.
   ========================================================================== */

.ns-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ns-sp-1);
  min-height: var(--ns-sp-5);
  padding: var(--ns-sp-0) var(--ns-sp-2);
  border-radius: var(--ns-r-xs);
  background: var(--ns-fill-1);
  color: var(--ns-label-secondary);
  font: var(--ns-type-caption-sm);
  letter-spacing: var(--ns-tracking-caption-sm);
  white-space: nowrap;
}

.ns-badge--wash {
  background: var(--ns-w);
  color: var(--ns-w-ink);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-wash-edge);
}

.ns-badge--tint {
  background: var(--ns-tint);
  color: var(--ns-on-tint);
}

.ns-badge--outline {
  background: transparent;
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-separator-strong);
  color: var(--ns-label-tertiary);
}

/* Pill shape for count badges. */
.ns-badge--pill {
  border-radius: var(--ns-r-full);
  padding-inline: var(--ns-sp-2);
}

/* --- score dot -----------------------------------------------------------
   Circular. Size is overridable per context via --ns-dot-size. */

.ns-score-dot {
  --ns-dot-size: var(--ns-sp-7);
  width: var(--ns-dot-size);
  height: var(--ns-dot-size);
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--ns-r-full);
  background: var(--ns-w);
  color: var(--ns-w-ink);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-wash-edge);
  font: var(--ns-type-caption);
  letter-spacing: var(--ns-tracking-caption);
  font-variant-numeric: tabular-nums;
}

.ns-score-dot--lg {
  --ns-dot-size: var(--ns-sp-10);
  font: var(--ns-type-subhead);
  letter-spacing: var(--ns-tracking-subhead);
}

.ns-score-dot--sm {
  --ns-dot-size: var(--ns-sp-5);
  font: var(--ns-type-caption-sm);
  letter-spacing: var(--ns-tracking-caption-sm);
}

/* Square variant kept for non-picker badges; picker uses circles. */
.ns-score-dot--square {
  border-radius: var(--ns-r-sm);
}

/* Selected score in the picker: full wash fill + hairline ring. */
.ns-score-dot--selected {
  background: var(--ns-w);
  color: var(--ns-w-ink);
  box-shadow:
    inset 0 0 0 var(--ns-hairline) var(--ns-wash-edge),
    0 0 0 var(--ns-hairline) var(--ns-separator-strong);
}

/* On a sunken or otherwise dark ground the containment edge is noise. */
.ns-score-dot--noedge {
  box-shadow: none;
}

/* ==========================================================================
   11. EMPTY STATE
   Centred column, generous breathing room, art that degrades to nothing if
   the image 404s (the <img> onerror handler swaps in the ink placeholder).
   ========================================================================== */

.ns-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--ns-sp-4);
  flex: 1 1 auto;
  padding: var(--ns-sp-16) var(--ns-sp-4) var(--ns-sp-12);
  text-align: center;
}

.ns-empty__art {
  width: 100%;
  max-width: calc(var(--ns-screen-w) / 2);
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--ns-r-photo);
  background: var(--ns-surface-sunken);
  box-shadow: var(--ns-shadow-1);
  margin-bottom: var(--ns-sp-2);
}

/* Wide banner art (grain-world) for journey / first-run empty states. */
.ns-empty__lottie {
  width: 100%;
  max-width: calc(var(--ns-screen-w) - var(--ns-sp-8));
  margin-bottom: var(--ns-sp-2);
  border-radius: var(--ns-r-card);
  background: var(--ns-wash-1);
  box-shadow: var(--ns-shadow-1);
  overflow: hidden;
}

@supports (corner-shape: squircle) {
  .ns-empty__lottie {
    corner-shape: var(--ns-corner-shape);
  }
}

.ns-empty__title {
  font: var(--ns-type-display-sm);
  letter-spacing: var(--ns-tracking-display);
  color: var(--ns-label);
}

.ns-empty__body {
  font: var(--ns-type-callout);
  letter-spacing: var(--ns-tracking-callout);
  color: var(--ns-label-secondary);
  max-width: 30ch;
}

.ns-empty__action {
  margin-top: var(--ns-sp-2);
}

.ns-empty__actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ns-sp-2);
  width: 100%;
  max-width: calc(var(--ns-sp-16) * 4);
}

.ns-empty__actions .ns-btn {
  width: 100%;
}

.ns-empty__actions .ns-empty__action {
  margin-top: 0;
}

/* ==========================================================================
   12. PHOTO
   Every image in the app goes through this so a missing file degrades to the
   "no photo — ink stands in" plate instead of a broken-image glyph.
   ========================================================================== */

.ns-photo {
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
  border-radius: var(--ns-r-photo);
  background: var(--ns-surface-sunken);
  aspect-ratio: 1 / 1;
}

.ns-photo--tall  { aspect-ratio: 3 / 4; }
.ns-photo--wide  { aspect-ratio: 4 / 3; }
.ns-photo--hero  { border-radius: var(--ns-r-xl); }

.ns-photo__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The ink stand-in. Shown when there is no photo, or when onerror fires and
   the handler adds --empty to the container. */
.ns-photo--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ns-label-quaternary);
  box-shadow: inset 0 0 0 var(--ns-hairline) var(--ns-separator);
}

.ns-photo--empty .ns-photo__img {
  display: none;
}

/* Hidden while the photo is present so the stand-in copy does not leak into
   the accessibility tree / innerText next to a successful <img>. Revealed
   only when .ns-photo--empty is set (no photo, or delegated onerror). */
.ns-photo__placeholder {
  display: none;
  position: absolute;
  inset: 0;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--ns-sp-2);
  padding: var(--ns-sp-4);
  text-align: center;
  font: var(--ns-type-caption);
  letter-spacing: var(--ns-tracking-caption);
  color: var(--ns-label-tertiary);
}

.ns-photo--empty .ns-photo__placeholder {
  display: flex;
}

.ns-photo__placeholder svg {
  width: var(--ns-icon-xl);
  height: var(--ns-icon-xl);
  color: var(--ns-label-quaternary);
  stroke-width: var(--ns-stroke-icon);
}

/* ==========================================================================
   13. ICON
   The hand-inked SVG set inherits currentColor and the token stroke width.
   ========================================================================== */

.ns-icon {
  display: inline-block;
  width: var(--ns-icon-md);
  height: var(--ns-icon-md);
  flex: none;
  color: inherit;
  vertical-align: middle;
}

.ns-icon svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--ns-stroke-icon);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Heroicons chrome — designed for 1.5; ink drinks keep --ns-stroke-icon. */
.ns-icon svg[data-family="hero"]:not([data-solid="true"]) {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

/* Solid marks — fill on the paths (Heroicons solid pattern). */
.ns-icon--filled svg,
.ns-icon svg[data-solid="true"] {
  fill: none;
  stroke: none;
}

.ns-icon--filled svg path,
.ns-icon svg[data-solid="true"] path {
  fill: currentColor;
  stroke: none;
}

/* Global safety net for Heroicons outside .ns-icon wrappers. */
svg[data-family="hero"][data-solid="true"] path {
  fill: currentColor;
  stroke: none;
}

.ns-icon--sm { width: var(--ns-icon-sm); height: var(--ns-icon-sm); }
.ns-icon--md { width: var(--ns-icon-md); height: var(--ns-icon-md); }
.ns-icon--lg { width: var(--ns-icon-lg); height: var(--ns-icon-lg); }
.ns-icon--xl { width: var(--ns-icon-xl); height: var(--ns-icon-xl); }

.ns-icon--tint      { color: var(--ns-tint); }
.ns-icon--secondary { color: var(--ns-label-secondary); }
.ns-icon--tertiary  { color: var(--ns-label-tertiary); }

/* ==========================================================================
   14. LOTTIE
   A responsive box for a vendored animation. Set --ns-lottie-ratio to the
   source composition's aspect ratio (coffee-cup 1/1, bean-check 1/1,
   grain-world 16/9). motion.js must pass
   rendererSettings: { preserveAspectRatio: 'xMidYMid meet' }.
   ========================================================================== */

.ns-lottie {
  --ns-lottie-ratio: 1 / 1;
  position: relative;
  width: 100%;
  max-width: 100%;
  aspect-ratio: var(--ns-lottie-ratio);
  overflow: hidden;
  pointer-events: none;
}

.ns-lottie > svg,
.ns-lottie > canvas {
  width: 100%;
  height: 100%;
  display: block;
}

.ns-lottie--wide  { --ns-lottie-ratio: 16 / 9; }
.ns-lottie--sm    { max-width: calc(var(--ns-screen-w) / 4); }
.ns-lottie--md    { max-width: calc(var(--ns-screen-w) / 2); }
.ns-lottie--inline {
  width: var(--ns-icon-xl);
  height: var(--ns-icon-xl);
  aspect-ratio: 1 / 1;
  display: inline-block;
  vertical-align: middle;
}

/* Centred hero animation, e.g. the splash mark.
   Clamp height so a 1:1 hero never overflows a short phone once copy + CTA
   are stacked (HANDOFF §2c). */
.ns-lottie--hero {
  margin-inline: auto;
  max-width: calc(var(--ns-screen-w) - var(--ns-sp-16));
  max-height: min(38dvh, calc(var(--ns-screen-w) - var(--ns-sp-16)));
}
