/*
 * Yeja AI — Shared Component Library (Phase 4, 2026-07-19)
 * apps/shared/design-tokens.css defines the raw tokens; this file
 * assembles them into the reusable UI shapes named in
 * memory/falble-design-1-plan.md's Phase 4. Additive, not a forced
 * rewrite — every app's existing local CSS keeps working untouched;
 * apps swap their local class names for these opportunistically.
 *
 * migration status: apps/profile is the first (and so far only)
 * adopter — see its styles.css/app.js for the reference migration.
 */

/* ── Card ──
 * Canonical shape already agreed on, byte-for-byte, by 6 of 7 apps that
 * have a "card" concept (Profile's .prof-card, Wallet's .wal-card,
 * Fundraisers' .fr-card, Galleries' #modal-inner, Auctions' .auc-card,
 * Themes' .themes-preview-frame) before this file existed — this is a
 * pure extraction of an already-converged pattern, not a new design. */
.yeja-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-card, 12px);
  padding: var(--space-3, 24px);
  background: var(--surface);
}

.yeja-card h2,
.yeja-card h3 {
  margin: 0 0 var(--space-2, 16px);
  font-size: var(--text-h3-size, 16px);
  font-weight: var(--text-h3-weight, 600);
  color: var(--ink);
}

/* ── Chip / tag-chip ──
 * .yeja-chip: a clickable filter pill (My Studio's .chip). .yeja-tag-chip:
 * a smaller, non-pill-radius-critical tag (My Studio's .tag-chip) — kept
 * distinct since a tag reads differently from a filter toggle even
 * though the base shape is close. */
.yeja-chip {
  padding: 4px 12px;
  border-radius: var(--radius-pill, 24px);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-size: var(--text-label-size, 12px);
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.12s ease-in-out, color 0.12s ease-in-out, background 0.12s ease-in-out;
}

.yeja-chip:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.yeja-chip.active {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

.yeja-tag-chip {
  padding: 3px 10px;
  border-radius: var(--radius-pill, 24px);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-size: var(--text-label-size, 12px);
  font-family: inherit;
}

/* ── Status chip ──
 * Deliberately bucket-based, NOT word-based (2026-07-19 design
 * decision). Profile, My Studio, and Social each previously defined a
 * bare, unscoped .status-chip with a DIFFERENT radius/sizing/casing/
 * color-vocabulary — and because apps/shared/inject-stylesheet.js never
 * removes a previous app's <link> on unmount, visiting multiple apps in
 * one shell session loaded all three simultaneously, so whichever was
 * injected last silently won the cascade for every .status-chip on the
 * page, including ones belonging to a DIFFERENT currently-mounted app.
 * A literal merged status-WORD table (draft/pending/scheduled/...) was
 * considered and rejected: Profile itself reuses its own bucket names
 * as loose color shorthand in places unrelated to their literal meaning
 * (e.g. status-cancelled for a SOLD-artwork badge, status-published for
 * a won-bid badge) — a shared word namespace would have just relocated
 * the collision risk, not removed it. Buckets have no word to collide
 * on: each app's own render code maps ITS OWN status word to whichever
 * bucket is semantically correct for that app, via a small local
 * mapping function (see apps/profile/app.js's orderStatusBucket() for
 * the reference implementation). */
.yeja-status-chip {
  display: inline-block;
  flex: 0 0 auto;
  padding: 4px 12px;
  border-radius: var(--radius-pill, 24px);
  font-size: var(--text-label-size, 12px);
  font-weight: 600;
  text-transform: capitalize;
}

.yeja-status-chip.neutral { background: var(--surface-alt); color: var(--muted); }
.yeja-status-chip.info    { background: var(--yeja-info-bg); color: var(--yeja-cobalt); }
.yeja-status-chip.success { background: var(--yeja-success-bg); color: var(--yeja-mint); }
.yeja-status-chip.warning { background: var(--yeja-warning-bg); color: var(--yeja-amber); }
.yeja-status-chip.danger  { background: var(--yeja-error-bg); color: var(--yeja-crimson); }

/* ── Row list ──
 * The repeated title+meta+trailing-value/chip row shape — extracted
 * verbatim from Profile's own orders/bids/RFPs rows, which were already
 * unified under one comma-selector rule internally before this file
 * existed (see apps/profile/styles.css's migration). */
.yeja-row-list-item {
  display: flex;
  align-items: center;
  gap: var(--space-2, 16px);
  padding: var(--space-2, 16px) 0;
  border-bottom: 1px solid var(--line);
}

.yeja-row-list-item:last-child {
  border-bottom: none;
}

.yeja-row-list-text {
  flex: 1;
  min-width: 0;
}

.yeja-row-list-title {
  margin: 0;
  font-size: var(--text-body-size, 14px);
  font-weight: 600;
  color: var(--ink);
}

.yeja-row-list-meta {
  margin: 2px 0 0;
  font-size: var(--text-label-size, 12px);
  color: var(--muted);
}

.yeja-row-list-value {
  flex: 0 0 auto;
  font-size: var(--text-body-size, 14px);
  font-weight: 600;
  color: var(--ink);
}

/* ── Row list group ──
 * Wraps a run of .yeja-row-list-item rows that need their own visual
 * boundary from a preceding form/section (e.g. Profile's organization
 * pending-invites list) — same border/spacing tokens as the row items
 * themselves, not a new visual language. */
.prof-org-pending-invites {
  margin-top: var(--space-2, 16px);
  border-top: 1px solid var(--line);
}

.prof-org-permissions-panel {
  padding: var(--space-1, 8px) 0 var(--space-2, 16px);
  border-bottom: 1px solid var(--line);
}

/* ── Empty state ──
 * Text-only tier (Wallet's .wal-empty, Profile's .prof-empty-state,
 * Galleries' .empty-msg) — the plainest, most common tier. A richer
 * icon+heading+text tier exists too (Fundraisers' .fr-empty, Social's
 * .social-empty) but is deliberately NOT unified in this pass: it's a
 * genuinely different, heavier shape (icon + h3 + p + optional action
 * button) that only 2 apps currently use, vs. this simpler tier which
 * ~4 apps already converged on independently. Revisit if a 3rd/4th app
 * adopts the richer tier. No text-align here — Profile's original
 * .prof-empty-state (this pattern's source) was left-aligned inline
 * text within a card, not centered; Wallet's own .wal-empty adds
 * text-align: center itself where it wants that, rather than baking a
 * center-alignment assumption into the shared shape. */
.yeja-empty-state {
  color: var(--muted);
  font-size: var(--text-body-size, 14px);
  padding: var(--space-2, 16px) 0;
}

/* ── Dialog / overlay ──
 * Backdrop-dialog shape only (dimmed backdrop + centered card) — see
 * apps/shared/overlay.js for the open/close/Escape/backdrop-click JS.
 * Deliberately does NOT cover Profile's portfolio-preview modal, which
 * is a different shape entirely (opaque full-viewport view, no
 * backdrop, closed only by an explicit button) — forcing it into this
 * shape would add behavior (Escape-to-close) it was never designed
 * with, a real product decision outside this phase's scope. */
.yeja-overlay-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: color-mix(in oklch, var(--yeja-backdrop) 55%, transparent);
  display: flex;
  align-items: center;
  justify-content: center;
}

