:root {
  color-scheme: dark;
  --bg: #100e18;
  --bg-elev: #1a1725;
  --bg-elev-2: #221e30;
  --border: #2e2a3f;
  --text: #f2eefb;
  --text-dim: #a79fc0;
  --text-faint: #766e8d;
  --accent: #ff7a45;
  --accent-2: #34d1bf;
  --danger: #ff5c7a;
  --radius: 20px;
  --radius-sm: 12px;
  --shadow: 0 12px 30px -12px rgba(0, 0, 0, 0.6);
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

* {
  box-sizing: border-box;
}

/* Several elements toggled via the `hidden` IDL property also set their own
   `display` (e.g. .icon-btn, .map-inbox-wrap) same specificity as the
   browser's default [hidden]{display:none} rule, so the later author rule
   would otherwise silently win and the element stays visible. */
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
}

body {
  min-height: 100dvh;
  padding-bottom: calc(24px + var(--safe-bottom));
  background-image: radial-gradient(circle at 15% -10%, rgba(255, 122, 69, 0.14), transparent 40%),
    radial-gradient(circle at 100% 0%, rgba(52, 209, 191, 0.1), transparent 35%);
  background-attachment: fixed;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select,
textarea {
  font-family: inherit;
  color: inherit;
}

img {
  max-width: 100%;
}

/* ---------- App shell ---------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: calc(16px + var(--safe-top)) 20px 14px;
  background: linear-gradient(var(--bg) 70%, transparent);
  backdrop-filter: blur(6px);
}

.topbar h1 {
  font-size: 22px;
  margin: 0;
  flex: 1;
  min-width: 0;
  letter-spacing: -0.02em;
  /* category.html sets this from a folder/tag's own (user-typed, so
     unbounded-length) name - without a min-width override a flex item
     never shrinks below its content's natural width, so a long name would
     push the row wider than the screen instead of truncating in place. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 19px;
}

.brand-mark {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: var(--bg-elev-2);
  display: grid;
  place-items: center;
  font-size: 15px;
  border: none;
  cursor: pointer;
  padding: 0;
}

/* The app's actual mark (same ring-and-dots design as the PWA icon) -
   sized relative to whichever badge it's dropped into (.brand-mark here,
   .welcome-logo below) instead of a fixed size, so one SVG serves every
   placement. currentColor picks up each badge's own color. */
.logo-mark {
  width: 62%;
  height: 62%;
  color: var(--accent);
}

.icon-btn {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text);
  display: grid;
  place-items: center;
  font-size: 18px;
  cursor: pointer;
  transition: transform 0.12s ease, background-color 0.15s ease, border-color 0.15s ease;
}

/* Sized in em (not a fixed px) so it tracks .icon-btn's own font-size -
   including .workspace-header .icon-btn's smaller 15px override - instead
   of needing its own separate size rule per context. */
.share-icon,
.link-plus-icon {
  width: 1.1em;
  height: 1.1em;
  color: currentColor;
}

.icon-btn:active {
  transform: scale(0.94);
}

/* Hover feedback only for devices that actually have a pointer - a touch
   tap would otherwise get stuck showing the hover state until the next
   tap elsewhere ("sticky hover"), which reads as a stale/wrong selection. */
@media (hover: hover) {
  .icon-btn:hover {
    background: var(--bg-elev-2);
    border-color: var(--accent);
  }
}

main {
  padding: 0 20px 40px;
  max-width: 720px;
  margin: 0 auto;
}

.sync-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-faint);
  display: inline-block;
  margin-right: 6px;
  transition: background 0.3s;
}

.sync-dot.online {
  background: var(--accent-2);
}

.sync-dot.syncing {
  background: var(--accent);
  animation: pulse 1s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* ---------- FAB ---------- */

.fab {
  position: fixed;
  right: 20px;
  bottom: calc(24px + var(--safe-bottom));
  width: 58px;
  height: 58px;
  border-radius: 50%;
  background: var(--accent);
  color: #1a1006;
  border: none;
  font-size: 28px;
  display: grid;
  place-items: center;
  box-shadow: 0 14px 30px -8px rgba(255, 122, 69, 0.55);
  cursor: pointer;
  z-index: 20;
}

.fab:active {
  transform: scale(0.94);
}

.section-title {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-faint);
  margin: 28px 0 12px;
}

.pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-size: 11px;
}

/* ---------- List view: folders, each with its links right underneath ---------- */

.cat-section {
  margin-bottom: 16px;
  padding: 14px 14px 6px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 0.15s ease;
}

.cat-section-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}

.cat-section-link {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.cat-section-icon {
  font-size: 17px;
  flex-shrink: 0;
}

.cat-section-name {
  font-weight: 700;
  font-size: 15px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cat-section-count {
  font-size: 12px;
  color: var(--text-faint);
  background: var(--bg-elev-2);
  border-radius: 999px;
  padding: 2px 8px;
}

.cat-section-action {
  margin-left: auto;
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 15px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background-color 0.15s ease;
}

/* Shape (trash-can icon) is the primary cue here color is a secondary
   reinforcement only, never the sole way a delete action is identified. */
.cat-section-action[data-action="delete"] {
  color: var(--danger);
}

.cat-section-action:active {
  background: var(--bg-elev-2);
}

.cat-section-empty {
  color: var(--text-faint);
  font-size: 13px;
  padding: 10px 4px;
  margin: 0;
}

.link-row-list {
  display: flex;
  flex-direction: column;
}

.link-row {
  display: flex;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--border);
}

.link-row:last-child {
  border-bottom: none;
}

.link-row-open {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 4px;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background-color 0.15s ease;
}

@media (hover: hover) {
  .link-row-open:hover {
    background: var(--bg-elev-2);
  }
}

.link-row-thumb,
.link-row-thumb-placeholder {
  width: 44px;
  height: 44px;
  border-radius: 10px;
  flex-shrink: 0;
  object-fit: cover;
  background: var(--bg-elev-2);
}

.link-row-thumb-placeholder {
  display: grid;
  place-items: center;
  font-size: 18px;
}

.link-row-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.link-row-title {
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.link-row-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-faint);
  min-width: 0;
}

/* Both the source-app pill and the timestamp are short, fixed pieces of
   text - without this, a flex item can shrink below its own content width
   and wrap that text onto a second line instead (seen concretely: "just
   now" breaking into "just" / "now" with almost no line-gap, reading as
   broken/overlapping rather than as a timestamp). The pill still gets an
   ellipsis of its own since a source app name is user/site-controlled and
   can run long; the timestamp is app-generated and short enough to just
   never wrap. */
.link-row-meta .pill {
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.link-row-meta span:last-child {
  flex-shrink: 0;
  white-space: nowrap;
}

.link-row-edit {
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 15px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background-color 0.15s ease;
}

.link-row-edit:active {
  background: var(--bg-elev-2);
}

.link-row-delete {
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--danger);
  font-size: 15px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background-color 0.15s ease;
}

