/* ═══════════════════════════════════════════
   READY-MADE CHARACTERS
   A full-screen pushed layer over the chats list (the catalog) and, over it,
   the community character sheet. Geometry and the level stack are the
   character-history layer's (charhistory.css); everything inside is composed
   from existing components: the catalog card (.card family, community.css),
   the sub-screen bar (.sub-bar + .large-title + .sub-bar-title), the app's
   search session, the community filter rows, the community modal
   (.modal-content, .modal-image-carousel, .scenario-pick, .similar-section)
   and the community character viewer itself (#charLightbox). Tokens only.
═══════════════════════════════════════════ */
.off-layer { position: fixed; inset: 0; z-index: 1000; pointer-events: none; }
.off-dim { position: absolute; inset: 0; background: #000; opacity: 0; pointer-events: none; }
.off-screen {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  background: var(--bg); pointer-events: auto; overflow: hidden;
  transform: translate3d(100%, 0, 0); will-change: transform;
}
.off-screen.off-in { transform: none; }
.off-scroll {
  flex: 1; min-height: 0; overflow-y: auto;
  padding-top: calc(56px + var(--tg-top, 0px));
  -webkit-overflow-scrolling: touch;
}
/* the app's bottom edge-band guard (edgeBandHit in app.js) is 84px tall and
   applies here, because the scroller carries .settings-screen: content has to
   end above it or the last row of cards is untappable. */
.off-end { height: calc(100px + var(--safe-bottom, 0px)); }

/* ── catalog head: title, search session, funnel ── */
.off-title { padding-bottom: 6px; }
/* the row outranks the grid: the funnel's reveal drops over the cards */
.off-search { position: relative; z-index: 10; margin: 4px var(--pad) 0; }
.off-search .off-shell { flex: 1; }
/* the scope placeholder outgrows a 320px field: trim it, never wrap it */
.off-shell .search-field-control { text-overflow: ellipsis; }
.off-funnel-anchor { position: relative; flex: none; margin-left: 8px; }
.off-funnel { width: 44px; height: 44px; }
/* solid stroke dimmed by opacity: a translucent stroke doubles where the
   funnel's outline meets itself (frontend.md, semi-transparent stroke icons) */
.off-funnel svg { width: 20px; height: 20px; stroke: var(--text); opacity: 0.72; }
.off-funnel.on { background: var(--accent-soft-solid); }
.off-funnel.on svg { stroke: var(--accent-text); opacity: 1; }
.off-funnel .filter-badge { top: -2px; right: -2px; }
.off-funnel .filter-badge[hidden] { display: none; }
.off-filter-reveal { top: calc(100% + 8px); right: 0; width: min(340px, calc(100vw - 32px)); }
.off-filter-reset-div[hidden], .off-filter-reset[hidden] { display: none; }
/* el.hidden loses to the component's own display (frontend.md) */
.off-shell .search-field-clear[hidden] { display: none; }
/* the sub-bar search chip grows in once the field scrolls away (publicrooms) */
.off-barchip {
  opacity: 0; transform: scale(0.86); pointer-events: none;
  transition: opacity 130ms cubic-bezier(0.4, 0, 0.2, 1), transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
}
.off-barchip.show { opacity: 1; transform: none; pointer-events: auto; }
/* fading out under a slide to the top, still the target of a second tap */
.off-barchip.landing, .glass-chip.off-totop.landing { pointer-events: auto; }
.off-barchip svg { stroke: var(--text); opacity: 0.72; }

/* ── catalog grid ── */
.off-grid { margin: 14px var(--pad) 0; }
/* the empty/error state is one box, not a grid cell */
.off-grid.off-grid-flat { display: block; }
/* The catalog card IS the community catalog card (square cover, the fixed
   166px content box, name, clamped description, tag row at the bottom); the
   data adds the title line under the name, and the layer has no author or
   stat chips. community.js clamps the description to the box. */
.off-card { width: 100%; text-align: left; padding: 0; font: inherit; color: inherit; }
/* the square takes the top of the portrait, as every /d/char/ cover does
   (style.css img[src*="/d/char/"]); the catalog family is not in that rule */
.off-card .card-image { object-position: 50% 0; }
.off-card-sub {
  font-size: 12px; font-weight: 600; color: var(--text2);
  margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex-shrink: 0;
}
.off-card .card-desc-wrap[hidden] { display: none; }
/* a search hit on an alias, not on the name: the card says why it is here */
.off-hit {
  flex: 1; min-height: 0; overflow: hidden;
  font-size: 13px; line-height: 1.4; color: var(--text3);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
}
.off-hit b { color: var(--text2); font-weight: 600; }
.off-hit[hidden] { display: none; }
/* the community card's tag row, whose values community.js writes inline
   (tagSt / tagCountSt of the plain, effect-less card) */
.off-card-tags {
  display: flex; flex-wrap: wrap; gap: 4px; margin-top: auto; padding-top: 8px;
  align-items: center; flex-shrink: 0;
}
.off-card-tag {
  background: var(--glass); color: var(--text2); border: 1px solid var(--glass-border);
  padding: 3px 10px; border-radius: var(--r-pill); font-size: 11px; font-weight: 500;
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.off-card-tag.more { color: var(--text3); display: inline-flex; align-items: center; }
.off-empty { padding: 56px var(--pad) 0; text-align: center; display: flex; flex-direction: column; align-items: center; }
.off-empty-title { font-size: 17px; font-weight: 600; color: var(--text); }
.off-empty-sub { margin-top: 6px; font-size: 14px; line-height: 1.45; color: var(--text2); }
.off-empty .btn-secondary { margin-top: 18px; width: auto; padding: 0 28px; }
/* skeletons: the catalog card's own, at the card's full height (square +
   the 166px content box), so the grid never reflows when the page lands */
.off-skel { pointer-events: none; }
.off-skel .skeleton-content { flex: none; height: 166px; }
/* the next page is asked for while this 1px line is still a screen below */
.off-sentinel { height: 1px; }
.off-more-error { display: flex; flex-direction: column; align-items: center; padding: 20px var(--pad) 0; text-align: center; }
.off-more-error .btn-secondary { margin-top: 12px; width: auto; padding: 0 28px; }
/* A card far off screen skips its own rendering: a feed of a thousand cards
   styles, lays out and paints about one screenful. The reserved size is the
   card's last measured one (auto), so the scrollbar does not jump. Before a
   card has ever rendered the guess is its real height: the card is a square
   of the column width plus the 166px content box, and the column is the
   .cards-container grid's (2 columns gap 12, 3 from 768px gap 16, 4 from
   1024px gap 20) over the screen minus --pad on both sides. Measured on the
   stand: 339 at 390, 494 at 700, 445 at 900, 399 at 1024; the desktop layout
   keeps 3 columns in the 720px content column at every width, 392.7. */
.off-card { content-visibility: auto; contain-intrinsic-size: auto calc((100vw - 2 * var(--pad) - 12px) / 2 + 166px); }
@media (min-width: 768px) {
  .off-card { contain-intrinsic-size: auto calc((100vw - 2 * var(--pad) - 32px) / 3 + 166px); }
}
@media (min-width: 1024px) {
  .off-card { contain-intrinsic-size: auto calc((100vw - 2 * var(--pad) - 60px) / 4 + 166px); }
}
html.layout-desktop .off-card { contain-intrinsic-size: auto 393px; }

/* The up arrow: the chat's scroll-down chip turned over (.scroll-bottom-btn:
   glass chip, 40px, fades and settles in), at the bottom right of the screen,
   above the selection's button while it is docked. */
.glass-chip.off-totop {
  position: absolute; right: 14px; bottom: calc(16px + var(--safe-bottom, 0px));
  width: 40px; z-index: 6; color: var(--text2);
  opacity: 0; pointer-events: none; transform: translateY(6px) scale(0.9);
  transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1),
              transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
              bottom 0.22s cubic-bezier(0.05, 0.7, 0.1, 1),
              scale 120ms ease-out;
}
.glass-chip.off-totop.show { opacity: 1; pointer-events: auto; transform: none; }
body.no-blur .glass-chip.off-totop.show:active { transform: scale(0.92); }
@media (prefers-reduced-motion: reduce) {
  .glass-chip.off-totop { transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1); transform: none; }
}

