@import url('https://fonts.googleapis.com/css2?family=Prata:ital,wght@0,400;0,500;1,400&family=Stoke:wght@300;400;500;600&display=swap');.auth-page {
  min-height: 100vh;
  min-height: 100svh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(2rem + env(safe-area-inset-top)) 1.5rem calc(3rem + env(safe-area-inset-bottom));
  background: radial-gradient(circle at 100% 100%, rgba(201, 167, 240, 0.1), transparent 45%),
    radial-gradient(circle at 80% 0%, rgba(157, 111, 212, 0.1), transparent 55%),
    var(--background);
  position: relative;
  overflow: hidden;
}

html[data-native-platform='ios'] .auth-page {
  min-height: 100dvh;
  height: auto;
  overflow: visible;
  align-items: flex-start;
  padding-top: calc(3rem + env(safe-area-inset-top));
  padding-bottom: calc(3rem + env(safe-area-inset-bottom));
  background: var(--background);
}

.auth-page::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, rgba(255, 255, 255, 0.1), transparent 45%, rgba(255, 255, 255, 0.03));
  opacity: 0.7;
  pointer-events: none;
}

.auth-container {
  position: relative;
  width: 100%;
  max-width: 440px;
  padding: 2.75rem;
  border-radius: 22px;
  border: 2px solid rgba(255, 255, 255, 0.1);
  background: rgba(89, 101, 170, 0.1);
  backdrop-filter: blur(10px);
  box-shadow: 0 32px 64px rgba(0, 0, 0, 0.5);
  animation: authFade 650ms cubic-bezier(0.2, 1, 0.3, 1);
  z-index: 1;
}

.auth-title {
  font-size: var(--fs-2xl);
  color: var(--text);
  margin-bottom: 0.4rem;
  letter-spacing: -0.02em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  width: 100%;
}

.auth-title-icon {
  width: 46px;
  height: 46px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.auth-title-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: drop-shadow(0px 2px 20px rgba(255, 255, 255, 0.2));
}

.auth-title-text {
  line-height: 1;
}

.auth-subtitle {
  text-align: center;
  color: var(--text-secondary);
  margin-bottom: 1.75rem;
}

.auth-tabs {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.6rem;
  background: rgba(255, 255, 255, 0.02);
  padding: 0.3rem;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.07);
}

.auth-tabs button {
  flex: 1;
  padding: 0.75rem;
  background: transparent;
  color: var(--text-secondary);
  font-weight: 600;
  border-radius: 10px;
  letter-spacing: -0.01em;
  transition: color 0.2s ease, background-color 0.2s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.auth-tabs button.active {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  box-shadow: none;
}

@media (hover: hover) and (pointer: fine) {
  .auth-tabs button:not(.active):hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.04);
  }
}