.link-row-delete:active {
  background: rgba(255, 92, 122, 0.18);
}

@media (hover: hover) {
  .link-row-edit:hover {
    background: var(--bg-elev-2);
  }
  .link-row-delete:hover {
    background: rgba(255, 92, 122, 0.18);
  }
  .cat-section-action:hover {
    background: var(--bg-elev-2);
  }
}

.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-faint);
}

.empty-state .emoji {
  font-size: 44px;
  display: block;
  margin-bottom: 12px;
}

/* ---------- Items ---------- */

.item-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.item-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-elev);
  border: 1px solid var(--border);
}

.item-card-img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  display: block;
}

.item-card-placeholder {
  width: 100%;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  font-size: 28px;
  background: var(--bg-elev-2);
}

.item-card-body {
  padding: 10px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.item-card-title {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.item-card-text {
  font-size: 12px;
  color: var(--text-dim);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.item-card-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  color: var(--text-faint);
  min-width: 0;
}

.item-card-meta .pill {
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.item-card-meta span:last-child {
  flex-shrink: 0;
  white-space: nowrap;
}

/* ---------- Forms / auth ---------- */

.auth-shell {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 24px;
  max-width: 420px;
  margin: 0 auto;
}

/* On a phone, edge-to-edge is the expected look for a full-screen form.
   On a desktop-width viewport the exact same markup just centers as a
   narrow column of text floating in a huge dark void - fine content-wise,
   but reads as unfinished/broken rather than "spacious". Above a real
   desktop breakpoint (not just "wider than a phone" - a tablet at ~500px
   should still get the plain full-bleed look), the same content becomes a
   proper bordered/shadowed card instead, the way a login screen actually
   looks on desktop. */
@media (min-width: 720px) {
  .auth-shell {
    max-width: 620px;
    padding: 48px 64px;
    margin: 40px auto;
    min-height: 0;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
  }

  body.auth-page {
    justify-content: center;
  }

  body.auth-page .auth-shell {
    flex: 0 0 auto;
  }
}

/* login.html now has its own .topbar (matching settings.html/index.html)
   above .auth-shell a plain min-height:100dvh on .auth-shell alone would
   add the header's height on top of a full viewport, forcing an extra
   scroll just to center content in space that doesn't actually exist.
   Body becomes the fixed-height flex column instead, so .auth-shell only
   needs to fill whatever's actually left under the header. */
body.auth-page {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

body.auth-page .auth-shell {
  min-height: 0;
  flex: 1 1 auto;
}

.auth-logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-bottom: 36px;
}

.auth-logo .brand-mark {
  width: 64px;
  height: 64px;
  border-radius: 20px;
  font-size: 30px;
  box-shadow: 0 0 0 1px var(--border), 0 14px 32px -12px color-mix(in srgb, var(--accent) 45%, transparent);
}

.auth-logo h1 {
  font-size: 24px;
  margin: 0;
  letter-spacing: -0.02em;
}

.auth-logo p {
  color: var(--text-dim);
  margin: 0;
  font-size: 14px;
}

.field {
  margin-bottom: 14px;
}

.field label {
  display: block;
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 6px;
}

.field input,
.field textarea,
.field select {
  width: 100%;
  padding: 13px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text);
  font-size: 15px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.field input:focus,
.field textarea:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 14px 18px;
  border-radius: var(--radius-sm);
  border: none;
  background: var(--accent);
  color: #1a1006;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.15s ease, background-color 0.15s ease;
}

.btn:active {
  transform: scale(0.98);
}

.btn.secondary {
  background: var(--bg-elev-2);
  color: var(--text);
  border: 1px solid var(--border);
}

.btn.danger {
  background: var(--danger);
  color: #240009;
}

.btn.ghost {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
}

@media (hover: hover) {
  .btn:not(:disabled):hover {
    filter: brightness(1.08);
  }
  .btn.secondary:not(:disabled):hover,
  .btn.ghost:not(:disabled):hover {
    background: var(--bg-elev);
    border-color: var(--accent);
    filter: none;
  }
}

.google-auth-icon,
.facebook-auth-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* .btn's own width:100% stacks these two OAuth buttons flush against each
   other with no gap of their own - this is the space between them (and
   before the "or" divider below). */
.google-auth-btn {
  margin-bottom: 10px;
}

/* A plain "or" between the OAuth button and the email/password form -
   two short rules either side (not a full-width line) so the word itself
   stays legible against them, same idiom as a typical login screen. */
.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 0;
  color: var(--text-faint);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.link-btn {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 14px;
  cursor: pointer;
  padding: 8px 0;
  transition: opacity 0.15s ease;
}

@media (hover: hover) {
  .link-btn:hover {
    opacity: 0.75;
  }
}

/* A feature that exists but needs an account (Notes, Share - see
   page-map.js's notesViewAllowed()/shareBtn) shows dimmed rather than
   hidden outright, same idea as a disabled button elsewhere - visibly
   present so its existence isn't a surprise, but clearly not fully
   available yet. Left clickable on purpose (no pointer-events:none): the
   caller's own click handler is what shows the "log in to access this"
   toast, which needs the click to actually land.*/
.feature-locked {
  opacity: 0.45;
  cursor: pointer;
}

.auth-switch {
  text-align: center;
  margin-top: 18px;
  font-size: 14px;
  color: var(--text-dim);
}

.error-box {
  background: rgba(255, 92, 122, 0.12);
  border: 1px solid rgba(255, 92, 122, 0.4);
  color: #ffb4c2;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  margin-bottom: 14px;
  display: none;
}

.error-box.show {
  display: block;
  animation: error-box-in 0.2s ease;
}

@keyframes error-box-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.share-saved-banner {
  background: rgba(52, 209, 191, 0.12);
  border: 1px solid rgba(52, 209, 191, 0.4);
  color: var(--accent-2);
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  margin-bottom: 14px;
}

/* ---------- Modal / sheet ---------- */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(6, 5, 10, 0.6);
  backdrop-filter: blur(2px);
  display: none;
  align-items: flex-end;
  justify-content: center;
  z-index: 50;
}

.sheet-backdrop.open {
  display: flex;
}

.sheet {
  width: 100%;
  max-width: 480px;
  background: var(--bg-elev);
  border-radius: 24px 24px 0 0;
  padding: 22px 20px calc(28px + var(--safe-bottom));
  max-height: 82dvh;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-bottom: none;
  animation: sheet-up 0.22s ease;
}

@keyframes sheet-up {
  from { transform: translateY(24px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

.sheet-handle {
  width: 36px;
  height: 4px;
  border-radius: 999px;
  background: var(--border);
  margin: 0 auto 16px;
}

.sheet h2 {
  margin: 0 0 16px;
  font-size: 18px;
  overflow-wrap: anywhere;
}

.category-picker-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 40vh;
  overflow-y: auto;
  margin-bottom: 16px;
}

.category-picker-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
  border: 1px solid transparent;
  cursor: pointer;
}

.category-picker-item.selected {
  border-color: var(--accent);
}

.category-picker-item .icon {
  font-size: 18px;
}

/* ---------- Share preview card (share-handler page) ---------- */

.share-preview {
  display: flex;
  gap: 14px;
  padding: 16px;
  border-radius: var(--radius);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  margin-bottom: 24px;
}

.share-preview img,
.share-preview .placeholder {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: var(--bg-elev-2);
  display: grid;
  place-items: center;
  font-size: 26px;
  flex-shrink: 0;
}

.share-preview-body {
  min-width: 0;
}

.share-preview-title {
  font-weight: 700;
  font-size: 15px;
  margin-bottom: 4px;
  overflow-wrap: anywhere;
}

.share-preview-url {
  font-size: 12px;
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

/* ---------- Category detail header ---------- */

.category-hero {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}

.category-hero h1 {
  margin: 0;
  font-size: 21px;
}

.category-actions {
  display: flex;
  gap: 10px;
  margin-bottom: 24px;
  flex-wrap: wrap;
}

.category-actions .btn {
  width: auto;
  padding: 10px 16px;
  font-size: 13px;
}

/* ---------- Legal pages (Terms / Privacy) ---------- */

.legal-page {
  max-width: 640px;
  margin: 0 auto;
  padding: 28px 20px 60px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--text-dim);
}

.legal-updated {
  font-size: 12px;
  color: var(--text-faint);
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 28px;
}

.legal-page h2 {
  font-size: 16px;
  color: var(--text);
  margin: 28px 0 8px;
}

.legal-page h2:first-of-type {
  margin-top: 0;
}

.legal-page a {
  color: var(--accent);
}

.legal-page ul {
  margin: 8px 0;
  padding-left: 22px;
}

.legal-page li {
  margin: 4px 0;
}

/* ---------- Settings ---------- */

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 12px;
  padding: 15px 16px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 8px;
}

.settings-row:last-child {
  margin-bottom: 0;
}

.settings-label {
  font-size: 14px;
  color: var(--text-dim);
}

.settings-value {
  font-size: 14px;
  font-weight: 600;
}

.share-link-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px;
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  margin-bottom: 10px;
}

