/* Lingogroove — visual design system.
   Palette and spacing lean on the same cues as the big social apps
   (Facebook/Messenger): a single confident blue, a soft neutral canvas,
   white cards that lift slightly on hover, and pill-shaped chips for
   status/metadata. Every class name below is already used across the view
   templates -- this file re-skins them, it doesn't rename or restructure. */

:root {
  --brand: #1877f2;
  --brand-dark: #145dbf;
  --brand-light: #e7f0ff;
  --ink: #1c1e21;
  --muted: #65676b;
  --bg: #f0f2f5;
  --card-bg: #ffffff;
  --border: #dde1e6;
  --error: #e41e3f;
  --error-bg: #fdecee;
  --success: #31a24c;
  --success-bg: #e9f7ec;
  --gold: #f5a623;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.06);
  --shadow-md: 0 4px 14px rgba(0,0,0,0.08);
  --shadow-lg: 0 10px 34px rgba(0,0,0,0.14);
  --radius: 10px;
}

* { box-sizing: border-box; }

/* <body> already paints the page's grey canvas (var(--bg)), but <html>
   itself has no background of its own -- on a phone, overscrolling past
   the top (the little rubber-band/pull bounce, most visible on the
   full-screen mobile chat thread once the browser's address bar has
   collapsed and freed up more scroll room) reveals the browser's default
   white behind <html> for an instant, which reads as a stray white gap
   above the chat header. Matching <html>'s background to the same grey
   means that peek-through blends in instead of standing out. */
html { background: var(--bg); }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

.auth-card, .card {
  max-width: 420px;
  margin: 8vh auto;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px;
  box-shadow: var(--shadow-md);
}

.card { max-width: 560px; margin: 32px auto; }

h1 { margin: 0 0 4px; font-size: 1.65rem; letter-spacing: -0.01em; }
h2 { font-size: 1.2rem; margin: 24px 0 10px; }

.tagline { color: var(--muted); margin: 0 0 24px; line-height: 1.5; }

label {
  display: block;
  margin-bottom: 16px;
  font-size: 0.9rem;
  color: var(--muted);
  font-weight: 500;
}

input, textarea, select {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 1rem;
  font-family: inherit;
  background: #fff;
  color: var(--ink);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

textarea { min-height: 80px; resize: vertical; }

button, .link-button {
  background: var(--brand);
  color: #fff;
  border: none;
  padding: 12px 20px;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  width: 100%;
  letter-spacing: 0.01em;
}

button:hover, .link-button:hover { background: var(--brand-dark); }
button:active, .link-button:active { transform: translateY(1px); }

.error {
  background: var(--error-bg);
  color: var(--error);
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 0.9rem;
  margin-bottom: 16px;
  border: 1px solid rgba(228,30,63,0.18);
}

a { color: var(--brand); text-decoration: none; }
a:hover { color: var(--brand-dark); text-decoration: underline; }

/* Top navigation bar — sticky, FB-blue accents on a white bar */

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 28px;
  background: var(--card-bg);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 100;
  box-shadow: var(--shadow-sm);
}

.topbar .brand {
  font-weight: 800;
  font-size: 1.3rem;
  color: var(--brand);
  letter-spacing: -0.02em;
  text-decoration: none;
}
.topbar .brand:hover { color: var(--brand-dark); text-decoration: none; }
.topbar .brand { display: flex; align-items: center; }
.topbar .brand img.brand-logo { display: block; height: 40px; width: auto; }

.topbar form { width: auto; }
.topbar .link-button { width: auto; padding: 8px 16px; font-weight: 600; }

.nav-links { display: flex; align-items: center; gap: 4px; }
.nav-links form { width: auto; margin-left: 6px; }

.nav-links > a {
  text-decoration: none;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--muted);
  padding: 8px 12px;
  border-radius: 8px;
  position: relative;
  transition: background 0.15s ease, color 0.15s ease;
}
.nav-links > a:hover { background: var(--brand-light); color: var(--brand); text-decoration: none; }

/* Mobile-only nav chrome (header activity/message icons, the bottom tab
   bar, the mobile Profile-tab quick-links row, and a DM thread's "back to
   messages" button) -- hidden by default so desktop is unaffected;
   switched on inside the max-width: 780px media query further down. */
.topbar-mobile-icons, .mobile-tabbar, .mobile-quick-links, .thread-back-btn,
.mobile-composer-trigger, .mobile-composer-header { display: none; }

.wide { max-width: 760px; }

.partner-list { list-style: none; margin: 0; padding: 0; }
.partner-item {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.partner-item:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.partner-item a { text-decoration: none; color: var(--ink); display: block; }
/* Inbox rows carve the avatar out into its own link to the person's
   profile, separate from the rest of the row (which still opens the
   conversation) -- .avatar-link needs to win back its inline layout from
   the broad ".partner-item a { display: block }" rule above. */
.partner-item-row { display: flex; align-items: flex-start; gap: 12px; }
.partner-item-row .avatar-link { display: inline-flex; flex-shrink: 0; margin-top: 2px; }
.partner-item-link { flex: 1; min-width: 0; }
.partner-item .muted { color: var(--muted); font-size: 0.9rem; }
.partner-item .partner-bio {
  display: block;
  color: var(--muted);
  font-size: 0.85rem;
  margin-top: 4px;
  line-height: 1.4;
}
.partner-item .partner-activity {
  display: block;
  color: var(--muted);
  font-size: 0.78rem;
  margin-top: 3px;
}

.muted { color: var(--muted); }

.notice {
  background: var(--success-bg);
  color: #1a7a37;
  padding: 12px 16px;
  border-radius: 8px;
  font-size: 0.9rem;
  margin-bottom: 16px;
  border: 1px solid rgba(49,162,76,0.18);
}

.badge {
  background: var(--error);
  color: #fff;
  border-radius: 999px;
  padding: 2px 8px;
  font-size: 0.72rem;
  font-weight: 700;
  margin-left: 6px;
  vertical-align: 2px;
}

.danger { background: var(--error); margin-top: 8px; }
.danger:hover { background: #b81531; }

/* Collapsed-by-default settings sections (Blocked users, Delete account) --
   these are the kind of controls someone reaches for occasionally, not
   things that need a full heading and always-visible button competing for
   attention on every visit to /account. */
.settings-collapse { margin: 0; }
.settings-collapse summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--muted);
  list-style: none;
  padding: 4px 0;
}
.settings-collapse summary::-webkit-details-marker { display: none; }
.settings-collapse summary::before { content: "▸ "; font-size: 0.75em; }
.settings-collapse[open] summary::before { content: "▾ "; }
.settings-collapse summary:hover { color: var(--ink); }
.settings-collapse-body { margin-top: 14px; }
.settings-collapse-danger summary { color: var(--error); }

.thread-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.thread-header h1 { margin: 0; }
.thread-header-actions { margin: 0; }
.thread-header-actions form { width: auto; display: inline-block; }
.thread-header-actions .link-button-plain {
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 7px 16px;
  background: #fff;
  color: var(--ink);
}
.thread-header-actions .link-button-plain:hover { background: var(--bg); color: var(--ink); }
.thread-header-actions .remove-link { color: var(--error); border-color: rgba(228,30,63,0.3); }

/* "Start a call" entry point (DM thread header, group page header) and the
   "Join call" chip a call posts into the thread/group chat as a message
   (see [[call:ID]] handling in bodyHtml(), app.js). Chip styling
   deliberately echoes .sticker's "bigger than plain text, obviously
   tappable" treatment rather than looking like a normal link. */
.call-start-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 7px 16px;
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  font: inherit;
}
.call-start-btn:hover { background: var(--bg); }
/* Shared sizing for the outline call icons (camera = video call, phone =
   voice call) used on the start-call buttons, the room title, and the
   "Join call" chip below -- a plain line-art SVG (currentColor stroke, no
   fill) instead of the 📹 emoji, which rendered as a busy, photo-like
   little picture rather than a clean icon. */
.call-icon-svg { width: 16px; height: 16px; flex-shrink: 0; vertical-align: -3px; }
.call-join-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--brand);
  color: #fff;
  padding: 8px 16px;
  border-radius: 999px;
  font-weight: 600;
  text-decoration: none;
}
.call-join-chip:hover { opacity: 0.9; text-decoration: none; }
.call-room-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.call-start-group { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.call-room-header h1 { margin: 0; }
.call-room-frame {
  margin-top: 12px;
  background: #000;
  border-radius: 12px;
  overflow: hidden;
}

/* "⋯" overflow menu -- tucks an occasionally-needed action (Block/Unblock)
   behind a small toggle instead of showing it by default. Shared by the DM
   thread header and the floating chat window header; see foot.ejs for the
   one delegated click handler that opens/closes every instance. */
.overflow-menu { position: relative; display: inline-block; width: auto; }
.overflow-menu-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  border-radius: 50%;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.overflow-menu-btn:hover { background: var(--bg); color: var(--ink); border-color: var(--border); }
.overflow-menu-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.12);
  padding: 6px;
  min-width: 150px;
}
.overflow-menu-panel form { width: 100%; }
.overflow-menu-panel .link-button-plain {
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  border-radius: 6px;
  border: none;
  background: none;
}
.overflow-menu-panel .link-button-plain:hover { background: var(--bg); }
.thread-header-actions .remove-link:hover { background: var(--error-bg); color: var(--error); }

/* Group chat's "⋮" menu (views/groups/show.ejs) reuses .overflow-menu/
   .overflow-menu-panel -- same dropdown mechanism as the DM thread's
   Block/Unblock menu (the click-to-toggle handler in partials/foot.ejs is
   already generic) -- but holds much more: the member list, admin tools
   (invite link, join requests, add-by-username), and Leave group, all of
   which used to sit permanently inline above the chat and made the page
   feel cluttered/inconsistent with the clean 1:1 DM thread. Moving them
   into this panel keeps the group chat page itself just as focused as a
   DM thread (header + messages + composer), while everything else stays
   one tap away instead of gone. */
.group-menu-panel {
  min-width: 300px;
  max-width: calc(100vw - 24px);
  max-height: min(70vh, 520px);
  overflow-y: auto;
  padding: 12px;
}
.group-menu-heading {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
  margin: 14px 0 8px;
}
.group-menu-panel > .group-menu-heading:first-child { margin-top: 2px; }
.group-menu-divider { height: 1px; background: var(--border); margin: 14px 0; }
.group-menu-panel .member-list { margin: 0; }
.group-menu-panel .member-row { padding: 8px 4px; }
.group-menu-panel .group-invite-box,
.group-menu-panel .group-requests-box,
.group-menu-panel .group-add-form { margin: 0; }
.group-menu-panel .group-leave-btn {
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  border-radius: 6px;
  border: none;
  background: none;
  color: var(--error);
}
.group-menu-panel .group-leave-btn:hover { background: var(--error-bg); }