.auth-tabs button:active {
  transform: scale(0.97);
  transition: transform 80ms ease;
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.auth-field-switch {
  position: relative;
  min-height: 0;
  transition: height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}

.auth-field-group {
  display: grid;
  gap: 0.75rem;
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: translateY(-12px);
  pointer-events: none;
  transition: opacity 0.35s ease, transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}

.auth-field-group.is-active {
  position: relative;
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.auth-input {
  padding: 0.9rem 1rem;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  font-size: var(--fs-base);
  transition: border-color 0.25s ease, background-color 0.25s ease, transform 0.25s ease;
}

.auth-input:focus {
  border-color: rgba(201, 167, 240, 0.75);
  background: rgba(255, 255, 255, 0.06);
}

.auth-password-wrap {
  position: relative;
}

.auth-password-wrap .auth-input {
  width: 100%;
  padding-right: 3rem;
}

.auth-password-toggle {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.25rem;
  border-radius: 6px;
  transition: color 0.2s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .auth-password-toggle:hover {
    color: var(--text);
  }
}

.auth-password-hint {
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  margin: -0.4rem 0 0;
}

.auth-policy-check {
  display: flex;
  /* See TermsGate.css: centred against the box now that it is 24px, rather than
     flex-start with a margin nudging a smaller box onto the first line. */
  align-items: center;
  gap: 0.55rem;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  line-height: 1.45;
  cursor: pointer;
  min-height: 44px;
  padding: 0.25rem 0;
}

/* These were browser default, roughly 13px, which is half the WCAG 2.5.8
   minimum and well under what a thumb expects to hit. */
.auth-policy-check input[type='checkbox'] {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  accent-color: var(--primary);
  cursor: pointer;
}

.auth-forgot-link {
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  text-align: right;
  align-self: flex-end;
  padding: 0;
  margin-top: -0.25rem;
  transition: color 0.2s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .auth-forgot-link:hover {
    color: var(--primary);
  }
}

.auth-forgot-desc {
  color: var(--text-secondary);
  font-size: var(--fs-base);
  line-height: 1.5;
  margin: 0;
}

.auth-success {
  color: var(--success);
  font-size: var(--fs-sm);
  margin: -0.5rem 0 0;
}

.auth-back-link {
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: var(--fs-base);
  text-align: center;
  padding: 0.25rem;
  transition: color 0.2s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .auth-back-link:hover {
    color: var(--primary);
  }
}

.auth-privacy-message {
  margin: 1.5rem 0 1rem;
  padding: 0.875rem 1rem;
  background: rgba(157, 111, 212, 0.08);
  border: 1px solid rgba(157, 111, 212, 0.2);
  border-radius: 10px;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  text-align: center;
  line-height: 1.5;
}

.auth-privacy-message p {
  margin: 0;
  color: inherit;
}

.auth-legal-links {
  margin-top: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.auth-legal-links a {
  padding: 0.5rem 0.25rem;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.auth-error {
  color: var(--error);
  font-size: var(--fs-sm);
  margin: -0.5rem 0 0;
}

.auth-submit {
  padding: 1rem;
  background: linear-gradient(120deg, #9d6fd4 0%, #c9a7f0 100%);
  color: #f4fbff;
  border-radius: 14px;
  font-weight: 600;
  font-size: var(--fs-base);
  border: 1px solid rgba(15, 54, 105, 0.8);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), filter 0.3s ease, opacity 0.2s ease;
  box-shadow: none;
}
@media (hover: hover) and (pointer: fine) {
  .auth-submit:hover:not(:disabled) {
    filter: brightness(1.12);
  }
}

.auth-submit:disabled {
  opacity: 0.6;
  cursor: wait;
}

.auth-divider {
  display: flex;
  align-items: center;
  text-align: center;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  margin: 0.5rem 0;
  gap: 1rem;
}

.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.1);
}

.auth-google-btn {
  padding: 0.95rem 1rem;
  background: linear-gradient(135deg, #121826, #1f2a3d);
  color: #f5f7ff;
  border-radius: 12px;
  font-weight: 600;
  font-size: var(--fs-base);
  border: 1px solid rgba(255, 255, 255, 0.08);
  transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}
@media (hover: hover) and (pointer: fine) {
  .auth-google-btn:hover:not(:disabled) {
    background: linear-gradient(135deg, #182235, #25324a);
    box-shadow: 0 18px 32px rgba(0, 0, 0, 0.4);
  }
}

.auth-google-btn:disabled {
  opacity: 0.6;
  cursor: wait;
}

.auth-google-btn svg {
  flex-shrink: 0;
}

/* Apple Sign In: black pill per Apple HIG */
.auth-apple-btn {
  padding: 0.95rem 1rem;
  background: #000;
  color: #fff;
  border-radius: 12px;
  font-weight: 600;
  font-size: var(--fs-base);
  border: 1px solid rgba(255, 255, 255, 0.12);
  transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  width: 100%;
  margin-top: 0.6rem;
}
@media (hover: hover) and (pointer: fine) {
  .auth-apple-btn:hover:not(:disabled) {
    background: #111;
    box-shadow: 0 18px 32px rgba(0, 0, 0, 0.5);
  }
}

.auth-apple-btn:disabled {
  opacity: 0.6;
  cursor: wait;
}

.auth-apple-btn svg {
  flex-shrink: 0;
}

@keyframes authFade {
  from {
    opacity: 0;
    transform: translateY(18px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes surfaceDrift {
  from {
    transform: translate3d(-2%, -1%, 0);
    opacity: 0.55;
  }

  to {
    transform: translate3d(3%, 2%, 0);
    opacity: 0.85;
  }
}

@media (max-width: 640px) {
  .auth-container {
    padding: 2.25rem 1.75rem;
    border-radius: 18px;
  }

  .auth-tabs button {
    font-size: var(--fs-base);
  }
}

@media (max-width: 400px) {
  .auth-page {
    padding: 1rem 0 calc(2rem + env(safe-area-inset-bottom));
    align-items: flex-end;
  }

  .auth-container {
    width: 100%;
    border-radius: 20px 20px 0 0;
    padding: 2rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
    border-bottom: none;
  }
}
.home-page {
  min-height: 100vh;
  min-height: 100svh;
  min-height: 100dvh;
  color: var(--text);
  padding: clamp(1.25rem, 2vw, 2rem);
  max-width: 960px;
  margin: 0 auto;
}

.home-hero {
  margin-top: 4.5rem;
  padding: clamp(1.75rem, 2.2vw, 2.5rem);
  border-radius: 20px;
  border: 1px solid rgba(90, 188, 234, 0.28);
  background:
    radial-gradient(circle at 20% -10%, rgba(90, 188, 234, 0.24), transparent 35%),
    radial-gradient(circle at 90% 110%, rgba(10, 104, 255, 0.2), transparent 40%),
    rgba(18, 24, 48, 0.78);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}

.home-logo-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 1.1rem;
}

.home-logo-icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
}

.home-logo-text {
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.home-hero h1 {
  margin: 0;
  font-size: clamp(1.9rem, 4vw, 3rem);
  line-height: 1.12;
}

.home-subtitle {
  margin: 1rem 0 0;
  max-width: 68ch;
  color: var(--text-secondary);
  line-height: 1.6;
}

.home-cta-row {
  margin-top: 1.3rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.home-cta-primary,
.home-cta-secondary {
  min-height: 44px;
  padding: 0.72rem 1.1rem;
  border-radius: 10px;
  text-decoration: none;
  font-weight: 600;
  transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.home-cta-primary {
  background: linear-gradient(120deg, #9d6fd4, #c9a7f0);
  color: #f4fbff;
  transition: filter 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.home-cta-secondary {
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--text);
  transition: background-color 0.18s ease, border-color 0.18s ease, filter 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

@media (hover: hover) and (pointer: fine) {
  .home-cta-primary:hover {
    filter: brightness(1.08);
  }

  .home-cta-secondary:hover {
    background: rgba(255, 255, 255, 0.13);
    border-color: rgba(201, 167, 240, 0.45);
  }
}

.home-section {
  margin-top: 1.1rem;
  padding: clamp(1rem, 2vw, 1.4rem);
  border-radius: 16px;
  border: 1px solid var(--border);
  background: var(--surface);
}

.home-section h2 {
  margin: 0 0 0.85rem;
}

.home-feature-grid {
  display: grid;
  gap: 0.8rem;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.home-feature-card {
  padding: 0.9rem;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  /* Stagger off the card's own index so the three arrive in reading order
     rather than all at once. `backwards` fill, never `both`, so no transform
     is left on the card afterwards. */
  animation: slideInUp 0.32s ease-out backwards;
  animation-delay: calc(0.06s * var(--card-index, 0) + 0.08s);
}

.home-feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin-bottom: 0.6rem;
  border-radius: 10px;
  font-size: 0.95rem;
  color: var(--primary);
  background: rgba(201, 167, 240, 0.12);
  border: 1px solid rgba(201, 167, 240, 0.22);
}

.home-feature-card h3 {
  margin: 0 0 0.4rem;
  font-size: var(--fs-base);
}

.home-feature-card p {
  margin: 0;
  color: var(--text-secondary);
  line-height: 1.5;
}

/* The icons replace the bullets rather than sitting alongside them, so the
   list gains meaning without gaining clutter. */
.home-security ul {
  margin: 0;
  padding-left: 0;
  list-style: none;
  color: var(--text-secondary);
  line-height: 1.6;
  display: grid;
  gap: 0.5rem;
}

.home-security li {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

.home-security li > svg {
  flex-shrink: 0;
  width: 15px;
  margin-top: 0.28em;
  color: var(--primary);
  opacity: 0.85;
}

.home-footer {
  margin: 1.15rem 0 0.85rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  color: var(--text-secondary);
}

@media (prefers-reduced-motion: reduce) {
  .home-feature-card {
    animation: none;
  }
}
.loading-indicator {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  text-align: center;
  color: var(--text-secondary);
  letter-spacing: 0.02em;
  animation: fadeIn 0.2s ease-out;
}

.loading-indicator-start {
  align-items: flex-start;
  text-align: left;
}

.loading-track {
  position: relative;
  height: 6px;
  width: 160px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
  box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.25);
}

.loading-track-sm {
  width: 120px;
  height: 5px;
}

.loading-track-lg {
  width: 200px;
}

.loading-track::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(201, 167, 240, 0.3), transparent);
  animation: loading-track-glow 1.4s ease-in-out infinite;
}

.loading-orb {
  position: absolute;
  top: 50%;
  width: 14px;
  height: 14px;
  margin-top: -7px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(142, 214, 255, 0.95) 0%, rgba(201, 167, 240, 0.15) 65%);
  box-shadow: 0 0 12px rgba(138, 210, 255, 0.85);
  animation: loading-orbit 1.35s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

.loading-orb-delay {
  animation-delay: 0.24s;
  opacity: 0.6;
  transform: scale(0.9);
}

.loading-label {
  font-weight: 600;
  font-size: var(--fs-base);
  letter-spacing: 0.01em;
}

@keyframes loading-orbit {
  0% {
    transform: translateX(-35%) scale(0.75);
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  85% {
    opacity: 1;
  }
  100% {
    transform: translateX(135%) scale(0.75);
    opacity: 0;
  }
}

@keyframes loading-track-glow {
  0% {
    transform: translateX(-40%);
    opacity: 0.3;
  }
  50% {
    transform: translateX(35%);
    opacity: 0.65;
  }
  100% {
    transform: translateX(120%);
    opacity: 0.3;
  }
}
/* ── Shimmer base ────────────────────────────────────────────────────────── */

@keyframes shimmer {
  0%   { background-position: -800px 0; }
  100% { background-position:  800px 0; }
}

.skel {
  display: block;
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.05) 25%,
    rgba(255, 255, 255, 0.10) 50%,
    rgba(255, 255, 255, 0.05) 75%
  );
  background-size: 800px 100%;
  animation: shimmer 1.8s infinite linear;
  flex-shrink: 0;
}

.sk-mt    { margin-top: 0.65rem; }
.sk-mt-sm { margin-top: 0.4rem;  }

/* ── Journal ─────────────────────────────────────────────────────────────── */
/* Mirrors dreams-list: repeat(auto-fill, minmax(280px, 1fr)).               */
/* 2 columns need ≥560px of grid width (~600px screen); 3 need ~860px.       */

/* Mirrors .dreams-list: one hairline separated column, not a tile grid. */
.sk-journal-grid {
  display: flex;
  flex-direction: column;
}

.sk-journal-card {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.9rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;          /* matches .dream-card gap */
}

.sk-journal-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.sk-journal-footer {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.2rem;
}

/* ── Feed ────────────────────────────────────────────────────────────────── */

.sk-feed-list {
  display: flex;
  flex-direction: column;
}

/* Mirrors .feed-post: hairline separated stream row, avatar column, no card. */
.sk-feed-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.7rem;
  padding: 0.9rem 1.25rem 0.75rem;
  border-bottom: 1px solid var(--border);
}

.sk-feed-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.1rem;
}

/* 40px circle, matching .feed-avatar */
.sk-feed-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  flex-shrink: 0;
}

.sk-feed-main {
  min-width: 0;
}

.sk-feed-footer {
  display: flex;
  gap: 1.1rem;
  margin-top: 0.7rem;
}

/* ── Profile ─────────────────────────────────────────────────────────────── */

.sk-profile {
  max-width: 900px;
  margin: 0 auto;
}

.sk-profile-header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 1rem 1.25rem 0.5rem;
}

.sk-profile-identity {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  width: 100%;
}

.sk-profile-name {
  flex: 1 1 auto;
  min-width: 0;
}

.sk-profile-stats {
  display: flex;
  gap: 0.75rem;
  justify-content: flex-start;
  padding: 0.35rem 1.25rem 0.9rem 0.5rem;
  margin: 0;
}

.sk-stat-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  gap: 0.35rem;
  padding: 0.5rem 1rem;
}

.sk-profile-btn {
  display: block;
  margin: 0 1.25rem 1.5rem;
}

/* Mirrors .profile-dreams-head: the heading and the tab pair on one row. */
.sk-profile-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1.25rem 0.85rem;
}

.sk-profile-head-top {
  flex: 1 1 auto;
  min-width: 0;
}

.sk-profile-head-tabs {
  display: flex;
  gap: 1.25rem;
  flex-shrink: 0;
}

/* Mirrors .profile-dream-grid: hairline separated column, no tiles. */
.sk-profile-grid {
  display: flex;
  flex-direction: column;
}

.sk-profile-card {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.9rem 1.25rem;
  display: flex;
  flex-direction: column;
}

/* ── Dream Detail ────────────────────────────────────────────────────────── */
/* Mirrors .detail-card exactly so the skeleton fills the same space.        */

.sk-detail-card {
  margin-top: 0.5rem;
  padding: 0;
  border-radius: 0;
  border: none;
  background: none;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

@media (max-width: 640px) {
  .sk-detail-card {
    padding: 0;
  }
}

.sk-detail-toolbar {
  display: flex;
  align-items: center;
}

.sk-detail-title-row {
  display: flex;
  flex-direction: column;
}

.sk-detail-author {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.sk-detail-content {
  display: flex;
  flex-direction: column;
}

.sk-detail-ai {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
}

/* ── Insights ────────────────────────────────────────────────────────────── */

.sk-insights {
  max-width: 680px;
  margin: 0 auto;
  padding: 1.5rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.sk-insights-header {
  margin-bottom: 0.75rem;
}

.sk-insights-card {
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.1rem 1.25rem;
  display: flex;
  flex-direction: column;
}

.sk-insights-card-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.75rem;
}

.sk-insights-count {
  margin-left: auto;
  flex-shrink: 0;
}

/* ── Settings ────────────────────────────────────────────────────────────── */

.sk-settings {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.sk-settings-section {
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  padding-top: 0.35rem;
}

.sk-settings-head {
  padding: 1.25rem 0 0;
  display: flex;
  flex-direction: column;
}

.sk-settings-body {
  padding: 0.85rem 0 1.25rem;
  display: flex;
  flex-direction: column;
}

.sk-toggle-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.sk-toggle-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.sk-toggle-row:first-child {
  padding-top: 0;
}

.sk-toggle-label {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.sk-toggle-switch {
  flex-shrink: 0;
}

/* ── Activity ────────────────────────────────────────────────────────────── */

.sk-activity-list {
  display: flex;
  flex-direction: column;
}

.sk-activity-group {
  margin: 0.75rem 1.25rem 0.6rem;
}

.sk-activity-card {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.9rem 1.25rem;
}

.sk-activity-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.sk-activity-action {
  flex-shrink: 0;
  align-self: center;
}

/* ── Search ──────────────────────────────────────────────────────────────── */

.sk-people-grid {
  display: flex;
  flex-direction: column;
}

.sk-person-card {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.9rem 1.25rem;
}

.sk-dream-grid {
  display: flex;
  flex-direction: column;
}

.sk-search-dream-card {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.9rem 1.25rem;
  display: flex;
  flex-direction: column;
}

/* ── Reduced motion ──────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .skel {
    animation: none;
    background: rgba(255, 255, 255, 0.06);
  }
}

/* ── Legacy aliases (keep existing names working) ────────────────────────── */

.dream-card-skeleton { display: none; }
.profile-skeleton    { display: none; }
.list-skeleton       { display: none; }
.toast {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom) + 5rem);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(20, 26, 36, 0.95);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: #e8eef8;
  font-size: var(--fs-sm);
  line-height: 1.45;
  padding: 0.7rem 1.2rem;
  border-radius: 14px;
  white-space: normal;
  max-width: min(420px, calc(100vw - 2.5rem));
  text-align: center;
  z-index: 600;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
  cursor: pointer;
  animation: toastIn 220ms cubic-bezier(0.32, 0.72, 0, 1) forwards;
}

/* The -50% has to be repeated in every frame: a transform replaces the one on
   the base rule rather than adding to it, so dropping it would snap the toast
   to the right by half its width for the length of the animation. */
@keyframes toastIn {
  from { opacity: 0; transform: translateX(-50%) translateY(12px) scale(0.96); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

.toast.is-leaving {
  animation: toastOut 180ms ease-in forwards;
  pointer-events: none;
}

@keyframes toastOut {
  from { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
  to   { opacity: 0; transform: translateX(-50%) translateY(8px) scale(0.98); }
}

@media (prefers-reduced-motion: reduce) {
  .toast,
  .toast.is-leaving {
    animation: none;
  }
}
/* Voice dictation control. Sits inside the bottom-right of a textarea, so the
   idle state is deliberately quiet, a single mic glyph, and only expands into
   the waveform bar while actually listening. */

.voice-input {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  pointer-events: none;
}

.voice-input > * {
  pointer-events: auto;
}

/* ── Trigger ─────────────────────────────────────────────────────────────── */

.voice-input-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(20, 14, 35, 0.85);
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  cursor: pointer;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: color 0.18s ease, border-color 0.18s ease, background-color 0.18s ease, transform 0.18s ease;
  /* Holding the button is a supported gesture, so suppress the iOS long-press
     callout and text selection that would otherwise fire underneath it. */
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

.voice-input-btn:active:not(:disabled) {
  transform: scale(0.92);
}

.voice-input-btn:focus-visible {
  color: var(--primary);
  border-color: rgba(201, 167, 240, 0.5);
}

@media (hover: hover) and (pointer: fine) {
  .voice-input-btn:hover:not(:disabled) {
    color: var(--primary);
    border-color: rgba(201, 167, 240, 0.5);
  }
}

.voice-input-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.voice-input-btn.is-recording {
  color: #ffd9df;
  border-color: rgba(255, 120, 140, 0.7);
  background: rgba(120, 26, 44, 0.55);
  animation: voicePulse 1.9s ease-in-out infinite;
}

.voice-input-btn.is-busy {
  color: var(--primary);
  border-color: rgba(201, 167, 240, 0.45);
}

@keyframes voicePulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 120, 140, 0.32); }
  50%      { box-shadow: 0 0 0 7px rgba(255, 120, 140, 0); }
}

.voice-input-spinner {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid rgba(201, 167, 240, 0.28);
  border-top-color: var(--primary);
  animation: voiceSpin 0.7s linear infinite;
}

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

/* ── Live recording strip ────────────────────────────────────────────────── */

.voice-input-live {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  height: 36px;
  padding: 0 0.7rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 120, 140, 0.35);
  background: rgba(22, 12, 20, 0.9);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  animation: voiceStripIn 200ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

@keyframes voiceStripIn {
  from { opacity: 0; transform: translateX(8px) scaleX(0.9); }
  to   { opacity: 1; transform: translateX(0) scaleX(1); }
}

.voice-input-cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  cursor: pointer;
  transition: color 0.16s ease;
}

.voice-input-cancel:focus-visible {
  color: #ff9fb1;
}

.voice-input-cancel:active {
  transform: scale(0.88);
  transition: transform 70ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .voice-input-cancel:hover { color: #ff9fb1; }
}

/* ── Waveform ────────────────────────────────────────────────────────────── */

.voice-wave {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 20px;
  width: 104px;
}

.voice-wave-bar {
  flex: 1;
  min-width: 2px;
  height: 100%;
  border-radius: 2px;
  background: linear-gradient(180deg, #f0a8bb, var(--primary));
  transform: scaleY(0.08);
  transform-origin: center;
  will-change: transform;
}

/* The native engine reports one amplitude per buffer rather than a spectrum, so
   its bars scroll as a history and are sampled well under frame rate. Easing
   between samples is what keeps that from looking like a strobe. */
.voice-input.is-live .voice-wave-bar {
  transition: transform 70ms linear;
}

.voice-input-clock {
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  flex-shrink: 0;
}

.voice-input-clock.is-ending {
  color: #ff9fb1;
}

/* ── Live caption ────────────────────────────────────────────────────────── */

/* Sits above the field while recording. On iOS this fills with words as they
   are spoken; on the upload path there is nothing to show until the recording
   is sent, so it carries the stop affordance instead. */
.voice-caption {
  position: absolute;
  right: 0;
  bottom: calc(100% + 0.6rem);
  width: min(320px, calc(100vw - 3.5rem));
  padding: 0.6rem 0.75rem 0.5rem;
  border-radius: 14px;
  border: 1px solid rgba(201, 167, 240, 0.28);
  background: rgba(18, 12, 30, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  /* Purely a readout, so it must never swallow a tap meant for the field. */
  pointer-events: none;
  animation: voiceCaptionIn 180ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

@keyframes voiceCaptionIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.voice-caption-text {
  max-height: 5.2em;
  overflow-y: auto;
  font-size: var(--fs-sm);
  line-height: 1.45;
  color: var(--text);
  scrollbar-width: none;
}

.voice-caption-text::-webkit-scrollbar { display: none; }

.voice-caption-idle {
  color: var(--text-secondary);
  font-style: italic;
}

.voice-caption-hint {
  margin: 0.45rem 0 0;
  padding-top: 0.4rem;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  font-size: var(--fs-micro);
  color: var(--text-secondary);
}

/* ── First-run affordance ────────────────────────────────────────────────── */

/* People reach for a mic expecting to hold it. Holding does work, but tapping
   is the one-handed path, and nothing on screen said so. */
.voice-hint {
  position: absolute;
  right: calc(100% + 0.4rem);
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  padding: 0.25rem 0.55rem;
  border-radius: 999px;
  border: 1px solid rgba(201, 167, 240, 0.28);
  background: rgba(18, 12, 30, 0.9);
  color: var(--text-secondary);
  font-size: var(--fs-micro);
  pointer-events: none;
  animation: voiceHintIn 2.4s ease-in-out infinite;
}

@keyframes voiceHintIn {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* The bars are a readout of the user's own voice, not decoration, so they keep
   moving under reduced motion, and only the ambient pulse and slide are dropped. */
@media (prefers-reduced-motion: reduce) {
  .voice-input-btn.is-recording { animation: none; }
  .voice-input-live { animation: none; }
  .voice-caption { animation: none; }
  .voice-hint { animation: none; opacity: 1; }
}

/* ── Textarea integration ────────────────────────────────────────────────── */

.voice-field {
  position: relative;
  display: block;
}

.voice-field > textarea {
  display: block;
  width: 100%;
}

.voice-field-affordance {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 2;
}

/* Keep dictated text clear of the button while it sits over the field. */
.voice-field > textarea {
  padding-bottom: 52px;
}

@media (max-width: 420px) {
  .voice-wave { width: 78px; }
}
.dream-title-input {
  width: 100%;
  padding: 0.9rem 1rem;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-md);
  font-weight: 600;
  margin-bottom: 0.75rem;
  transition: border-color 0.22s ease, box-shadow 0.22s ease;
}

.dream-title-input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.18);
}

.dream-title-input::placeholder {
  color: var(--text-secondary);
  font-weight: 400;
}

.dream-date-section {
  margin-bottom: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.dream-date-section label {
  color: var(--text-secondary);
  font-size: var(--fs-base);
}

.dream-date-input {
  padding: 0.75rem 1rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-base);
  width: fit-content;
  max-width: 100%;
  transition: border-color 0.22s ease;
}

.dream-date-input:focus {
  border-color: var(--primary);
}

/* Same stream as the feed: one column, hairline separated, gutter on the row
   rather than on the page. It was a responsive tile grid, which is why the
   journal read as a dashboard of the same card repeated. */
.dreams-list {
  display: flex;
  flex-direction: column;
}

/* The list is full bleed now, so the divider needs the gutter the rows carry or
   it sits against the screen edge. It also needs air above it: it separates two
   years, so it should read as a bigger break than the hairline between two
   dreams, and at fs-xs in secondary it was quieter than the rows it divides. */
.dreams-year-header {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 0;
  padding: 2.25rem 1.25rem 0.7rem;
}

.dreams-year-header:first-child {
  padding-top: 1rem;
}

.dreams-year-header span {
  font-size: var(--fs-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text);
  white-space: nowrap;
}

.dreams-year-header::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

.journal-calendar-shell {
  display: grid;
  gap: 1rem;
}

.journal-calendar {
  border: none;
  border-radius: 0;
  background: none;
  padding: 0.5rem 0.25rem 1rem;
  box-shadow: none;
  /* Left to fill the page the columns stretch to ~150px and the month reads as
     a row of letterboxes. Capping it keeps the cells close to square. */
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  /* Clips the incoming month to the card, so it enters from the edge instead of
     briefly hanging outside the border. Nothing here needs to overflow. */
  overflow: hidden;
}

.calendar-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.calendar-toolbar h2 {
  margin: 0;
  font-size: var(--fs-md);
}

/* Month arrows. They were ← and → glyphs in a text button: about the size of a
   word, with the standard 0.965 button press that is far too subtle on
   something this small. Now a 44px circle, the iOS minimum, around a centred
   svg chevron, with the chip-tier press and a colour flash so a tap reads. */
.calendar-nav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font-size: var(--fs-base);
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}

@media (hover: hover) and (pointer: fine) {
  .calendar-nav-btn:hover {
    background: rgba(201, 167, 240, 0.14);
    border-color: rgba(201, 167, 240, 0.4);
    color: var(--primary);
  }
}

.calendar-nav-btn:active {
  transform: scale(0.93);
  background: rgba(201, 167, 240, 0.22);
  border-color: rgba(201, 167, 240, 0.5);
  color: var(--primary);
  transition: transform 70ms ease, background-color 70ms ease, border-color 70ms ease;
}

.calendar-weekdays {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.35rem;
  margin-bottom: 0.35rem;
}

.calendar-weekdays span {
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  font-weight: 600;
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.35rem;
}

.calendar-spacer {
  min-height: 54px;
}

.calendar-day {
  min-height: 54px;
  padding: 0;
  border: none;
  background: none;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  color: var(--text-secondary);
  -webkit-tap-highlight-color: transparent;
  transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* The dream marker. A night with an entry gets a lit dot rather than a count,
   one dream a night is the norm, and the exact number is in the panel below.
   The dot keeps its slot when empty so numbers never shift between days. */
.calendar-day::after {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: transparent;
  transition: background-color 0.18s ease, box-shadow 0.18s ease;
}

.calendar-day-number {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: var(--fs-sm);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

.calendar-day.has-dreams {
  color: var(--text);
}

.calendar-day.has-dreams .calendar-day-number {
  font-weight: 600;
}

.calendar-day.has-dreams::after {
  background: var(--primary);
  box-shadow: 0 0 6px rgba(201, 167, 240, 0.5);
}

.calendar-day.is-today .calendar-day-number {
  color: var(--primary);
  box-shadow: inset 0 0 0 1px rgba(201, 167, 240, 0.5);
}

/* After is-today on purpose: same specificity, so a selected today fills. */
.calendar-day.is-selected .calendar-day-number {
  background: var(--primary);
  color: var(--background);
  box-shadow: none;
  font-weight: 700;
}

/* Month navigation. The grid used to swap instantly, which read as a flicker
   rather than a move. Sliding it in from the side the month came from gives
   the arrows a direction. `backwards` fill so no transform is left behind.

   The travel and the fade are two separate animations on purpose. Running them
   as one is what made the first attempt invisible: a hard ease-out spends most
   of its distance in the first few frames, so by the time the month was solid
   enough to look at, it had already stopped moving. Splitting them lets the
   fade finish early and long before the slide does, so the part you actually
   watch is the part that still has somewhere to go. */
@keyframes calendarMonthNext {
  from { transform: translate3d(44px, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

@keyframes calendarMonthPrev {
  from { transform: translate3d(-44px, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* The label is a fraction of the grid's width, so matching its travel would
   read as the two racing rather than moving together. */
@keyframes calendarMonthLabelNext {
  from { transform: translate3d(16px, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

@keyframes calendarMonthLabelPrev {
  from { transform: translate3d(-16px, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

.calendar-grid[data-month-direction='next'] {
  animation: calendarMonthNext 380ms cubic-bezier(0.33, 1, 0.68, 1) backwards,
             fadeIn 140ms ease-out backwards;
}

.calendar-grid[data-month-direction='prev'] {
  animation: calendarMonthPrev 380ms cubic-bezier(0.33, 1, 0.68, 1) backwards,
             fadeIn 140ms ease-out backwards;
}

.calendar-toolbar h2[data-month-direction='next'] {
  animation: calendarMonthLabelNext 380ms cubic-bezier(0.33, 1, 0.68, 1) backwards,
             fadeIn 140ms ease-out backwards;
}

.calendar-toolbar h2[data-month-direction='prev'] {
  animation: calendarMonthLabelPrev 380ms cubic-bezier(0.33, 1, 0.68, 1) backwards,
             fadeIn 140ms ease-out backwards;
}

/* Switching over from the list view used to drop the calendar in fully formed. */
.journal-calendar-shell {
  animation: fadeIn 0.24s ease-out backwards;
}

.calendar-day-panel {
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
  box-shadow: none;
  padding: 1.1rem 0 0;
}

/* Keyed on the selected date in the JSX, so picking another day replays this
   instead of silently swapping the text underneath the cursor. A fade rather
   than a slide, because the dream cards inside already travel, and stacking the two
   would move them twice as far as anything else on the page. */
.calendar-day-panel-body {
  animation: fadeIn 0.2s ease-out backwards;
}

.calendar-day-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.8rem;
}

.calendar-day-panel-head h3 {
  margin: 0;
  font-size: var(--fs-base);
}

.calendar-day-panel-head p {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.dreams-list.calendar-dreams-list {
  margin-top: 0.25rem;
}

@media (min-width: 1001px) {
  .dreams-list.calendar-dreams-list {
    grid-template-columns: repeat(3, 1fr);
  }
}

.dream-card {
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  box-shadow: none;
  padding: 0.9rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  transition: background-color 0.18s ease, border-color 0.18s ease;
  isolation: isolate;
  cursor: pointer;
  position: relative;
}

.dream-card:last-child {
  border-bottom: none;
}
@media (hover: hover) and (pointer: fine) {
  .dream-card:hover {
    background-color: rgba(255, 255, 255, 0.022);
  }
}

.dream-card:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
}

.dream-card--pending {
  cursor: default;
  opacity: 0.6;
}

.dream-footer-meta {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.dream-tag-count {
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

/* meta row: date + visibility + AI badge */
.dream-meta {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: nowrap;
  min-width: 0;
  overflow: hidden;
}

/* Same badge as the feed, off the same tokens. */
.dream-vis-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  border: none;
  font-size: var(--fs-micro);
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
}

.dream-vis--public    { color: var(--vis-public);    background: var(--vis-public-soft); }
.dream-vis--followers { color: var(--vis-followers); background: var(--vis-followers-soft); }
.dream-vis--mutuals   { color: var(--vis-mutuals);   background: var(--vis-mutuals-soft); }
.dream-vis--anonymous { color: var(--vis-anonymous); background: var(--vis-anonymous-soft); }
.dream-vis--private   { color: var(--vis-private);   background: var(--vis-private-soft); }

.dream-ai-badge {
  font-size: 1.25rem;
  color: var(--accent);
  opacity: 0.9;
  flex-shrink: 0;
  letter-spacing: 0;
}

@media (max-width: 768px) {
  .dream-ai-badge {
    font-size: 1.75rem; /* 1.25rem × (16/14), restores 20px at 14px base */
  }
}

.dream-chevron {
  font-size: 1.25rem;
  color: var(--text-secondary);
  transition: transform 0.16s ease;
}
@media (hover: hover) and (pointer: fine) {
  .dream-card:hover .dream-chevron {
    color: var(--primary);
  }
}

.dream-title {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--text);
  font-size: var(--fs-lg);
  line-height: 1.25;
  margin: 0.1rem 0 0.15rem;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  line-clamp: 2;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.dream-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
}

.dream-date {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  white-space: nowrap;
  flex-shrink: 0;
}

.dream-content {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  color: var(--text);
  line-height: 1.5;
  font-size: var(--fs-base);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  line-clamp: 4;
  -webkit-line-clamp: 4;
  overflow: hidden;
  white-space: normal;
}

.dream-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.tags-section {
  display: grid;
  gap: 0.75rem;
}

.tags-input {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.tags-input input {
  flex: 1;
  padding: 0.8rem 0.9rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

.tags-input input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(127, 179, 194, 0.14);
}

.add-tag-btn {
  padding: 0.75rem 1rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface-muted);
  color: var(--text);
  font-weight: 600;
  transition: border-color 0.22s ease, color 0.22s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .add-tag-btn:hover {
    border-color: var(--primary);
    color: var(--primary);
  }
}

.tags-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.tag .remove-tag {
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  font-weight: 600;
  padding: 0 0.25rem;
}

.dream-textarea {
  width: 100%;
  border-radius: 14px;
  border: 1px solid var(--border);
  padding: 1rem;
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-base);
  line-height: 1.5;
  resize: vertical;
  min-height: 180px;
  transition: border-color 0.22s ease, box-shadow 0.22s ease;
  /* Ensure scrolling happens only within textarea, not the modal */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.dream-textarea:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.18);
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  backdrop-filter: blur(8px);
  padding: 1.25rem;
  touch-action: none;
}

.modal-content {
  background: #131219;
  border-radius: 20px;
  padding: 1.85rem;
  width: 100%;
  max-width: 580px;
  max-height: calc(100dvh - 2.5rem - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  padding-bottom: 1.85rem;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(16px);
  touch-action: pan-y;
}

/* Wrapper that holds the fade gradients outside the scrollable form */
.modal-form-wrap {
  flex: 1 1 auto;
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.modal-form-wrap::before,
.modal-form-wrap::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 3rem;
  pointer-events: none;
  z-index: 1;
  opacity: 1;
  transition: opacity 0.2s ease;
}

.modal-form-wrap::before {
  top: 0;
  background: linear-gradient(to bottom, #131219, transparent);
}

.modal-form-wrap::after {
  bottom: 0;
  background: linear-gradient(to top, #131219, transparent);
}

.modal-form-wrap.at-top::before {
  opacity: 0;
}

.modal-form-wrap.at-bottom::after {
  opacity: 0;
}

/* The form scrolls internally; the page behind never scrolls while modal is open */
.modal-content form {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 0;
  touch-action: pan-y;
  scrollbar-width: none;
}

.modal-content form::-webkit-scrollbar {
  display: none;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.close-btn {
  background: none;
  border: 1px solid var(--border);
  width: 38px;
  height: 38px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-size: 1.1rem;
  color: var(--text-secondary);
  transition: border-color 0.22s ease, color 0.22s ease, background-color 0.22s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .close-btn:hover {
    border-color: var(--primary);
    color: var(--primary);
    background: rgba(255, 255, 255, 0.04);
  }
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

.visibility-section {
  margin-top: 1rem;
  padding: 1.1rem 0 0;
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.visibility-section .section-label {
  font-weight: 600;
  margin: 0;
  color: var(--text);
}

.visibility-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.visibility-chip {
  border: 1px solid var(--border);
  border-radius: 100px;
  padding: 0.4rem 1rem;
  background: var(--surface);
  color: var(--text-secondary);
  font-weight: 600;
  transition: all 0.16s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

@media (hover: hover) and (pointer: fine) {
  .visibility-chip:not(.active):hover {
    border-color: rgba(201, 167, 240, 0.45);
    color: var(--text);
  }
}

.visibility-chip.active {
  color: var(--text);
}

.visibility-chip.active[data-vis='public'] {
  color: #34d399;
  border-color: rgba(52, 211, 153, 0.5);
  background: rgba(52, 211, 153, 0.1);
  box-shadow: 0 0 0 2px rgba(52, 211, 153, 0.15);
}

.visibility-chip.active[data-vis='followers'] {
  color: #c9a7f0;
  border-color: rgba(201, 167, 240, 0.5);
  background: rgba(201, 167, 240, 0.1);
  box-shadow: 0 0 0 2px rgba(201, 167, 240, 0.15);
}

.visibility-chip.active[data-vis='mutuals'] {
  color: #f2a8b8;
  border-color: rgba(242, 168, 184, 0.5);
  background: rgba(242, 168, 184, 0.1);
  box-shadow: 0 0 0 2px rgba(242, 168, 184, 0.15);
}

.visibility-chip.active[data-vis='anonymous'] {
  color: #f59e0b;
  border-color: rgba(245, 158, 11, 0.5);
  background: rgba(245, 158, 11, 0.1);
  box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.15);
}

.visibility-chip.active[data-vis='private'] {
  color: #f87171;
  border-color: rgba(248, 113, 113, 0.5);
  background: rgba(248, 113, 113, 0.1);
  box-shadow: 0 0 0 2px rgba(248, 113, 113, 0.15);
}

.visibility-chip:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.visibility-helper {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-base);
}

.audience-section,
.tag-people-section {
  margin-top: 1rem;
  padding: 1.1rem 0 0;
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.control-headline {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.section-helper {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-base);
}

.tag-people-input {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.tag-people-input input {
  flex: 1;
  min-width: 220px;
  padding: 0.8rem 0.95rem;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

.tag-people-input input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.15);
}

.tagged-pill-row {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.tagged-pill {
  display: inline-flex;
  gap: 0.35rem;
  align-items: center;
  padding: 0.35rem 0.8rem;
  border-radius: 100px;
  border: 1px solid var(--border);
  background: var(--surface-muted);
  color: var(--text);
  font-size: var(--fs-sm);
}

.tagged-pill button {
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font-size: var(--fs-base);
}

.status-hint {
  color: var(--primary);
}

.ai-hint {
  padding: 10px 0 0 0;
}

.tag-suggestion-list {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  padding: 0.35rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  box-shadow: var(--shadow-light);
  margin-top: 0.35rem;
}

.tag-suggestion-item {
  border: none;
  background: transparent;
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1), border-color 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
  .tag-suggestion-item:hover {
    background: rgba(201, 167, 240, 0.08);
  }
}

.suggestion-name {
  font-weight: 600;
  color: var(--text);
}

.suggestion-username {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.alert-banner {
  margin: 0.5rem 0;
  padding: 0.75rem 1rem;
  border-radius: 12px;
  border: 1px solid rgba(208, 104, 104, 0.35);
  background: rgba(208, 104, 104, 0.08);
  color: #f9d6d6;
  font-weight: 600;
}

.dreams-loading {
  text-align: center;
  color: var(--text-secondary);
  padding: 3rem 1rem;
  font-size: var(--fs-base);
  animation: fadeIn 0.22s ease-out;
}

.journal-header {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  animation: fadeIn 0.22s ease-out;
}

/* Title and the new dream button keep the page gutter; the tab bar underneath
   runs to both edges so its rule lines up with the row dividers below it. */
.journal-header-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  /* Explicit, because the mobile .page-header rule sets align-items: flex-start
     on the column above. Without it this row shrinks to its content and
     space-between has no room to push the button to the edge. */
  width: 100%;
  padding: 0 1.25rem 0.85rem;
}

.journal-header h1 {
  animation: slideInUp 0.22s ease-out 0.03s both;
}

.journal-header .page-subtitle {
  animation: slideInUp 0.22s ease-out 0.05s both;
}

.journal-header .action-group {
  animation: fadeIn 0.24s ease-out 0.08s both;
}


.dreams-list {
  animation: fadeIn 0.24s ease-out 0.1s both;
}

.journal-search-row {
  animation: fadeIn 0.22s ease-out 0.09s both;
}

.journal-search-input {
  width: 100%;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.7rem 1rem;
  color: var(--text);
  font-size: var(--fs-base);
  transition: border-color 0.18s ease, background-color 0.18s ease;
  box-sizing: border-box;
}

.journal-search-input::placeholder {
  color: var(--text-secondary);
}

.journal-search-input:focus {
  outline: none;
  border-color: rgba(201, 167, 240, 0.45);
  background: rgba(255, 255, 255, 0.07);
}

.dream-card {
  animation: slideInUp 0.22s ease-out both;
}

@media (max-width: 768px) {
  .modal-overlay {
    padding: 1rem;
  }
}

@media (max-width: 640px) {
  .calendar-day,
  .calendar-spacer {
    min-height: 46px;
    transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

  .calendar-day-panel-head {
    flex-direction: column;
    align-items: flex-start;
  }

  .dream-card {
    padding: 0.85rem;
    gap: 0.45rem;
  }

  .dream-title {
    line-clamp: 2;
    -webkit-line-clamp: 2;
  }

  .dream-content {
    font-size: var(--fs-sm);
    line-clamp: 3;
    -webkit-line-clamp: 3;
  }

  .modal-content {
    padding: 1.25rem;
  }

  .modal-actions {
    flex-direction: column-reverse;
    align-items: stretch;
  }
}

@media (max-width: 360px) {
  .dreams-list {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  .calendar-grid[data-month-direction],
  .calendar-toolbar h2[data-month-direction],
  .journal-calendar-shell,
  .calendar-day-panel-body {
    animation: none;
  }
}
.reaction-popover {
  position: fixed;
  z-index: 1200;
  pointer-events: none;
  padding-bottom: env(safe-area-inset-bottom);
}

.reaction-popover-card {
  pointer-events: auto;
  width: min(360px, 90vw);
  max-height: calc(100dvh - 160px);
  border-radius: 18px;
  background: rgba(15, 21, 36, 0.98);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 22px 50px rgba(6, 8, 18, 0.45);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  position: relative;
}

.reaction-popover-card::after {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  background: rgba(15, 21, 36, 0.98);
  border-left: 1px solid rgba(255, 255, 255, 0.08);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  transform: translate(-50%, 0) rotate(45deg);
  left: var(--pointer-offset, 50%);
}

.reaction-popover.placement-top .reaction-popover-card::after {
  bottom: -8px;
}

.reaction-popover.placement-bottom .reaction-popover-card::after {
  top: -8px;
}

.reaction-popover-head {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.reaction-popover-title {
  margin: 0;
  color: var(--text, #fff);
  font-weight: 600;
  font-size: var(--fs-base);
}

.reaction-popover-subtitle {
  margin: 0;
  color: var(--text-secondary, rgba(255, 255, 255, 0.75));
  font-size: var(--fs-sm);
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.reaction-popover-emoji {
  font-size: 1.15rem;
  display: inline-flex;
  align-items: center;
  color: var(--text, #fff);
}

.reaction-popover-emoji-heart {
  color: var(--primary, #5ac8fa);
}

.reaction-popover-empty {
  margin: 0;
  color: var(--text-secondary, rgba(255, 255, 255, 0.75));
}

.reaction-popover-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  overflow-y: auto;
}

.reaction-popover-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.45rem;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.05);
}

.reaction-popover-avatar {
  width: 38px;
  height: 38px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 1rem;
  box-shadow: var(--shadow-light, 0 4px 16px rgba(0, 0, 0, 0.25));
}

.reaction-popover-avatar svg {
  width: 1em;
  height: 1em;
}

.reaction-popover-meta {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.reaction-popover-name {
  color: var(--text, #fff);
  font-weight: 600;
  font-size: var(--fs-base);
}

.reaction-popover-username {
  color: var(--primary, #5ac8fa);
  font-size: var(--fs-xs);
  font-weight: 600;
}
.confirm-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: 550;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 0 env(safe-area-inset-bottom);
}

@media (min-width: 480px) {
  .confirm-modal-backdrop {
    align-items: center;
    padding: 1.5rem;
  }
}

.confirm-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px 20px 0 0;
  width: 100%;
  max-width: 420px;
  padding: 1.75rem 1.5rem calc(1.75rem + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

@media (min-width: 480px) {
  .confirm-modal {
    border-radius: 20px;
    padding: 1.75rem 1.5rem;
  }
}

.confirm-modal-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  margin: 0;
}

.confirm-modal-message {
  font-size: var(--fs-base);
  color: var(--text-secondary);
  margin: 0;
  line-height: 1.5;
}

.confirm-modal-input-label {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.confirm-modal-input {
  background: var(--surface-hover);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  padding: 0.65rem 0.85rem;
  font-size: var(--fs-base);
  outline: none;
  transition: border-color 0.15s ease;
}

.confirm-modal-input:focus {
  border-color: var(--primary);
}

.confirm-modal-actions {
  display: flex;
  gap: 0.75rem;
  justify-content: flex-end;
  margin-top: 0.25rem;
}
.dream-detail-page {
  padding-top: calc(88px + env(safe-area-inset-top) + 0.75rem);
}

.dream-detail-page .detail-card {
  margin-top: 0.75rem;
}

/* This page was boxes inside boxes: a card around the dream, another around the
   text of it, another around each section, another around every comment. Four
   borders deep before you reach a word anyone wrote. Sections are separated by
   rules now and the page itself is the only container. */
.detail-card {
  margin-top: 0.5rem;
  padding: 0;
  border-radius: 0;
  border: none;
  background: none;
  box-shadow: none;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  animation: slideInUp 0.24s ease-out 0.05s both;
}

.detail-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-bottom: 0.25rem;
  animation: fadeIn 0.22s ease-out;
}

.detail-back-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.45rem 1.1rem;
  border-radius: 100px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font-weight: 600;
  letter-spacing: -0.01em;
  box-shadow: var(--shadow-light);
  transition: border-color 0.18s ease, background-color 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
@media (hover: hover) and (pointer: fine) {
  .detail-back-btn:hover {
    border-color: var(--primary);
    background: rgba(201, 167, 240, 0.12);
    color: var(--primary);
  }
}

.detail-back-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  font-size: 0.9rem;
}

.detail-head {
  display: flex;
  flex-wrap: nowrap;
  gap: 1rem;
  align-items: flex-start;
}

.detail-title-block {
  flex: 1;
  min-width: 240px;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 0px;
}

.detail-title {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  /* It is an h1, so it was inheriting Prata italic 600 and coming out as the
     only dream title in the app set in the display serif. Same face and weight
     as the feed and journal titles now, just at page-heading size. */
  font-family: inherit;
  font-style: normal;
  font-weight: 700;
  font-size: var(--fs-2xl);
  letter-spacing: -0.02em;
  line-height: 1.2;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
}

.detail-title-editable {
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1), color 0.18s ease;
}

.detail-date-block {
  padding: 0;
  width: 100%;
  min-width: 160px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  align-self: stretch;
  flex-wrap: wrap;
}

.detail-edit-content-btn {
  margin-left: auto;
  flex-shrink: 0;
}

.detail-date-pill {
  border: none;
  border-radius: 999px;
  padding: 0.4rem 0.95rem;
  min-height: 40px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text-secondary);
  font-weight: 600;
  letter-spacing: 0.02em;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--fs-base);
}

.detail-date-pill--interactive {
  position: relative;
  cursor: pointer;
  background: rgba(201, 167, 240, 0.05);
  color: var(--text);
  transition: border-color 0.18s ease, background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .detail-date-pill--interactive:hover {
    border-color: var(--primary);
    box-shadow: 0 0 0 2px rgba(201, 167, 240, 0.15);
  }
}

/* 44px target without making the badge itself that tall, same overlay the other
   small controls use. */
.detail-date-pill--interactive::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 44px;
  transform: translateY(-50%);
}

.detail-date-edit--standalone {
  justify-content: flex-end;
}

/* display, not opacity. At opacity 0 it still held its width and the pill's
   gap, so the date sat off-centre in a badge with an empty slot beside it. On
   touch there is no hover, so it simply never takes up room. */
.edit-hint {
  display: none;
  font-size: var(--fs-sm);
}
@media (hover: hover) and (pointer: fine) {
  .detail-title-editable:hover .edit-hint, .detail-date-pill--interactive:hover .edit-hint {
    display: inline;
  }
}

.detail-title-edit,
.detail-date-edit {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}

.detail-title-input {
  padding: 0.6rem 0.9rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-xl);
  font-weight: 600;
  flex: 1;
  min-width: 200px;
}

.detail-title-input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.15);
}

.detail-date-input {
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-base);
}

.detail-date-input:focus {
  border-color: var(--primary);
}

.detail-visibility {
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  padding: 1.1rem 0 0;
  background: none;
}

.detail-hint {
    margin: 0;
    color: var(--text-secondary);
    font-size: var(--fs-base);
}

.detail-audience,
.detail-tagged,
.detail-comments {
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  padding: 1.1rem 0 0;
  background: none;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.detail-share-accordion {
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
  box-shadow: none;
  padding: 1.1rem 0 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.detail-share-toggle {
  border: none;
  background: transparent;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  text-align: left;
  cursor: pointer;
  transition: opacity 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .detail-share-toggle:hover {
    opacity: 0.82;
  }
}

/* Was a `›` glyph rotated 90deg to point down. Flex centres a glyph's inline
   box, not its ink, and `›` sits high in its box with uneven side bearings, so
   it never sat in the middle of the circle, and rotating moved that offset to
   a different edge, which is why it looked wrong both open and closed. An svg
   chevron is centred in its own viewBox, so it lands dead centre and stays
   there when the 180deg flip points it back up. */
.share-toggle-icon {
  font-size: 14px;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  transition: transform 0.2s ease;
}

.share-toggle-icon.open {
  transform: rotate(180deg);
}

.detail-share-panel {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.comments-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.comment-thread {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.comment-thread.nested {
  gap: 0.4rem;
}

.comment-thread-children {
  margin-left: 1.5rem;
  padding-left: 1rem;
  border-left: 1px solid rgba(255, 255, 255, 0.08);
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.comment-card-reply {
  background: rgba(255, 255, 255, 0.03);
}

.comment-replies-toggle {
  align-self: flex-start;
  border: none;
  background: transparent;
  color: var(--primary);
  font-weight: 600;
  font-size: var(--fs-base);
  cursor: pointer;
  padding: 0.15rem 0;
  transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1), color 0.18s ease;
}

.comment-card {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.8rem 0;
  background: none;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.comment-card:last-child {
  border-bottom: none;
}

.comment-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
}

.comment-meta-identity {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.comment-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 0.72rem;
}

.comment-meta-names {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  align-items: baseline;
}

.comment-author {
  font-weight: 600;
  color: var(--text);
}

.comment-handle {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.comment-time {
  color: var(--text-secondary);
  font-size: var(--fs-xs);
}

.comment-body {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  margin: 0;
  color: var(--text);
  white-space: pre-wrap;
}

.comment-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.comment-action-links {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.comment-heart-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  position: relative;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text-secondary);
  padding: 0.35rem 0.8rem;
  font-size: var(--fs-base);
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Comments are a dense list, so this one takes the 44px target as an overlay
   rather than as height it would have to spend on every row. */
.comment-heart-btn::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 44px;
  transform: translate(-50%, -50%);
}

.comment-heart-btn svg {
  color: inherit;
}
@media (hover: hover) and (pointer: fine) {
  .comment-heart-btn:hover:not(:disabled) {
    border-color: rgba(201, 167, 240, 0.45);
    color: var(--text);
  }

  .comment-heart-btn.active:hover:not(:disabled) {
    color: var(--primary);
  }
}

.comment-heart-btn.active {
  border-color: rgba(201, 167, 240, 0.65);
  background: rgba(201, 167, 240, 0.12);
  color: var(--primary);
}

.comment-heart-btn.active svg {
  color: var(--primary);
}

.comment-heart-btn:disabled {
  opacity: 0.6;
  cursor: wait;
}

.comment-heart-count {
  font-size: var(--fs-sm);
}

.comment-reply-context {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  font-style: italic;
}

.comment-reply-btn,
.comment-report-btn,
.comment-delete-btn {
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  cursor: pointer;
  text-decoration: underline;
  transition: color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .comment-reply-btn:hover:not(:disabled) {
    color: var(--primary);
  }
}
@media (hover: hover) and (pointer: fine) {
  .comment-report-btn:hover:not(:disabled) {
    color: var(--primary);
  }
}
@media (hover: hover) and (pointer: fine) {
  .comment-delete-btn:hover:not(:disabled) {
    color: var(--text);
  }
}

.comment-reply-btn:disabled,
.comment-report-btn:disabled,
.comment-delete-btn:disabled {
  opacity: 0.5;
  cursor: wait;
}

.comment-composer {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Was a bare <button>, so it picked up no hover, no press and none of the
   button classes' transitions. */
.reply-cancel-btn {
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  padding: 0.25rem 0.35rem;
  transition: color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .reply-cancel-btn:hover {
    color: var(--text);
  }
}

.reply-cancel-btn:active {
  color: var(--text);
  transform: scale(0.94);
  transition: transform 70ms ease, color 70ms ease;
}

.reply-context {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  padding: 0.45rem 0.75rem;
}

.reply-context span {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.reply-context button {
  border: none;
  background: transparent;
  color: var(--primary);
  font-weight: 600;
  cursor: pointer;
  font-size: var(--fs-sm);
}

.comment-composer textarea {
  width: 100%;
  min-height: 50px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 0.75rem 0.9rem;
  line-height: 1.5;
  resize: vertical;
}

.comment-composer textarea:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.15);
}

.comment-composer-actions {
  display: flex;
  justify-content: flex-end;
}

.composer-status {
  margin: 0;
  color: var(--text-secondary);
}

.detail-section-head {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.tag-people-input {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.tag-people-input input {
  flex: 1;
  min-width: 220px;
  padding: 0.75rem 0.9rem;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

.tag-people-input input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.15);
}

.tagged-pill-row {
  display: flex;
  gap: 0.45rem;
  flex-wrap: wrap;
}

.tagged-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0.8rem;
  border-radius: 100px;
  border: 1px solid var(--border);
  background: var(--surface-muted);
  color: var(--text);
}

.tagged-pill button {
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.tag-suggestion-list {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  padding: 0.35rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  box-shadow: var(--shadow-light);
  margin-top: 0.35rem;
}

.tag-suggestion-item {
  border: none;
  background: transparent;
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 0.12rem;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1), border-color 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
  .tag-suggestion-item:hover {
    background: rgba(201, 167, 240, 0.08);
  }
}

.suggestion-name {
  font-weight: 600;
  color: var(--text);
}

.suggestion-username {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.detail-label {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  margin-bottom: 0.6rem;
}

.detail-visibility-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.detail-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.detail-tags-inline {
  margin: 1rem 0 0.5rem;
}

@media (max-width: 640px) {
  .detail-head {
    flex-direction: column;
    gap: 0.85rem;
  }

  .detail-title-block,
  .detail-date-block {
    width: 100%;
  }
}

/* The poster's row also carries the actions you can take on their dream, at the
   right end of it. They used to be two outlined buttons in the footer, level
   with Close, which gave reporting the same weight as leaving the page. */
.detail-author-block {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.65rem 0 0.1rem;
}

/* Alone in the row on an anonymous dream, where there is no author to show. */
.detail-author-menu {
  margin-left: auto;
}

.detail-author-btn {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
  transition: opacity 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .detail-author-btn:hover {
    opacity: 0.8;
  }
}

.detail-author-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.detail-author-meta {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.detail-author-name {
  font-weight: 600;
  font-size: var(--fs-base);
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.detail-author-handle {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.detail-body {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  padding: 0;
  border-radius: 0;
  border: none;
  background: none;
  font-size: var(--fs-md);
  line-height: 1.6;
  color: var(--text);
  white-space: pre-wrap;
}

.detail-reactions {
  margin: 0.9rem 0 0.4rem;
  padding-top: 1rem;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

/* The negative margin pays back the first chip's padding, so the heart lines up
   with the text above it rather than sitting a target's worth of padding in. */
.detail-reactions .reaction-buttons {
  margin-left: -0.7rem;
}

.detail-reactions .custom-emoji-popover {
  margin-top: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

.detail-reactions .custom-emoji-popover input {
  min-width: 140px;
  padding: 0.5rem 0.8rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
}

.detail-reactions .custom-emoji-popover input:focus {
  border-color: rgba(201, 167, 240, 0.6);
  outline: none;
  box-shadow: 0 0 0 2px rgba(201, 167, 240, 0.2);
}

.detail-textarea {
  width: 100%;
  min-height: 180px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-base);
  padding: 0.9rem 1rem;
  line-height: 1.5;
  margin-bottom: 1rem;
}

.detail-textarea:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.15);
}

.detail-tags-editor {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.detail-tag-input-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.detail-tag-input-row input {
  flex: 1;
  padding: 0.7rem 0.9rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

.detail-tag-input-row input:focus {
  border-color: var(--primary);
}

.detail-tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.detail-tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.75rem;
  border-radius: 100px;
  background: var(--surface-muted);
  border: 1px solid var(--border);
}

.detail-tag-remove {
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .detail-tag-remove:hover {
    color: var(--text);
  }
}

.detail-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
}

/* The AI reading gets a rule down its left the way the feed summary does, so it
   reads as a second voice rather than as a tinted panel. */
.detail-summary {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  border: none;
  border-left: 2px solid rgba(201, 167, 240, 0.32);
  border-radius: 0;
  padding: 0.15rem 0 0.15rem 0.9rem;
  background: none;
  margin-top: 0.9rem;
}

.detail-summary-text {
  flex: 1 1 auto;
}

.detail-summary-footer {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.detail-summary-controls {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@media (min-width: 1000px) {
  .detail-summary-footer {
    flex-direction: row;
    align-items: flex-start;
    gap: 1.25rem;
  }

  .detail-summary-controls {
    flex: 1 1 auto;
  }

  .ai-upgrade-cta {
    flex: 0 0 260px;
    margin-top: 0;
  }
}

.detail-summary h3 {
  margin: 0 0 0.25rem;
}

.detail-insight {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  margin: 0;
  color: var(--text);
}

.ai-disclaimer {
  margin: 0.55rem 0 0;
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  line-height: 1.3;
}

.detail-connections {
  margin: 1rem 0 0;
  padding: 0.9rem 0 0;
  background: none;
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
}

.detail-connections-label {
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--primary);
  margin: 0 0 0.55rem;
  opacity: 0.85;
}

.detail-connections-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.detail-connections-item {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  padding-left: 1rem;
  position: relative;
  line-height: 1.4;
}

.detail-connections-item::before {
  content: '↺';
  position: absolute;
  left: 0;
  color: var(--primary);
  opacity: 0.7;
  font-size: var(--fs-xs);
  top: 0.05em;
}

.ai-privacy-notice {
  margin: 0.75rem 0 0;
  padding: 0.75rem 1rem;
  background: rgba(216, 182, 255, 0.05);
  border-left: 3px solid rgba(157, 111, 212, 0.4);
  border-radius: 6px;
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  line-height: 1.3;
}

.detail-insight.muted {
  color: var(--text-secondary);
  font-style: italic;
}

.detail-status-message {
  margin: 0.25rem 0 0.5rem;
  color: var(--primary);
  font-size: var(--fs-base);
  font-weight: 500;
}

.ai-quota-badge {
  margin: 0 0 0.5rem;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  font-style: italic;
}

.reanalyze-controls {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  position: relative;
}

.reanalyze-controls .ghost-btn {
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
  .reanalyze-controls .ghost-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.08);
    border-color: var(--primary);
  }
}

/* First-analysis style picker. These classes had no CSS at all, so the picker
   rendered as unstyled text in the page flow and read as a stray expansion
   rather than as the choice it is.

   It stays inline rather than becoming an overlay on purpose: `.detail-card`
   has both a backdrop-filter and a `both`-filled animation, and either one
   makes it the containing block for `position: fixed` descendants, so a fixed
   overlay in here would size itself against the card, not the viewport. */
.prompt-selector-modal {
  display: block;
}

.prompt-selector-content {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
  border-radius: 14px;
  border: 1px solid rgba(201, 167, 240, 0.28);
  background: rgba(201, 167, 240, 0.05);
  animation: slideInUp 0.22s ease-out backwards;
}

.prompt-selector-content h3 {
  margin: 0;
  font-size: var(--fs-base);
}

.prompt-selector-hint {
  margin: 0 0 0.35rem;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

@media (prefers-reduced-motion: reduce) {
  .prompt-selector-content {
    animation: none;
  }
}

/* Sits in the flow rather than hanging off the button. Absolutely positioned it
   did not extend the page's scrollable area, so when the button was near the
   bottom the last options were below everything you could ever scroll to.
   Inline it pushes the page down and the list is always reachable, and it now
   matches the first-analysis picker, which is the same choice in the same
   place. */
.prompt-selector-panel {
  margin-top: 0.25rem;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
  border-radius: 14px;
  border: 1px solid rgba(201, 167, 240, 0.28);
  background: rgba(201, 167, 240, 0.05);
  animation: slideInUp 0.22s ease-out backwards;
}

@media (prefers-reduced-motion: reduce) {
  .prompt-selector-panel {
    animation: none;
  }
}

/* One column, because each row can open a description underneath it. Two
   columns left the expanded row dragging its neighbour's height with it. */
.prompt-options-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.prompt-selector-label {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--text);
}

/* Every option is one row of the same rhythm, so the two panels line up with
   each other and the grid does not sit tighter than the button above it. */
/* Confirm sits right of cancel and carries the filled treatment, so the
   committing action is the one the eye lands on and the two are never mistaken
   for each other. Right aligned because it closes the panel. */
.prompt-selector-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.35rem;
  flex-wrap: wrap;
}

.prompt-selector-actions .ghost-btn,
.prompt-selector-actions .primary-btn {
  padding: 0.55rem 1.1rem;
  font-size: var(--fs-sm);
}

.prompt-selector-actions .primary-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.prompt-option-btn {
  width: 100%;
  display: flex;
  flex-direction: column;
  text-align: left;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.prompt-option-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
}

.prompt-option-label {
  font-weight: 600;
}

/* Collapsed by default and revealed by selection, so only one description is
   ever open. max-height rather than a height animation: the text is two lines
   at most, and auto height cannot be transitioned. */
.prompt-option-desc {
  display: block;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  line-height: 1.5;
  transition: max-height 0.22s ease, opacity 0.18s ease, margin-top 0.22s ease;
}

.prompt-option-btn.is-selected .prompt-option-desc {
  max-height: 9rem;
  opacity: 1;
  margin-top: 0.4rem;
}

/* The chosen style. This is a real selected state now. Something is picked and
   nothing has run yet, so it is allowed to look selected. */
.prompt-option-btn.is-selected {
  border-color: var(--primary);
  background: rgba(201, 167, 240, 0.12);
}

@media (prefers-reduced-motion: reduce) {
  .prompt-option-desc { transition: none; }
}
@media (hover: hover) and (pointer: fine) {
  .prompt-option-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.08);
    border-color: var(--primary);
  }
}

.prompt-option-btn:disabled:not(.locked) {
  opacity: 0.5;
  cursor: wait;
}

.prompt-option-btn.locked {
  border-style: dashed;
  border-color: rgba(251, 191, 36, 0.35);
  opacity: 0.6;
  cursor: not-allowed;
  pointer-events: none;
  user-select: none;
}

.prompt-lock-icon {
  font-size: 0.7rem;
  margin-right: 0.35rem;
  color: rgba(251, 191, 36, 0.8);
  vertical-align: middle;
}

.prompt-pro-badge {
  display: inline-block;
  margin-left: 0.4rem;
  font-size: var(--fs-micro);
  font-weight: 600;
  padding: 0.1rem 0.35rem;
  border-radius: 0.25rem;
  background: rgba(251, 191, 36, 0.15);
  border: 1px solid rgba(251, 191, 36, 0.45);
  color: rgba(251, 191, 36, 0.9);
  vertical-align: middle;
  letter-spacing: 0.03em;
}

/* The style saved in Settings leads the list and spans the full width, so it
   reads as the top of the list rather than as whichever cell it happened to
   land in. The tint is what marks it; the tag is what stops the tint being
   read as "already chosen", which is the trap the old separate button fell
   into, so it looked selected while being the one thing not yet picked. */
.prompt-option-btn.current-prompt {
  border-color: rgba(255, 255, 255, 0.18);
}

.prompt-current-tag {
  margin-left: auto;
  flex-shrink: 0;
  padding: 0.1rem 0.4rem;
  border-radius: 0.25rem;
  border: 1px solid rgba(201, 167, 240, 0.4);
  background: rgba(201, 167, 240, 0.14);
  color: var(--primary);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.03em;
}

.ai-upgrade-cta {
  border: 1px dashed rgba(251, 191, 36, 0.45);
  border-radius: 10px;
  padding: 0.75rem;
  background: rgba(251, 191, 36, 0.08);
}

.ai-upgrade-cta p {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.ai-upgrade-cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.detail-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
}

.detail-error,
.detail-placeholder {
  margin-top: 2rem;
  padding: 1.25rem 0;
  border-radius: 0;
  border: none;
  background: none;
  color: var(--text-secondary);
  text-align: center;
}

.danger-btn {
  border-radius: 12px;
  padding: 0.8rem 1.2rem;
  font-weight: 600;
  border: 1px solid rgba(239, 68, 68, 0.4);
  background: rgba(239, 68, 68, 0.15);
  color: #ffe5e5;
  transition: background-color 0.16s ease, transform 0.16s ease;
}
@media (hover: hover) and (pointer: fine) {
  .danger-btn:hover:not(:disabled) {
    background: rgba(239, 68, 68, 0.28);
    border-color: rgba(239, 68, 68, 0.6);
  }
}

.danger-btn:disabled {
  opacity: 0.6;
  cursor: wait;
}

@media (max-width: 768px) {
  /* Nav is sticky on mobile, so it occupies flow space and needs no top padding */
  .dream-detail-page {
    padding-top: 0.75rem;
  }
}

@media (max-width: 640px) {
  .detail-card {
    padding: 1.5rem;
  }

  .detail-back-btn {
    width: 100%;
    justify-content: center;
  }

  .comment-thread-children {
    margin-left: 1rem;
    padding-left: 0.75rem;
  }
}

/* ── Report modal ─────────────────────────────────────────────────────────── */
.report-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: 500;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 0 env(safe-area-inset-bottom);
}

@media (min-width: 480px) {
  .report-modal-backdrop {
    align-items: center;
    padding: 1.5rem;
  }
}

.report-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px 20px 0 0;
  width: 100%;
  max-width: 480px;
  padding: 1.75rem 1.5rem calc(1.75rem + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

@media (min-width: 480px) {
  .report-modal {
    border-radius: 20px;
    padding: 1.75rem 1.5rem;
  }
}

.report-modal h3 {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  text-transform: capitalize;
}

.report-modal-desc {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  margin: 0;
}

.report-reasons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.report-reason-btn {
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  min-height: 44px;
}

.report-reason-btn.selected {
  border-color: #f87171;
  color: #f87171;
  background: rgba(248, 113, 113, 0.08);
}

.report-modal-actions {
  display: flex;
  gap: 0.75rem;
  justify-content: flex-end;
  margin-top: 0.25rem;
}

/* Generation placeholder. A button that only changes its label leaves the rest
   of the page looking exactly as it did, which is what reads as frozen. This
   puts the movement where the answer is going to appear. */
.analysis-pending {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding-top: 0.15rem;
}

.analysis-pending-line {
  height: 0.7rem;
  border-radius: 999px;
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.05) 0%,
    rgba(201, 167, 240, 0.16) 50%,
    rgba(255, 255, 255, 0.05) 100%
  );
  background-size: 200% 100%;
  animation: analysisShimmer 1.35s ease-in-out infinite;
}

.analysis-pending-line:nth-child(2) { width: 92%; animation-delay: 0.12s; }
.analysis-pending-line:nth-child(3) { width: 68%; animation-delay: 0.24s; }

@keyframes analysisShimmer {
  from { background-position: 140% 0; }
  to   { background-position: -40% 0; }
}

.analysis-pending-note {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.35rem 0 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* The bars carry no information, so under reduced motion the spinner in the
   note is left to do the work alone. */
@media (prefers-reduced-motion: reduce) {
  .analysis-pending-line {
    animation: none;
  }
}
.feed-header {
  display: flex;
  flex-direction: column;
  gap: 0;
  animation: fadeIn 0.22s ease-out;
}

.feed-header h1 {
  animation: slideInUp 0.22s ease-out 0.03s both;
}

.feed-header-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  width: 100%;
  padding: 0 1.25rem 0.85rem;
  animation: slideInUp 0.22s ease-out 0.03s both;
}

/* ── Feed tabs ─────────────────────────────────────────────────────────────── */

.feed-tab-count {
  font-size: var(--fs-micro);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-secondary);
  padding: 0.1rem 0.42rem;
  border-radius: 999px;
  font-weight: 500;
}

.feed-tab.active .feed-tab-count {
  background: rgba(201, 167, 240, 0.18);
  color: var(--primary);
}

/* ── The stream ────────────────────────────────────────────────────────────────
 * A post used to announce itself six ways at once: a border, a gradient fill, a
 * shadow, a 12px blur, a 16px radius and its own padding. None of that is how a
 * feed reads. What separates one post from the next here is a hairline and the
 * avatar column, and what separates title from body is type, not a box.
 *
 * The stream also runs to both screen edges. The page gutter moved onto the
 * post itself, so the divider is a full width rule rather than a line floating
 * between two margins.
 * --------------------------------------------------------------------------- */
.feed-list {
  display: flex;
  flex-direction: column;
  animation: fadeIn 0.24s ease-out 0.1s both;
}

.feed-post {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.7rem;
  padding: 0.9rem 1.25rem 0.75rem;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  animation: slideInUp 0.22s ease-out both;
  transition: background-color 0.18s ease;
}

.feed-post:last-child {
  border-bottom: none;
}
@media (hover: hover) and (pointer: fine) {
  .feed-post:hover {
    background-color: rgba(255, 255, 255, 0.022);
  }
}

/* The byline row. Name first, then everything else in one muted run, which is
   the line every social app opens a post with. */
.feed-post-head {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  min-height: 30px;
}

.feed-post-byline {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  min-width: 0;
  padding-top: 0.15rem;
}

.feed-post-meta {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  white-space: nowrap;
}

.feed-card-actions {
  position: relative;
  display: flex;
  align-items: flex-start;
  margin-left: auto;
  flex-shrink: 0;
}

.feed-card-menu-btn {
  width: 30px;
  height: 30px;
  border-radius: 999px;
  border: none;
  background: none;
  color: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: border-color 0.18s ease, background-color 0.18s ease, color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .feed-card-menu-btn:hover {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
  }
}

.feed-card-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  z-index: 3;
  min-width: 200px;
  padding: 0.45rem;
  border-radius: 16px;
  border: 1px solid var(--border);
  background: rgba(10, 14, 22, 0.98);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.feed-card-menu-item {
  width: 100%;
  border: none;
  background: transparent;
  color: var(--text);
  text-align: left;
  border-radius: 12px;
  padding: 0.75rem 0.85rem;
  font-weight: 600;
  min-height: 44px;
  transition: background-color 0.18s ease, border-color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .feed-card-menu-item:hover {
    background: rgba(255, 255, 255, 0.06);
  }
}

.feed-card-menu-item-danger {
  color: #ff9fb1;
}
@media (hover: hover) and (pointer: fine) {
  .feed-card-menu-item-danger:hover {
    background: rgba(255, 138, 171, 0.12);
  }
}

/* Circular, and smaller. A 14px rounded square reads as an app icon; a circle
   reads as a person, which is the whole point of the avatar column. */
.feed-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  flex-shrink: 0;
}

.feed-avatar--anon {
  filter: grayscale(1);
}

.feed-avatar svg {
  width: 1.1em;
  height: 1.1em;
}

/* A person's name is not a tag. It was a tinted pill, which made the author
   read as metadata about the post rather than as the person who wrote it. */
.feed-author-handle {
  color: var(--text);
  font-size: var(--fs-base);
  font-weight: 600;
  background: none;
  border: none;
  padding: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.feed-author-handle.feed-author-link {
  text-decoration: none;
  transition: color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .feed-author-handle.feed-author-link:hover {
    color: var(--primary);
  }
}

/* With the box gone, type has to do the separating. The title takes the app's
   display serif so it is distinct from the body at a glance rather than by
   being one notch larger. */
.feed-title {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  /* A serif at 1.1rem against a sans at 0.95rem is only a 1.16 ratio, which the
     eye reads as the same rank in a different face rather than as a heading.
     At lg it is 1.37, and the extra space underneath stops the title and the
     first line of the dream running together as one block. */
  margin: 0.3rem 0 0.4rem;
  /* Not the display serif. Prata ships a single 400 weight, so asking it for
     bold gets a browser-smeared faux bold rather than a real face. The body
     sans has a true 700, which is what actually makes a title carry, and it is
     what every text feed uses for a post title anyway. Size still does half the
     work: lg over a base body is a 1.37 ratio. */
  font-weight: 700;
  font-size: var(--fs-lg);
  line-height: 1.25;
  letter-spacing: -0.015em;
  color: var(--text);
}

/* The AI reading is a second voice, so it is set off by a rule rather than by
   being loud. It used to be primary-coloured and semibold, which made it
   compete with the dream it was describing. */
.feed-summary {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  margin: 0.55rem 0 0;
  padding-left: 0.7rem;
  border-left: 2px solid rgba(201, 167, 240, 0.32);
  color: var(--text-secondary);
  font-weight: 400;
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.feed-content {
  /* User-authored text: break long unbroken strings instead of overflowing. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  color: var(--text);
  line-height: 1.5;
  margin: 0;
}

.feed-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.65rem;
  margin-top: 0.55rem;
}

/* Tags were bordered pills, which is three containers deep inside a post. */
.feed-tags .tag {
  background: none;
  border: none;
  padding: 0;
  border-radius: 0;
  color: var(--primary);
  font-size: var(--fs-sm);
  font-weight: 500;
}

/* Bare icons with counts, the way every feed does it. The bordered chip made
   three reactions read as a row of buttons rather than as a footer. */
/* The negative margins pay back the first button's padding and absorb the row's
   own height, so a 44px target does not push the post's bottom edge down. */
.feed-reactions {
  margin: 0.15rem 0 -0.4rem -0.7rem;
}

.feed-reactions .reaction-button {
  margin-right: 0.5rem;
}

/* ── Empty + discovery states ──────────────────────────────────────────────── */
/* A dashed box around "nothing here yet" draws a container around an absence.
   Centred text in open space says it without the outline. */
.feed-empty-card {
  border: none;
  border-radius: 0;
  padding: 3rem 1.25rem;
  text-align: center;
  color: var(--text-secondary);
  background: none;
  box-shadow: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: center;
  animation: fadeIn 0.24s ease-out 0.1s both;
}

.feed-empty-card .primary-btn {
  margin-top: 0.5rem;
}

.feed-discovery-nudge {
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  padding: 0.95rem 0 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: none;
  animation: fadeIn 0.22s ease-out both;
}

.feed-discovery-nudge p {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.feed-discovery-nudge button {
  border: none;
  background: transparent;
  color: var(--primary);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  padding: 0;
  flex-shrink: 0;
}
@media (hover: hover) and (pointer: fine) {
  .feed-discovery-nudge button:hover {
    text-decoration: underline;
  }
}

/* ── Responsive ────────────────────────────────────────────────────────────── */
/* The stream is single column at every width now, so the grid collapse that
   used to live here has nothing left to do. A feed reading as two columns of
   tiles on a wide screen was most of why this looked like a dashboard. */
@media (max-width: 768px) {
  .feed-card-menu {
    min-width: 184px;
  }
}

/* ── Report modal (shared with DreamDetail) ──────────────────────────────── */
.report-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: 500;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 0 env(safe-area-inset-bottom);
}
@media (min-width: 480px) {
  .report-modal-backdrop { align-items: center; padding: 1.5rem; }
}
.report-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px 20px 0 0;
  width: 100%;
  max-width: 480px;
  padding: 1.75rem 1.5rem calc(1.75rem + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
@media (min-width: 480px) {
  .report-modal { border-radius: 20px; padding: 1.75rem 1.5rem; }
}
.report-modal h3 { font-size: var(--fs-md); font-weight: 600; color: var(--text); text-transform: capitalize; }
.report-modal-desc { font-size: var(--fs-sm); color: var(--text-secondary); margin: 0; }
.report-reasons { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.report-reason-btn {
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  min-height: 44px;
}
.report-reason-btn.selected { border-color: #f87171; color: #f87171; background: rgba(248, 113, 113, 0.08); }
.report-modal-actions { display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 0.25rem; }
.navigation {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding-top: env(safe-area-inset-top);
  background: rgba(13, 13, 17, 0.7);
  backdrop-filter: blur(14px) saturate(150%);
  border-bottom: 1px solid var(--border);
  z-index: 100;
  height: calc(88px + env(safe-area-inset-top));
  transition: background-color 0.2s ease, border-color 0.2s ease, height 0.34s cubic-bezier(0.22, 1, 0.36, 1);
  transform: translateZ(0);
  will-change: background-color;
}

.navigation-compact {
  height: 60px;
  background: rgba(10, 14, 18, 0.9);
}

.nav-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1rem;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  transform: translateZ(0);
}

.nav-logo {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.75rem;
  font-weight: bold;
  color: var(--text);
  letter-spacing: -0.02em;
  transition: color 0.2s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), filter 0.2s ease;
  transform: translateZ(0) scale(1);
  transform-origin: left center;
  will-change: transform;
  backface-visibility: hidden;
}

.nav-logo span {
  transition: opacity 0.22s ease, max-width 0.22s ease, transform 0.22s ease;
  max-width: 11rem;
  opacity: 1;
  transform: translateX(0);
  overflow: hidden;
  white-space: nowrap;
  color: var(--text);
  font: 700 1.25rem "Playfair Display", serif;
  margin-bottom:-1px;
}

.nav-logo-icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), border-radius 0.3s ease;
  transform: translateZ(0) scale(1);
  transform-origin: center;
  will-change: transform;
  backface-visibility: hidden;
  margin-right: -5px;
}
@media (hover: hover) and (pointer: fine) {
  .nav-logo:hover {
    color: var(--primary);
  }
}

.nav-links {
  display: flex;
  gap: 2rem;
  position: relative;
  align-items: center;
  padding-bottom: 12px;
  margin-top: 15px;
  overflow: visible;
  transform: translateZ(0);
  transition: margin-top 0.3s cubic-bezier(0.22, 1, 0.36, 1), padding-bottom 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

.nav-links a {
  color: var(--text-secondary);
  font-weight: 600;
  transition: color 0.18s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
  padding: 0.35rem 0;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  position: relative;
  transform: translateZ(0);
  will-change: transform;
  backface-visibility: hidden;
}
@media (hover: hover) and (pointer: fine) {
  .nav-links a:hover {
    color: var(--primary);
  }
}

.nav-links a.active {
  color: var(--primary);
}

.navigation:not(.navigation-compact) .nav-links a:not(.active) .nav-icon {
  opacity: 0.5;
}

.nav-icon-wrapper {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  min-width: 1.8rem;
  min-height: 1.8rem;
}

.nav-icon {
  font-size: 1.25rem;
  color: currentColor;
  opacity: 1;
  transition: opacity 0.24s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
  transform: translateZ(0) scale(1);
  will-change: transform, opacity;
  backface-visibility: hidden;
}

.nav-tab-label {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  color: var(--text);
  transition: opacity 0.22s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
  transform: translateZ(0) translateY(0);
  will-change: transform, opacity;
  backface-visibility: hidden;
}

.nav-activity-indicator {
  position: absolute;
  top: -0.5rem;
  right: -0.5rem;
  transform: none;
  padding: 0.15rem 0.36rem;
  min-width: 0.95rem;
  border-radius: 999px;
  background: var(--primary);
  color: #05070b;
  font-size: 0.58rem;
  font-weight: 600;
  line-height: 1;
  text-align: center;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(5, 10, 14, 0.4);
  z-index: 2;
  scale: 0.9;
}

.navigation-compact .nav-icon-wrapper {
  min-width: 1.6rem;
  min-height: 1.6rem;
}

.navigation-compact .nav-activity-indicator {
  top: -0.25rem;
  right: -0.25rem;
  padding: 0.12rem 0.3rem;
  min-width: 0.85rem;
  font-size: 0.5rem;
}

.navigation-compact .nav-logo {
  transform: translateZ(0) scale(1.08);
}

.navigation-compact .nav-logo span {
  max-width: 0;
  opacity: 0;
  transform: translateX(-5px);
}

.navigation-compact .nav-logo-icon {
  transform: translateZ(0) scale(1);
  border-radius: 6px;
}

.navigation-compact .nav-links {
  margin-top: 0;
  padding-bottom: 0px;
  align-items: center;
}

.navigation-compact .nav-links a {
  flex-direction: column;
  gap: 0.12rem;
  align-items: center;
}

.navigation-compact .nav-icon {
  opacity: 0.18;
  transform: translateZ(0) translateY(-2px) scale(0.88);
  pointer-events: none;
}

.navigation-compact .nav-tab-label {
  transform: translateZ(0) translateY(-2px);
}

@media (max-width: 768px) {
  /* Keep icons visible on mobile even when compact */
  .navigation-compact .nav-icon {
    opacity: 1;
    transform: translateZ(0) scale(1);
    pointer-events: auto;
  }
  .navigation {
    position: fixed;
    top: 0;
    height: calc(74px + env(safe-area-inset-top));
  }

  .navigation.navigation-compact {
    height: calc(48px + env(safe-area-inset-top));
  }

  .nav-container {
    padding: 0 0.75rem;
  }

  .nav-logo {
    font-size: 1rem;
  }

  .nav-logo-icon {
    width: 30px;
    height: 30px;
  }

  .navigation:not(.navigation-compact) .nav-logo {
    transform: translateZ(0) scale(1);
  }

  .navigation.navigation-compact .nav-logo {
    transform: translateZ(0) scale(1);
  }

  .navigation:not(.navigation-compact) .nav-logo-icon {
    transform: translateZ(0) scale(0.78);
  }

  .navigation.navigation-compact .nav-logo-icon {
    transform: translateZ(0) scale(1);
  }

  .nav-links {
    flex: 1;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0 0.25rem 10px;
    overflow-x: auto;
    overflow-y: visible;
    scrollbar-width: none;
  }

  .nav-links::-webkit-scrollbar {
    display: none;
  }

  .navigation:not(.navigation-compact) .nav-links a {
    padding: 0.65rem 0.6rem;
    flex-direction: column;
    gap: 0.25rem;
  }

  .nav-links a {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    min-width: 0;
    gap: 0.2rem;
    font-size: var(--fs-sm);
    padding: 0.35rem 0.4rem;
    border-radius: 12px;
    white-space: nowrap;
    flex-direction: row;
  }

  .navigation.navigation-compact .nav-links a {
    padding: 0.25rem 0.3rem;
    gap: 0.15rem;
    flex-direction: row;
    align-items: center;
    min-height: 44px;
  }

  .nav-icon {
    display: block;
    font-size: 1rem;
  }

  .navigation:not(.navigation-compact) .nav-icon {
    font-size: 1.3rem;
  }

  .nav-icon-wrapper {
    min-width: 1.4rem;
    min-height: 1.4rem;
  }

  .nav-tab-label {
    display: none;
    font-size: var(--fs-micro);
    line-height: 1;
  }

  .nav-activity-indicator {
    top: -0.35rem;
    right: -0.35rem;
    transform: none;
    padding: 0.13rem 0.28rem;
    min-width: 0.9rem;
    font-size: 0.55rem;
  }

  .navigation:not(.navigation-compact) .nav-tab-label {
    display: block;
  }
}

@media (max-width: 620px) {
  .nav-links {
    gap: 0.2rem;
  }

  .nav-links a {
    padding: 0.4rem 0.2rem;
  }
}

@media (max-width: 480px) {
  .nav-links a {
    gap: 0.2rem;
    font-size: var(--fs-xs);
    padding: 0.3rem 0.2rem;
  }
}

@media (min-width: 769px) {
  .navigation:not(.navigation-compact) .nav-links a {
    gap: 0.2rem;
  }

  .navigation:not(.navigation-compact) .nav-tab-label {
    margin-top: -0.08rem;
  }

  .navigation-compact .nav-links a {
    gap: 0.35rem;
    align-items: center;
  }

  .navigation-compact .nav-icon {
    opacity: 1;
    transform: translateZ(0) scale(0.92);
    pointer-events: auto;
    font-size: 1rem;
  }

  .navigation-compact .nav-tab-label {
    font-size: var(--fs-sm);
  }

  .nav-logo span {
    font-size: 1.5rem;
    margin-left: 5px;
    margin-bottom: 0;
  }
}

/* ── iOS native: top header ─────────────────────────────────────────────── */

.nav-ios-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: calc(52px + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
  display: flex;
  align-items: center;
  background: rgba(13, 13, 17, 0.7);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  z-index: 100;
  transform: translateY(0px);
  will-change: transform;
}

/* Three-column layout: back | logo | spacer */
.nav-ios-header-side {
  flex: 1;
  display: flex;
  align-items: center;
  min-width: 52px;
}

.nav-ios-header-right {
  justify-content: flex-end;
}

.nav-ios-header-center {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
}

.nav-ios-back-btn {
  background: none;
  color: var(--primary);
  font-size: var(--fs-lg);
  padding: 0 0.75rem;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
  transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.nav-ios-logo-icon {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  margin-bottom: 3px;
}

.nav-ios-logo-text {
  font-size: 1.15rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--text);
  font-family: 'Prata', serif;
}

/* ── iOS native: bottom tab bar ─────────────────────────────────────────── */

.nav-ios-tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(52px + env(safe-area-inset-bottom));
  padding-top: 15px;
  padding-bottom: env(safe-area-inset-bottom);
  display: flex;
  align-items: flex-start;
  background: rgba(13, 13, 17, 0.7);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  z-index: 100;
  transform: translateZ(0);
  transition: transform 0.25s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.18s ease;
}

/* `resize: 'native'` shrinks the web view, which would leave this fixed bar
   perched on top of the keyboard. A real UITabBar is covered by the keyboard,
   so slide it away instead, and give the text the room back. */
html.keyboard-open .nav-ios-tabbar {
  transform: translateZ(0) translateY(100%);
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .nav-ios-tabbar { transition: none; }
}

.nav-ios-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  color: var(--text-secondary);
  -webkit-tap-highlight-color: transparent;
  transition: color 0.15s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.nav-ios-tab .nav-icon-wrapper {
  min-width: 1.9rem;
  min-height: 1.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.nav-ios-tab .nav-icon {
  font-size: 1.6rem;
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1), color 0.15s ease;
  transform: translateZ(0) scale(1);
}

.nav-ios-tab .nav-tab-label {
  display: none;
}

.nav-ios-tab:not(.active) {
  opacity: 0.4;
}

/* iOS tints the selected tab with the app's accent and greys the rest. This bar
   was separating them by brightness alone, a white icon against a dimmed grey
   one, which is a difference you only notice by comparing two icons to each
   other. Hue does not need the comparison: purple is the one you are on. */
.nav-ios-tab.active {
  color: var(--primary);
}

.nav-ios-tab.active .nav-icon {
  transform: translateZ(0) scale(1.06);
}
.offline-indicator {
  background: linear-gradient(90deg, #ff5050 0%, #ff6b6b 100%);
  color: white;
  padding: 0.75rem 1rem;
  text-align: center;
  font-size: var(--fs-sm);
  font-weight: 500;
  box-shadow: 0 2px 8px rgba(255, 80, 80, 0.25);
  position: fixed;
  top: calc(88px + env(safe-area-inset-top));
  left: 0;
  right: 0;
  z-index: 90;
}

@media (max-width: 768px) {
  .offline-indicator {
    top: calc(74px + env(safe-area-inset-top));
  }
}

.offline-indicator-text {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.offline-indicator-text::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: white;
  display: inline-block;
  animation: pulse 2s infinite;
}

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

@media (max-width: 600px) {
  .offline-indicator {
    padding: 0.5rem;
    font-size: var(--fs-xs);
  }
}
/* Hidden everywhere by default. The keyboard-open class is only ever set on the
   native iOS shell, so this never appears on the web build. */
.keyboard-dismiss {
  display: none;
}

html[data-native-platform='ios'].keyboard-open .keyboard-dismiss {
  position: fixed;
  /* `resize: 'native'` shrinks the web view to sit on top of the keyboard, so
     the bottom of the viewport is the top of the keyboard. */
  right: 12px;
  bottom: 12px;
  /* Above the composer modal (1000) and the reaction popover (1200): the dream
     composer is itself a modal with a textarea in it. */
  z-index: 1400;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 38px;
  padding: 0 1rem;
  border-radius: 999px;
  /* Filled rather than another dark translucent pill. It sits over whatever the
     user is writing on an all-dark UI, so a tinted surface with light text read
     as one more piece of chrome, the one control that has to be findable
     without looking for it is the one that should not blend in. */
  border: none;
  background: linear-gradient(135deg, #c9a7f0, #9d6fd4);
  box-shadow: 0 6px 20px rgba(157, 111, 212, 0.45), 0 2px 8px rgba(0, 0, 0, 0.4);
  color: #17112b;
  font-size: var(--fs-sm);
  font-weight: 700;
  -webkit-tap-highlight-color: transparent;
  animation: keyboardDismissIn 160ms cubic-bezier(0.32, 0.72, 0, 1) backwards;
}

.keyboard-dismiss svg {
  font-size: 0.72rem;
  opacity: 0.8;
}

.keyboard-dismiss:active {
  transform: scale(0.93);
  transition: transform 70ms ease;
}

@keyframes keyboardDismissIn {
  from { opacity: 0; transform: translate3d(0, 8px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  html[data-native-platform='ios'].keyboard-open .keyboard-dismiss {
    animation: none;
  }
}
.error-boundary {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100dvh;
  padding: 2rem;
  background: linear-gradient(180deg, #0e0a14 0%, #17112b 55%, #0e0a14 100%);
}

.error-boundary-content {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 3rem;
  max-width: 500px;
  text-align: center;
  backdrop-filter: blur(10px);
}

.error-boundary-content h1 {
  font-size: var(--fs-2xl);
  margin-bottom: 1rem;
  color: var(--error);
}

.error-boundary-content p {
  color: var(--text-secondary);
  margin-bottom: 1.5rem;
  line-height: 1.5;
}

.error-details {
  margin: 1.5rem 0;
  text-align: left;
}

.error-details summary {
  cursor: pointer;
  color: var(--text-secondary);
  padding: 0.5rem;
  user-select: none;
}
@media (hover: hover) and (pointer: fine) {
  .error-details summary:hover {
    color: var(--text);
  }
}

.error-details pre {
  background: rgba(0, 0, 0, 0.5);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1rem;
  margin-top: 1rem;
  overflow-x: auto;
  font-size: var(--fs-sm);
  color: var(--error);
  white-space: pre-wrap;
  word-break: break-word;
}

.error-boundary-button {
  background: var(--primary-strong);
  color: white;
  border: none;
  padding: 0.75rem 2rem;
  border-radius: 8px;
  font-size: var(--fs-base);
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
  .error-boundary-button:hover {
    background: #0052cc;
    transform: translateY(-2px);
  }
}

.error-boundary-button:active {
  transform: translateY(0);
  transition: transform 80ms ease-out;
}
.terms-gate {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
  background: var(--bg);
}

.terms-gate-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 2rem 1.75rem;
  width: 100%;
  max-width: 400px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.terms-gate-icon img {
  width: 48px;
  height: 48px;
}

.terms-gate-title {
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text);
  margin: 0;
  text-align: center;
}

.terms-gate-desc {
  font-size: var(--fs-base);
  color: var(--text-secondary);
  margin: 0;
  text-align: center;
  line-height: 1.5;
}

.terms-gate-checks {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  width: 100%;
}

.terms-gate-check {
  display: flex;
  /* Centred, not flex-start. The old 16px box was nudged down with a margin to
     sit level with the first line of text, which is the usual trick when the
     box is smaller than the line box. At 24px it no longer is, so the text
     block gets centred against the box and the nudge goes away. */
  align-items: center;
  gap: 0.65rem;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  line-height: 1.5;
  cursor: pointer;
  /* The label toggles the box, so the row is the real target. Holding it to 44px
     gives the shorter of the two consents a full-height target instead of one
     line of text. */
  min-height: 44px;
  padding: 0.25rem 0;
}

.terms-gate-check input[type='checkbox'] {
  flex-shrink: 0;
  /* Was 16px. 24px is the WCAG 2.5.8 minimum, and it matters here because people
     aim at the box even though the whole label works. */
  width: 24px;
  height: 24px;
  accent-color: var(--primary);
  cursor: pointer;
}


.terms-gate-btn {
  width: 100%;
  margin-top: 0.25rem;
}


* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --safe-area-inset-top: env(safe-area-inset-top);
  --safe-area-inset-bottom: env(safe-area-inset-bottom);
  --primary: #c9a7f0;
  /* Links only. Brighter than --primary so it separates from body copy without
     losing ground against the background: 10.94:1, up from 9.48:1. */
  --link: #d9b3ff;
  --primary-strong: #9d6fd4;
  --accent: #e8c87a;
  --background: #0d0d11;
  --background-secondary: #13121a;
  --surface: rgba(255, 255, 255, 0.04);
  --surface-muted: rgba(255, 255, 255, 0.02);
  --text: #eeeafa;
  /* Secondary text used to be #cdbded, a 20% saturated purple. That sat only
     10 saturation points off --primary, so a link inside a sentence read as
     "slightly more purple" rather than as a link. Neutralising it widens that
     gap to 23 points and lets purple mean interactive across the whole app. It
     also lifts headings off body copy, 1.50:1 to 1.72:1. */
  --text-secondary: #b9b6c4;
  --border: rgba(255, 255, 255, 0.07);
  --shadow-soft: 0 20px 60px rgba(0, 0, 0, 0.35);
  --shadow-light: 0 10px 30px rgba(0, 0, 0, 0.28);
  --glow-primary: 0 0 50px rgba(157, 111, 212, 0.22);
  --error: #ff5050;
  --success: #2cd44e;

  /* Dream visibility. One colour per audience, used by the feed badge and the
     profile pill, which were carrying the same five hex values separately. */
  --vis-public: #34d399;
  --vis-public-soft: rgba(52, 211, 153, 0.13);
  --vis-followers: #c9a7f0;
  --vis-followers-soft: rgba(201, 167, 240, 0.14);
  --vis-mutuals: #f2a8b8;
  --vis-mutuals-soft: rgba(242, 168, 184, 0.13);
  --vis-anonymous: #f59e0b;
  --vis-anonymous-soft: rgba(245, 158, 11, 0.13);
  --vis-private: #f87171;
  --vis-private-soft: rgba(248, 113, 113, 0.13);
  --app-background-gradient: linear-gradient(180deg, #0d0d11 0%, #110f1c 55%, #0d0d11 100%);

  /* Type scale. One step per role. If two things are the same size they are
     meant to read as the same rank. Anything not on this scale was drift.
     Note the root shrinks to 14px under 768px, so every step scales with it.

     micro  count and status badges
     xs     timestamps, footnotes, helper text
     sm     secondary text, meta rows, small controls
     base   body copy and standard controls
     md     lead paragraphs, card titles, h3
     lg     sub-section headings
     xl     section headings, h2
     2xl    page titles, h1
     3xl    display

     Sizing a glyph, avatar or emoji is not typography, so those keep their own
     font-size values and are deliberately off this scale. */
  --fs-micro: 0.7rem;
  --fs-xs: 0.78rem;
  --fs-sm: 0.85rem;
  --fs-base: 0.95rem;
  --fs-md: 1.1rem;
  --fs-lg: 1.3rem;
  --fs-xl: 1.55rem;
  --fs-2xl: 1.85rem;
  --fs-3xl: 2.25rem;
}

html {
  background: var(--app-background-gradient);
  background-color: #0d0d11;
  min-height: 100%;
  overscroll-behavior: none;
  scroll-behavior: smooth;
  color-scheme: dark;
}

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 400;
  background: var(--app-background-gradient);
  background-color: #0e0a14;
  background-attachment: scroll;
  position: relative;
  color: var(--text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100dvh;
  overscroll-behavior: contain;
}

html[data-native-platform='ios'] {
  overscroll-behavior: auto;
  /* Prevent WKWebView's black background from bleeding through on rubber-band scroll */
  background: var(--background);
  background-color: var(--background);
}

html[data-native-platform='ios'] body {
  overscroll-behavior: auto;
  background-color: var(--background);
}

/* Scrollbars are a pointer affordance. On touch the content already tracks your
   finger, and iOS itself only ever flashes a transient indicator, so a bar
   sitting in the gutter is the single loudest tell that this is a web page
   inside an app. The native build hides every one of them: the document, inner
   overflow panes, modal bodies, textareas. The web build keeps them, where with
   a mouse the bar is the only cue that there is more below.
   The main document's indicator is drawn by WKWebView's own UIScrollView rather
   than by CSS, so it is turned off natively in AppDelegate.swift. */

html[data-native-platform='ios'],
html[data-native-platform='ios'] * {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

html[data-native-platform='ios']::-webkit-scrollbar,
html[data-native-platform='ios'] *::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

html.no-overscroll,
html.no-overscroll body {
  overscroll-behavior: none !important;
}

.page-title,
h1 {
  font-family: 'Prata', Georgia, serif;
  font-weight: 600;
  font-style: italic;
}

/* Headings carried no font-size at all, so any heading without a page specific
   rule fell through to the browser default of 2em/1.5em/1.17em, in em, which
   compounds with whatever it is nested in. That was the main reason the same
   rank of heading came out different sizes on different pages. */

h1 { font-size: var(--fs-2xl); }

h2 { font-size: var(--fs-xl); }

h3 { font-size: var(--fs-md); }

/* .dream-title was in this list, which is why the journal was the one page
   setting a dream title in the display serif. Dream titles are bold sans
   everywhere now, so it comes out. */

.entry-title,
.card-title {
  font-family: 'Prata', Georgia, serif;
  font-weight: 500;
}

h2, h3, .section-heading {
  font-weight: 500;
  color: #edeef6;
}

p {
  color: #d8d6e8;
}

.pull-refresh-indicator {
  position: fixed;
  top: calc(52px + env(safe-area-inset-top) + 24px);
  left: 50%;
  transform: translate(-50%, calc(-18px + (var(--pull-progress, 0) * 18px)));
  opacity: 0;
  pointer-events: none;
  z-index: 260;
  padding: 0.45rem 0.75rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(14, 10, 20, 0.86);
  color: #d7dfed;
  font-size: var(--fs-sm);
  letter-spacing: 0.01em;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: opacity 160ms ease, transform 160ms ease, border-color 160ms ease;
}

.pull-refresh-indicator.is-visible {
  opacity: 1;
}

.pull-refresh-indicator.is-armed {
  border-color: rgba(201, 167, 240, 0.8);
  color: #eef5ff;
}

.pull-refresh-indicator.is-refreshing {
  border-color: rgba(201, 167, 240, 0.8);
  color: #eef5ff;
  animation: pullRefreshPulse 900ms ease-in-out infinite;
}

@keyframes pullRefreshPulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

@keyframes slideInUp {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Page entrance. `backwards` fill only, because a lingering `transform` on a page root
   would make it the containing block for every `position: fixed` modal and
   toast inside it, so the transform must be gone once the animation ends. */

@keyframes pageEnter {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.page-container,
.insights-page,
.home-page {
  animation: pageEnter 200ms ease-out backwards;
}

/* iOS: replace the vertical fade with a directional slide, so a push reads as
   coming from the right and a back gesture as returning from the left. It is the
   single strongest cue that this is an app rather than a page. The curve is
   UIKit's navigation ease. Tabs keep the plain fade. */

@keyframes pageEnterForward {
  from { opacity: 0; transform: translate3d(20px, 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes pageEnterBack {
  from { opacity: 0; transform: translate3d(-20px, 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

html[data-native-platform='ios'] [data-nav-direction='forward'] .page-container,
html[data-native-platform='ios'] [data-nav-direction='forward'] .insights-page {
  animation: pageEnterForward 260ms cubic-bezier(0.32, 0.72, 0, 1) backwards;
}

html[data-native-platform='ios'] [data-nav-direction='back'] .page-container,
html[data-native-platform='ios'] [data-nav-direction='back'] .insights-page {
  animation: pageEnterBack 260ms cubic-bezier(0.32, 0.72, 0, 1) backwards;
}

html[data-native-platform='ios'] [data-nav-direction='tab'] .page-container,
html[data-native-platform='ios'] [data-nav-direction='tab'] .insights-page {
  animation: pageEnter 170ms ease-out backwards;
}

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* Inline activity spinner. Sized from the current font-size so it drops into a
   button label without needing its own dimensions. Static text alone ("Working
   …") reads as a frozen screen; something has to actually move. */

.inline-spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  opacity: 0.75;
  flex-shrink: 0;
  animation: inlineSpin 0.7s linear infinite;
}

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

/* Small round controls sit below the 44px minimum a finger actually needs. The
   fix is an invisible overlay rather than a bigger circle: the design keeps the
   size it was drawn at, the target grows, and nothing on the page shifts. */

.voice-input-btn,
.close-btn,
.feed-card-menu-btn {
  position: relative;
}

.voice-input-btn::after,
.close-btn::after,
.feed-card-menu-btn::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* Same idea for controls that are already wide enough and only short: a feed
   tab is 34px tall and a toggle 28px, so the overlay grows the height and
   leaves the width alone. The toggles are the ones that matter, since only the
   switch itself is the target here, not the settings row around it. */

.feed-tab,
.toggle-switch,
.profile-toggle-switch {
  position: relative;
}

.feed-tab::after,
.toggle-switch::after,
.profile-toggle-switch::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

.btn-loading {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
}

/* Reduced motion slows the spinner rather than stopping it, because a frozen ring is
   exactly the "is this broken?" signal it exists to prevent. */

@media (prefers-reduced-motion: reduce) {
  .inline-spinner { animation-duration: 2.4s; }
}

@keyframes glowSweep {
  0% { transform: translateX(-120%); opacity: 0; }
  45% { opacity: 0.35; }
  100% { transform: translateX(160%); opacity: 0; }
}

#root {
  min-height: 100dvh;
  position: relative;
  z-index: 2;
}

button {
  font-family: inherit;
  cursor: pointer;
  border: none;
  outline: none;
}

button:focus-visible,
a:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

input, textarea, select {
  font-family: inherit;
  outline: none;
}

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

/* Links sitting inside a sentence. Colour cannot carry these on its own here:
   clearing 3:1 against body copy would need a link brighter than white or dark
   enough to fail against the background, so the underline is what actually does
   the work. It is always on, because a hover-only underline never fires on a
   phone, which is where all of these are read. Defined once and nowhere else,
   so no component stylesheet can win the cascade and take it back off. */

.terms-gate-check a,
.auth-policy-check a,
.auth-legal-links a,
.home-footer a,
.settings-footnote a,
.legal-card a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: rgba(217, 179, 255, 0.5);
  transition: color 0.18s ease, text-decoration-color 0.18s ease, opacity 0.18s ease;
}

/* A link is the one control in the app you can tap without anything moving, so
   it needs a press of its own: the underline fills in and the whole thing dims
   for as long as the finger is down. */

@media (hover: hover) and (pointer: fine) {
  .terms-gate-check a:hover,
  .auth-policy-check a:hover,
  .auth-legal-links a:hover,
  .home-footer a:hover,
  .settings-footnote a:hover,
  .legal-card a:hover {
    text-decoration-color: var(--link);
  }
}

.terms-gate-check a:active,
.auth-policy-check a:active,
.auth-legal-links a:active,
.home-footer a:active,
.settings-footnote a:active,
.legal-card a:active {
  opacity: 0.65;
  text-decoration-color: var(--link);
  transition: opacity 60ms ease-out, text-decoration-color 60ms ease-out;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1rem;
}

.app-main--with-nav {
  padding-top: calc(88px + env(safe-area-inset-top));
}

html[data-native-platform='ios'] .app-main--with-nav {
  padding-top: calc(52px + env(safe-area-inset-top));
}

/* Reserve the tab bar's height on iOS, the home indicator's everywhere else. */

html[data-native-platform='ios'] .app-main {
  padding-bottom: calc(49px + env(safe-area-inset-bottom));
}

html:not([data-native-platform='ios']) .app-main {
  padding-bottom: env(safe-area-inset-bottom);
}

/* The keyboard hides the tab bar, so stop holding space for it, because otherwise the
   last line of a long entry sits behind a gap that no longer has anything in it. */

html[data-native-platform='ios'].keyboard-open .app-main {
  padding-bottom: 0;
}

.page-container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 24px 1.25rem 50px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}

.page-header h1 {
  font-size: var(--fs-3xl);
  letter-spacing: -0.02em;
  color: #eef2fb;
}

.page-subtitle {
  color: var(--text-secondary);
  font-size: var(--fs-base);
}

.action-group {
  display: flex;
  gap: 12px;
  align-items: center;
}

.primary-btn,
.secondary-btn,
.ghost-btn {
  border-radius: 12px;
  padding: 0.8rem 1.2rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  border: 1px solid transparent;
  transition: filter 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease, color 0.25s ease, opacity 0.22s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  background: var(--surface);
  color: var(--text);
  backdrop-filter: blur(10px);
}

.primary-btn {
  background: linear-gradient(135deg, #120d24, #1e1535);
  color: #ede8f5;
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: 0 10px 30px rgba(14, 10, 20, 0.2);
  position: relative;
  overflow: hidden;
}

.primary-btn::after {
  content: '';
  position: absolute;
  inset: -20%;
  background: linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.28) 45%, transparent 70%);
  transform: translateX(-120%);
  animation: glowSweep 8s ease-in-out infinite;
}

@media (hover: hover) and (pointer: fine) {
  .primary-btn:hover {
    filter: brightness(1.12);
    box-shadow: var(--shadow-soft), 0 0 20px rgba(201, 167, 240, 0.15);
  }
}

.primary-btn:disabled {
  opacity: 0.25;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.secondary-btn {
  background: var(--surface-muted);
  color: var(--text);
  border-color: var(--border);
}

@media (hover: hover) and (pointer: fine) {
  .secondary-btn:hover {
    border-color: var(--primary);
    color: var(--primary);
  }
}

/* This used to set `background: transparent`, which cancelled the surface the
   shared button rule gives it. With a transparent border as well there was
   nothing left to see: "Regenerate with different prompt" and "Find people"
   were indistinguishable from the text around them. Stripping containers off
   content is the point of the revamp; a control is the opposite case and has to
   look pressable. */

.ghost-btn {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text);
}

/* For buttons that sit inline with something else rather than in a button row. */

.ghost-btn--compact {
  padding: 0.4rem 0.85rem;
  font-size: var(--fs-sm);
  border-radius: 999px;
  min-height: 34px;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0.25rem 0.6rem;
  border-radius: 100px;
  color: var(--text-secondary);
  font-size: var(--fs-base);
  border: 1px solid var(--border);
  line-height: 1;
  transition: border-color 0.18s ease, color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.pill-active {
  background: rgba(201, 167, 240, 0.1);
  color: var(--text);
  border-color: var(--primary);
  box-shadow: var(--glow-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.25rem 0.8rem;
}

*:not(input):not(textarea) {
  -webkit-tap-highlight-color: transparent;
}

button, a, [role="button"] {
  touch-action: manipulation;
}

.primary-btn, .secondary-btn, .ghost-btn {
  min-height: 44px;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

.card-shell {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.5rem;
  box-shadow: var(--shadow-light);
  transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.16s ease, background-color 0.16s ease;
  backdrop-filter: blur(14px);
}

@media (hover: hover) and (pointer: fine) {
  .card-shell:hover {
    box-shadow: var(--shadow-soft);
    background: rgba(255, 255, 255, 0.07);
    border-color: rgba(255, 255, 255, 0.14);
  }
}

.empty-state {
  text-align: center;
  padding: 3rem 2rem;
  border: 1px dashed var(--border);
  border-radius: 14px;
  color: var(--text-secondary);
  background: var(--surface-muted);
  line-height: 1.7;
}

.empty-subtitle {
  color: var(--text-secondary);
  margin-top: 0.35rem;
}

.app-loading-shell {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

.lazy-route-loading {
  min-height: calc(100dvh - 120px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem calc(2rem + env(safe-area-inset-bottom));
}

html[data-native-platform='ios'] .lazy-route-loading {
  padding-top: calc(2rem + env(safe-area-inset-top) + 56px);
}

/* On iOS WebKit, transform is unreliable on flex items and the ✦ glyph has a
   larger bounding box than its visual. Collapsing line-height removes the
   oversized box so the glyph overflows symmetrically, then position:relative
   nudges it without triggering the flex+transform bug. */

html:not([data-native-platform='ios']) .dream-ai-badge {
  font-size: 1.25rem;
  margin-top: -0.1rem;
}

html[data-native-platform='ios'] .dream-ai-badge{
  line-height: 0;
  position: relative;
  top: -3px;
}

html[data-native-platform='ios'] .profile-ai-badge {
  line-height: 0;
  position: relative;
  top: -1px;
}

.loading-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
}

.loading-inline {
  padding: 0.35rem 0;
}

.pro-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2rem 0.42rem;
  border-radius: 5px;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  padding-top: 0.3rem;
  color: #fbbf24;
  background: rgba(251, 191, 36, 0.12);
  border: 1px solid rgba(251, 191, 36, 0.35);
  vertical-align: middle;
  line-height: 1;
  flex-shrink: 0;
}

/* A page whose main content is a stream runs edge to edge, so the tab bar and
   the row dividers can reach both sides. The gutter moves onto each row. Direct
   children that are not the stream keep the old gutter as a margin, so headers,
   banners and empty states stay inset. */

.stream-page {
  padding-left: 0;
  padding-right: 0;
  gap: 0;
}

/* Written as a base plus overrides rather than one long `:not()` chain. Every
   `:not(.foo)` carries the specificity of its argument, so nine of them made
   that selector (0,10,0) and it beat every attempt to opt an element out. This
   way the default is (0,1,0) and each exemption is (0,2,0), which is the order
   you would expect. */

.stream-page > * {
  margin: 1rem 1.25rem 0;
}

/* Full bleed: these run to both screen edges and each row pays for its own
   gutter, so the dividers reach the sides. Headers are here too because they
   carry their own padded row, which keeps the tab bar underneath full width. */

.stream-page > .feed-list,
.stream-page > .dreams-list,
.stream-page > .activity-list,
.stream-page > .connections-list,
.stream-page > .connection-list,
.stream-page > .search-results,
.stream-page > .activity-section,
.stream-page > .search-box,
.stream-page > .page-header,
.stream-page > .activity-head,
.stream-page > .connections-header,
.stream-page > .connections-tab-group,
.stream-page > .profile-dreams {
  margin: 0;
}

/* ── Tab bars ──────────────────────────────────────────────────────────────────
 * One pattern for every set of tabs in the app. They were four different
 * controls doing the same job: a pill inside a bordered track on the feed, a
 * segmented control on the journal, a rounded box on the profile, and a grid of
 * outlined buttons on connections. An underline says "you are here" without any
 * of them, and the tabs sit at their label width so the bar lines up with the
 * heading above rather than stretching across the screen.
 *
 * Listed by page selector rather than a shared class so the markup does not
 * have to change on five pages. New tab sets should use `.tab-bar`.
 * --------------------------------------------------------------------------- */

.tab-bar,
.feed-tabs,
.view-toggle,
.dream-tab-group,
.connections-tab-group {
  display: flex;
  gap: 1.5rem;
  width: 100%;
  padding: 0 1.25rem;
  margin: 0;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  overflow: visible;
  grid-template-columns: none;
}

.tab-bar-btn,
.feed-tab,
.view-toggle-btn,
.dream-tab,
.connections-tab {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  min-height: 44px;
  padding: 0.6rem 0.1rem;
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: var(--text-secondary);
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  width: auto;
  transition: color 0.18s ease, background-color 0.18s ease, border-color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Tabs had no press state at all, so tapping one did nothing until the list
   underneath changed. There is no border or background here to move, so the
   press is the label itself: it lights to full white and takes the same small
   scale the other label-sized controls take. */

.tab-bar-btn:active,
.feed-tab:active,
.view-toggle-btn:active,
.dream-tab:active,
.connections-tab:active {
  color: var(--text);
  transform: scale(0.94);
  transition: transform 70ms ease, color 70ms ease;
}

.tab-bar-btn.active,
.feed-tab.active,
.view-toggle-btn.active,
.dream-tab.active,
.connections-tab.active {
  color: var(--text);
  background: none;
  border: none;
  box-shadow: none;
}

/* ::before, because ::after is the 44px touch overlay. The bar spans the tab,
   and the tab is the width of its label, so it underlines the word. Every tab
   set in the app selects the same way: this bar and a step up in text colour,
   nothing else. .dream-tab was carrying a bold weight instead of the bar, from
   when those two tabs sat directly under the follower counts and a second rule
   that close to them read as a stray line. They live on the dreams heading row
   now, so there is nothing for the bar to collide with. */

.tab-bar-btn.active::before,
.feed-tab.active::before,
.view-toggle-btn.active::before,
.dream-tab.active::before,
.connections-tab.active::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2.5px;
  border-radius: 2px 2px 0 0;
  background: var(--primary);
}

@media (hover: hover) and (pointer: fine) {
  .tab-bar-btn:not(.active):hover,
  .feed-tab:not(.active):hover,
  .view-toggle-btn:not(.active):hover,
  .dream-tab:not(.active):hover,
  .connections-tab:not(.active):hover {
    color: var(--text);
    background: none;
  }
}

/* ── Reactions ────────────────────────────────────────────────────────────────
 * The heart is an outline until you press it and a fill afterwards, which is
 * what a heart means everywhere else. It is drawn by stroking the solid glyph's
 * own path, so the app does not carry a second icon set for one state.
 * --------------------------------------------------------------------------- */

.reaction-button--heart:not(.active) .reaction-icon path,
.reaction-button--comment .reaction-icon path,
.comment-heart-btn:not(.active) svg path {
  fill: none;
  stroke: currentColor;
  stroke-width: 38;
}

/* The row and its buttons were defined in Activity.css, which is a route chunk
   that the feed and the dream detail page do not load, so on a cold start they
   were falling back to the browser's own button box and only matched the design
   once you had visited the notifications tab. Shared, so one definition covers
   every surface that draws a reaction.

   44px in both directions, per Apple's minimum: these are icon-sized controls
   sitting next to each other, and the old 0.2rem of padding gave them a target
   about half that. */

.activity-reactions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.reaction-buttons {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem;
}

.reaction-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  min-height: 44px;
  min-width: 44px;
  padding: 0.5rem 0.7rem;
  border: none;
  border-radius: 999px;
  background: none;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background-color 0.18s ease, color 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.reaction-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.reaction-button.active {
  color: var(--primary);
}

/* Sized to the target rather than a size the target had to be padded out to.
   A 44px button around a 15px glyph reads as a small control with a lot of
   space around it, and the space is invisible until you hover it. */

.reaction-icon {
  font-size: 1.5rem;
  color: inherit;
}

/* Only one of these. There was a second, later copy at 1.1rem further down the
   file, so every size set here was being thrown away and the emoji stayed
   smaller than the icons beside it no matter what this said. */

.reaction-emoji-text {
  font-size: 1.65rem;
  line-height: 1;
}

.reaction-count {
  font-size: var(--fs-base);
  color: inherit;
  font-variant-numeric: tabular-nums;
}

@media (hover: hover) and (pointer: fine) {
  .reaction-button:hover:not(:disabled) {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
  }

  .reaction-button.active:hover:not(:disabled) {
    color: var(--primary);
  }
}

/* An emoji is already full colour, so `color` cannot say whether you are in on
   a reaction: the count beside it was the only thing changing. A tinted pill
   with a ring says it the way every reaction chip does. After the hover block
   on purpose, so hovering a chip you have reacted to does not wash the tint
   back out. */

.reaction-button--emoji.active {
  background: rgba(201, 167, 240, 0.16);
  box-shadow: inset 0 0 0 1.5px rgba(201, 167, 240, 0.6);
}

@media (hover: hover) and (pointer: fine) {
  .reaction-button--emoji.active:hover:not(:disabled) {
    background: rgba(201, 167, 240, 0.24);
  }
}

/* ── Overflow menu ────────────────────────────────────────────────────────────
 * The three dots and its dropdown. Block and report live behind one of these on
 * every surface that carries other people's dreams, because moderation should
 * be reachable without sitting at the same weight as the things you came to the
 * page to do. The feed and the profile still carry their own copies of this;
 * new ones should use these classes.
 * --------------------------------------------------------------------------- */

.overflow-menu-root {
  position: relative;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  z-index: 4;
}

.overflow-menu-btn {
  position: relative;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 999px;
  background: none;
  color: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color 0.18s ease, background-color 0.18s ease;
}

/* 44px target without a 44px glyph. */

.overflow-menu-btn::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

@media (hover: hover) and (pointer: fine) {
  .overflow-menu-btn:hover {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
  }
}

.overflow-menu-btn:active {
  background: rgba(255, 255, 255, 0.1);
  transition: background-color 60ms ease-out;
}

.overflow-menu {
  position: absolute;
  top: calc(100% + 0.35rem);
  right: 0;
  min-width: 190px;
  padding: 0.4rem;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: rgba(10, 14, 22, 0.98);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  z-index: 5;
}

.overflow-menu-item {
  width: 100%;
  border: none;
  background: transparent;
  color: var(--text);
  text-align: left;
  border-radius: 10px;
  padding: 0.7rem 0.8rem;
  font-weight: 600;
  font-size: var(--fs-base);
  min-height: 44px;
  cursor: pointer;
  transition: background-color 0.18s ease;
}

@media (hover: hover) and (pointer: fine) {
  .overflow-menu-item:hover {
    background: rgba(255, 255, 255, 0.06);
  }
}

.overflow-menu-item:active {
  background: rgba(255, 255, 255, 0.1);
  transition: background-color 60ms ease-out;
}

.overflow-menu-item-danger {
  color: #ff9fb1;
}

@media (hover: hover) and (pointer: fine) {
  .overflow-menu-item-danger:hover {
    background: rgba(255, 138, 171, 0.12);
  }
}

/* Who a dream was shared with. Small enough not to compete with the name it
   sits beside, and the only thing in a post allowed a fill, because the colour
   is the information here rather than decoration. */

.vis-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  flex-shrink: 0;
}

.vis-badge--public    { color: var(--vis-public);    background: var(--vis-public-soft); }

.vis-badge--followers { color: var(--vis-followers); background: var(--vis-followers-soft); }

.vis-badge--mutuals   { color: var(--vis-mutuals);   background: var(--vis-mutuals-soft); }

.vis-badge--anonymous { color: var(--vis-anonymous); background: var(--vis-anonymous-soft); }

.vis-badge--private   { color: var(--vis-private);   background: var(--vis-private-soft); }

.tag,
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--surface-muted);
  color: var(--primary-strong);
  padding: 0.35rem 0.75rem;
  border-radius: 100px;
  font-weight: 600;
  font-size: var(--fs-base);
  border: 1px solid var(--border);
}

.reaction-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.5rem 0;
}

.reaction-popover-list,
.modal-content,
.search-results,
.app-loading-shell {
  -webkit-overflow-scrolling: touch;
}

.reaction-chip {
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  padding: 0.3rem 0.85rem;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--fs-base);
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.2s ease;
}

.reaction-chip.active {
  border-color: var(--primary);
  background: rgba(201, 167, 240, 0.12);
  box-shadow: var(--glow-primary);
}

.reaction-chip:focus-visible {
  border-color: var(--primary);
  background: rgba(201, 167, 240, 0.1);
  outline: none;
}

@media (hover: hover) and (pointer: fine) {
  .reaction-chip:hover {
    border-color: var(--primary);
    background: rgba(201, 167, 240, 0.1);
  }
}

.reaction-emoji-icon {
  font-size: 1rem;
  color: var(--accent);
}

.custom-emoji-popover {
  margin-top: 0.5rem;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: rgba(20, 14, 35, 0.85);
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.emoji-picker-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.emoji-option {
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.05);
  width: 44px;
  height: 44px;
  font-size: 1.7rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
}

.emoji-option:focus-visible {
  border-color: var(--primary);
  outline: none;
}

@media (hover: hover) and (pointer: fine) {
  .emoji-option:hover { border-color: var(--primary); }
}

.emoji-option.selected {
  border-color: rgba(201, 167, 240, 0.7);
  background: rgba(201, 167, 240, 0.12);
  box-shadow: 0 0 0 1px rgba(201, 167, 240, 0.2);
}

.emoji-input-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.emoji-input-row input {
  min-width: 160px;
  padding: 0.5rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
}

.emoji-input-row input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px rgba(201, 167, 240, 0.2);
  outline: none;
}

.reaction-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

.reaction-summary-item {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.reaction-summary-item .reaction-emoji-icon {
  font-size: 0.85rem;
}

.emoji-clear-btn {
  width: 100%;
  margin-top: 0.35rem;
  padding: 0.55rem 0.75rem;
  border-radius: 10px;
  border: 1px solid rgba(255, 100, 100, 0.25);
  background: rgba(255, 100, 100, 0.06);
  color: #ff9fb1;
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

@media (hover: hover) and (pointer: fine) {
  .emoji-clear-btn:hover {
    background: rgba(255, 100, 100, 0.12);
    border-color: rgba(255, 100, 100, 0.45);
  }
}

.search-highlight {
  background: rgba(232, 200, 122, 0.25);
  color: #f0d58a;
  border-radius: 2px;
  padding: 0 1px;
  font-style: inherit;
  font-weight: inherit;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.pending-title {
  color: var(--text-secondary);
  font-size: var(--fs-base);
  font-style: italic;
  margin: 0 0 0.4rem;
}

.text-muted {
  color: var(--text-secondary);
}

.surface-row {
  display: grid;
  gap: 1rem;
}

.audience-search-input {
  width: 100%;
  margin-bottom: 0.5rem;
}

.audience-search-input input {
  width: 100%;
  padding: 0.75rem 0.95rem;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.audience-search-input input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(201, 167, 240, 0.12);
}

.audience-result-list {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.audience-result {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 0.75rem 1rem;
  background: var(--surface);
  color: var(--text);
  transition: border-color 0.18s ease, background-color 0.18s ease, box-shadow 0.18s ease, transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  text-align: left;
}

.audience-result.active {
  border-color: var(--primary);
  background: rgba(201, 167, 240, 0.1);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
}

.audience-result-meta {
  display: flex;
  flex-direction: column;
  gap: 0.12rem;
}

.result-name {
  font-weight: 600;
  color: var(--text);
}

.result-handle {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.result-status {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.audience-result.active .result-status {
  color: var(--primary);
}

.selected-pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 0.65rem;
}

.selected-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-muted);
  font-size: var(--fs-sm);
  color: var(--text);
}

.selected-pill button {
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font-size: var(--fs-base);
  line-height: 1;
}

@media (hover: hover) and (pointer: fine) {
  .selected-pill button:hover { color: var(--primary); }
}

@media (max-width: 768px) {
  html {
    font-size: 14px;
  }

  /* Prevent iOS auto-zoom on input focus (triggered when font-size < 16px) */
  input,
  textarea,
  select {
    font-size: 16px;
  }

  .app-main.app-main--with-nav {
    padding-top: calc(74px + env(safe-area-inset-top));
  }

  .app-main.app-main--no-nav {
    padding-top: 0;
  }

  .page-container {
    padding: 10px 1.25rem calc(50px + env(safe-area-inset-bottom));
  }

  .page-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .action-group {
    width: 100%;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── iOS native: suppress active/hover highlights while scrolling ─────────────
 * `is-scrolling` is added to <html> by src/utils/iosTapFix.js as soon as a
 * touch moves more than 4 px. Removed on touchend so intentional taps still
 * receive full visual feedback. Scoped to data-native-platform='ios' so web
 * is completely unaffected.
 *
 * Strategy:
 *   1. Kill all transitions so mid-flight hover animations can't linger.
 *   2. Strip only the visual "highlight" properties (filter, transform,
 *      box-shadow) that change on hover/active. Intentionally leave
 *      background-color and color alone, and resetting those causes cards to
 *      flash transparent which looks worse.
 *   3. Restore semantic .active states (selected chips, pills) which are
 *      driven by JS state, not pointer events.
 * --------------------------------------------------------------------------- */

/* 1. Kill transitions on everything during scroll so no highlights linger */

html[data-native-platform='ios'].is-scrolling * {
  transition: none !important;
}

/* 2. Strip pointer-driven visual changes. Split in two now that hover is gated
      behind `hover: hover`: the :active half still matters on every iOS device
      (it stops a press state firing mid flick-scroll), while the :hover half
      only has anything to undo on an iPad driven by a trackpad or mouse. */

html[data-native-platform='ios'].is-scrolling *:active {
  filter: none !important;
  transform: none !important;
  box-shadow: none !important;
  outline: none !important;
  border-color: var(--border) !important;
  text-decoration-color: inherit !important;
}

@media (hover: hover) and (pointer: fine) {
  html[data-native-platform='ios'].is-scrolling *:hover {
    filter: none !important;
    transform: none !important;
    box-shadow: none !important;
    outline: none !important;
    border-color: var(--border) !important;
    text-decoration-color: inherit !important;
  }
}

/* 3. Restore semantic selected states (driven by JS, not pointer) */

html[data-native-platform='ios'].is-scrolling .reaction-chip.active, html[data-native-platform='ios'].is-scrolling .reaction-chip.active:active {
  border-color: var(--primary) !important;
  background-color: rgba(183, 183, 240, 0.12) !important;
  box-shadow: var(--glow-primary) !important;
}

@media (hover: hover) and (pointer: fine) {
  html[data-native-platform='ios'].is-scrolling .reaction-chip.active:hover {
    border-color: var(--primary) !important;
    background-color: rgba(183, 183, 240, 0.12) !important;
    box-shadow: var(--glow-primary) !important;
  }
}

html[data-native-platform='ios'].is-scrolling .audience-result.active, html[data-native-platform='ios'].is-scrolling .audience-result.active:active {
  border-color: var(--primary) !important;
  background-color: rgba(183, 183, 240, 0.1) !important;
}

@media (hover: hover) and (pointer: fine) {
  html[data-native-platform='ios'].is-scrolling .audience-result.active:hover {
    border-color: var(--primary) !important;
    background-color: rgba(183, 183, 240, 0.1) !important;
  }
}

html[data-native-platform='ios'].is-scrolling .pill-active, html[data-native-platform='ios'].is-scrolling .pill-active:active {
  border-color: var(--primary) !important;
  background-color: rgba(183, 183, 240, 0.1) !important;
  box-shadow: var(--glow-primary) !important;
}

@media (hover: hover) and (pointer: fine) {
  html[data-native-platform='ios'].is-scrolling .pill-active:hover {
    border-color: var(--primary) !important;
    background-color: rgba(183, 183, 240, 0.1) !important;
    box-shadow: var(--glow-primary) !important;
  }
}

/* ── Interaction feedback ────────────────────────────────────────────────────
 * Two separate concerns, deliberately kept apart:
 *
 *   Press: applies on every device. `-webkit-tap-highlight-color` is cleared
 *            globally above, so without these a tap on a phone gives no
 *            feedback at all. Two durations, on purpose: the short one in each
 *            `:active` rule drives the press, and the 160ms `transform` on the
 *            resting rule drives the release. The release used to have no
 *            transition at all, because the only declaration lived inside
 *            `:active` and went away with it, so the button snapped back to
 *            full size the instant a finger lifted.
 *   Hover: gated behind `hover: hover` + `pointer: fine`. Ungated `:hover`
 *            sticks to whatever a finger last touched until something else is
 *            tapped, which is what `iosTapFix.js` exists to paper over.
 * ------------------------------------------------------------------------- */

.primary-btn:active,
.secondary-btn:active,
.ghost-btn:active,
.danger-btn:active,
.auth-submit:active,
.auth-google-btn:active,
.auth-apple-btn:active,
.request-btn:active,
.follow-action-btn:active,
.moderation-action-btn:active,
.edit-profile-btn:active,
.settings-btn:active,
.add-tag-btn:active,
.close-btn:active,
.report-reason-btn:active,
.feed-card-menu-item:active,
.activity-clear-btn:active,
.prompt-option-btn:active,
.prompt-option:active,
.more-options-toggle:active,
.detail-share-toggle:active,
.voice-input-btn:active {
  transform: scale(0.965);
  transition: transform 80ms ease;
}

/* Cards are large, so they take a much smaller scale, since anything more reads as
   the whole page lurching rather than the card acknowledging the tap. */

.card-shell:active,
.stat-button:active {
  transform: scale(0.99);
  transition: transform 80ms ease;
}

.pill:active,
.chip:active,
.reaction-chip:active,
.emoji-option:active,
.visibility-chip:active,
.filter-toggle .chip:active,
.reaction-button:active,
.comment-heart-btn:active,
.comment-reply-btn:active,
.comment-report-btn:active,
.comment-delete-btn:active,
.selected-pill button:active,
.tagged-pill button:active,
.detail-tag-remove:active,
.avatar-option:active,
.color-swatch:active,
.calendar-day:active,
.nav-ios-tab:active,
.nav-ios-back-btn:active {
  transform: scale(0.93);
  transition: transform 70ms ease;
}

/* A stream row is the full width of the screen, so scaling it reads as the page
   flinching. Social feeds flash the row instead, which is also the only press
   feedback that survives having no border or shadow to change. */

.feed-post:active,
.dream-card:active,
.profile-dream-card:active,
.tagged-dream-card:active,
.search-dream-card:active,
.person-card:active,
.connection-card:active,
.connections-card:active,
.connections-card-main:active,
.activity-card:active,
.activity-card-main:active {
  background-color: rgba(255, 255, 255, 0.08);
  transition: background-color 60ms ease-out;
}

/* Disabled controls must not look pressable. */

.primary-btn:disabled:active,
.secondary-btn:disabled:active,
.ghost-btn:disabled:active,
.danger-btn:disabled:active,
button:disabled:active {
  transform: none;
}

@media (hover: hover) and (pointer: fine) {
  .ghost-btn:hover:not(:disabled),
  .danger-btn:hover:not(:disabled) {
    color: var(--text);
    filter: brightness(1.15);
  }

  .pill:hover,
  .chip:hover,
  .view-toggle-btn:hover,
  .feed-tab:hover,
  .dream-tab:hover,
  .connections-tab:hover {
    border-color: rgba(201, 167, 240, 0.45);
    color: var(--text);
  }

  .dream-card:hover,
  .profile-dream-card:hover,
  .tagged-dream-card:hover,
  .search-dream-card:hover,
  .person-card:hover,
  .connections-card:hover,
  .activity-card:hover {
    border-color: rgba(201, 167, 240, 0.28);
    background-color: rgba(255, 255, 255, 0.055);
  }

  .calendar-day:hover:not(.is-selected) .calendar-day-number {
    background: rgba(201, 167, 240, 0.16);
  }

  .avatar-option:hover,
  .color-swatch:hover,
  .report-reason-btn:hover,
  .feed-card-menu-item:hover,
  .prompt-option-btn:hover,
  .audience-result:hover {
    border-color: rgba(201, 167, 240, 0.45);
  }

  .activity-clear-btn:hover,
  .comment-reply-btn:hover,
  .comment-report-btn:hover {
    color: var(--primary);
  }

  .comment-delete-btn:hover {
    color: #ff9fb1;
  }
}

/* Presses are feedback, not decoration, but they still respect the global
   reduced-motion rule above, which zeroes out every transition duration. */

@media (prefers-reduced-motion: reduce) {
  .page-container,
  .insights-page,
  .home-page,
  [data-nav-direction] .page-container,
  [data-nav-direction] .insights-page {
    animation: none;
  }
}

/* Remaining standalone controls and links. Grouped separately from the block
   above only because these are not variants of the shared button classes. */

.auth-back-link:active,
.auth-forgot-link:active,
.auth-password-toggle:active,
.comment-replies-toggle:active,
.detail-author-btn:active,
.detail-back-btn:active,
.detail-title-editable:active,
.detail-date-pill--interactive:active,
.feed-author-link:active,
.feed-card-menu-btn:active,
.legal-back-btn:active,
.memory-patterns-link:active,
.nav-logo:active,
.rc-restore-btn:active,
.tag-suggestion-item:active,
.tagged-view-profile:active,
.emoji-clear-btn:active,
.navigation .nav-links a:active {
  transform: scale(0.94);
  transition: transform 70ms ease;
}

.home-cta-primary:active,
.home-cta-secondary:active {
  transform: scale(0.965);
  transition: transform 80ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .detail-title-editable:hover,
  .detail-date-pill--interactive:hover,
  .feed-author-link:hover,
  .tagged-view-profile:hover,
  .memory-patterns-link:hover,
  .auth-forgot-link:hover,
  .auth-back-link:hover,
  .comment-replies-toggle:hover,
  .rc-restore-btn:hover {
    color: var(--primary);
  }

  .feed-card-menu-btn:hover,
  .auth-password-toggle:hover,
  .detail-back-btn:hover,
  .legal-back-btn:hover {
    color: var(--text);
  }

  .tag-suggestion-item:hover {
    border-color: rgba(201, 167, 240, 0.45);
  }

  .nav-logo:hover {
    filter: brightness(1.15);
  }
}

/* ── Native touch behaviour (iOS shell only) ─────────────────────────────────
 * Without these, a long press anywhere pops WebKit's Copy / Look Up / Share
 * callout and drags start selecting UI text, the clearest tell that an app is
 * a web view. Chrome is locked down; anything the user might genuinely want to
 * copy is opted back in below, and text entry is restored explicitly because
 * inheriting `user-select: none` into a field breaks selection in WKWebView.
 * ------------------------------------------------------------------------- */

html[data-native-platform='ios'] body {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

html[data-native-platform='ios'] input,
html[data-native-platform='ios'] textarea,
html[data-native-platform='ios'] [contenteditable='true'] {
  -webkit-touch-callout: default;
  -webkit-user-select: text;
  user-select: text;
}

/* Real content, meaning a dream, a comment or an analysis, stays long-pressable so it
   can be copied, the way selectable text behaves in a native app. */

html[data-native-platform='ios'] .detail-body,
html[data-native-platform='ios'] .detail-insight,
html[data-native-platform='ios'] .detail-connections-item,
html[data-native-platform='ios'] .comment-body,
html[data-native-platform='ios'] .feed-content,
html[data-native-platform='ios'] .feed-summary,
html[data-native-platform='ios'] .insights-entry-text,
html[data-native-platform='ios'] .profile-bio,
html[data-native-platform='ios'] .legal-page {
  -webkit-touch-callout: default;
  -webkit-user-select: text;
  user-select: text;
}

/* ── Modal motion ────────────────────────────────────────────────────────────
 * Nothing in the app animated on open. Sheets and dialogs simply appeared,
 * which is the other big "web view" tell after the long-press callout. The
 * class names are shared across the three stylesheets that define report
 * modals, so styling them once here covers every caller.
 *
 * `backwards` fill again: these end at a transform, and leaving one applied
 * would make the panel a containing block for anything fixed inside it.
 * ------------------------------------------------------------------------- */

@keyframes modalBackdropIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes modalSheetIn {
  from { opacity: 0; transform: translate3d(0, 100%, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes modalDialogIn {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}

.confirm-modal-backdrop,
.report-modal-backdrop,
.modal-overlay,
.crop-modal-backdrop {
  animation: modalBackdropIn 180ms ease-out backwards;
}

.report-modal,
.modal-content,
.crop-modal-card {
  animation: modalDialogIn 220ms cubic-bezier(0.32, 0.72, 0, 1) backwards;
}

/* The confirm modal is a bottom sheet below 480px and a centred dialog above,
   so its entrance has to follow the same breakpoint. */

.confirm-modal {
  animation: modalSheetIn 260ms cubic-bezier(0.32, 0.72, 0, 1) backwards;
}

@media (min-width: 480px) {
  .confirm-modal {
    animation: modalDialogIn 220ms cubic-bezier(0.32, 0.72, 0, 1) backwards;
  }
}

/* Scrolling inside a sheet must not chain to the page behind it. */

.confirm-modal,
.report-modal,
.modal-content,
.crop-modal-card {
  overscroll-behavior: contain;
}

@media (prefers-reduced-motion: reduce) {
  .confirm-modal-backdrop,
  .report-modal-backdrop,
  .modal-overlay,
  .crop-modal-backdrop,
  .confirm-modal,
  .report-modal,
  .modal-content,
  .crop-modal-card {
    animation: none;
  }
}

/* ---------------------------------------------------------------------------
 * Popovers. These appeared fully formed the instant they mounted, which is the
 * most jarring version of the problem, because they arrive right under the finger that
 * asked for them. Growing them a little out of their anchor corner ties the
 * panel to the control that opened it.
 * `backwards` fill again, for the same containing-block reason as the modals.
 * ------------------------------------------------------------------------- */

@keyframes popoverIn {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}

.feed-card-menu {
  transform-origin: top right;
  animation: popoverIn 150ms cubic-bezier(0.32, 0.72, 0, 1) backwards;
}

/* Opacity only, deliberately. This card is measured with getBoundingClientRect
   in a layout effect to place itself next to its anchor, and a scale would be
   folded into that measurement and put the popover in the wrong spot. */

.reaction-popover-card {
  animation: fadeIn 150ms ease-out backwards;
}

@media (prefers-reduced-motion: reduce) {
  .feed-card-menu,
  .reaction-popover-card {
    animation: none;
  }
}