.share-link-row input {
  flex: 1;
  /* A flex item never shrinks below its own content's natural width
     without this - and this input's content is a full share URL, easily
     wider than a phone screen, which would otherwise push the whole row
     (and the page) wider than the viewport instead of just clipping the
     text in place. */
  min-width: 0;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 12px;
}

.share-link-row-full {
  margin-bottom: 14px;
}

.share-link-row-full .share-link-row {
  margin-bottom: 6px;
}

.share-link-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 8px;
  padding: 0 4px;
  font-size: 13px;
  color: var(--text-dim);
}

.share-tag-list {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 8px;
  margin-bottom: 18px;
  /* Caps the tag row itself, not the whole sheet - a board with many tags
     scrolls in here instead of pushing the rest of the popup (permission,
     notes, share button...) further down or off-screen. */
  max-height: 150px;
  overflow-y: auto;
  padding: 2px 4px 2px 2px;
}

.share-tag-option {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev-2);
  font-size: 13px;
  cursor: pointer;
  /* Inside .share-tag-list's flex-wrap row, this keeps every pill sized to
     its own label instead of stretching to the row's full width. */
  flex: 0 0 auto;
}

.share-tag-select-all {
  margin-bottom: 10px;
  border-style: dashed;
  color: var(--text-dim);
}

.share-tag-option input {
  accent-color: var(--accent);
}

/* A read-only list of someone else's shared links no edit/delete. */
.link-row-list-readonly .link-row-edit,
.link-row-list-readonly .link-row-delete {
  display: none;
}

/* ---------- Board notes (discussion sheet) ---------- */

.notes-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-height: 40vh;
  overflow-y: auto;
  margin-bottom: 16px;
}

.note-row {
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
}

.note-row-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
}

.note-row-author {
  font-weight: 700;
  font-size: 13px;
}

.note-row-time {
  font-size: 11px;
  color: var(--text-faint);
}

.note-row-text {
  font-size: 14px;
  line-height: 1.5;
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

.note-row-actions {
  display: flex;
  gap: 14px;
  margin-top: 8px;
}

.note-edit-input {
  width: 100%;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  padding: 8px 10px;
  resize: vertical;
}

/* ---------- Toasts ---------- */

#toast-host {
  position: fixed;
  top: calc(76px + var(--safe-top));
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  z-index: 100;
  pointer-events: none;
}

.toast {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 18px;
  border-radius: 999px;
  font-size: 13px;
  opacity: 0;
  transform: translateY(-8px);
  transition: all 0.25s ease;
  box-shadow: var(--shadow);
}

.toast.show {
  opacity: 1;
  transform: translateY(0);
}

.toast-error {
  border-color: rgba(255, 92, 122, 0.5);
  color: #ffb4c2;
}

.toast-success {
  border-color: rgba(52, 209, 191, 0.5);
}

/* ---------- Icon/color pickers ---------- */

.swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

.swatch {
  width: 38px;
  height: 38px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-size: 17px;
  border: 2px solid transparent;
  cursor: pointer;
  background: var(--bg-elev-2);
}

.swatch.color-swatch {
  font-size: 0;
}

.swatch.selected {
  border-color: var(--text);
}

.tag-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

/* A tag chip is a single button the whole thing toggles selection. There
   is no delete affordance here on purpose: deleting a tag is a separate,
   deliberate action (see "Manage tags" / .tag-manage-*) so a normal tap can
   never accidentally destroy something everyone else is using too. */
.tag-chip {
  display: inline-flex;
  align-items: center;
  padding: 10px 16px;
  border-radius: 999px;
  border: 2px solid var(--border);
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-size: 14px;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

.tag-chip.selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-elev-2));
  color: var(--text);
  font-weight: 600;
}

@media (hover: hover) {
  .tag-chip:not(.selected):hover {
    border-color: var(--text-faint);
    color: var(--text);
  }
}

/* ---------- Manage tags (separate, deliberate delete flow) ---------- */

.tag-manage-list {
  margin-bottom: 16px;
}

.tag-manage-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 15px;
}

.tag-manage-row:last-child {
  border-bottom: none;
}

.tag-manage-edit,
.tag-manage-delete {
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 18px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.tag-manage-delete {
  color: var(--danger);
}

.tag-manage-edit:hover {
  background: var(--bg-elev-2);
}

.tag-manage-delete:hover {
  background: rgba(255, 92, 122, 0.18);
}

/* ---------- Icon picker (button that opens the full emoji sheet) ---------- */
/* Styled like a profile-photo picker (big circle + pencil badge) a
   pattern normal users already recognize from WhatsApp/Instagram, so it
   doesn't need to explain itself with paragraphs of hint text. */

.icon-picker-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.icon-picker-current {
  position: relative;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--bg-elev-2);
  font-size: 28px;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  overflow: hidden;
}