.yeja-dialog-card {
  background: var(--paper);
  color: var(--ink);
  border-radius: var(--radius-menu, 16px);
  padding: var(--space-4, 32px);
  max-width: 480px;
  width: 90vw;
  display: flex;
  flex-direction: column;
  gap: var(--space-3, 24px);
  animation: yeja-dialog-enter 0.18s ease-out;
}

@keyframes yeja-dialog-enter {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* ── Dialog text input ──
 * For apps/shared/confirm-dialog.js's promptDialog() — a themed
 * replacement for native prompt(). Deliberately its own class, not a
 * reuse of .yeja-pill-btn (a button shape, not an editable field). */
.yeja-dialog-input {
  border: 1px solid var(--line);
  border-radius: var(--radius-control, 8px);
  padding: 0.5em 0.75em;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
}

/* ── Danger pill button ──
 * Same base shape as .yeja-pill-btn, recolored for a destructive
 * confirm action (e.g. "Delete", "Withdraw") so its stakes read at a
 * glance next to a neutral Cancel button. */
.yeja-pill-btn-danger {
  background: var(--yeja-error-bg);
  border-color: var(--yeja-crimson);
  color: var(--yeja-crimson);
}

/* ── Ghost pill button ──
 * Same base shape as .yeja-pill-btn, transparent fill with a border only
 * — for a secondary action sitting next to a primary one (e.g. "Cancel"
 * next to "Send"), added for Wallet's Phase 3 send-USDT modal
 * (2026-07-13) but kept here rather than scoped to apps/wallet since the
 * primary/secondary pairing is a platform-wide pattern, not Wallet-specific. */
.yeja-pill-btn-ghost {
  background: transparent;
  /* Design guide §5 Buttons: Ghost border must read --muted (--yeja-
   * text-muted, "ink-secondary"), not --line/--yeja-border — the guide's
   * own dark-border contrast rule (design-tokens.css's dark block,
   * "Components needing a visible dark-mode border should read
   * --yeja-text-muted... not --line/--yeja-border") flags --line as
   * ~1.05:1 against --yeja-canvas in dark mode, i.e. functionally
   * invisible. Fixed 2026-07-18 — every Ghost button platform-wide
   * shares this one base rule. */
  border: 1px solid var(--muted);
  color: var(--ink);
}
.yeja-pill-btn-ghost:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ── Yeja Toolbar system (2026-07-15) ──
 * Extracted from apps/my_studio/styles.css, the platform's first real
 * implementation of this bar (Folders dropdown / category pills /
 * Settings dropdown / collapse toggle). User instruction: "no single
 * app's bar can be different the my_studio bar" — every app's own
 * content-level toolbar should be built from these classes so a future
 * design change updates every app at once, instead of each app carrying
 * its own copy that silently drifts (exactly what caused the star/cart/
 * notif floater bug: My Studio's own bare, unscoped .dropdown__panel
 * rule leaked into and broke the universal shell's top bar once My
 * Studio had ever been mounted in a session — apps/shared/
 * inject-stylesheet.js never removes a <link> on unmount, so a stale
 * app stylesheet stays loaded for the rest of the session). Every class
 * here is .yeja--prefixed specifically so it can never again collide
 * with an app-local bare class name the way .dropdown/.dropdown__panel/
 * .action-item did.
 *
 * Anatomy: .yeja-toolbar (persistent outer bar, never collapses, holds
 * the toggle) > .yeja-toolbar__content (the collapsible part — real
 * toolbar content goes here) + .yeja-toolbar__toggle (chevron button,
 * floats over the canvas via position:absolute only while collapsed).
 * This exact split is the toolbar's 4th and final design after 3 wrong
 * attempts in apps/my_studio/app.js's own history (button inside the
 * collapsing element = no way back once collapsed; a whole separate
 * bar below the toolbar = visually disconnected; content-only collapse
 * with the bar still sized to the toggle button = never actually
 * disappeared) — see memory/project_my_studio_redesign_plan_20260714.md
 * for the full incident history before changing this shape again. */