/* Group chat header redesign: the group avatar itself (h1 .group-avatar-btn)
   is now the trigger, and the panel becomes a full-screen overlay instead of
   the small anchored dropdown every other .overflow-menu-panel uses. Scoped
   to .group-menu-panel specifically so the DM thread / floating chat
   widget's Block/Unblock dropdowns are unaffected -- this rule wins on
   source order (declared after the shared .overflow-menu-panel rule and
   after the earlier .group-menu-panel rule above), no !important. */
/* Popup vs full-screen: user asked for "popup div with x button in
   desktop and for mobile it should be full screen". 780px matches the
   .topbar mobile-nav breakpoint used elsewhere, so the panel's own
   desktop/mobile split lines up with when the rest of the chrome
   switches to its mobile layout. */
@media (max-width: 780px) {
  .group-menu-panel {
    position: fixed;
    inset: 0;
    z-index: 200;
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
    min-width: 0;
    width: 100%;
    max-width: none;
    max-height: none; /* deploy45-fix */
    overflow-y: hidden;
    display: flex;
    flex-direction: column;
  }
}
@media (min-width: 781px) {
  .group-menu-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    right: auto;
    transform: translate(-50%, -50%);
    z-index: 200;
    border: none;
    border-radius: var(--radius);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
    padding: 0;
    min-width: 0;
    width: min(560px, calc(100vw - 48px));
    max-width: none;
    max-height: min(80vh, 640px);
    overflow-y: hidden;
    display: flex;
    flex-direction: column;
  }
  /* Dimmed backdrop behind the popup, shown only while it's open.
     :has() lets this stay CSS-only -- no change needed to the
     existing click-outside-to-close handler in partials/foot.ejs,
     since a click on the backdrop lands on .group-menu itself,
     which isn't inside .overflow-menu-panel, so that handler
     already closes the panel. */
  .group-menu:has(.group-menu-panel:not([hidden]))::before {
    content: "";
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: 199;
  }
}
.group-menu-panel[hidden] {
  display: none;
}
.group-menu-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}
.group-menu-panel-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.group-menu-panel-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 24px;
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
  box-sizing: border-box;
}
.group-menu-panel-close {
  margin: 0;
  flex: 0 0 auto;
}
.group-menu-panel-body .group-menu-heading {
  margin-top: 28px;
}
.group-menu-panel-body .tagline {
  margin: 0 0 4px;
}
.group-menu-panel-body > .group-menu-heading:first-child {
  margin-top: 0;
}
.group-menu-panel .group-leave-btn {
  margin-top: 12px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}
.group-avatar-btn {
  display: inline-flex;
  align-items: center;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  width: auto;
}
.group-avatar-btn:hover {
  background: none;
}
.group-menu-panel-close {
  display: block;
  width: 32px;
  height: 32px;
  margin: 0 0 12px auto;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 50%;
  font-size: 1rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}
.group-menu-panel-close:hover {
  background: var(--bg);
  color: var(--ink);
}

.thread {
  /* Subtle dotted "wallpaper" instead of a flat fill -- cheap to render
     (no image asset), but it's the single biggest cue that reads as "chat
     app" rather than "plain list of boxes". */
  background-color: #e7ebef;
  background-image: radial-gradient(rgba(0, 0, 0, 0.045) 1px, transparent 1px);
  background-size: 18px 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  height: 380px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* Small base gap keeps consecutive bubbles from the same side close
     together; the sender-switch rule below adds extra breathing room only
     where the sender actually changes, mimicking WhatsApp's grouping. */
  gap: 3px;
}

/* Extra spacing exactly where the sender switches -- combined with the
   tight 3px flex gap above, this groups consecutive messages from the same
   side and separates alternating ones, instead of every bubble getting the
   same uniform gap regardless of who sent what. Matches both a 1:1 DM
   thread's flat .msg children and group chat's .group-msg wrapper
   children, since both carry the msg-mine/msg-theirs classes. */
.msg-theirs + .msg-mine,
.msg-mine + .msg-theirs {
  margin-top: 10px;
}

.thread-empty {
  align-items: center;
  justify-content: center;
}

.thread-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--muted);
  gap: 4px;
}

.thread-empty-icon {
  font-size: 2rem;
  margin-bottom: 4px;
}

.thread-empty-state p {
  margin: 0;
  font-weight: 600;
  color: var(--ink);
}

.thread-empty-sub {
  font-size: 0.85rem;
  max-width: 260px;
}

.msg {
  max-width: 75%;
  padding: 9px 13px;
  border-radius: 16px;
  font-size: 0.95rem;
  word-wrap: break-word;
  line-height: 1.4;
}

.msg-mine {
  align-self: flex-end;
  background: var(--brand);
  color: #fff;
  border-bottom-right-radius: 4px;
}

.msg-theirs {
  align-self: flex-start;
  background: #fff;
  border: 1px solid var(--border);
  border-bottom-left-radius: 4px;
}

/* WhatsApp/Messenger-style "is typing…" indicator -- a bubble styled to
   match .msg-theirs (same side, same shape) but holding three bouncing
   dots instead of text, appended/removed at the bottom of the thread as
   the partner's poll response reports isTyping true/false (see
   thread.ejs). Kept as a bubble rather than a header subtitle so it reads
   as "part of the conversation" the way real chat apps show it. */
.typing-indicator {
  align-self: flex-start;
  background: #fff;
  border: 1px solid var(--border);
  border-bottom-left-radius: 4px;
  padding: 11px 14px;
  border-radius: 16px;
  display: flex;
  align-items: center;
  gap: 4px;
}
/* The [hidden] attribute (see thread.ejs's server-rendered markup and
   setTypingIndicator() toggling it) only hides an element via the
   browser's own low-priority UA stylesheet -- an author rule at the same
   specificity, like the plain `.typing-indicator { display: flex; }`
   above, wins the cascade and silently overrides it, so the bubble was
   showing on every page load instead of only while the partner is
   actually typing. This rule outranks that on selector specificity
   (0,1,1,0 vs 0,0,1,0) so [hidden] reliably wins regardless of source
   order. */
.typing-indicator[hidden] {
  display: none;
}
.typing-indicator-dots { display: flex; align-items: center; gap: 3px; }
.typing-indicator-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted);
  opacity: 0.6;
  animation: typing-dot-bounce 1.2s infinite ease-in-out;
}
.typing-indicator-dots span:nth-child(2) { animation-delay: 0.15s; }
.typing-indicator-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes typing-dot-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.6; }
  30% { transform: translateY(-4px); opacity: 1; }
}

/* Per-message clock time (see msgTime() in app.js) plus, on the current
   user's own sent messages, a WhatsApp-style read receipt tick -- both sat
   on one trailing line under the bubble's text, nudged to the right edge,
   rather than a relative "5m ago" that keeps changing while you're
   reading. */
.msg-meta {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
}
.msg-time {
  font-size: 0.68rem;
  line-height: 1;
  opacity: 0.75;
  letter-spacing: 0.01em;
}
.msg-mine .msg-time { color: rgba(255, 255, 255, 0.85); }
.msg-theirs .msg-time { color: var(--muted); }
/* Single check = sent, not yet read by the other person; double check =
   read (see read_at on the messages table / readUpTo in messageController
   .poll). Only ever rendered on .msg-mine -- you don't see a receipt on
   messages you received, same as most chat apps. */
.msg-receipt {
  font-size: 0.72rem;
  line-height: 1;
  color: rgba(255, 255, 255, 0.6);
  letter-spacing: -1px;
}
.msg-receipt-read { color: #7ee0ff; }

/* Group chat: each row can be from a different person (unlike a 1:1 DM
   thread, which only ever has "me" and one fixed "them"), so the
   mine/theirs alignment classes live on a wrapper (.group-msg) around an
   optional author line + the message bubble, rather than directly on the
   bubble the way the DM thread's .msg-mine/.msg-theirs do. */
.group-thread { max-width: 640px; }

.group-msg {
  display: flex;
  flex-direction: column;
  max-width: 75%;
}

.group-msg.msg-mine { align-self: flex-end; }
.group-msg.msg-theirs { align-self: flex-start; }

.group-msg .msg { max-width: 100%; }

.group-msg.msg-mine .msg {
  background: var(--brand);
  color: #fff;
  border-bottom-right-radius: 4px;
}

.group-msg.msg-theirs .msg {
  background: #fff;
  border: 1px solid var(--border);
  border-bottom-left-radius: 4px;
}

.group-msg-author {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  margin: 0 0 3px 2px;
}

.group-msg-author .avatar, .group-msg-author .avatar-placeholder { margin-right: 0; }

.google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  background: #fff;
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 16px;
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  box-sizing: border-box;
}

.google-btn:hover { background: #f5f5f5; text-decoration: none; }

.divider {
  display: flex;
  align-items: center;
  text-align: center;
  color: var(--muted);
  font-size: 0.85rem;
  margin: 18px 0;
}

.divider::before, .divider::after {
  content: '';
  flex: 1;
  border-bottom: 1px solid var(--border);
}

.divider span { padding: 0 12px; }

/* Registration / login shell — split hero + form panel */

.auth-shell {
  min-height: 100vh;
  display: flex;
}

.auth-hero {
  flex: 1 1 46%;
  background: linear-gradient(135deg, var(--brand) 0%, #0a3f91 100%);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px;
}

.auth-hero-inner { max-width: 420px; }

.hero-brand { font-size: 1.9rem; font-weight: 800; letter-spacing: -0.02em; }
.hero-brand img.hero-logo { display: block; width: 200px; max-width: 60%; height: auto; }

.hero-tagline { font-size: 1.15rem; opacity: 0.92; margin: 8px 0 36px; }

.value-props {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.value-props li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 0.98rem;
  line-height: 1.45;
}

.value-icon { font-size: 1.3rem; line-height: 1.2; flex-shrink: 0; }

.auth-panel {
  flex: 1 1 54%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;
  background: var(--bg);
}

.auth-form-card {
  width: 100%;
  max-width: 420px;
  margin: 0;
  box-shadow: var(--shadow-lg);
}

@media (max-width: 860px) {
  .auth-hero { display: none; }
  .auth-panel { padding: 32px 20px; width: 100%; }
}

/* Step indicator (signup → profile completion) */

.step-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 28px;
}

.step {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--border);
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 700;
  position: relative;
}