.icon-picker-current img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.icon-picker-edit-badge {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  display: grid;
  place-items: center;
  font-size: 12px;
  border: 2px solid var(--bg-elev-1, var(--bg));
}

.icon-picker-hint {
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.4;
}

/* ---------- Full emoji picker sheet ---------- */

.emoji-tabs {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 8px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.emoji-tab {
  flex-shrink: 0;
  padding: 7px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-size: 12px;
  cursor: pointer;
}

.emoji-tab.active {
  border-color: var(--accent);
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-elev-2));
}

.emoji-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: 2px;
  max-height: 38vh;
  overflow-y: auto;
  margin-bottom: 14px;
}

.emoji-item {
  border: none;
  background: none;
  font-size: 22px;
  padding: 6px 0;
  border-radius: 8px;
  cursor: pointer;
  line-height: 1;
}

.emoji-item:hover {
  background: var(--bg-elev-2);
}

.spinner {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid rgba(0, 0, 0, 0.25);
  border-top-color: currentColor;
  animation: spin 0.7s linear infinite;
  display: inline-block;
}

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

.loading-center {
  display: flex;
  justify-content: center;
  padding: 60px 0;
  color: var(--text-faint);
}

.breadcrumbs {
  font-size: 13px;
  color: var(--text-faint);
  margin-bottom: 10px;
}

.breadcrumbs a {
  color: var(--text-dim);
}

/* ---------- Workspace (unified Grid / Map page) ---------- */
/* One page, one header. The header's view-switch flips body[data-view]
   between "grid" and "map"; everything below reacts to that purely with
   CSS, so page-map.js and the grid renderer never need to coordinate with
   each other directly. The 25% inbox strip lives outside the toggle
   entirely it's identical in both modes. */

body.workspace-page {
  padding-bottom: 0;
  overflow: hidden;
  height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* ---------- Welcome / about screen ---------- */
/* First-visit landing content, reachable again any time via the header
   logo. Its job is purely to explain the app and hand off into one of the
   two real views it isn't a working view itself, so it replaces the
   whole body below the header rather than living inside workspace-main. */

.welcome-screen {
  display: none;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

[data-view="welcome"] .welcome-screen {
  display: block;
}

[data-view="welcome"] .workspace-main {
  display: none;
}

[data-view="welcome"] .guest-banner {
  display: none;
}

[data-view="welcome"] .map-search,
[data-view="welcome"] .map-tag-filter-wrap,
[data-view="grid"] .map-tag-filter-wrap,
[data-view="grid"] #share-board-btn,
[data-view="welcome"] .grid-only-action,
[data-view="welcome"] .map-only-action {
  display: none !important;
}

.welcome-inner {
  max-width: 480px;
  margin: 0 auto;
  padding: 36px 24px 40px;
  text-align: center;
}

/* Mobile: illustration reads as a hero banner up top (order: -1 puts it
   before the text column, which is source-first for screen readers/no-CSS).
   Desktop (below) switches to a side-by-side row and un-reverses it. */
.welcome-hero {
  display: flex;
  flex-direction: column;
}

.welcome-hero-art {
  order: -1;
  max-width: 300px;
  margin: 0 auto 8px;
}

.hero-anim {
  width: 100%;
  height: auto;
  display: block;
}

.welcome-logo {
  display: inline-grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: 20px;
  background: var(--bg-elev-2);
  font-size: 30px;
  margin-bottom: 16px;
}

.welcome-inner h1 {
  margin: 0 0 8px;
  font-size: 26px;
  letter-spacing: -0.02em;
}

.welcome-tagline {
  margin: 0 0 16px;
  font-size: 15px;
  font-weight: 600;
  color: var(--accent-2);
}

.welcome-body {
  margin: 0 0 28px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-dim);
}

.welcome-choices {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 28px;
}

.welcome-choice-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  padding: 22px 20px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: linear-gradient(155deg, color-mix(in srgb, var(--accent) 14%, var(--bg-elev)), var(--bg-elev));
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

.welcome-choice-card:active {
  transform: scale(0.98);
}

@media (hover: hover) {
  .welcome-choice-card:hover {
    transform: translateY(-3px);
    border-color: var(--accent);
    box-shadow: 0 16px 34px -14px rgba(0, 0, 0, 0.65);
  }
}

.welcome-choice-icon {
  font-size: 28px;
  color: var(--accent);
  margin-bottom: 4px;
}

.welcome-choice-title {
  font-size: 16px;
  font-weight: 700;
}

.welcome-choice-desc {
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.45;
}

.welcome-features {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 13px;
  color: var(--text-faint);
  text-align: left;
}

.welcome-features li {
  display: flex;
  align-items: center;
  gap: 10px;
}

.welcome-features li::before {
  content: "";
  flex-shrink: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

/* Only on the welcome screen - the app's actual "index page" (first
   thing shown, scrollable marketing-style content) - not on the Map/List
   working views, which are a fixed-height app shell with no room to
   spare for a persistent footer bar without eating into the canvas/list
   itself. */
.app-footer {
  max-width: 480px;
  margin: 40px auto 0;
  padding: 20px 24px 32px;
  border-top: 1px solid var(--border);
  text-align: center;
}

.app-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 18px;
  margin-bottom: 10px;
}

.app-footer-links a {
  font-size: 13px;
  color: var(--text-dim);
  transition: color 0.15s ease;
}

@media (hover: hover) {
  .app-footer-links a:hover {
    color: var(--accent);
  }
}

.app-footer-copy {
  margin: 0;
  font-size: 12px;
  color: var(--text-faint);
}

/* Desktop gets a real two-column hero (illustration beside the copy,
   choice cards side by side) instead of the same narrow stacked column
   stretched wide this is the app's only breakpoint, scoped to the
   welcome screen specifically since it's the one place a wider viewport
   actually has room to do something different with. */
@media (min-width: 860px) {
  .welcome-inner {
    max-width: 1080px;
    padding: 88px 48px;
  }

  .welcome-hero {
    flex-direction: row;
    align-items: center;
    gap: 64px;
  }

  .welcome-hero-text {
    flex: 1 1 54%;
    text-align: left;
  }

  .welcome-tagline,
  .welcome-body {
    max-width: 46ch;
  }

  .welcome-hero-art {
    order: 0;
    flex: 1 1 46%;
    max-width: 420px;
    margin: 0;
  }

  .welcome-choices {
    flex-direction: row;
  }

  .welcome-choice-card {
    flex: 1 1 0;
  }

  .app-footer {
    max-width: 1080px;
    padding: 24px 48px 40px;
  }
}

/* Deliberately one line, no wrapping this is the "normal size" header
   the two view panes start right below. On a phone too narrow to fit every
   control at once, the row scrolls sideways rather than wrapping to a
   second line and pushing the 75% pane down. */
.workspace-header {
  flex: 0 0 auto;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 6px 16px -10px rgba(0, 0, 0, 0.5);
  padding: calc(10px + var(--safe-top)) 14px calc(10px + var(--safe-top) * 0);
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
}

.header-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: inherit;
  transition: opacity 0.15s ease;
}