.yeja-toolbar {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  flex-shrink: 0;
  position: relative;
  padding: 0 24px;
  max-height: 200px;
  overflow: visible;
  transition: max-height 0.15s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.yeja-toolbar.is-collapsed {
  max-height: 0;
  border-bottom-color: transparent;
  overflow: visible;
}
.yeja-toolbar__content {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 1;
  min-width: 0;
  padding: 16px 0;
}
.yeja-toolbar.is-collapsed .yeja-toolbar__content {
  visibility: hidden;
}
.yeja-toolbar.is-collapsed .yeja-toolbar__toggle {
  position: absolute;
  top: 8px;
  right: 24px;
  z-index: 2;
  background: var(--surface);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
.yeja-toolbar__toggle {
  flex-shrink: 0;
  margin-left: 8px;
}
.yeja-toolbar__right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* ── Icon button — 36px circle, transparent until hover. Used for the
 * toolbar's collapse toggle and any other icon-only action (Upload,
 * Settings trigger, etc). ── */
.yeja-btn-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-pill, 999px);
  border: none;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.15s cubic-bezier(0.4, 0, 0.2, 1), transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.yeja-btn-icon:hover { background: var(--surface-alt); }
.yeja-btn-icon.is-flipped { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .yeja-btn-icon.is-flipped { transition: none; }
}

/* ── Outline button — a dropdown trigger that's navigational chrome, not
 * a primary action (e.g. a "Folders"-style trigger). ── */
.yeja-btn-outline {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill, 999px);
  padding: 8px 16px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.yeja-btn-outline:hover { background: var(--surface-alt); }

/* ── Category / filter pills — inline row of toggle pills inside a
 * toolbar (e.g. Featured/Draft/Ready/Minted/Sold/Starred). Real pill
 * treatment: ink-fill active, muted text inactive, no background at
 * rest. ── */
.yeja-category-pills { display: flex; align-items: center; gap: 8px; overflow-x: auto; scrollbar-width: none; }
.yeja-category-pills::-webkit-scrollbar { display: none; }
.yeja-category-pill {
  flex-shrink: 0;
  padding: 8px 16px;
  border-radius: var(--radius-pill, 999px);
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.yeja-category-pill:hover:not(.active) { background: var(--surface-alt); color: var(--ink); }
.yeja-category-pill.active { background: var(--ink); color: var(--paper); }
/* A .yeja-category-pill used as a dropdown menu item (e.g. My Studio's
 * Manage NFTs dropdown, 2026-07-15) stacks full-width instead of sitting
 * in a horizontal scrolling row — same click/active-state contract as the
 * flat pill row, different container shape. */
.yeja-dropdown__panel .yeja-category-pill {
  display: block;
  width: 100%;
  text-align: left;
}

/* ── Dropdown panel — trigger + absolutely-positioned panel, 16px
 * radius, Panel shadow, 8px outer padding. Matches the universal shell's
 * own top-bar Favorites/Cart/Create dropdowns exactly (same classes —
 * this IS that same system, not a lookalike), so a dropdown built with
 * these classes anywhere in the platform behaves identically. Add
 * .yeja-toolbar__right as an ancestor to right-anchor the panel (grows
 * leftward) instead of the default left-anchor (grows rightward) —
 * same convention as the shell's own top-bar icons. ── */
.yeja-dropdown { position: relative; }
.yeja-dropdown__panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  min-width: 280px;
  background: var(--surface);
  border-radius: var(--radius-menu, 16px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14);
  padding: 8px;
  display: none;
  z-index: 200;
}
.yeja-toolbar__right .yeja-dropdown__panel { left: auto; right: 0; }
.yeja-dropdown__panel.is-open { display: block; }
.yeja-dropdown__divider { height: 1px; background: var(--line); margin: 6px 4px; }

.yeja-action-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  gap: 2px;
  padding: 10px 12px;
  border: 0;
  background: transparent;
  border-radius: 12px;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.yeja-action-item:hover { background: var(--surface-alt); }
.yeja-action-item__label { font-size: 14px; font-weight: 700; color: var(--ink); }
.yeja-action-item__desc { font-size: 12px; color: var(--muted); }