.step.done, .step.current { background: var(--brand); color: #fff; }
.step.current { box-shadow: 0 0 0 4px rgba(24,119,242,0.18); }

.step-label {
  position: absolute;
  top: 34px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

.step-line { flex: 1; height: 2px; background: var(--border); }

/* Form polish */

.optional { font-weight: 400; color: var(--muted); font-size: 0.8rem; }

.field-row { display: flex; gap: 16px; }
.field-row label { flex: 1; }

.field-hint {
  font-size: 0.82rem;
  margin: -10px 0 16px;
  min-height: 1em;
}

.field-hint.match { color: var(--success); }
.field-hint.mismatch { color: var(--error); }

.fine-print {
  font-size: 0.78rem;
  color: var(--muted);
  margin: 12px 0 0;
  text-align: center;
  width: 100%;
}

input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(24,119,242,0.15);
}

button, .link-button, .google-btn {
  transition: background 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease, transform 0.05s ease;
}

button[disabled] { opacity: 0.7; cursor: not-allowed; }

.flag-icon {
  display: inline-block;
  vertical-align: -2px;
  margin-right: 8px;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.08);
  object-fit: cover;
}

/* Avatars (profile photos) */

.avatar {
  display: inline-block;
  vertical-align: middle;
  margin-right: 8px;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 0 0 2px #fff, 0 0 0 3px var(--border);
}

.avatar-placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  margin-right: 8px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand) 0%, #4f9dff 100%);
  color: #fff;
  font-weight: 700;
  box-shadow: 0 0 0 2px #fff, 0 0 0 3px var(--border);
}

/* Profile photo upload field (profile edit page) */

.photo-field {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
  padding: 14px;
  background: var(--bg);
  border-radius: var(--radius);
}

.photo-field .avatar, .photo-field .avatar-placeholder { margin-right: 0; }

/* Wraps an avatar in a link to that person's profile (see avatarLink() in
   app.js) -- transparent inline wrapper, no underline/color change, just a
   subtle ring highlight on hover so it reads as clickable. */
.avatar-link { display: inline-flex; vertical-align: middle; line-height: 0; cursor: pointer; text-decoration: none; }
.avatar-link:hover .avatar, .avatar-link:hover .avatar-placeholder { box-shadow: 0 0 0 2px #fff, 0 0 0 3px var(--brand); }

.photo-field-controls { flex: 1; }
.photo-field-controls label { margin-bottom: 6px; }

.photo-field-controls input[type="file"] {
  margin-top: 6px;
  padding: 8px 0;
  border: none;
  font-size: 0.85rem;
}

.remove-photo-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.82rem;
  color: var(--muted);
  margin: 0;
}

.remove-photo-label input { width: auto; margin: 0; }

/* Feed (grooves) */

.post-form {
  margin: 20px 0 24px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.15s ease;
}
.post-form:focus-within { box-shadow: var(--shadow-md); }
/* Soft, filled textarea instead of a plain bordered box -- same "chat app"
   cue as the redesigned .chat-composer-input: a quiet fill at rest, and a
   brand-colored border only once you're actually typing. */
.post-form textarea {
  min-height: 60px;
  background: var(--bg);
  border: 1px solid transparent;
  border-radius: 18px;
  padding: 12px 16px;
}
.post-form textarea:focus {
  background: #fff;
  border-color: var(--brand);
}
/* Pill-shaped, slightly lifted Post button -- matches the rounder,
   softer language used by the redesigned chat composers instead of the
   sharper 8px-radius default button. */
.post-form button[type="submit"] {
  width: auto;
  padding: 11px 26px;
  border-radius: 22px;
  box-shadow: var(--shadow-sm);
}
.post-form button[type="submit"]:hover { box-shadow: var(--shadow-md); }
/* Emoji/sticker toggle buttons (inserted by emoji-picker.js right after
   the textarea) and the photo/voice-note buttons in the footer below --
   unify all four into the same flat, borderless circular icon style
   instead of the emoji/sticker pair looking like a separate bordered
   pill next to the plain circles the photo/mic buttons already use. */
.post-form .emoji-picker-btn, .post-form .sticker-picker-btn {
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 50%;
  font-size: 1.2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.post-form .emoji-picker-btn:hover, .post-form .sticker-picker-btn:hover { background: var(--bg); }
.post-form .media-picker-btn, .post-form .voice-record-btn { border: none; }
.post-form .media-picker-btn:hover, .post-form .voice-record-btn:hover { background: var(--border); }
/* emoji-picker.js now prepends its wrap into .composer-media-actions
   itself (see that file) so it sits in the same row as the photo/voice
   buttons -- drop the default top margin meant for sitting on its own
   line under the textarea, matching how .chat-composer already zeroes it. */
.composer-media-actions .emoji-picker-wrap { margin: 0; }
/* .media-field and .voice-note-field each wrap their 40px round button
   together with an invisible helper (a visually-hidden file input / an
   empty filename label) that, despite taking no visible space, still
   pads the wrapper's own content-box out to ~56px tall. align-items:
   center on the row still centers the icons at the *same point*, but the
   photo button's 40px box sits inside a taller 56px wrapper than the
   emoji/sticker/mic buttons' 40px wrappers, and it ends up top-aligned
   within that extra height rather than centered -- the photo icon
   visibly sits ~8px higher than the other three (reported as the row
   "not in a straight line"). Pinning every wrapper to exactly 40px,
   matching the buttons themselves, removes the extra box entirely so
   there's nothing left to misalign. */
.composer-media-actions .emoji-picker-wrap,
.composer-media-actions .media-field,
.composer-media-actions .voice-note-field { height: 40px; }

/* Visually hides an element while keeping it readable by screen readers --
   used for the icon-only composer buttons below, whose action is
   conveyed by the icon + a title tooltip rather than visible text. */
.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;
}

.media-field {
  margin-top: -4px;
  margin-bottom: 16px;
  font-size: 0.85rem;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.media-field input[type="file"] {
  margin-top: 6px;
  padding: 8px 0;
  border: none;
  font-size: 0.85rem;
}
.media-picker-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.media-picker-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--ink);
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  letter-spacing: normal;
  margin-bottom: 0;
}
.media-picker-btn:hover { background: var(--border); }
.media-picker-icon { font-size: 1.2rem; line-height: 1; }
.media-picker-filename {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--ink);
  word-break: break-all;
}

.post-list, .comment-list { list-style: none; margin: 0; padding: 0; }

.post-item {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 14px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.15s ease;
}
.post-item:hover { box-shadow: var(--shadow-md); }
.post-item-solo { margin-bottom: 0; box-shadow: none; }

.post-author { font-size: 0.9rem; margin-bottom: 8px; display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }

.post-body {
  margin: 0 0 10px;
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.5;
}
.post-body-link { color: var(--ink); text-decoration: none; }
.post-body-link:hover { color: var(--brand); text-decoration: none; }

.post-media {
  display: block;
  position: relative;
  margin-bottom: 10px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg);
  line-height: 0;
}
.post-media img, .post-media video {
  width: 100%;
  max-height: 420px;
  object-fit: cover;
  display: block;
}
.post-media-solo {
  cursor: default;
}
.post-media-solo img, .post-media-solo video {
  max-height: 520px;
  object-fit: contain;
  background: #000;
}
.post-media-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(0,0,0,0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  pointer-events: none;
}

.post-media-audio {
  padding: 10px 12px;
  line-height: normal;
  cursor: default;
}
.post-media-audio audio { width: 100%; display: block; }
.post-media-solo.post-media-audio { background: var(--bg); }

/* Voice-note recorder (Groove composer) -- mirrors .media-field's layout so
   the two "add media" controls sit consistently in the composer footer. */
.voice-note-field {
  margin-top: -4px;
  margin-bottom: 16px;
  font-size: 0.85rem;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.voice-record-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--ink);
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  letter-spacing: normal;
}
.voice-record-btn:hover { background: var(--border); }
.voice-record-icon { font-size: 1.2rem; line-height: 1; }
.voice-record-btn.is-recording {
  background: var(--error);
  border-color: var(--error);
  color: #fff;
  animation: voice-record-pulse 1.4s ease-in-out infinite;
}
@keyframes voice-record-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.75; }
}
.voice-record-timer { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--error); }
.voice-note-preview { display: flex; align-items: center; gap: 10px; }
.voice-note-preview audio { height: 32px; max-width: 220px; }
.voice-note-preview .remove-link {
  width: auto;
  background: none;
  color: var(--error);
  font-size: 0.82rem;
  font-weight: 600;
  padding: 4px;
}
.voice-record-error { font-size: 0.8rem; width: 100%; }

.post-actions { display: flex; align-items: center; gap: 10px; border-top: 1px solid var(--border); padding-top: 10px; margin-top: 4px; }
.post-actions form { width: auto; }

.post-like {
  background: none;
  color: var(--muted);
  width: auto;
  padding: 6px 10px;
  font-size: 0.9rem;
  font-weight: 600;
  border-radius: 6px;
}
.post-like:hover { background: var(--bg); color: var(--ink); }
.post-like.liked { color: var(--error); }

.post-comment-link {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 6px;
}
.post-comment-link:hover { color: var(--brand); background: var(--bg); text-decoration: none; }

.comment-item {
  border-left: 3px solid var(--border);
  padding: 8px 0 8px 14px;
  margin-bottom: 10px;
}

/* Corrections — a comment flagged as a correction gets a distinct amber
   treatment throughout, so "someone fixed my writing" stands out from a
   plain reply at a glance. */

.comment-item.correction {
  border-left-color: var(--gold);
  background: #fff9ee;
  border-radius: 0 8px 8px 0;
  padding: 10px 14px;
}

.correction-badge {
  display: inline-block;
  background: var(--gold);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  margin: 0 6px;
}

.correction-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  margin: -6px 0 14px;
  color: var(--ink);
}
.correction-toggle input { width: auto; margin: 0; }

/* Word-level correction diff -- unchanged text stays plain, removed text is
   struck through in red, added text is bolded in green, so comparing the
   original to the correction is a glance, not a re-read. See
   src/utils/diff.js (correctionDiffHtml), used on post.ejs. */
.diff-body { line-height: 1.55; }
.diff-removed {
  color: var(--error);
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  opacity: 0.85;
}
.diff-added {
  color: var(--success);
  font-weight: 700;
}

.accepted-badge {
  display: inline-block;
  background: var(--success);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  margin: 0 6px;
}

.comment-actions {
  display: flex;
  gap: 14px;
  align-items: center;
  margin-top: 6px;
}
.comment-actions .link-button {
  width: auto;
  background: none;
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 600;
  padding: 0;
}
.comment-actions .link-button:hover { color: var(--brand); }
.comment-actions .accept-btn { color: var(--success); }
.comment-actions .accept-btn:hover { color: var(--success); opacity: 0.8; }