@media (hover: hover) {
  .header-brand:hover {
    opacity: 0.82;
  }
}

/* Bigger than the 40px .icon-btn circles either side of it this is the
   one thing in the header that isn't just another action, so it reads as
   the anchor of the row rather than one more icon among equals. */
.workspace-header .brand-mark {
  width: 46px;
  height: 46px;
  border-radius: 14px;
  flex-shrink: 0;
  box-shadow: 0 2px 6px -2px rgba(0, 0, 0, 0.5);
}

.header-brand-name {
  font-weight: 800;
  font-size: 17px;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

/* Account-level controls (sync status, settings) read as a separate group
   from the content controls to their left a small visual cue that these
   are "about your account" rather than "about what you're looking at". */
.workspace-header-end {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  padding-left: 10px;
  border-left: 1px solid var(--border);
  /* The header scrolls horizontally instead of wrapping (see
     .workspace-header) once every icon+the search box can't all fit - on a
     narrow phone that used to mean Settings scrolled off the right edge
     with nothing on screen hinting it's still reachable. Sticking this
     group to the scroll container's own right edge keeps it in view (and
     tappable) no matter how far the header has scrolled. */
  position: sticky;
  right: 0;
  background: var(--bg-elev);
}

/* The sticky-right-edge trick above (plus its solid bg-elev fill) only
   earns its keep on .workspace-header, which actually scrolls
   horizontally. profile.html/map-settings.html/shared-links.html reuse
   this same element in a plain .topbar instead (gradient+blur background,
   never scrolls) - the solid fill there just reads as a stray box, so it's
   switched back to a normal, non-sticky, transparent group on those pages. */
.topbar .workspace-header-end {
  position: static;
  background: none;
}

/* Desktop/tablet: .workspace-header-row is purely a semantic grouping in
   the markup (nav controls vs. filter controls), not a layout box - these
   two wrapper divs disappear from the box tree entirely, so their
   children lay out as direct children of .workspace-header's own single
   flex row, exactly as if the wrappers weren't there at all. The `order`
   values below put them back in the right visual sequence, since
   otherwise every row-nav child would render before every row-filters
   child regardless of which one is source-first in the DOM. */
.workspace-header-row {
  display: contents;
}

.header-brand {
  order: 1;
}

.view-switch {
  order: 2;
}

.map-tag-filter-wrap {
  order: 3;
}

.map-search {
  order: 4;
}

.workspace-header-end {
  order: 5;
}

@media (max-width: 480px) {
  /* Phone width: the wrappers become real, full-width flex rows instead
     (overriding display:contents above), stacking nav controls (logo,
     List/Map toggle, share/help/avatar) on their own line above tag
     filter + search on a second one - nothing hidden, nothing needing the
     horizontal scroll .workspace-header still falls back to past this
     breakpoint if a row's own content ever runs long regardless (a very
     long email in "Log in"-state, say). */
  .header-brand-name {
    display: none;
  }

  .workspace-header {
    flex-wrap: wrap;
    row-gap: 8px;
    padding-left: calc(8px + env(safe-area-inset-left, 0px));
    padding-right: calc(8px + env(safe-area-inset-right, 0px));
    /* The base rule's overflow-y: hidden exists for the single-row
       desktop/tablet horizontal-scroll fallback - left in place here it
       would clip the second row entirely now that wrapping makes the
       header two rows tall on phone width. */
    overflow-y: visible;
  }

  .workspace-header-row {
    display: flex;
    align-items: center;
    gap: 5px;
    width: 100%;
  }

  .map-search {
    flex: 1 1 auto;
    max-width: none;
    min-width: 0;
  }

  .workspace-header-end {
    gap: 6px;
    padding-left: 6px;
  }

  .header-auth {
    gap: 6px;
  }

  .header-auth-btn {
    padding: 6px 10px;
    font-size: 12px;
  }

  .workspace-header .icon-btn {
    width: 30px;
    height: 30px;
    font-size: 13px;
  }

  .avatar-btn {
    width: 30px;
    height: 30px;
    font-size: 12px;
  }
}

.workspace-header .icon-btn {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  font-size: 15px;
}

.workspace-header .sync-dot {
  flex-shrink: 0;
  margin: 0 2px;
}

.header-auth {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 6px;
}

.header-auth-btn {
  flex-shrink: 0;
  white-space: nowrap;
  padding: 7px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: opacity 0.15s ease, background 0.15s ease;
}

.header-auth-btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #100e18;
}

@media (hover: hover) {
  .header-auth-btn:hover {
    opacity: 0.85;
  }
}

/* The one circular control in the header that stands for "you" - same
   34px footprint as the icon buttons either side of it, but filled with
   the account's own initial instead of an icon so it reads as identity,
   not another action. */
.avatar-menu {
  position: relative;
  flex-shrink: 0;
}

.avatar-btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  color: #100e18;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
  padding: 0;
}

.avatar-btn-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* position: fixed (not absolute) and placed via JS from the button's own
   getBoundingClientRect() - .workspace-header clips overflow-y (needed for
   its own horizontal icon scroll on narrow screens), which would otherwise
   clip this dropdown right along with it since an absolutely-positioned
   descendant stays clipped by that ancestor no matter its own z-index. */
.avatar-dropdown {
  position: fixed;
  min-width: 220px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 12px 28px -8px rgba(0, 0, 0, 0.6);
  padding: 6px;
  /* Above the map pane's own canvas/pins/hover-preview (highest there is
     .hover-preview at 60) and any open .sheet-backdrop (50) - only
     #toast-host (100) still outranks it, deliberately, so a toast fired
     from a dropdown action is never hidden behind the menu itself. */
  z-index: 90;
  display: none;
}

.avatar-dropdown.open {
  display: block;
}

.avatar-dropdown-header {
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}