/* ── Tooltip system (2026-07-15) — apps/shared/tooltip.js's initTooltips()
 * repositions and shows/hides this ONE shared node for whichever
 * [data-tooltip] element is currently hovered/focused, platform-wide.
 * z-index above every other layered surface (dropdowns at 200, the
 * upload progress overlay at 9999) since a tooltip must always be able
 * to appear over anything else that's open. Dark, high-contrast bubble
 * on purpose — unlike .yeja-dropdown__panel (which follows the
 * light/dark theme), a tooltip needs to read clearly regardless of
 * what's underneath it, same convention most platforms use. */
.yeja-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 10000;
  max-width: 240px;
  padding: 6px 10px;
  border-radius: var(--radius-control, 6px);
  background: var(--ink);
  color: var(--paper);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  pointer-events: none;
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 0.12s ease-in-out, transform 0.12s ease-in-out;
}
.yeja-tooltip.is-visible {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .yeja-tooltip { transition: opacity 0.12s ease-in-out; transform: none; }
}

/* ── Shared "Choose From My Studio" asset picker (extracted 2026-07-16
 * from apps/auctions/app.js — Fundraisers needed the same real picker to
 * fix a bug where its plain-text "Cover Image URL" field let a
 * time-limited signed Storage URL get pasted in instead of a permanent
 * public_url). Used by apps/shared/studio-asset-picker.js's
 * openStudioAssetPicker(). ── */
.yeja-studio-picker-card { max-width: 720px; }
.yeja-studio-picker-title { font-size: var(--text-h2-size); line-height: var(--text-h2-line); font-weight: 700; margin-bottom: var(--space-1); }
.yeja-studio-picker-loading { color: var(--yeja-text-muted); font-size: var(--text-body-size); }
.yeja-studio-picker-empty { color: var(--yeja-text-muted); text-align: center; padding: var(--space-4) 0; }

.yeja-studio-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-2);
  max-height: 50vh;
  overflow-y: auto;
  margin-bottom: var(--space-2);
}

.yeja-studio-picker-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1);
  border: 1px solid var(--yeja-border);
  border-radius: var(--radius-control);
  background: var(--yeja-canvas);
  cursor: pointer;
  text-align: left;
  transition: background 0.15s ease-in-out, transform 0.15s ease-in-out;
}
.yeja-studio-picker-item:hover { background: var(--yeja-active-hover); transform: translateY(-1px); }

.yeja-studio-picker-thumb {
  width: 48px;
  height: 48px;
  flex: 0 0 auto;
  border-radius: var(--radius-control);
  overflow: hidden;
  background: var(--yeja-sidebar);
  display: grid;
  place-items: center;
}
.yeja-studio-picker-thumb img { width: 100%; height: 100%; object-fit: cover; }
.yeja-studio-picker-thumb-placeholder { color: var(--yeja-text-muted); font-weight: 700; }

.yeja-studio-picker-item-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.yeja-studio-picker-item-text strong {
  font-size: var(--text-body-size);
  color: var(--yeja-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.yeja-studio-picker-item-text span {
  font-size: var(--text-label-size);
  color: var(--yeja-text-muted);
}
.yeja-studio-picker-no-image { color: var(--yeja-crimson) !important; }

.yeja-studio-picker-section-label {
  font-size: var(--text-label-size);
  font-weight: 700;
  color: var(--yeja-text-muted);
  margin: var(--space-2) 0 var(--space-1);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.yeja-studio-picker-section-label:first-of-type { margin-top: 0; }

.yeja-studio-picker-back { align-self: center; }

/* ── Cover-image preview chip (thumbnail + clear button) shown once an
 * image has been picked from the studio-asset picker — same pattern
 * apps/auctions/styles.css's own .auc-cover-picker/.auc-cover-preview
 * originated 2026-07-15, generalized here so Fundraisers' cover-image
 * field can reuse it too. ── */
.yeja-cover-picker { display: flex; flex-direction: column; gap: var(--space-1); align-items: flex-start; }
.yeja-cover-preview {
  position: relative;
  width: 160px;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-control);
  overflow: hidden;
  background: var(--yeja-sidebar);
}
.yeja-cover-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.yeja-cover-clear {
  position: absolute; top: 4px; right: 4px;
  width: 22px; height: 22px;
  border: none; border-radius: 50%;
  background: rgba(20, 20, 20, 0.6); color: #fff;
  font-size: 14px; line-height: 1; cursor: pointer;
  display: grid; place-items: center;
}

/* ── Toast notifications (2026-07-22) ──
 * Shared apps/shared/toast.js's real component — lifted verbatim from
 * apps/admin/admin.css's own block (the only one of 8 duplicated toast
 * implementations that already used real design tokens rather than
 * hardcoded hex). Every app migrating onto the shared toast.js module
 * gets this styling for free; no per-app toast CSS needed anymore. */
.toast-container {
  position: fixed;
  bottom: var(--space-3);
  right: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  z-index: 9999;
}
.toast {
  padding: 0.6rem 1rem;
  border-radius: var(--radius-control);
  font-size: var(--text-body-size);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s, transform 0.2s;
  pointer-events: none;
  max-width: 320px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.16);
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast-success { background: var(--yeja-success-bg); color: var(--yeja-mint); border: 1px solid var(--yeja-mint); }
.toast-error   { background: var(--yeja-error-bg); color: var(--yeja-crimson); border: 1px solid var(--yeja-crimson); }
.toast-info    { background: var(--yeja-canvas); color: var(--yeja-text-primary); border: 1px solid var(--yeja-border); }
@media (prefers-reduced-motion: reduce) {
  .toast { transition: none; }
}

/* ── Ink Pill / Ghost buttons (Master Design Guide §5) ──
 * Promoted here 2026-07-25 (Plan v2 Phase 0, tracker a441bf7b) — previously
 * three independent local copies (apps/my_studio/styles.css, apps/crm/
 * styles.css, apps/art_board/styles.css) that had already drifted: Art
 * Board's copy used font-weight 600 (spec says 500), 0.9 hover opacity
 * (spec says 0.85), and a different border token, plus never had the
 * --compact modifier. My Studio's/CRM's copy matched the Guide's §5 spec
 * exactly, so it's the source of truth here, with Art Board's design-token
 * padding (--space-1/--space-3) kept since it resolves to the same 6px/24px
 * values as the hardcoded numbers. Every app already injects this file
 * before its own local styles.css (apps/shared/inject-stylesheet.js), so
 * removing each app's local copy lets this definition take over with no
 * load-order change needed. */
.ink-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--ink);
  color: var(--paper);
  border: none;
  border-radius: var(--radius-pill, 999px);
  padding: 10px 24px;
  font-size: 15.5px;
  font-weight: 500;
  cursor: pointer;
  transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.ink-pill:hover { opacity: 0.85; }
.ink-pill:disabled { opacity: 0.5; cursor: default; }
.ink-pill--compact { padding: 8px 16px; font-size: 14px; }
.ghost-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--muted);
  border-radius: var(--radius-pill, 999px);
  padding: 10px 24px;
  font-size: 15.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.ghost-pill:hover { background: var(--surface-alt); }