.correction-compose {
  margin-top: 10px;
  background: #fff9ee;
  border: 1px solid var(--gold);
  border-radius: 8px;
  padding: 12px;
}
.correction-compose .field-hint { margin: 4px 0 10px; }
/* Explicit width/padding here because .post-form button[type="submit"]
   (which used to cover this via a broader, unscoped ".post-form button"
   selector) no longer matches a type="button" element like Cancel --
   scoping that rule to submit buttons only was needed so it would stop
   also resizing the icon-only voice-note/photo composer buttons, so
   Cancel needs its own sizing to keep looking the same as before. */
.correct-cancel, .edit-cancel { margin-left: 10px; width: auto; padding: 10px 22px; }

.transfer-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
}
.transfer-form {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  margin-top: 10px;
}
.transfer-form label { flex: 1; margin: 0; }
.transfer-form button { width: auto; white-space: nowrap; }

/* Emoji + sticker picker -- see public/js/emoji-picker.js, which injects
   this markup after any <textarea class="emoji-enabled">. */
.emoji-picker-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 6px 0 0;
}
.emoji-picker-btn, .sticker-picker-btn {
  width: auto;
  background: none;
  border: 1px solid var(--border);
  color: var(--ink);
  border-radius: 6px;
  padding: 3px 9px;
  font-size: 1rem;
  line-height: 1.4;
}
.emoji-picker-btn:hover, .sticker-picker-btn:hover { background: var(--bg); }
.emoji-picker-panel, .sticker-picker-panel {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 30;
  margin-top: 4px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  display: grid;
  gap: 2px;
  max-height: 220px;
  overflow-y: auto;
}
.emoji-picker-panel { grid-template-columns: repeat(8, 1fr); width: 300px; }
.sticker-picker-panel { grid-template-columns: repeat(4, 1fr); width: 180px; }
.emoji-picker-item, .sticker-picker-item {
  width: 28px;
  height: 28px;
  background: none;
  border: none;
  color: var(--ink);
  font-size: 1.15rem;
  border-radius: 6px;
  padding: 0;
  line-height: 1;
  opacity: 1;
}
.sticker-picker-item { width: 38px; height: 38px; font-size: 1.6rem; }
.emoji-picker-item:hover, .sticker-picker-item:hover { background: var(--bg); }

/* A rendered [[sticker:X]] token (see bodyHtml() in app.js) -- shown
   noticeably bigger than a plain inline emoji so it reads as a sticker
   someone chose to send, not just decorated text. */
.sticker { font-size: 2rem; line-height: 1.2; display: inline-block; vertical-align: middle; }

/* Chat-style composers (DM thread, group chat) -- textarea, the emoji/
   sticker tools wrap emoji-picker.js injects right after it, and the Send
   button all sit as flex siblings in one row/bar instead of the textarea
   floating above a separate Send button with the emoji tools sandwiched
   awkwardly between them on their own line. */
.chat-composer {
  display: flex;
  align-items: center;
  gap: 4px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 24px;
  padding: 4px 6px;
}
/* The attach (📎) button reuses .media-picker-btn from the Groove
   composer -- same circular icon-button look, just sized down a touch
   (34px vs 40px) to sit flush inside this tighter pill alongside the
   38px send button, and borderless like the redesigned emoji/sticker
   buttons below rather than the bordered circle it has elsewhere. */
/* Same wrapper-taller-than-its-button issue documented above for the
   Groove composer's .media-field (a hidden file input + empty filename
   span pad the wrapper to ~56px even though nothing renders) -- here it
   made the 📎 attach button sit visibly higher than the textarea/send
   button in this pill. Pinning the wrapper to the button's own 34px height
   removes the extra box so align-items:center on .chat-composer actually
   centers it. */
.chat-composer .media-field { margin: 0; display: flex; height: 34px; }
.chat-composer .chat-attach-btn {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border: none;
}
.chat-composer .chat-attach-btn:hover { background: var(--bg); }
/* Voice-note record button, right after the attach button in the same
   pill -- reuses .voice-record-btn's circular-button look from the Groove
   composer (style.css, "Voice-note recorder" block above) but sized/
   bordered to match .chat-attach-btn just above rather than Groove's
   larger 40px bordered version. */
.chat-composer .voice-record-btn {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border: none;
}
.chat-composer .voice-record-btn:hover { background: var(--bg); }
.chat-composer .voice-record-btn .voice-record-icon { font-size: 1rem; }
.chat-composer .voice-record-timer { font-size: 0.78rem; padding: 0 2px; flex-shrink: 0; white-space: nowrap; }
.chat-composer-input {
  flex: 1;
  margin: 0;
  min-height: 24px;
  max-height: 120px;
  padding: 9px 0;
  border: none;
  border-radius: 0;
  background: transparent;
  resize: none;
}
.chat-composer-input:focus { outline: none; box-shadow: none; }
.chat-composer .emoji-picker-wrap { margin: 0; }
.chat-composer .emoji-picker-panel, .chat-composer .sticker-picker-panel { left: auto; right: 0; }
.chat-composer .emoji-picker-btn, .chat-composer .sticker-picker-btn {
  border: none;
  border-radius: 50%;
  padding: 6px;
  font-size: 1.15rem;
}
.chat-composer .emoji-picker-btn:hover, .chat-composer .sticker-picker-btn:hover { background: var(--bg); }
/* Circular send button with a paper-plane glyph instead of the word
   "Send" -- the button text stays in the markup (for accessibility/no-JS
   fallback), just visually hidden via font-size:0 and swapped for the
   icon via ::after. */
.chat-composer-send {
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 50%;
  font-size: 0;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-composer-send::after {
  content: '\27A4';
  font-size: 1rem;
  transform: translateX(-1px);
}

/* Filename chip shown above a composer once a photo/video is picked but
   not sent yet -- used on the DM thread, group chat, and the floating
   widget composers alike. */
.chat-attachment-preview {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--brand-light);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 6px 10px 6px 14px;
  margin-bottom: 6px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink);
}
.chat-attachment-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Swapped in for .chat-attachment-name when the attached file is a
   recorded/picked voice note -- "voice-note.webm" as plain text isn't
   useful to look at before sending, so this chip shows a playable preview
   instead (see the voice-note recording script in each composer). */
.chat-attachment-preview audio { flex: 1; min-width: 0; height: 30px; }
.chat-attachment-remove {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-attachment-remove:hover { background: #fff; color: var(--ink); }

/* A photo/video attached to a chat message -- sits above the text (or
   alone, for a media-only message) inside the bubble. Inset with its own
   rounding rather than bleeding to the bubble's edges, which keeps this
   simple across three different composer surfaces (DM thread, group
   chat, floating widget) without needing to also clip the bubble itself. */
.msg-media { margin-bottom: 6px; border-radius: 10px; overflow: hidden; line-height: 0; }
.msg-media:last-child { margin-bottom: 2px; }
.msg-media img, .msg-media video {
  display: block;
  width: 100%;
  max-height: 260px;
  object-fit: cover;
  border-radius: 10px;
  background: var(--bg);
}
.msg-media audio { display: block; width: 220px; max-width: 100%; }
.chat-window-body .msg-media img, .chat-window-body .msg-media video { max-height: 160px; }
.chat-window-body .msg-media audio { width: 170px; height: 32px; }
/* Narrower footprint for the attach button + preview chip inside the
   280px-wide floating window, matching the emoji/sticker buttons' scale
   in that context rather than the full-size 34px used on the DM thread
   and group chat pages. */
.chat-window-form .chat-attach-btn { width: auto; height: auto; padding: 6px 8px; font-size: 0.95rem; }
.chat-window-form .voice-record-btn { width: auto; height: auto; padding: 6px 8px; font-size: 0.95rem; border: none; flex-shrink: 0; }
.chat-window-form .voice-record-btn .voice-record-icon { font-size: 0.9rem; }
.chat-window-form .voice-record-timer { font-size: 0.72rem; padding: 0 2px; flex-shrink: 0; white-space: nowrap; }
.chat-window .chat-attachment-preview { font-size: 0.78rem; padding: 5px 8px 5px 12px; margin: 8px 8px 0; }
.chat-window .chat-attachment-preview audio { width: 150px; height: 28px; }

/* Groups */
.group-list-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.group-list-header-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.group-create-btn { width: auto; padding: 10px 18px; margin-bottom: 0; }
.group-browse-btn {
  display: inline-block;
  padding: 10px 18px;
  border-radius: 8px;
  border: 1px solid var(--border);
  color: var(--ink);
  font-weight: 700;
  background: var(--card-bg);
}
.group-browse-btn:hover { background: var(--bg); text-decoration: none; }
.group-list { list-style: none; margin: 0; padding: 0; }
.group-item {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
}
.group-item a { text-decoration: none; color: var(--ink); display: block; }
.group-item .muted { margin-left: 8px; font-size: 0.85rem; }
/* The description reuses .partner-bio's text styling, but that class is
   only block-level inside .partner-item (see above) -- without this, the
   description ran on the same line as the name/member-count with no break,
   crowding everything together. */
.group-item .partner-bio {
  display: block;
  color: var(--muted);
  font-size: 0.85rem;
  margin-top: 4px;
  line-height: 1.4;
}
.group-role-badge {
  display: inline-flex;
  align-items: center;
  background: var(--brand);
  color: #fff;
  border-radius: 999px;
  padding: 1px 9px;
  font-size: 0.7rem;
  font-weight: 700;
  margin-left: 8px;
  vertical-align: middle;
}
.group-invite-box {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin: 16px 0;
}
.group-invite-row { display: flex; gap: 8px; margin-top: 8px; }
.group-invite-row input {
  flex: 1;
  font-size: 0.85rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
}
.group-invite-row button { width: auto; padding: 8px 16px; }
.group-add-form { display: flex; align-items: flex-end; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
.group-add-form label { flex: 1; min-width: 200px; margin-bottom: 0; }
.group-add-form button { width: auto; padding: 10px 18px; }
.recordings-toggle-form { display: flex; align-items: center; gap: 10px; margin: 0; }
.toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; }
.toggle-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  box-sizing: border-box;
  opacity: 0;
  cursor: pointer;
}
.toggle-switch .toggle-slider {
  position: absolute;
  inset: 0;
  background: #ccc;
  border-radius: 24px;
  transition: background 0.2s;
  pointer-events: none;
}
.toggle-switch .toggle-slider::before {
  content: "";
  position: absolute;
  height: 18px;
  width: 18px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s;
}
.toggle-switch input:checked + .toggle-slider { background: var(--brand); }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(20px); }
.toggle-switch input:focus-visible + .toggle-slider { outline: 2px solid var(--brand); outline-offset: 2px; }
.toggle-switch-label { font-size: 14px; color: var(--ink); }

/* Tucks the invite-link box + add-member form behind one "⚙️ Admin tools"
   toggle (see views/groups/show.ejs) instead of sitting exposed on every
   visit to the group -- same .settings-collapse pattern used on /account. */