.avatar-dropdown-name {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.avatar-dropdown-email {
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.avatar-dropdown-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 14px;
  text-align: left;
  border-radius: 8px;
  cursor: pointer;
  text-decoration: none;
}

.avatar-dropdown-item i {
  width: 16px;
  color: var(--text-dim);
  text-align: center;
}

@media (hover: hover) {
  .avatar-dropdown-item:hover {
    background: var(--bg-elev-2);
  }
}

.avatar-dropdown-item.danger {
  color: var(--danger);
}

.avatar-dropdown-item.danger i {
  color: var(--danger);
}

/* Grid/Map segmented toggle the one control this whole layout hinges on. */
.view-switch {
  display: flex;
  flex-shrink: 0;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
}

.view-switch-btn {
  border: 2px solid transparent;
  background: none;
  color: var(--text-dim);
  font-size: 14px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  cursor: pointer;
  white-space: nowrap;
  display: grid;
  place-items: center;
  transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}

/* Active state doesn't rely on color/background alone (color-blind users may
   not perceive the accent-vs-dim contrast) it also gets a visible ring
   border and a bolder icon weight, both shape/contrast cues. */
.view-switch-btn.active {
  background: var(--accent);
  color: #1a1006;
  border-color: var(--text);
  font-weight: 900;
}

@media (hover: hover) {
  .view-switch-btn:not(.active):hover {
    background: var(--bg-elev-2);
    color: var(--text);
  }
}

/* .grid-only-action ("+ New folder") stays grid-only a folder is a Grid
   concept with no map equivalent. Every other header action (search, add a
   link, map style, notes, share) used to be hidden here in Grid view too
   (".map-only-action"); now they stay visible in both modes none of them
   actually depend on the canvas being on-screen, they just act on the board
   or its items. .map-only-action itself is kept only for the welcome-screen
   hide rule above, not for anything mode-specific anymore. */
[data-view="map"] .grid-only-action {
  display: none !important;
}

/* Filters map pins by tag - sits to the left of the search box (not part
   of it), so it reads as its own independent filter rather than a search
   modifier. Same pill shape/height as the search box for a matched look. */
.map-tag-filter-wrap {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
}

.map-tag-filter {
  /* A native <select> ships its own OS-drawn box (background, border,
     dropdown arrow) that a plain border-radius/background override can't
     fully replace - the two just paint on top of each other, which is
     what read as a broken/lopsided oval instead of a clean pill.
     appearance: none hands all of that rendering to this CSS instead;
     the arrow removed this way is replaced by the .map-tag-filter-arrow
     icon below. */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 100%;
  max-width: 120px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 7px 26px 7px 14px;
  color: var(--text);
  font-size: 13px;
  line-height: 1.3;
  cursor: pointer;
}

.map-tag-filter-arrow {
  position: absolute;
  right: 11px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 9px;
  color: var(--text-faint);
  pointer-events: none;
}

@media (max-width: 480px) {
  .map-tag-filter {
    max-width: 64px;
    padding: 7px 20px 7px 10px;
    font-size: 12px;
  }

  .map-tag-filter-arrow {
    right: 8px;
  }
}

/* flex-basis/max-width capped (not flex-grow:1) so the box reads as a
   compact search field, not a bar stretching to fill whatever room the
   header has left - it now shares that space with .map-tag-filter too. */
.map-search {
  flex: 0 1 280px;
  max-width: 280px;
  min-width: 60px;
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 7px 12px;
}

.map-search input {
  flex: 1;
  min-width: 0;
  width: 100%;
  background: none;
  border: none;
  color: var(--text);
  font-size: 14px;
}

.map-search input:focus {
  outline: none;
}

.map-search-btn {
  flex: none;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: 14px;
  cursor: pointer;
}

.map-search-btn:hover {
  color: var(--accent);
}

.workspace-main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Sits between the header and the List/Map panes (same spot in the DOM
   for both - the workspace-body toggle below it doesn't touch this), so
   it reads as "true for whichever view you're looking at" rather than
   belonging to just one of them. Hidden on the welcome screen above (that
   screen already makes the logged-out state obvious via its own Grid
   card being hidden) and toggled by page-workspace.js based on isLoggedIn(). */
.guest-banner {
  flex: 0 0 auto;
  padding: 10px 16px;
  background: var(--bg-elev-2);
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--text-dim);
  text-align: center;
  border-bottom: 1px solid var(--border);
}

.workspace-body {
  flex: 1 1 75%;
  min-height: 0;
  display: grid;
}

/* Both panes always occupy the same grid cell (and so always have real,
   non-zero layout dimensions) only visibility/pointer-events toggle.
   That matters specifically for the map pane: fabric.js sizes its canvas
   from canvas-wrap's actual box on load, so it can never be display:none. */
.view-pane {
  grid-area: 1 / 1;
  min-height: 0;
  min-width: 0;
  visibility: hidden;
  pointer-events: none;
}

[data-view="grid"] .view-pane-grid,
[data-view="map"] .view-pane-map {
  visibility: visible;
  pointer-events: auto;
}

.view-pane-grid {
  overflow-y: auto;
  padding: 16px 20px 24px;
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
}

.map-canvas-wrap {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: var(--bg);
  /* background-image/size/position are set per-style below and kept in
     sync with pan/zoom from page-map.js (updateCanvasBackground). */
  background-position: 0 0;
  touch-action: none;
}

.map-canvas-wrap canvas {
  display: block;
  position: relative;
  z-index: 1;
}

/* A separate layer behind the canvas just for the background image, so the
   brightness setting below (settings.html - a per-device preference, see
   page-map.js's MAP_BRIGHTNESS_KEY) can dim the map itself without also
   dimming the pins/labels drawn on the canvas on top of it. Only page-map.js
   uses this - shared-map-overlay.js keeps painting its background straight
   onto its own wrapper element, so a shared link always shows the map at
   its default (unfiltered) brightness regardless of what the owner set. */
.map-bg-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-position: 0 0;
  filter: brightness(var(--map-brightness, 1));
}

/* The only map background: a real, detailed world map (full country
   outlines, not a stylized placeholder). page-map.js and
   shared-map-overlay.js both just apply this one class — there's no picker
   anymore, so background-size/position here are the only ones that matter,
   kept in sync with pan/zoom the same way the old multi-style picker did. */
.map-bg-world {
  background-color: #000;
  background-image: url("/img/world-map.svg");
  background-repeat: no-repeat;
}

.map-toolbar {
  position: absolute;
  right: 14px;
  bottom: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 5;
}