.ghost-pill:disabled { opacity: 0.5; cursor: default; pointer-events: none; }
.ghost-pill--compact { padding: 8px 16px; font-size: 14px; }
.ghost-pill--danger { color: var(--yeja-crimson, #E53935); border-color: var(--yeja-crimson, #E53935); }
.ghost-pill--danger:hover { background: color-mix(in oklch, var(--yeja-crimson, #E53935) 12%, transparent); }
.ghost-pill--full { width: 100%; height: 40px; padding-top: 0; padding-bottom: 0; }

/* Provenance card (2026-07-26, Plan v2 Phase 1, tracker 75416daa) — one
 * shared definition backing apps/shared/provenance-card.js, wired into
 * Art Board, Auctions, Profile/Portfolio, and My Studio's detail modals.
 * Built here from day one per the backlog item's own instruction, rather
 * than letting it become a 4th/5th/6th per-app copy the way .ink-pill did
 * before its own Phase 0 fix above. */
.provenance-card {
  border: 1px solid var(--muted);
  border-radius: var(--radius-card, 12px);
  padding: 16px;
  margin-top: 16px;
}
.provenance-card__heading {
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 0 0 12px;
}
.provenance-card__row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--surface-alt);
  font-size: 13.5px;
}
.provenance-card__row:last-of-type { border-bottom: none; }
.provenance-card__label { color: var(--muted); }
.provenance-card__value { color: var(--ink); text-align: right; }
.provenance-card__empty { font-size: 13px; color: var(--muted); margin: 0; }
.provenance-card__cert-btn { margin-top: 12px; width: 100%; }

/* Collapsible variant (backlog 26c851a9) — Art Board's detail box opens
 * this only for minted pieces. A native <details>/<summary>, so keyboard
 * operation and expanded/collapsed state come from the platform rather
 * than being reimplemented. */
.provenance-card--collapsible > .provenance-card__heading {
  cursor: pointer;
  /* Collapsed, the heading IS the whole card — the 12px bottom margin
   * inherited from the base rule would otherwise leave dead space under
   * it. Restored when open, below. */
  margin-bottom: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  list-style: none; /* Firefox */
}
/* Safari/Chrome draw their own triangle via this pseudo-element, which
 * ::marker does not cover. Both are needed to fully replace the default
 * with the chevron below. */
.provenance-card--collapsible > .provenance-card__heading::-webkit-details-marker { display: none; }
.provenance-card--collapsible > .provenance-card__heading::marker { content: ''; }
.provenance-card--collapsible[open] > .provenance-card__heading { margin-bottom: 12px; }
/* CSS-drawn chevron rather than a Material Symbols ligature: this
 * component renders in four apps and cannot assume every host page loads
 * the icon font — an unloaded ligature paints its NAME as literal text,
 * which already shipped broken once on the public portfolio. */
.provenance-card--collapsible > .provenance-card__heading::after {
  content: '';
  width: 6px;
  height: 6px;
  margin-left: auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.15s ease;
}
.provenance-card--collapsible[open] > .provenance-card__heading::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}
.provenance-card--collapsible > .provenance-card__heading:focus-visible {
  outline: 2px solid var(--yeja-cobalt);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .provenance-card--collapsible > .provenance-card__heading::after { transition: none; }
}

/* ── Breadcrumbs (backlog 5f81ff03, 2026-08-02) ──
 * Consolidates FIVE separately-maintained copies (profile, my_studio,
 * art_board, world_galleries, admin).
 *
 * On the colour drift specifically: the copies disagreed on token NAMES
 * (--yeja-text-primary/--yeja-text-muted in Art Board vs --ink/--muted
 * elsewhere). That was checked rather than assumed — --ink is defined as
 * var(--yeja-text-primary) in all three themes, so both always resolved
 * identically and there was no visible difference. The real cost was
 * five implementations to keep in step, NONE of which had list
 * semantics, aria-current, or a focus ring.
 *
 * --ink/--muted are canonical here: what most copies used and what this
 * file already uses throughout, so adopting the shared block changes
 * nothing visually in any app.
 *
 * Markup is semantic (nav > ol > li) per apps/shared/breadcrumbs.js, so
 * this styles a real list rather than a row of spans. */
.yeja-crumbs {
  font-size: var(--text-body-size, 14px);
  font-weight: 600;
  color: var(--muted);
}
.yeja-crumbs-list {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.yeja-crumb {
  display: flex;
  align-items: center;
  gap: 6px;
  /* min-width:0 lets a long crumb shrink instead of forcing the row to
   * overflow its container — the trail wraps rather than pushing the
   * page sideways. */
  min-width: 0;
}
.yeja-crumb-current {
  color: var(--ink);
  /* The current crumb is the one most worth reading in full, but it is
   * also the most likely to be a long user-supplied name — truncate
   * rather than wrap it awkwardly mid-word. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.yeja-crumb-btn {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: inherit;
  font: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.yeja-crumb-btn:hover { color: var(--ink); text-decoration: underline; }
/* Cobalt focus ring, 2px/2px (guide §3). None of the five copies had a
 * focus style at all, so keyboard users had no visible position in the
 * trail. */
.yeja-crumb-btn:focus-visible {
  outline: 2px solid var(--yeja-cobalt, #1E88E5);
  outline-offset: 2px;
  border-radius: 2px;
}
.yeja-crumb-sep { color: var(--muted); }

/* ── Universal loading spinner (backlog fdf9ccb3, 2026-08-03) ──
 * The user's ask: "replace all the 'loading' text with our cool universal
 * loading page 'spinner' UI design". There was no universal loader when
 * this was written — each app had either the bare word "Loading…" or its
 * own local shimmer (.pf-skeleton in Profile, a separate @keyframes
 * shimmer in My Studio/World Galleries/Art Board). This is that one
 * component, and it is the brand mark in motion rather than a generic
 * arc-on-a-circle, so a load reads as *this* product loading.
 *
 * GEOMETRY IS THE BRAND MARK'S, deliberately. The crimson ring plus three
 * offset box-shadow blobs (cobalt/amber/mint) is copied from
 * apps/platform/styles.css's .brand-mark — same border, same shadow
 * offsets, scaled by --yeja-spinner-size. Keeping the silhouette
 * identical is the point: the topbar logo and the loader are recognisably
 * the same object.
 *
 * WHY A CONTINUOUS SPIN HERE, when .yeja-brand-animated (design-tokens.css)
 * deliberately abandoned continuous idle spin for a hover-triggered
 * spin+scale pulse: that rule's problem was that a rotation on this
 * near-3-fold-symmetric blob arrangement barely changes the silhouette at
 * rest, so ambient rotation read as "nothing is happening" in the topbar.
 * A LOADER has the opposite requirement — it must read as ongoing, and
 * the blob colours visibly cycling position is exactly the signal. No
 * scale pulse: a pulsing loader competes with the content arriving.
 *
 * Tokens only, no literal hex (CLAUDE.md non-negotiable #1) — the
 * fallbacks mirror design-tokens.css's own values purely for the case
 * where this file loads before the token sheet does. */
.yeja-spinner {
  --yeja-spinner-size: 24px;
  width: var(--yeja-spinner-size);
  height: var(--yeja-spinner-size);
  flex: 0 0 var(--yeja-spinner-size);
  box-sizing: border-box;
  border: calc(var(--yeja-spinner-size) / 10) solid var(--yeja-crimson, #E53935);
  border-radius: 50%;
  box-shadow:
    calc(var(--yeja-spinner-size) / 5) 0 0 calc(var(--yeja-spinner-size) / -14) var(--yeja-cobalt, #1E88E5),
    calc(var(--yeja-spinner-size) / -7.7) calc(var(--yeja-spinner-size) / 6.1) 0 calc(var(--yeja-spinner-size) / -14) var(--yeja-amber, #FFB300),
    calc(var(--yeja-spinner-size) / 15.4) calc(var(--yeja-spinner-size) / -6.1) 0 calc(var(--yeja-spinner-size) / -14) var(--yeja-mint, #43A047);
  animation: yeja-spinner-rotate 1.1s linear infinite;
}
@keyframes yeja-spinner-rotate {
  to { transform: rotate(360deg); }
}
/* The small size gets a slightly heavier border than the size/10 formula
 * would give it. At 16px that formula lands on 1.6px, which rounds to a
 * hairline ring the three blobs then visually merge into — caught in the
 * light/dark screenshots, where the sm spinner read as a single muddy
 * circle rather than the brand mark. 2px keeps the ring and the blobs
 * distinguishable at this size. */
.yeja-spinner-sm {
  --yeja-spinner-size: 16px;
  border-width: 2px;
}
.yeja-spinner-lg { --yeja-spinner-size: 40px; }

/* The block form: a centred spinner with an optional caption, for the
 * whole-pane "this surface is still loading" case that the word
 * "Loading…" used to occupy on its own. */
.yeja-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 40px 20px;
  color: var(--muted);
}
.yeja-loading-row {
  flex-direction: row;
  padding: 12px 0;
}

/* prefers-reduced-motion: the global safety net in design-tokens.css
 * neutralises animation-duration for everything, which would freeze this
 * mid-rotation and leave a static ring that looks like a broken or
 * finished state. A loader still has to signal "working", so it opts back
 * in with a slow opacity pulse — no rotation, no movement, and well
 * inside the intent of the setting. !important is required to beat that
 * global reset, which is itself !important. */
@media (prefers-reduced-motion: reduce) {
  .yeja-spinner {
    animation: yeja-spinner-fade 1.6s ease-in-out infinite !important;
    animation-duration: 1.6s !important;
    animation-iteration-count: infinite !important;
  }
  @keyframes yeja-spinner-fade {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.45; }
  }
}

/* Screen-reader-only text (moved here from apps/profile/styles.css,
 * 2026-08-03, backlog fdf9ccb3). It lived in one app's stylesheet while
 * being used by apps/portfolio-public and apps/collection-public too, and
 * apps/shared/loading.js now emits it from shared code reachable by every
 * app — a utility that shared code depends on cannot live in one app's
 * private stylesheet. Rule copied verbatim; behaviour unchanged. */
.yeja-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── Artwork specifications (tracker 4bd0a715, Track A / A0) ──────────────
 * Shared promotion of apps/profile's .pf-spec-* block so Art Board, My
 * Studio and Profile render artwork details identically rather than each
 * inventing a layout. Consumed by apps/shared/asset-specs.js.
 *
 * Values only in mono (guide §2 numeric/technical metadata voice); labels
 * stay in the body face so the block does not read as code. */
.yeja-spec-block {
  margin-top: var(--space-3, 24px);
  padding-top: var(--space-2, 16px);
  border-top: 1px solid var(--line);
}
.yeja-spec-heading {
  margin: 0 0 var(--space-1, 8px);
  font-size: var(--text-label-size, 12px);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.yeja-spec-list { margin: 0; }
.yeja-spec-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2, 16px);
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}
.yeja-spec-row:last-child { border-bottom: 0; }
.yeja-spec-label {
  flex: 0 0 auto;
  font-size: var(--text-label-size, 12px);
  color: var(--muted);
}
.yeja-spec-value {
  margin: 0;
  flex: 1 1 auto;
  text-align: end;
  font-family: var(--font-family-mono, 'JetBrains Mono', ui-monospace, monospace);
  font-size: var(--text-label-size, 12px);
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* ── Reaction picker (2026-08-04, backlog 4b717850) ────────────────────
 * The five-emotion panel behind the merged heart/star control.
 *
 * BODY PORTAL + position:fixed, NOT .yeja-dropdown__panel. That panel is
 * position:absolute and would be CLIPPED by .masonry__frame's
 * overflow:hidden — the exact bug apps/art_board's card-menu portal was
 * built to dodge (see its comment at styles.css:344). It is also
 * min-width:280px, far too wide for five 40px icons.
 *
 * The portal node carries the owning app's scope class for --yeja-*
 * resolution, which means it also matches that class's base rule — in
 * Art Board that sets min-height:100% and would stretch this to full
 * viewport height. Hence the override below, same as the card menu. */
/* ⚠️ SPECIFICITY, not just the property. An app scope class like
 * .art-board-app sets `min-height: 100%` on ITSELF, and this portal
 * CARRIES that class (it needs it for --yeja-* resolution), so that base
 * rule matches the portal too. A plain `.yeja-reaction-picker` is 0-1-0 —
 * the SAME specificity — and the app stylesheet loads AFTER
 * components.css, so the app won on source order and the panel stretched
 * to the full viewport (measured live: 900px tall, twice).
 *
 * :where() was the WRONG fix and is recorded here so it is not retried:
 * it zeroes the scope class's specificity, leaving the compound selector
 * still at 0-1-0 — measured live, .art-board-app still won.
 *
 * :is() keeps the class's weight, making this 0-2-0 and an unambiguous
 * win regardless of load order — the same shape as
 * .art-board-app.card-menu__panel, generalised so any app's portal is
 * covered without naming each one twice. */
.yeja-reaction-picker,
:is(.art-board-app, .my-studio-app, .auctions-app, .wg-app).yeja-reaction-picker {
  position: fixed;
  min-height: auto;
  height: auto;
  max-height: none;
  width: auto;
  display: none;
  z-index: 210;            /* above dropdowns (200), below tooltips (10000) */
  padding: 6px;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  /* Panel-strong, per the guide's shadow scale for portalled menus. */
  box-shadow: 0 12px 32px color-mix(in oklch, var(--yeja-gray-1000) 22%, transparent);
}
.yeja-reaction-picker.is-open { display: flex; align-items: center; }

.yeja-reaction-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  background: none;
  border-radius: var(--radius-circle);
  cursor: pointer;
  color: var(--muted);
  transition: transform 0.14s cubic-bezier(0.22, 1, 0.36, 1), background-color 0.14s ease;
}
.yeja-reaction-option:hover,
.yeja-reaction-option:focus-visible {
  background: var(--surface-alt);
  transform: scale(1.15);
}
.yeja-reaction-option:focus-visible {
  outline: 2px solid var(--yeja-cobalt);
  outline-offset: 2px;
}
/* Each option paints in its own tint ALWAYS (not only when chosen) — the
 * picker is a palette, so a row of grey glyphs would hide which is which.
 * The tint is set inline from REACTIONS[].tone. */
.yeja-reaction-option .yeja-icon { font-size: 24px; }

/* The currently-chosen reaction. Structural, not colour-only (guide §7):
 * the glyph FILLS and the option gets a ring, so it survives greyscale. */
.yeja-reaction-option[aria-checked="true"] {
  background: var(--surface-alt);
  box-shadow: inset 0 0 0 2px currentColor;
}

/* ── The trigger, in the card/modal action row ── */
.yeja-reaction-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: none;
  padding: 4px 8px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  color: var(--muted);
  /* Kills the 300ms tap delay and stops a long-press selecting text or
   * raising the iOS callout menu — both required for press-and-hold. */
  touch-action: manipulation;
  user-select: none;
  -webkit-touch-callout: none;
}
.yeja-reaction-trigger:focus-visible {
  outline: 2px solid var(--yeja-cobalt);
  outline-offset: 2px;
}
/* Reacted: the glyph fills AND takes its reaction's tint (set inline). */
.yeja-reaction-trigger.is-reacted .yeja-icon { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 20; }
.yeja-reaction-count { font-size: var(--text-label-size, 12px); font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .yeja-reaction-option { transition: none; }
  .yeja-reaction-option:hover,
  .yeja-reaction-option:focus-visible { transform: none; }
}

/* ── Rich text editor (Quill) ─────────────────────────────────────────
 * Quill's snow theme ships HARDCODED light-mode colors (#ccc borders,
 * #444 icon strokes, #06c active state). Loaded as-is it is unreadable in
 * dark and high-contrast: dark ink on a dark canvas. These overrides map
 * every one of those onto ramp tokens, which are defined three times in
 * design-tokens.css (:root, [data-theme="dark"], [data-theme="high-contrast"]),
 * so all four theme modes come free and no [data-theme] selector is
 * needed here.
 *
 * The .ql-* class names belong to a third-party stylesheet, so they are
 * the one place in this file that cannot follow the .yeja-* convention.
 * Scoped under .yeja-rich-text (the host element rich-text-editor.js
 * creates) so nothing leaks onto unrelated markup. */
.yeja-rich-text .ql-container {
  font-family: inherit;
  font-size: var(--text-body-size, 14px);
  color: var(--yeja-text-primary);
  border-color: var(--yeja-border);
  border-bottom-left-radius: var(--radius-control, 6px);
  border-bottom-right-radius: var(--radius-control, 6px);
  background: var(--yeja-canvas);
}
.yeja-rich-text .ql-editor { min-height: inherit; }
.yeja-rich-text .ql-editor.ql-blank::before {
  color: var(--yeja-text-muted);
  font-style: normal;   /* Quill italicizes the placeholder; nothing else here does */
}
.yeja-rich-text .ql-toolbar {
  border-color: var(--yeja-border);
  border-top-left-radius: var(--radius-control, 6px);
  border-top-right-radius: var(--radius-control, 6px);
  background: var(--yeja-sidebar);
}
/* Toolbar icons are inline SVG with `stroke`/`fill` attributes, so they
 * only follow the theme if the stroke is re-pointed at a token. */
.yeja-rich-text .ql-toolbar .ql-stroke { stroke: var(--yeja-text-primary); }
.yeja-rich-text .ql-toolbar .ql-fill { fill: var(--yeja-text-primary); }
.yeja-rich-text .ql-toolbar .ql-picker-label { color: var(--yeja-text-primary); }
.yeja-rich-text .ql-toolbar button:hover .ql-stroke,
.yeja-rich-text .ql-toolbar button.ql-active .ql-stroke {
  stroke: var(--yeja-cobalt);
}
.yeja-rich-text .ql-toolbar button:hover .ql-fill,
.yeja-rich-text .ql-toolbar button.ql-active .ql-fill {
  fill: var(--yeja-cobalt);
}
.yeja-rich-text .ql-toolbar button.ql-active .ql-picker-label { color: var(--yeja-cobalt); }
/* The dropdown panel is positioned absolutely and inherits nothing. */
.yeja-rich-text .ql-picker-options {
  background: var(--yeja-sidebar);
  border-color: var(--yeja-border);
  color: var(--yeja-text-primary);
}
.yeja-rich-text .ql-editor a { color: var(--yeja-cobalt); }
.yeja-rich-text .ql-editor blockquote {
  border-inline-start: 2px solid var(--yeja-border);
  padding-inline-start: 12px;
  margin-inline-start: 0;
  color: var(--yeja-text-muted);
}
/* Guide §6: every interactive element carries the cobalt ring. Quill's
 * editing surface is a contenteditable div, which gets no ring by
 * default, and its toolbar buttons suppress theirs. */
.yeja-rich-text .ql-editor:focus-visible,
.yeja-rich-text .ql-toolbar button:focus-visible {
  outline: 2px solid var(--yeja-cobalt);
  outline-offset: 2px;
}