/* ── character sheet ──
   #characterModal's id-scoped rules, twinned for this instance. The sheet is a
   child of the layer (pointer-events: none) and needs its own hit-testing. */
.off-sheet { pointer-events: auto; }
/* a hero card still on its way after a tap in a chat: the loader rides over the
   tapped avatar or name (catalog.js cardPendingOn), never takes a touch */
.off-card-pending { position: fixed; z-index: 300; pointer-events: none; }
.off-sheet > .modal-content { overflow: hidden; display: flex; flex-direction: column; height: calc(90vh - var(--tg-top, 0px)); }
.off-sheet .modal-name { min-width: 0; font-size: 28px; line-height: 1.12; letter-spacing: -0.4px; font-weight: 700; margin: 0; }
.off-sheet-title { font-size: 14px; font-weight: 500; color: var(--text2); }
/* The community sheet puts its reaction tiles between this row and the
   description (.modal-actions, 20px below them). The layer has no reactions,
   so the description keeps that distance from the button row instead of
   closing up to 10px under it. */
.off-sheet .char-start-row { margin-bottom: 20px; }
/* with the described second row under it (.char-start-alt carries the 20px) */
.off-sheet .char-start-row.has-alt { margin-bottom: 10px; }
/* the community tag row (#modalTags carries this inline) */
.off-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 16px; }
.off-sheet .scenarios-section { margin-bottom: 20px; }
.off-scen-actions { display: flex; gap: 8px; }
.off-scen-actions .scenario-pick-start { flex: 1; }
/* the similar strip's card (loadSimilarCharacters writes these inline; its
   #ddd name is the full text tone dimmed, its cover top-anchored as /d/char/) */