.map-toolbar button {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(26, 23, 37, 0.9);
  color: var(--text);
  font-size: 16px;
  cursor: pointer;
  position: relative;
  display: grid;
  place-items: center;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

@media (hover: hover) {
  .map-toolbar button:hover {
    background: var(--bg-elev-2);
    border-color: var(--accent);
  }
}

/* Small +/- badge layered on the pin-size buttons' location-dot icon,
   pinned to the corner so it reads as a modifier rather than a second
   separate icon. */
.map-toolbar-badge {
  position: absolute;
  right: 4px;
  bottom: 4px;
  font-size: 8px;
}

.map-toolbar-divider {
  height: 1px;
  background: var(--border);
  margin: 2px 4px;
}

/* flex-basis targets "roughly a quarter of the screen" on tall phones, but
   min-height is the one that actually matters: it's a hard floor sized to
   fit the label plus one full-height capsule (128px) without either being
   clipped on a short viewport, 25% alone could compute smaller than the
   tiles it needs to hold, which cropped their bottom edge off-screen. */
.map-inbox-wrap {
  flex: 0 0 10%;
  min-height: 134px;
  border-top: 1px solid var(--border);
  background: var(--bg-elev);
  display: flex;
  flex-direction: column;
}

/* The tray only makes sense next to the map itself (it's literally what
   you drag a pin onto/off of) - List view has its own drag targets
   (folder/tag sections, see page-workspace.js), so the 25% strip is just
   dead space eating into the list there. Higher specificity than
   .map-inbox-wrap's own `display: flex` above (an attribute + element
   selector beats a lone class), so this wins regardless of source order;
   the JS-driven `hidden` attribute (page-map.js's canEdit check) still
   wins over *this* too, since [hidden] carries !important further up this
   file - the two conditions combine correctly either way. */
body:not([data-view="map"]) .map-inbox-wrap {
  display: none;
}

.map-inbox-row {
  flex: 1;
  display: flex;
  gap: 10px;
  min-height: 0;
  padding: 6px 0 14px 16px;
}

.map-inbox-row .map-inbox {
  flex: 1;
  min-width: 0;
  padding: 0 16px 0 0;
}

/* Same footprint as an inbox card (.capsule), so "add" reads as the first
   slot in the tray rather than a control from somewhere else. A faint
   accent-tinted fill (not transparent) plus the "+" in its own solid
   accent badge (not just a colored glyph) is what actually catches the
   eye here - plain dashed-border-and-icon reads as empty space next to
   real capsule cards, not as a tappable action. */
.inbox-add-tile {
  flex: 0 0 104px;
  width: 104px;
  height: 84px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: var(--radius-sm);
  background: rgba(255, 122, 69, 0.08);
  border: 1px dashed var(--accent);
  color: var(--text);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.inbox-add-tile i {
  position: relative;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent);
  color: #1a1006;
  font-size: 15px;
  display: grid;
  place-items: center;
}

/* A ring expanding out from the badge and fading as it goes - the classic
   "notification ping" - draws the eye to the one actually-empty slot in
   the tray without animating the whole tile (which would compete with the
   real capsule cards sitting right next to it). ::after (not a second real
   element) so the badge's own icon/markup stays untouched. */
.inbox-add-tile i::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--accent);
  animation: inbox-add-ripple 1.8s ease-out infinite;
}

@keyframes inbox-add-ripple {
  0% {
    transform: scale(1);
    opacity: 0.6;
  }
  100% {
    transform: scale(1.9);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .inbox-add-tile i::after {
    animation: none;
  }
}

@media (hover: hover) {
  .inbox-add-tile:hover {
    background: rgba(255, 122, 69, 0.16);
  }
}

.map-inbox-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-faint);
  padding: 10px 16px 4px;
}

.map-inbox {
  flex: 1;
  display: flex;
  gap: 10px;
  padding: 6px 16px 14px;
  overflow-x: auto;
  overflow-y: hidden;
  align-items: flex-start;
}

.map-inbox-empty {
  color: var(--text-faint);
  font-size: 12px;
  padding: 10px 4px;
}

.capsule {
  position: relative;
  flex: 0 0 104px;
  width: 104px;
  height: 84px;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  overflow: hidden;
  cursor: grab;
  user-select: none;
}

.capsule.dragging {
  opacity: 0.4;
}

.link-row.dragging {
  opacity: 0.4;
}

/* Floating clone the touch-drag fallback drags around under the finger -
   position:fixed + left/top set directly in JS on every touchmove, same
   idea as the real OS-drawn drag image a mouse drag gets for free. */
.row-drag-ghost {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  opacity: 0.9;
  pointer-events: none;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.capsule.selected-for-placement {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}

.capsule-body {
  cursor: pointer;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.capsule-place-btn {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 2;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(16, 14, 24, 0.85);
  color: var(--text);
  font-size: 13px;
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
}

.capsule-delete-btn {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 2;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(16, 14, 24, 0.85);
  color: var(--text-dim);
  font-size: 11px;
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
}

@media (hover: hover) {
  .capsule-delete-btn:hover {
    border-color: var(--danger);
    color: var(--danger);
  }
}

.capsule-media {
  width: 100%;
  height: 28px;
  flex: 0 0 28px;
  object-fit: cover;
  display: block;
  background: var(--bg-elev);
}

.capsule-placeholder {
  width: 100%;
  height: 28px;
  flex: 0 0 28px;
  display: grid;
  place-items: center;
  font-size: 16px;
  background: var(--bg-elev);
}

/* Fixed to exactly the space left in the 128px card (64px media leaves
   64px), regardless of the title's own length a one-word title and a
   two-line title otherwise made same-type capsules different heights. */
.capsule-title {
  flex: 1;
  font-size: 10.5px;
  padding: 4px 6px 4px;
  line-height: 1.25;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.map-drop-target {
  outline: 2px dashed var(--accent);
  outline-offset: -6px;
}

.cat-section-drop-target {
  outline: 2px dashed var(--accent);
  outline-offset: 4px;
  border-radius: 8px;
}

.place-popover .swatch-row {
  margin-bottom: 20px;
}

/* Desktop-only hover preview for pins/capsules never shown on touch since
   there's no hover concept there (tap already opens the real detail view). */
.hover-preview {
  /* left/top are computed and clamped to the viewport in JS (page-map.js's
     positionHoverPreview) rather than via a fixed transform a transform
     offset alone can't know it's about to run off-screen near an edge. */
  position: fixed;
  z-index: 60;
  width: 220px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  overflow: hidden;
  pointer-events: none;
}

.hover-preview-media {
  width: 100%;
  height: 110px;
  object-fit: cover;
  display: block;
  background: var(--bg-elev-2);
}

/* Every link's detail-view preview is this exact size regardless of the
   source image's own dimensions object-fit: cover crops to fill it
   instead of letting each popup's height vary with a random aspect ratio. */
.detail-preview-img {
  width: 100%;
  height: 180px;
  object-fit: cover;
  display: block;
  border-radius: 14px;
  margin-bottom: 12px;
  background: var(--bg-elev-2);
}

.hover-preview-placeholder {
  width: 100%;
  height: 110px;
  display: grid;
  place-items: center;
  font-size: 30px;
  background: var(--bg-elev-2);
}

.hover-preview-body {
  padding: 10px 12px;
}

.hover-preview-title {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 4px;
  overflow-wrap: anywhere;
}

.hover-preview-url {
  font-size: 11px;
  color: var(--text-faint);
  overflow-wrap: anywhere;
}

.hover-preview-tags {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.hover-preview-date {
  margin-top: 6px;
  font-size: 11px;
  color: var(--text-faint);
}

.place-hint-bar {
  position: fixed;
  left: 50%;
  top: calc(64px + var(--safe-top));
  transform: translateX(-50%);
  z-index: 40;
  background: var(--accent);
  color: #1a1006;
  padding: 8px 16px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  box-shadow: var(--shadow);
}

/* ---------- Shared map overlay ----------
   A share link's board rendered as a popup on top of the viewer's own map,
   never mixed into it: own canvas, own state, own close button. Sits below
   .sheet-backdrop (z-index 50, for the notes/detail popups opened from
   inside it) and #toast-host (100), but above everything else on the page. */
.shared-map-overlay {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.shared-map-overlay-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(14px + var(--safe-top)) 16px 14px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
}

.shared-map-overlay-title {
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shared-map-overlay-close {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-elev-2);
  color: var(--text);
  font-size: 16px;
  cursor: pointer;
}

.shared-map-overlay-banner {
  flex: 0 0 auto;
  padding: 10px 16px;
  background: var(--bg-elev-2);
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--text-dim);
  text-align: center;
}

.shared-map-overlay-body {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
}

.shared-map-overlay-body canvas {
  position: absolute;
  inset: 0;
}

.shared-map-overlay-add {
  position: absolute;
  left: 16px;
  bottom: calc(16px + var(--safe-bottom));
  width: auto;
  padding: 10px 18px;
  box-shadow: var(--shadow);
}

/* --- Trash page --- */
.trash-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.trash-note {
  margin: 0;
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.4;
}

.trash-count {
  margin-left: 6px;
  font-size: 12px;
  color: var(--text-faint);
}

.trash-list {
  display: flex;
  flex-direction: column;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 20px;
}

.trash-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border-bottom: 1px solid var(--border);
}

.trash-row:last-child {
  border-bottom: none;
}

.trash-thumb {
  width: 56px;
  height: 56px;
  flex: none;
  border-radius: 10px;
  object-fit: cover;
}

.trash-thumb-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elev-2);
  font-size: 22px;
}