.group-admin-tools { margin: 16px 0; }
.group-admin-tools .group-invite-box { margin-top: 0; }
.group-admin-tools .group-add-form { margin-bottom: 0; margin-top: 16px; }
.group-requests-box {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin: 16px 0;
}
.group-requests-box .member-list { margin-top: 10px; }
.member-list { list-style: none; margin: 0; padding: 0; }
.member-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  padding: 10px 0;
}
.member-row:last-child { border-bottom: none; }
.member-info { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* .partner-bio's own display:block isn't enough to force a line break
   inside a flex-wrap row like .member-info (a block-display flex item
   still just sizes to content, not the full row) -- flex-basis: 100% makes
   it claim the whole row so the description drops to its own line instead
   of crowding into the name/member-count line (used by the group browse
   list, views/groups/browse.ejs). */
.member-info .partner-bio {
  display: block;
  flex-basis: 100%;
  color: var(--muted);
  font-size: 0.85rem;
  margin-top: 2px;
  line-height: 1.4;
}
.member-actions { display: flex; gap: 12px; }
.member-actions form { width: auto; }
.member-actions .remove-link { color: var(--error); }
.member-name-link { color: var(--ink); text-decoration: none; }
.member-name-link:hover { color: var(--brand); text-decoration: underline; }

/* "Find groups to join" browse list (views/groups/browse.ejs) -- reuses the
   .member-list/.member-row/.member-info layout above rather than the
   full-row-link .partner-item pattern, since a browse row must NOT link
   straight to /groups/:id before joining (that route 404s for non-members). */
.group-join-btn {
  display: inline-block;
  width: auto;
  margin-bottom: 0;
  padding: 8px 16px;
  background: var(--brand);
  color: #fff;
  font-weight: 700;
  border-radius: 8px;
}
.group-join-btn:hover { background: var(--brand-dark); }

/* Shown in place of the Join button once a request is pending admin review
   (see Group.requestJoin/browseFor's `requested` flag) -- muted and
   non-interactive so it doesn't look clickable. */
.group-requested-label {
  display: inline-block;
  padding: 8px 16px;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.9rem;
  font-style: italic;
}

/* Pending join-request queue in the group admin tools panel
   (views/groups/show.ejs) -- reuses .member-list/.member-row/.member-info
   with a two-button approve/decline action pair instead of a single Join
   or remove/promote action. */
.group-request-actions { display: flex; gap: 8px; }
.group-request-actions form { width: auto; }
.group-approve-btn {
  display: inline-block;
  width: auto;
  margin-bottom: 0;
  padding: 6px 14px;
  background: var(--brand);
  color: #fff;
  font-weight: 700;
  border-radius: 8px;
  font-size: 0.9rem;
}
.group-approve-btn:hover { background: var(--brand-dark); }
.group-decline-btn {
  display: inline-block;
  width: auto;
  margin-bottom: 0;
  padding: 6px 14px;
  background: transparent;
  color: var(--error);
  font-weight: 700;
  border: 1px solid var(--error);
  border-radius: 8px;
  font-size: 0.9rem;
}
.group-decline-btn:hover { background: var(--error); color: #fff; }

/* Teacher program */
.teacher-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: #fff4e0;
  color: #8a5a00;
  border: 1px solid #f0d29a;
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  margin-left: 6px;
  vertical-align: middle;
}
.teacher-apply-form label { margin-bottom: 14px; }
.teacher-status-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 20px;
}
.teacher-status-pending { color: #8a5a00; }
.teacher-status-approved { color: #1a7a3c; }
.teacher-status-rejected { color: var(--error); }
.admin-app-list { list-style: none; margin: 0; padding: 0; }
.admin-app-row {
  border-bottom: 1px solid var(--border);
  padding: 14px 0;
}
.admin-app-row:last-child { border-bottom: none; }
.admin-app-meta { font-size: 0.85rem; margin: 4px 0; }
.admin-app-actions { display: flex; gap: 10px; margin-top: 10px; }
.admin-app-actions form { width: auto; }
.admin-app-actions button { width: auto; padding: 8px 16px; font-size: 0.85rem; }
.admin-app-actions .reject-btn { background: var(--error); }

/* Teacher certifications -- both the dynamic-row set on the apply form and
   the always-visible "Your certifications" manage list below it. */
.cert-form-section {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 16px;
}
.cert-section-label { font-weight: 700; margin-bottom: 4px; display: block; }
.cert-section-hint { margin: 0 0 12px; font-size: 0.85rem; }
.cert-row {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 12px;
}
.cert-row-fields {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px 14px;
}
.cert-row-fields label { margin-bottom: 0; }
.cert-visible-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
  margin: 10px 0 0;
}
.cert-visible-toggle input[type="checkbox"] { width: auto; }
.cert-add-row-btn, .remove-cert-row-btn {
  width: auto;
  background: none;
  border: 1px solid var(--border);
  color: var(--ink);
  padding: 8px 14px;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
}
.cert-add-row-btn:hover { background: var(--brand-light); color: var(--brand); }
.remove-cert-row-btn { margin-top: 10px; color: var(--error); border-color: var(--error); }
.remove-cert-row-btn:hover { background: #fdecec; }

.cert-manage-section { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--border); }
.cert-list { list-style: none; margin: 0 0 20px; padding: 0; }
.cert-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  padding: 12px 0;
}
.cert-item:last-child { border-bottom: none; }
.cert-item-info { flex: 1; min-width: 220px; }
.cert-item-actions { display: flex; gap: 12px; align-items: center; }
.cert-item-actions form { width: auto; }
.cert-visibility-badge {
  display: inline-block;
  margin-top: 6px;
  font-size: 0.78rem;
  font-weight: 600;
  border-radius: 999px;
  padding: 2px 10px;
}
.cert-visibility-badge.cert-visible { background: #e6f6ea; color: #1a7a3c; }
.cert-visibility-badge.cert-hidden { background: var(--bg); color: var(--muted); border: 1px solid var(--border); }
.cert-add-form { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.cert-add-form label { flex: 1; min-width: 180px; margin-bottom: 0; }
.cert-add-form .cert-visible-toggle { flex-basis: 100%; margin: 0; }
.cert-add-form button { width: auto; padding: 10px 18px; }

/* Certifications shown on a teacher's public partner profile */
.profile-cert-section {
  margin-top: 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
}
.profile-cert-heading { margin: 0 0 8px; font-size: 1rem; }
.profile-cert-list { list-style: none; margin: 0; padding: 0; }
.profile-cert-item { padding: 6px 0; font-size: 0.9rem; }
.admin-app-actions .reject-btn:hover { background: #b23a3a; }

/* Admin dashboard */
.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin: 20px 0;
}
.admin-stat-tile {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  text-align: center;
}
.admin-stat-tile.admin-stat-alert {
  background: var(--error-bg);
  border-color: var(--error);
}
.admin-stat-value { display: block; font-size: 1.6rem; font-weight: 800; }
.admin-stat-label { display: block; font-size: 0.8rem; color: var(--muted); margin-top: 4px; }

.admin-quick-links {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
.admin-quick-link { width: auto; flex: 1; min-width: 220px; text-align: center; }

.admin-settings-group { margin-bottom: 18px; }
.admin-settings-group label { display: block; margin-bottom: 4px; }

.admin-user-list { list-style: none; padding: 0; margin: 0 0 12px; }
.admin-user-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.admin-user-row a { flex: 1; text-decoration: none; color: var(--ink); }
.admin-user-row a:hover { color: var(--brand); }

.admin-report-list { list-style: none; padding: 0; margin: 0; }
.admin-report-row {
  border-left: 3px solid var(--border);
  padding: 10px 14px;
  margin-bottom: 10px;
}
.admin-report-row.admin-report-open { border-left-color: var(--error); background: var(--error-bg); border-radius: 0 8px 8px 0; }

/* Notifications + favorites */

.favorite-btn {
  background: #fff;
  color: var(--brand);
  border: 1px solid var(--brand);
}
.favorite-btn:hover { background: var(--brand-light); }
.favorite-btn.favorited { background: var(--brand); color: #fff; }

.follow-btn {
  background: #fff;
  color: var(--brand);
  border: 1px solid var(--brand);
}
.follow-btn:hover { background: var(--brand-light); }
.follow-btn.following { background: var(--brand); color: #fff; }

.notification-list { list-style: none; margin: 0; padding: 0; }

.notification-item {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
}

.notification-item.unread {
  background: var(--brand-light);
  border-color: var(--brand);
}

.notification-item a { color: var(--brand); text-decoration: none; }
.notification-item a:hover { text-decoration: underline; }

/* notifications.ejs reuses .post-body (styled for user-typed groove text,
   where pre-wrap preserves intentional line breaks) for its own
   server-generated <p>, whose content is built from a chain of EJS
   if/else-if blocks -- the indentation/newlines around those tags
   that sit OUTSIDE the taken branch (e.g. right after the opening <p> and
   right before the closing </p>) still get emitted as literal text. With
   pre-wrap that surrounding whitespace rendered as visible blank lines
   above and below the one line of actual text, making every notification
   card way taller than its content (reported as "why is it this big" on
   mobile, but the same bug shows on desktop too -- collapsing whitespace
   here doesn't touch the template, just stops pre-wrap from preserving
   whitespace that was never meant to be visible in the first place). */
.notification-item .post-body { white-space: normal; margin: 0; }

/* m2j coins + activity/ranking */

.coin-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: #fff7e6;
  color: #8a5a00;
  border: 1px solid #f5d68a;
  font-weight: 700;
  font-size: 0.85rem;
  padding: 5px 12px;
  border-radius: 999px;
  text-decoration: none;
}
.coin-pill:hover { background: #ffedc2; text-decoration: none; }

.activity-pill {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 700;
  padding: 3px 9px;
  border-radius: 999px;
}
.activity-hot  { background: var(--success-bg); color: #1a7a37; }
.activity-warm { background: #fff4e0; color: #a15c00; }
.activity-cool { background: var(--brand-light); color: var(--brand-dark); }
.activity-cold { background: #f1f2f4; color: var(--muted); }
.activity-new  { background: var(--brand-light); color: var(--brand-dark); }

/* Online/offline presence dot -- wraps whatever avatarIcon()/avatarLink()
   already rendered without disturbing its layout (avatar-wrap is just an
   inline-block positioning context), then pins a small ring-bordered dot to
   the bottom-right corner. Green = pinged the server within the last 90s
   (see isOnline() in app.js); gray = anything else, including "never seen
   this user online". */
.avatar-wrap { position: relative; display: inline-block; line-height: 0; vertical-align: middle; }
.presence-dot {
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 2px solid var(--card-bg);
  z-index: 1;
}
.presence-dot-online { background: #2ecc71; }
.presence-dot-offline { background: #b0b6bd; }

.profile-meta-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin: -6px 0 18px;
}

.stats-bar {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 10px;
  margin-top: 24px;
}

.stat-tile {
  background: var(--bg);
  border-radius: var(--radius);
  padding: 14px 12px;
  text-align: center;
  text-decoration: none;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: background 0.15s ease, transform 0.15s ease;
}
a.stat-tile:hover { background: var(--brand-light); transform: translateY(-2px); text-decoration: none; }
.stat-tile-coins { background: #fff7e6; }
a.stat-tile-coins:hover { background: #ffedc2; }

.stat-value { font-size: 1.15rem; font-weight: 800; }
.stat-label { font-size: 0.72rem; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }

.rank-list { list-style: none; margin: 0; padding: 0; }

.rank-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 8px;
  box-shadow: var(--shadow-sm);
}

.rank-row-top { background: linear-gradient(90deg, #fffaf0 0%, #ffffff 60%); border-color: #f5d68a; }
.rank-row-me { border-color: var(--brand); box-shadow: 0 0 0 2px rgba(24,119,242,0.15); }

.rank-place {
  width: 36px;
  flex-shrink: 0;
  text-align: center;
  font-weight: 800;
  font-size: 1.1rem;
  color: var(--muted);
}

.rank-info { flex: 1; min-width: 0; }
.rank-name { font-size: 0.98rem; }
.rank-sub { font-size: 0.78rem; margin-top: 2px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

.rank-coins { flex-shrink: 0; }

/* Floating Messenger-style chat widget */

.chat-widget {
  position: fixed;
  right: 20px;
  bottom: 0;
  z-index: 500;
  display: flex;
  align-items: flex-end;
  gap: 12px;
}

.chat-launcher {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  margin-bottom: 18px;
  position: relative;
  transition: transform 0.15s ease;
}
.chat-launcher:hover { transform: scale(1.06); }

.chat-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 20px;
  height: 20px;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
  margin-left: 0;
}

.chat-panel {
  position: fixed;
  right: 88px;
  bottom: 16px;
  width: 280px;
  max-height: 420px;
  z-index: 2;
  background: var(--card-bg);
  border-radius: 12px 12px 0 0;
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border);
  border-bottom: none;
}

.chat-panel-header {
  background: var(--brand);
  color: #fff;
  padding: 12px 14px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.chat-panel-close {
  background: none;
  border: none;
  color: #fff;
  font-size: 1.3rem;
  cursor: pointer;
  width: auto;
  padding: 0;
  line-height: 1;
}

.chat-list-body { overflow-y: auto; padding: 6px; }
.chat-list-empty { padding: 14px; }

.chat-list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border-radius: 8px;
  cursor: pointer;
}
.chat-list-row:hover { background: var(--bg); }
.chat-list-name { flex: 1; font-size: 0.9rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.chat-windows {
  position: fixed;
  right: 88px;
  bottom: 0;
  display: flex;
  flex-direction: row-reverse;
  gap: 12px;
  align-items: flex-end;
  pointer-events: none;
}
.chat-windows .chat-window { pointer-events: auto; }

.chat-window {
  width: 280px;
  height: 360px;
  background: var(--card-bg);
  border-radius: 12px 12px 0 0;
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border);
  border-bottom: none;
}

.chat-window-minimized { height: 46px; }
.chat-window-minimized .chat-window-body, .chat-window-minimized .chat-window-form, .chat-window-minimized .chat-blocked-notice { display: none; }

.chat-window-header {
  background: var(--brand);
  color: #fff;
  padding: 8px 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.chat-window-header .avatar, .chat-window-header .avatar-placeholder { margin-right: 0; box-shadow: 0 0 0 2px rgba(255,255,255,0.5); }
.chat-window-avatar-link { display: inline-flex; line-height: 0; cursor: pointer; }
.chat-window-avatar-link:hover .avatar, .chat-window-avatar-link:hover .avatar-placeholder { box-shadow: 0 0 0 2px #fff; }
.chat-window-name { flex: 1; font-weight: 700; font-size: 0.88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.chat-window-controls { display: flex; align-items: center; gap: 4px; }
/* Scoped to the specific icon-button classes (not a bare "button" selector)
   so this white-on-brand round-icon styling doesn't also apply to the
   Block/Unblock button tucked inside the "⋯" panel below -- that one needs
   to read as ordinary dark text on a white dropdown, not a white icon. */
.chat-window-controls .overflow-menu-btn,
.chat-window-controls .chat-window-min,
.chat-window-controls .chat-window-close {
  background: rgba(255,255,255,0.2);
  border: none;
  color: #fff;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0;
}
.chat-window-controls .overflow-menu-btn:hover,
.chat-window-controls .chat-window-min:hover,
.chat-window-controls .chat-window-close:hover { background: rgba(255,255,255,0.34); }
.chat-window-overflow .overflow-menu-panel .chat-window-block-btn { font-size: 0.82rem; color: var(--ink); }
.chat-blocked-notice { padding: 12px; text-align: center; font-size: 0.85rem; }

.chat-window-body {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  background-color: #e7ebef;
  background-image: radial-gradient(rgba(0, 0, 0, 0.045) 1px, transparent 1px);
  background-size: 14px 14px;
}

.chat-window-body .msg { max-width: 85%; font-size: 0.85rem; }
.chat-window-body .msg-time { font-size: 0.63rem; }

.chat-window-form {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px;
  border-top: 1px solid var(--border);
  background: #fff;
  position: relative;
}
.chat-window-form input {
  flex: 1;
  margin: 0;
  padding: 8px 12px;
  font-size: 0.85rem;
  border-radius: 18px;
  background: var(--bg);
}
.chat-window-form input:focus { box-shadow: none; }
.chat-window-form button {
  width: auto;
  padding: 8px 14px;
  font-size: 0.85rem;
}
/* Send is a plain <button type="submit">Send</button> in the JS-built
   markup (see chat-widget.ejs) -- keep the text node for a11y/no-JS, hide
   it visually, and swap in a circular paper-plane icon button to match
   the redesigned .chat-composer-send elsewhere. */
.chat-window-form button[type="submit"] {
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  font-size: 0;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-window-form button[type="submit"]::after {
  content: '\27A4';
  font-size: 0.9rem;
  transform: translateX(-1px);
}
.chat-window-form .emoji-picker-btn, .chat-window-form .sticker-picker-btn {
  border: none;
  border-radius: 50%;
  background: transparent;
}
.chat-window-form .emoji-picker-btn:hover, .chat-window-form .sticker-picker-btn:hover { background: var(--bg); }
/* Emoji/sticker toggle buttons built directly in chat-widget.ejs's JS
   (the floating window's input isn't a <textarea class="emoji-enabled">,
   so the shared public/js/emoji-picker.js doesn't attach here -- this
   reuses the same button/panel classes and token-insertion approach by
   hand). The window sits at the bottom-right of the viewport, so panels
   open upward and right-aligned instead of the default down-left, or
   they'd render off past the window's own bottom edge / the browser
   viewport. */
.chat-window-form .emoji-picker-btn, .chat-window-form .sticker-picker-btn {
  padding: 6px 8px;
  font-size: 0.95rem;
}
.chat-window-form .emoji-picker-panel, .chat-window-form .sticker-picker-panel {
  top: auto;
  left: auto;
  right: 0;
  bottom: 100%;
  margin-top: 0;
  margin-bottom: 6px;
}
/* The window itself is only 280px wide (see .chat-window) -- the default
   panel widths would overhang past both edges, so narrow them to fit. */
.chat-window-form .emoji-picker-panel { width: 260px; grid-template-columns: repeat(7, 1fr); }
.chat-window-form .sticker-picker-panel { width: 168px; }

@media (max-width: 640px) {
  .chat-widget { display: none; }
}

/* Active nav link (Grooves, the actual home of the app) */

.nav-links > a.active { background: var(--brand-light); color: var(--brand); }

/* ---------------------------------------------------------------------
   App shell — three-column layout used on every authenticated page (see
   views/partials/head.ejs / foot.ejs): "welcome back" profile/level card on
   the left, the page's own content in the middle, community stats +
   leaderboard + levels + earn-rates on the right. Mirrors the classic
   forum/community-hub layout (profile widget, filter pills, sidebar
   stat cards) but skinned in Lingogroove's existing blue/white system
   rather than introducing a new palette. Originally built for the Grooves
   feed specifically -- the class names kept the "feed" prefix since so much
   else already matched it, but this is the persistent shell for the whole
   app now, not just that one page.
   --------------------------------------------------------------------- */

.feed-shell {
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr) 288px;
  gap: 20px;
  max-width: 1180px;
  margin: 28px auto;
  padding: 0 20px 40px;
  align-items: start;
}

.feed-col-left, .feed-col-right { display: flex; flex-direction: column; gap: 16px; position: sticky; top: 76px; }
.feed-col-main { min-width: 0; }
.feed-col-main h1 { margin-top: 4px; }

/* Most non-feed pages wrap their content in the existing .card/.card.wide
   look for visual consistency with the rest of the app -- but .card's own
   rules (auth-card, .card { margin: 8vh auto; ... } and .card { max-width:
   560px; margin: 32px auto; }) were written for a page that IS the full
   width viewport, centering and capping the card itself. Nested one level
   deeper inside the middle grid column, that centering/capping just eats
   into space the column layout already manages -- so it's neutralized here
   while every other .card rule (background, border, radius, padding,
   shadow) still applies unchanged. */
.feed-col-main .card { max-width: none; margin: 0; }

.side-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px;
  box-shadow: var(--shadow-sm);
}

.side-card-title {
  margin: 0 0 12px;
  font-size: 0.82rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.side-card-more { display: block; margin-top: 12px; font-size: 0.85rem; font-weight: 600; }

/* Profile / level card */

.profile-card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.profile-card-header .avatar, .profile-card-header .avatar-placeholder { margin-right: 0; }
.profile-card-hello { margin: 0; font-size: 0.82rem; color: var(--muted); }
.profile-card-name { margin: 2px 0 0; font-size: 1.05rem; font-weight: 800; letter-spacing: -0.01em; }
.profile-card-username { margin: 1px 0 0; font-size: 0.8rem; color: var(--muted); }

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

.level-chip, .tier-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.78rem;
  font-weight: 800;
  padding: 4px 10px;
  border-radius: 999px;
}
.level-chip { background: var(--brand); color: #fff; }
.level-chip-cap { opacity: 0.75; font-weight: 600; }
.tier-chip { background: #fff7e6; color: #8a5a00; border: 1px solid #f5d68a; }

.profile-card-coins {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: #fff7e6;
  border-radius: 8px;
  padding: 12px 14px;
  margin-bottom: 12px;
}
.profile-card-coins-value { font-size: 1.35rem; font-weight: 800; color: #8a5a00; }

.level-progress { margin-bottom: 14px; }
.level-progress-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--bg);
  overflow: hidden;
  margin-bottom: 6px;
}
.level-progress-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--brand) 0%, #4f9dff 100%);
  transition: width 0.3s ease;
}
.level-progress-label { margin: 0; font-size: 0.8rem; color: var(--muted); }
.level-progress-label strong { color: var(--ink); }

.sidebar-link {
  display: block;
  font-size: 0.85rem;
  font-weight: 700;
  margin-bottom: 14px;
}

.btn-block {
  display: block;
  text-align: center;
  background: var(--brand);
  color: #fff;
  font-weight: 700;
  padding: 11px 16px;
  border-radius: 8px;
  margin-bottom: 14px;
}
.btn-block:hover { background: var(--brand-dark); color: #fff; text-decoration: none; }

.sidebar-actions { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.sidebar-actions a {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--ink);
  background: var(--bg);
  padding: 8px 12px;
  border-radius: 8px;
}
.sidebar-actions a:hover { background: var(--brand-light); color: var(--brand); text-decoration: none; }

.sidebar-footer { display: flex; align-items: center; gap: 6px; font-size: 0.82rem; padding-top: 12px; border-top: 1px solid var(--border); }
.sidebar-footer-dot { color: var(--border); }
.inline-form { display: inline; width: auto; }
.link-button-plain {
  background: none;
  border: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.82rem;
  padding: 0;
  width: auto;
  cursor: pointer;
}
.link-button-plain:hover { color: var(--brand); background: none; }

/* Search + filter pills above the composer */

.feed-toolbar { margin-bottom: 16px; }

.search-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 9px 16px;
  margin-bottom: 10px;
}
.search-icon { opacity: 0.6; }
.search-bar input {
  border: none;
  padding: 0;
  margin: 0;
  width: 100%;
  font-size: 0.95rem;
}
.search-bar input:focus { outline: none; box-shadow: none; }

.discover-search-form { display: flex; gap: 10px; align-items: center; margin-bottom: 16px; }
.discover-search-form .search-bar { flex: 1; margin-bottom: 0; }
.discover-search-form button { width: auto; padding: 9px 18px; }
.discover-search-heading { font-size: 1rem; margin: 4px 0 12px; }

.filter-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.filter-pill {
  width: auto;
  background: var(--card-bg);
  color: var(--muted);
  border: 1px solid var(--border);
  font-size: 0.82rem;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 999px;
}
.filter-pill:hover { background: var(--brand-light); color: var(--brand); }
.filter-pill.is-active { background: var(--ink); color: #fff; border-color: var(--ink); }

/* Composer */

.composer-row { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 12px; }
.composer-row .avatar, .composer-row .avatar-placeholder { margin-right: 0; margin-top: 2px; }
.composer-field { flex: 1; margin: 0; }
.composer-field textarea { margin-top: 0; }

.composer-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.composer-media-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: 1; min-width: 0; }
.composer-footer .media-field { margin: 0; }
.composer-footer .voice-note-field { margin: 0; }
.composer-footer button[type="submit"] { width: auto; padding: 10px 22px; }

/* Per-post level badge next to the author's name in the feed */

.post-author-level {
  display: inline-flex;
  align-items: center;
  font-size: 0.7rem;
  font-weight: 800;
  color: var(--brand-dark);
  background: var(--brand-light);
  padding: 2px 8px;
  border-radius: 999px;
  margin: 0 2px;
}

.post-share {
  background: none;
  color: var(--muted);
  width: auto;
  padding: 6px 10px;
  font-size: 0.9rem;
  font-weight: 600;
  border-radius: 6px;
  margin-left: auto;
}
.post-share:hover { background: var(--bg); color: var(--ink); }

.load-more-link { font-weight: 700; }

/* Community / top earners / levels / ways-to-earn side cards */

.side-stat-list, .top-earner-list, .tier-list, .earn-list { list-style: none; margin: 0; padding: 0; }

.side-stat-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 7px 0;
  font-size: 0.9rem;
  color: var(--muted);
}
.side-stat-list li strong { color: var(--ink); font-size: 0.95rem; }
.side-stat-list li + li { border-top: 1px solid var(--border); }

.top-earner-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 0;
}
.top-earner-list li + li { border-top: 1px solid var(--border); }
.top-earner-rank { width: 20px; flex-shrink: 0; font-weight: 800; font-size: 0.82rem; color: var(--muted); }
.top-earner-list .avatar, .top-earner-list .avatar-placeholder { margin-right: 0; }
.top-earner-name { flex: 1; min-width: 0; font-size: 0.86rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.top-earner-coins { flex-shrink: 0; font-size: 0.8rem; font-weight: 700; color: #8a5a00; }

.tier-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 7px 8px;
  border-radius: 6px;
  font-size: 0.85rem;
}
.tier-row-title { font-weight: 700; color: var(--muted); }
.tier-row-range { font-size: 0.78rem; color: var(--muted); }
.tier-row-mine { background: var(--brand-light); }
.tier-row-mine .tier-row-title, .tier-row-mine .tier-row-range { color: var(--brand-dark); }
.tier-row-you {
  background: var(--brand);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 800;
  padding: 1px 6px;
  border-radius: 999px;
  margin-left: 4px;
  vertical-align: 1px;
}

.earn-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 7px 0;
  font-size: 0.86rem;
  color: var(--muted);
}
.earn-list li + li { border-top: 1px solid var(--border); }
.earn-list li strong { color: var(--success); font-weight: 800; }

@media (max-width: 1020px) {
  .feed-shell { grid-template-columns: 1fr; max-width: 640px; }
  .feed-col-left, .feed-col-right { position: static; }
  .feed-col-main { order: 1; }
  .feed-col-left { order: 2; }
  .feed-col-right { order: 3; }
}

/* ---------------------------------------------------------------------
   Mobile app shell — below 780px, Lingogroove stops looking like a shrunk
   desktop site and switches to a native-app layout (Instagram is the
   reference point): a compact header with just two persistent icons
   (activity, messages), a five-tab bottom bar (Home / Discover / new-post
   "+" / Groups / Profile-as-your-own-avatar), and the "welcome back"
   profile card + sitewide stats sidebar -- both meant for a spacious
   three-column desktop layout -- no longer trail below every single page's
   content. Instead the profile card becomes the top of the Profile tab
   (see body.page-profile / views/account.ejs), and the stats sidebar is
   simply not needed on a phone (Ranking already has the full leaderboard).
   A DM thread additionally goes fully edge-to-edge, chrome-free, with its
   own back button (see body.page-chat-thread / views/messages/thread.ejs)
   -- the way opening a conversation in a real chat app replaces the list
   with the thread instead of squeezing both into one scroll.
   .topbar-mobile-icons/.mobile-tabbar/.mobile-quick-links/.thread-back-btn
   are all display:none above this breakpoint -- desktop is untouched.
   --------------------------------------------------------------------- */
@media (max-width: 780px) {
  .topbar { padding: 10px 14px; }
  .nav-links { display: none; }

  .topbar-mobile-icons { display: flex; align-items: center; gap: 4px; }
  .topbar-icon-link {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    font-size: 1.25rem;
    color: var(--ink);
  }
  .topbar-icon-link:hover, .topbar-icon-link.active { background: var(--brand-light); }
  .topbar-icon-badge { position: absolute; top: 2px; right: 2px; padding: 1px 5px; font-size: 0.6rem; margin: 0; }

  .mobile-tabbar {
    display: flex;
    justify-content: space-around;
    align-items: center;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 58px;
    background: var(--card-bg);
    border-top: 1px solid var(--border);
    box-shadow: 0 -2px 10px rgba(0,0,0,0.06);
    z-index: 100;
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .mobile-tabbar a {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 44px;
    border-radius: 12px;
    font-size: 1.35rem;
    color: var(--muted);
  }
  .mobile-tabbar a.active { background: var(--brand-light); color: var(--brand); }
  .mobile-tabbar .mobile-tabbar-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    padding: 1px 5px;
    font-size: 0.62rem;
  }
  /* The center "new Groove" tab reads as a distinct action (not just
     another destination), Instagram/TikTok-style: a solid brand-colored
     circle that sits slightly above the rest of the bar. */
  .mobile-tabbar-add {
    width: 44px !important;
    height: 44px !important;
    margin-top: -14px;
    border-radius: 50% !important;
    background: var(--brand);
    color: #fff !important;
    font-size: 1.6rem !important;
    font-weight: 400;
    box-shadow: 0 3px 10px rgba(24,119,242,0.4);
  }
  .mobile-tabbar-add:hover { background: var(--brand-dark); }
  /* The Profile tab shows the user's real avatar rather than a 🧑 glyph --
     avatarIcon() already returns an <img>/.avatar-placeholder, so this just
     nudges its default 26px box to fill the same 44px tap target as every
     other tab and gives it a ring when active, mirroring how Instagram
     outlines your own profile photo in the tab bar. */
  .mobile-tabbar-profile { padding: 0; }
  .mobile-tabbar-profile .avatar, .mobile-tabbar-profile .avatar-placeholder { margin: 0; box-shadow: 0 0 0 2px var(--border); }
  .mobile-tabbar-profile.active .avatar, .mobile-tabbar-profile.active .avatar-placeholder { box-shadow: 0 0 0 2px var(--brand); }

  body { padding-bottom: 58px; }

  /* The persistent profile card (left) and sitewide-stats sidebar (right)
     are a desktop three-column-layout convenience -- on a phone they'd
     otherwise repeat below every single page's actual content. Both are
     hidden by default; body.page-profile (the Profile tab / views/
     account.ejs) brings the profile card back as that page's own header. */
  .feed-col-left, .feed-col-right { display: none; }

  body.page-profile .feed-col-left {
    display: flex;
    order: 1;
    position: static;
  }
  body.page-profile .feed-col-main { order: 2; }
  /* The card/button/link/log-out styling inside the profile card already
     fits a narrow column on desktop -- it just needs to not look like a
     leftover sidebar once it's the first thing on the page. */
  body.page-profile .profile-card { box-shadow: none; }

  .mobile-quick-links {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    margin-bottom: 12px;
    padding-bottom: 2px;
  }
  .mobile-quick-links a {
    flex: 1 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    min-width: 76px;
    padding: 12px 8px;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--ink);
    text-align: center;
  }
  .mobile-quick-links a:hover { background: var(--brand-light); color: var(--brand); text-decoration: none; }

  /* ---- Full-screen chat thread (Instagram DM-style) ------------------- */
  body.page-chat-thread .topbar,
  body.page-chat-thread .mobile-tabbar,
  body.page-chat-thread .chat-widget { display: none; }
  body.page-chat-thread { padding-bottom: 0; }
  body.page-chat-thread .feed-shell { margin: 0; padding: 0; max-width: none; }
  /* .feed-col-main's general mobile padding (12px 12px 24px, a few lines
     down) is meant for ordinary pages where a card sits with visible
     margin around it. The full-screen chat thread is edge-to-edge instead
     -- .card.wide/.thread-header/#send-form already carry their own
     internal padding for breathing room -- so that outer 12px top padding
     was just leaving a plain gap above the header instead of the header
     sitting flush against the top of the screen. Zeroing it here matches
     the same treatment .card.wide and .feed-shell already get on this
     page (and the precedent already set for body.composer-open below). */
  body.page-chat-thread .feed-col-main { padding: 0; }
  /* 100vh on a phone browser is the height with the address bar HIDDEN,
     but the address bar is showing when the page first loads -- so a
     100vh-tall layout is actually taller than what's on screen at that
     moment, and everything below the fold (the composer, the tail of the
     thread) stays hidden until the user scrolls and the browser chrome
     collapses to reveal the rest. 100dvh tracks the *current* visible
     height instead, so the layout is correctly sized from the first
     paint. Declared after the 100vh fallback so older browsers that don't
     understand dvh keep the old (imperfect but working) behavior. */
  body.page-chat-thread .feed-col-main { min-height: 100vh; min-height: 100dvh; }
  body.page-chat-thread .card.wide {
    max-width: none;
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 0;
    box-shadow: none;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
  }
  body.page-chat-thread .thread-header {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--card-bg);
    padding: 10px 14px;
    margin-bottom: 0;
    border-bottom: 1px solid var(--border);
    flex-wrap: nowrap;
  }
  body.page-chat-thread .thread-back-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin-right: 2px;
    font-size: 1.6rem;
    line-height: 1;
    color: var(--ink);
    flex-shrink: 0;
  }
  body.page-chat-thread .thread-header h1 {
    display: flex;
    align-items: center;
    font-size: 1.05rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Without this, a long name (a group name is more likely to run long
     than a person's display name, which is what originally shipped here)
     forces the flex layout to shrink-to-fit -- and since the name text
     itself can't shrink below its full width (white-space: nowrap gives
     it a min-content width equal to its full width), the browser takes
     the space from the avatar instead, squashing the circle into an oval.
     flex-shrink: 0 keeps the avatar a fixed size no matter how long the
     name next to it is. */
  body.page-chat-thread .thread-header h1 .avatar,
  body.page-chat-thread .thread-header h1 .avatar-placeholder,
  body.page-chat-thread .thread-header h1 .avatar-link {
    flex-shrink: 0;
  }
  /* #group-thread/#group-chat-form (views/groups/show.ejs) share this same
     full-screen treatment as #thread/#send-form (the 1:1 DM thread) -- see
     the group chat header rework below for why group chat now also sets
     body.page-chat-thread. */
  body.page-chat-thread #thread,
  body.page-chat-thread #group-thread {
    flex: 1;
    max-height: none;
    height: calc(100vh - 130px);
    height: calc(100dvh - 130px);
    border: none;
    border-radius: 0;
  }
  body.page-chat-thread #send-form,
  body.page-chat-thread #group-chat-form { padding: 10px 14px; margin: 0; border-top: 1px solid var(--border); }
  /* Icon-only call buttons in the full-screen chat header -- there's only
     room for name + a couple of small controls next to the back button on
     a phone, so the label collapses and just the icon remains for both the
     video-call and voice-call buttons (same pattern as Instagram/
     WhatsApp's call icons in a DM header). */
  body.page-chat-thread .call-start-btn {
    width: 32px;
    height: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    flex-shrink: 0;
  }
  body.page-chat-thread .call-start-label { display: none; }
  body.page-chat-thread .call-start-icon .call-icon-svg { width: 18px; height: 18px; }
  body.page-chat-thread .thread-header-actions { flex-shrink: 0; display: flex; align-items: center; gap: 6px; }

  /* ---- Messages inbox rows (Instagram DM-list-style) ------------------- */
  .partner-item-row { align-items: center; }
  .partner-item-link { display: flex; flex-direction: column; gap: 2px; }
  .partner-item-top-row, .partner-item-bottom-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .partner-item-time { font-size: 0.75rem; flex-shrink: 0; }
  .partner-item-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .partner-item-unread .partner-item-preview,
  .partner-item-unread strong { color: var(--ink); }
  .partner-item-unread .partner-item-preview { font-weight: 600; }

  /* ---- Feed cards, edge-to-edge like a real app's timeline ------------- */
  .feed-shell { padding: 0; margin: 0 auto; gap: 0; max-width: 100%; }
  .feed-col-main { padding: 12px 12px 24px; }
  .card, .side-card { padding: 16px; }
  .wide { max-width: 100%; }
  h1 { font-size: 1.4rem; }
  .post-list .post-item, .post-item-solo {
    border-radius: 12px;
    margin-bottom: 10px;
  }

  /* ---- Full-screen "New Groove" composer (Instagram-create-style) ------
     The real composer (#groove-composer) stays in the DOM exactly as
     desktop has it -- same fields, same submit -- but starts hidden and
     only becomes a full-screen overlay once body.composer-open is set (see
     the inline script after the form). The compact trigger bar below is
     what's visible on the feed by default. */
  .mobile-composer-trigger {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    text-align: left;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 14px;
    margin-bottom: 12px;
    box-shadow: var(--shadow-sm);
  }
  .mobile-composer-trigger .avatar, .mobile-composer-trigger .avatar-placeholder { margin-right: 0; flex-shrink: 0; }
  .mobile-composer-trigger-text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--muted);
    font-size: 0.92rem;
  }
  .mobile-composer-trigger-icons { flex-shrink: 0; opacity: 0.7; font-size: 0.95rem; }

  #groove-composer { display: none; }
  body.composer-open .topbar,
  body.composer-open .mobile-tabbar,
  body.composer-open .mobile-composer-trigger,
  body.composer-open .feed-toolbar,
  body.composer-open .post-list,
  body.composer-open #groove-empty-filter,
  body.composer-open .load-more-link { display: none; }
  body.composer-open { overflow: hidden; }
  body.composer-open .feed-shell { margin: 0; padding: 0; }
  body.composer-open .feed-col-main { padding: 0; }
  body.composer-open #groove-composer {
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--card-bg);
    padding: 14px;
    overflow-y: auto;
    margin: 0;
    border: none;
    border-radius: 0;
  }
  .mobile-composer-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding-bottom: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--border);
    font-size: 1.05rem;
  }
  .mobile-composer-close {
    width: 32px;
    height: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    color: var(--ink);
    border-radius: 50%;
    font-size: 1.1rem;
    flex-shrink: 0;
  }
  .mobile-composer-close:hover { background: var(--bg); }
  /* Your avatar moves up into the header, next to "New Groove", instead of
     sitting beside the text box -- matches how a lot of native apps lay
     out their compose screen (identity in the title bar, text field gets
     the full width below it). */
  .mobile-composer-header-avatar { flex-shrink: 0; }
  body.composer-open .composer-row .avatar-link { display: none; }
  body.composer-open .composer-field textarea { min-height: 160px; }

  /* Inputs below 16px trigger an auto-zoom on iOS Safari when focused --
     keeping every text input/textarea at exactly 16px avoids that jarring
     zoom-in-then-back-out on every form field tap. */
  input, textarea, select { font-size: 16px; }

  /* .composer-footer's base rule (desktop) is a horizontal row with
     justify-content: space-between, pushing the icon row to the left edge
     and the Post button to the right edge. Switching it to flex-direction:
     column here for the stacked mobile layout does NOT reset
     justify-content -- it stays "space-between", which on a column axis
     means "top edge / bottom edge" instead. That's invisible as long as
     the footer's own box is exactly as tall as its two children, but if
     anything makes the footer taller than its content (seen live: the
     full-screen overlay's real rendered height on a phone can end up
     taller than the icon row + button), space-between stretches the gap
     between them across that extra height -- exactly the "too much empty
     space" bug reported. flex-start keeps them packed together regardless
     of how tall the footer's box ends up being. */
  .composer-footer { flex-direction: column; align-items: stretch; justify-content: flex-start; }
  .composer-footer button[type="submit"] { width: 100%; }
  /* Same reasoning for the icon row: its base rule (desktop) uses flex: 1
     so it fills the remaining horizontal space next to the Post button.
     In the stacked mobile column that would let it grow to fill any extra
     vertical space instead, which would space the individual icons apart
     rather than keeping them snug together. */
  .composer-media-actions { flex: 0 0 auto; }
  .media-field { justify-content: space-between; }
  .voice-note-field { justify-content: space-between; }

  .discover-search-form { flex-direction: column; align-items: stretch; }
  .discover-search-form button { width: 100%; }

  .group-add-form { flex-direction: column; align-items: stretch; }
  .group-add-form button { width: 100%; }
  .group-invite-row { flex-direction: column; }
  .group-invite-row button { width: 100%; }

  .cert-row-fields { grid-template-columns: 1fr; }
  .cert-add-form { flex-direction: column; align-items: stretch; }
  .cert-add-form button { width: 100%; }
  .cert-item-actions { width: 100%; justify-content: flex-start; }

  .admin-stat-grid { grid-template-columns: repeat(2, 1fr); }

  .filter-pills { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 4px; }

  /* The floating chat widget and the bottom tab bar would otherwise sit
     in the same corner and overlap. */
  .chat-widget { display: none; }
}

/* Dashboard quick-links grid (Ranking/Favorites/Teach/Groups/Recordings/
   Account/Log out) -- consolidates items previously in the desktop
   .nav-links bar into a single icon-tile grid on the Dashboard page. */
.quick-links-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 10px;
  margin-top: 14px;
}
.quick-link-tile {
  background: var(--bg);
  border-radius: var(--radius);
  padding: 16px 12px;
  text-align: center;
  text-decoration: none;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  border: none;
  width: 100%;
  font: inherit;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}
.quick-link-tile:hover { background: var(--brand-light); transform: translateY(-2px); text-decoration: none; }
.quick-link-icon { font-size: 1.6rem; line-height: 1; }
.quick-link-label { font-size: 0.78rem; font-weight: 600; color: var(--ink); }
.quick-link-form { display: contents; }

/* Extra items appended to the mobile Profile tab's quick-links row
   (Groups, Recordings, Log out) -- see views/account.ejs. */
.mobile-quick-links form { display: contents; }
.mobile-quick-links button {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 76px;
  padding: 12px 8px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--ink);
  text-align: center;
  cursor: pointer;
}
.mobile-quick-links button:hover { background: var(--brand-light); color: var(--brand); }