.off-same-card { flex: 0 0 110px; cursor: pointer; display: flex; flex-direction: column; gap: 6px; }
.off-same-card img {
  width: 110px; height: 140px; object-fit: cover; object-position: 50% 0;
  border-radius: 12px; background: var(--glass); -webkit-user-drag: none;
}
.off-same-name { font-size: 12px; color: var(--text); opacity: 0.87; line-height: 1.25; max-height: 2.5em; overflow: hidden; }
/* The layer covers the nav island, so the sheet sits on the screen edge the
   way it does in community-only mode, not above an island that is not there. */
.off-layer .modal.active { padding-bottom: calc(10px + var(--safe-bottom, 0px)) !important; }

/* ── selection (N6): the cast of a group chat, chosen on the catalog itself ──
   The mode is .is-picking on the screen (catalog.js applyPickMode). Its chrome
   exists from the screen's birth and only shows: the shelf pinned under the
   sub-bar (the picks strip over the search row, which moves in from the
   feed), a filter-free band behind it, the "i" and the check on every card,
   and the one CTA docked at the bottom over its own fade. */
.off-shelf {
  position: absolute; z-index: 21; left: 0; right: 0;
  top: calc(56px + var(--tg-top, 0px));
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 150ms;
}
.is-picking .off-shelf {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.off-shelf .off-search { margin: 8px var(--pad) 0; }
/* the header grew by the strip and lost the large title: the feed starts under
   the pinned shelf (56 bar + 36 strip + 8 + 44 search) */
.is-picking .off-scroll { padding-top: calc(144px + var(--tg-top, 0px)); }
.is-picking .off-scroll > .off-title { display: none; }
/* the search chip of the bar has nothing to reveal: the field is pinned */
.is-picking .off-barchip { visibility: hidden; }
/* The band under the shelf: the edge-fade grammar (filter-free), solid where
   text chips and the field sit, dissolving below them. */
.off-pick-fade {
  position: absolute; top: 0; left: 0; right: 0; z-index: 16; pointer-events: none;
  height: calc(184px + var(--tg-top, 0px));
  background: linear-gradient(to bottom, var(--bg) calc(100% - 40px), transparent);
  opacity: 0; transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.is-picking .off-pick-fade { opacity: 1; }
/* a toast raised in the mode lands under the shelf, not over the picks */
body.off-picking .toast-stack { top: calc(var(--header-h) + 8px + 96px + max(var(--safe-top), var(--tg-top))); }
/* a ghost of what the flip leaves behind (the large title, the field) */
.off-pick-ghost { z-index: 22; pointer-events: none; }

/* the picks strip: a full-width scroller whose first and last chips rest on
   the page margin (frontend.md, horizontal strip variant B; catalog.js's own
   swipe-back yields to it) */
/* flow-root: the scroller's negative block margins stay inside the row */
.off-chips { position: relative; display: flow-root; height: 36px; }
.off-chips-scroll {
  display: flex; align-items: center; box-sizing: border-box;
  height: 44px; margin-block: -4px; padding-block: 4px;
  padding-inline: var(--pad) calc(var(--pad) - 8px);
  overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  scrollbar-width: none; touch-action: pan-x;
  /* The side that hides chips dissolves them, rim and shadow included: a mask,
     not a band painted over them (a --bg gradient left a hard cut at the
     screen edge). Clear across the --pad zone, opaque by 48px; a side with
     nothing past it has no mask at all (a flip per crossing, syncChipFades).
     The mask stays on at 0 rather than coming and going, so the strip never
     changes its layer topology mid-scroll (a mask is a backdrop root: the
     member chips' blur sees the strip only; it sits on the solid band). */
  --off-fl: 0; --off-fr: 0;
  -webkit-mask-image: linear-gradient(to right, transparent calc(var(--pad) * var(--off-fl)), #000 calc(48px * var(--off-fl)),
    #000 calc(100% - 48px * var(--off-fr)), transparent calc(100% - var(--pad) * var(--off-fr)));
  mask-image: linear-gradient(to right, transparent calc(var(--pad) * var(--off-fl)), #000 calc(48px * var(--off-fl)),
    #000 calc(100% - 48px * var(--off-fr)), transparent calc(100% - var(--pad) * var(--off-fr)));
  transition: --off-fl 150ms cubic-bezier(0.4, 0, 0.2, 1), --off-fr 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
@property --off-fl { syntax: '<number>'; inherits: false; initial-value: 0; }
@property --off-fr { syntax: '<number>'; inherits: false; initial-value: 0; }
.off-chips-scroll::-webkit-scrollbar { display: none; }
.off-chips.fade-l .off-chips-scroll { --off-fl: 1; }
.off-chips.fade-r .off-chips-scroll { --off-fr: 1; }
/* the limit, said where the chips will land, while none has */
.off-chips-hint {
  position: absolute; inset: 0 var(--pad); display: flex; align-items: center; padding: 0 4px;
  font-size: 14px; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: 0; pointer-events: none; transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.off-chips.is-empty .off-chips-hint { opacity: 1; }

/* Input chip (M3 input chip: avatar, label, trailing remove). A pick wears
   the selection tone, solid because it floats (design-system 2b); a member of
   the running chat is neutral glass with no remove. */
.off-chip {
  position: relative; flex: none; display: inline-flex; align-items: center;
  height: 36px; max-width: 70vw; margin-right: 8px; border-radius: var(--r-pill);
  background: var(--accent-soft-solid); color: var(--accent-text);
  box-shadow: var(--chrome-glass-shadow-thin);
  transition: transform 130ms cubic-bezier(0.4, 0, 0.2, 1);
}
.off-chip:active { transform: scale(0.97); }
.off-chip.is-member {
  background: var(--chrome-glass-fill-readable);
  backdrop-filter: var(--chrome-glass-filter-readable);
  -webkit-backdrop-filter: var(--chrome-glass-filter-readable);
  color: var(--text);
}
.off-chip-face {
  position: relative; display: inline-flex; align-items: center; min-width: 0; height: 36px;
  padding: 0 14px 0 4px; border: none; background: transparent; color: inherit; font: inherit;
  border-radius: var(--r-pill); cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.off-chip-face::before { content: ''; position: absolute; inset: -4px 0; }
.off-chip:not(.is-member) .off-chip-face { padding-right: 0; }
.off-chip-ava {
  width: 28px; height: 28px; flex: none; border-radius: var(--r-pill);
  object-fit: cover; object-position: 50% 0; -webkit-user-drag: none;
}
.off-chip-mono {
  display: flex; align-items: center; justify-content: center;
  background: var(--glass-bold); color: var(--text); font-size: 13px; font-weight: 700;
}
.off-chip-name {
  margin-left: 8px; min-width: 0; font-size: 14px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.off-chip-x {
  position: relative; flex: none; width: 36px; height: 36px; padding: 0 4px 0 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; color: inherit; border-radius: var(--r-pill);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.off-chip-x::before { content: ''; position: absolute; inset: -4px -6px -4px 0; }
.off-chip-x svg { width: 14px; height: 14px; }
.off-chip-x:disabled { opacity: 0.32; cursor: default; }
/* a new chip grows in from its leading edge; the band of the clip reaches
   past the rim so the shadow is whole when it lands */
.off-chip.is-growing { clip-path: inset(-8px 100% -8px -8px round 26px); opacity: 0; transition: none; }
.off-chip.is-grown {
  clip-path: inset(-8px -8px -8px -8px round 26px);
  transition: clip-path 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 120ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* a removed one collapses in place and its neighbours close the gap */
.off-chip.is-leaving {
  overflow: hidden; pointer-events: none; min-width: 0; margin-right: 0; opacity: 0;
  transition: width 200ms cubic-bezier(0.4, 0, 0.2, 1), margin-right 200ms cubic-bezier(0.4, 0, 0.2, 1),
              opacity 120ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* "Group chat 2/3": the counter never truncates, the name does */
.sub-bar-title.off-pick-title { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.off-pick-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.off-pick-n {
  flex: none; color: var(--text2); font-variant-numeric: tabular-nums;
  transition: color 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* a refused pick lights the counter for a moment */
.off-pick-n.flash { color: var(--accent-text); transition: none; animation: off-count-flash 700ms step-end forwards; }
@keyframes off-count-flash { to { color: var(--text2); } }

/* cards in selection: the whole card toggles, the check says the state, the
   "i" (HIG detail disclosure) opens details. Both ride on every card and
   show with the mode: visibility keeps them out of the tab order meanwhile. */
/* the hold owns a long press: no callout and no text selection racing it
   (WebKit starts selecting at ~500ms; the bookmark hold in community.css) */
.off-card { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
.off-ring {
  position: absolute; top: 10px; right: 10px; z-index: 3; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--chrome-glass-fill-neutral) 40%, transparent); color: transparent;
  box-shadow: inset 0 0 0 2px var(--text), var(--chrome-glass-elev-chip);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 150ms,
              background-color 130ms cubic-bezier(0.4, 0, 0.2, 1), color 130ms cubic-bezier(0.4, 0, 0.2, 1);
}
.off-ring svg { width: 14px; height: 14px; }
.off-info {
  position: absolute; top: 8px; left: 8px; z-index: 3; width: 30px; height: 30px; padding: 0;
  display: flex; align-items: center; justify-content: center; border: none; border-radius: var(--r-pill);
  color: var(--text); cursor: pointer; -webkit-tap-highlight-color: transparent;
  background: var(--chrome-glass-fill-media);
  backdrop-filter: var(--chrome-glass-filter-media);
  -webkit-backdrop-filter: var(--chrome-glass-filter-media);
  box-shadow: var(--chrome-glass-shadow-thin);
  opacity: 0; visibility: hidden;
  transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 150ms, scale 120ms ease-out;
}
.off-info::before { content: ''; position: absolute; inset: -7px; border-radius: inherit; }
.off-info svg { width: 18px; height: 18px; }
.off-info:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.off-inchat {
  position: absolute; top: 9px; right: 8px; z-index: 3; height: 28px; padding: 0 11px;
  display: none; align-items: center; border-radius: var(--r-pill);
  font-size: 12px; font-weight: 600; color: var(--text);
  background: var(--chrome-glass-fill-media);
  backdrop-filter: var(--chrome-glass-filter-media);
  -webkit-backdrop-filter: var(--chrome-glass-filter-media);
  box-shadow: var(--chrome-glass-shadow-thin);
}
.is-picking .off-ring, .is-picking .off-info {
  opacity: 1; visibility: visible;
  transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), background-color 130ms cubic-bezier(0.4, 0, 0.2, 1),
              color 130ms cubic-bezier(0.4, 0, 0.2, 1), scale 120ms ease-out;
}
.off-card.is-picked .off-ring, .off-card.off-held .off-ring {
  background: var(--accent); color: var(--text); box-shadow: var(--chrome-glass-elev-chip);
}
/* the hold's threshold: the check pops in filled while the finger is down */
.off-card.off-held { transform: scale(0.97); background: var(--glass-bold); }
.off-card.off-held .off-ring { opacity: 1; visibility: visible; transition: none; animation: off-ring-pop 120ms cubic-bezier(0.05, 0.7, 0.1, 1); }
@keyframes off-ring-pop { from { transform: scale(0.6); } to { transform: none; } }
/* picked: the check AND the tone (two cues, never a stroke) */
.off-card.is-picked { background: var(--accent-soft-solid); }
.off-card.is-picked .card-name { color: var(--accent-text); }
/* the limit: the rest dims, their "i" stays alive. The cover dims under a
   scrim of the card's own tone, not by its opacity: that one belongs to the
   cover's arrival fade. */
.off-card .card-image-wrapper::after {
  content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: color-mix(in srgb, var(--glass) 60%, transparent);
  opacity: 0; transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.off-card .card-content { transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1); }
.off-card.is-full .card-image-wrapper::after { opacity: 1; }
.off-card.is-full .card-content { opacity: 0.4; }
.is-picking .off-card.is-full .off-ring { opacity: 0.4; }
/* a member of the running chat: a label instead of the check */
.is-picking .off-card.is-member .off-ring { display: none; }
.is-picking .off-card.is-member .off-inchat { display: inline-flex; }
/* the up arrow's frozen copies (totop.js) carry the card chips too: no blur
   re-rasters while they travel (.totop-ghost in style.css) */
.totop-ghost .off-info, .totop-ghost .off-inchat { backdrop-filter: none; -webkit-backdrop-filter: none; }
body.no-blur .off-info, body.no-blur .off-inchat {
  background: var(--chrome-dense-fill-raised); backdrop-filter: none; -webkit-backdrop-filter: none;
  box-shadow: var(--chrome-glass-shadow-solid);
}
body.no-blur .off-chip.is-member {
  background: var(--chrome-glass-fill-solid); backdrop-filter: none; -webkit-backdrop-filter: none;
  box-shadow: var(--chrome-glass-shadow-solid);
}

/* the one CTA docks at the bottom (.art-cta's dialect) over a filter-free fade;
   it rises with the mode and steps out while a sheet is up */
.off-pick-bfade {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 19; pointer-events: none;
  height: calc(112px + var(--safe-bottom, 0px));
  background: linear-gradient(to bottom, transparent, color-mix(in srgb, var(--bg) 80%, transparent) 70%);
  opacity: 0; transition: opacity 250ms cubic-bezier(0.05, 0.7, 0.1, 1);
}
.btn-primary.off-pick-cta {
  position: absolute; z-index: 20; left: var(--pad); right: var(--pad);
  bottom: calc(16px + var(--safe-bottom, 0px)); width: auto; max-width: 420px; margin: 0 auto;
  box-shadow: 0 1px 2px rgb(0 0 0 / 30%), 0 8px 12px 6px rgb(0 0 0 / 15%);
  transform: translateY(calc(100% + 32px)); visibility: hidden;
  transition: transform 250ms cubic-bezier(0.05, 0.7, 0.1, 1), background-color 130ms cubic-bezier(0.4, 0, 0.2, 1),
              color 130ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 250ms;
}
.is-picking .off-pick-bfade { opacity: 1; }
.is-picking .btn-primary.off-pick-cta {
  transform: none; visibility: visible;
  transition: transform 250ms cubic-bezier(0.05, 0.7, 0.1, 1), background-color 130ms cubic-bezier(0.4, 0, 0.2, 1),
              color 130ms cubic-bezier(0.4, 0, 0.2, 1);
}
.is-picking .btn-primary.off-pick-cta:active:not(.is-empty) { transform: scale(0.97); }
/* nothing picked: the button stays OPAQUE (a translucent disabled one over
   the feed would let the cards and the tap through) and says so on tap */
.btn-primary.off-pick-cta.is-empty { background: var(--glass-bold); color: var(--text3); box-shadow: none; }
.is-picking.sheet-up .btn-primary.off-pick-cta { transform: translateY(calc(100% + 32px)); }
.is-picking.sheet-up .off-pick-bfade { opacity: 0; }
body.kb-open .off-pick-cta, body.kb-open .off-pick-bfade { display: none; }
/* the feed ends above the docked button */
.is-picking .off-end { height: calc(120px + var(--safe-bottom, 0px)); }
.is-picking .glass-chip.off-totop { bottom: calc(80px + var(--safe-bottom, 0px)); }
/* the sheet's one action in a selection: pick (primary) or unpick (tonal) */
.off-sheet .off-sheet-pick { flex: 1; }
.off-sheet .off-sheet-pick.off-unpick { background: var(--accent-soft-solid); color: var(--accent-text); }
/* the consent over a selection: an app sheet inside the layer */
.off-consent { pointer-events: auto; }
.off-consent-avas { display: flex; justify-content: center; }
.off-consent-avas > * + * { margin-left: -12px; box-shadow: 0 0 0 3px var(--bg-tint); }
@media (prefers-reduced-motion: reduce) {
  .off-shelf, .off-pick-fade, .off-pick-bfade, .btn-primary.off-pick-cta, .off-ring, .off-info, .off-chips-hint, .off-chips-scroll,
  .off-chip, .off-card .card-image-wrapper::after, .off-card .card-content { transition: none !important; }
  .off-card.off-held { transform: none; }
  .off-card.off-held .off-ring { animation: none; }
}

/* ── desktop: the pushed screen keeps the app's centred content column ── */
html.layout-desktop .off-scroll > .large-title,
html.layout-desktop .off-scroll > .off-search,
html.layout-desktop .off-scroll > .off-grid {
  max-width: 720px; margin-left: auto; margin-right: auto;
}
/* The sidebar is a flush panel. Move the whole layer with its width so the
   screens and scrim meet its edge, independently of their own push/pop motion. */
html.layout-desktop:has(.desktop-sidebar) .off-layer {
  left: var(--sidebar-w);
  transition: left 0.18s cubic-bezier(0.4,0,0.2,1);
}
html.layout-desktop body.sb-resizing .off-layer { transition: none; }
/* the grid lives inside the 720px column here, so the catalog's 4 desktop
   columns would be narrower than the phone's two */
html.layout-desktop .off-grid { grid-template-columns: repeat(3, 1fr); }
/* the selection's shelf keeps to the same centred column as the grid */
html.layout-desktop .off-shelf .off-search { max-width: 720px; margin-left: auto; margin-right: auto; }
html.layout-desktop .off-chips { max-width: calc(720px + 2 * var(--pad)); margin: 0 auto; }
/* the arrow keeps to the right edge of the same column */
html.layout-desktop .glass-chip.off-totop { right: max(14px, calc((100% - 720px) / 2 + 14px)); }