.trash-row-body {
  flex: 1;
  min-width: 0;
}

.trash-row-title {
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.trash-row-url {
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.trash-row-tags {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 6px;
}

.trash-row-meta {
  margin-top: 6px;
  font-size: 11px;
  color: var(--text-faint);
}

.trash-row-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  flex: none;
}

.trash-row-actions .btn {
  width: auto;
  padding: 8px 14px;
  font-size: 13px;
}

@media (max-width: 560px) {
  .trash-row {
    flex-wrap: wrap;
  }
  .trash-row-actions {
    flex-direction: row;
    width: 100%;
    justify-content: space-between;
  }
}

/* --- Notifications toggle (account menu) + one-time prompt --- */
.avatar-dropdown-toggle {
  display: flex;
  align-items: center;
  width: 100%;
}

.share-link-webhook-toggle {
  position: relative;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  cursor: pointer;
}

.avatar-toggle-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  text-align: left;
  line-height: 1.25;
}

.avatar-toggle-text small {
  font-size: 11px;
  color: var(--text-faint);
}

.switch {
  flex: none;
  position: relative;
  width: 34px;
  height: 20px;
  margin-left: 12px;
  border-radius: 999px;
  background: var(--border);
  transition: background-color 0.18s ease;
}

.switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transition: transform 0.18s ease;
}

[aria-checked="true"] > .switch {
  background: var(--accent);
}

[aria-checked="true"] > .switch::after {
  transform: translateX(14px);
}

.avatar-dropdown-toggle:disabled {
  opacity: 0.6;
  cursor: default;
}

.notify-prompt {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 70;
  width: min(440px, calc(100% - 32px));
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}

.notify-prompt > i {
  font-size: 18px;
  color: var(--accent);
}

.notify-prompt-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  font-size: 12px;
  line-height: 1.35;
  color: var(--text-dim);
}

.notify-prompt-text strong {
  font-size: 13px;
  color: var(--text);
}

.notify-prompt .btn {
  width: auto;
  padding: 8px 14px;
  font-size: 13px;
}

@media (max-width: 480px) {
  .notify-prompt {
    flex-wrap: wrap;
  }
  .notify-prompt-text {
    flex-basis: calc(100% - 40px);
  }
}

.share-link-edit-panel {
  margin-top: 8px;
}

/* --- Credits: header pill, dropdown row, buy sheet --- */
.header-credit-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 40px;
  box-sizing: border-box;
  padding: 0 14px;
  border-radius: 999px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
  cursor: pointer;
}

.header-credit-pill i {
  color: var(--accent);
  font-size: 12px;
}

.avatar-dropdown-credits {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 12px 10px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  color: var(--text-dim);
}

.avatar-dropdown-credits i {
  color: var(--accent);
}

.credit-pack-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.credit-pack {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
}

.credit-pack-amount {
  font-weight: 700;
  font-size: 14px;
}

.credit-pack-amount i {
  color: var(--accent);
}

.credit-pack-label {
  flex: 1;
  color: var(--text-faint);
  font-size: 12px;
}

.credit-pack-buy {
  width: auto;
  padding: 8px 16px;
}

@media (max-width: 560px) {
  .header-credit-pill {
    height: 36px;
    padding: 0 10px;
    font-size: 12px;
  }
}

#account-menu-mount {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* --- Credit usage / purchase history (Profile page) --- */
.history-list {
  display: flex;
  flex-direction: column;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 20px;
  overflow: hidden;
}

.history-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  flex-wrap: wrap;
}

.history-row:last-child {
  border-bottom: none;
}

.history-label {
  flex: 1;
  min-width: 0;
  max-width: 420px;
  color: var(--text);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.history-label-link {
  color: inherit;
  text-decoration: none;
}

.history-label-link:hover {
  color: var(--accent);
  text-decoration: underline;
}

.history-label-text {
  color: inherit;
}

.history-label i {
  color: var(--accent);
}

.history-amount {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.history-amount.positive {
  color: var(--accent-2);
}

.history-amount.negative {
  color: var(--danger);
}

.history-balance {
  color: var(--text-faint);
  font-size: 12px;
}

.history-time {
  color: var(--text-faint);
  font-size: 12px;
  white-space: nowrap;
}

.history-status-pending {
  color: var(--text-dim);
}

.history-status-completed {
  background: rgba(52, 209, 191, 0.15);
  color: var(--accent-2);
}

.history-status-failed {
  background: rgba(255, 92, 122, 0.15);
  color: var(--danger);
}

.history-status-refunded {
  color: var(--text-faint);
}

/* --- Pagination footer (credit history / purchases) --- */
.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 6px 0 20px;
}

.pagination-label {
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
}

.pagination .link-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Brief highlight for an inbox tile jumped to from elsewhere (e.g. a
   credit-history entry linking to a pin that's currently unplaced). */
.capsule-jump-highlight {
  animation: capsule-jump-pulse 2.2s ease-out;
}

@keyframes capsule-jump-pulse {
  0%, 30% {
    box-shadow: 0 0 0 3px var(--accent);
  }
  100% {
    box-shadow: 0 0 0 3px transparent;
  }
}
