/*
  Comet login card styles — visually equivalent to jeffagi/wilex
  components/PasswordGate.tsx. Every value maps 1:1 from the Tailwind
  classes on that component (no scaling, no bump).

  Tailwind → CSS mapping:
    bg-white          → #fff
    text-slate-600    → #475569
    border-slate-200  → #e2e8f0
    border-slate-300  → #cbd5e1
    text-slate-500    → #64748b
    placeholder-…400  → #94a3b8
    bg-slate-50       → #f8fafc
    hover:bg-slate-800→ #1e293b
    rounded-xl        → 12px
    shadow-xl         → 0 20px 25px …, 0 8px 10px …
    px-4 py-3         → 16px 12px
    mb-2              → 8px
    text-xs            → 12px
    font-medium        → 500
    font-semibold      → 600
    uppercase          → text-transform: uppercase
    tracking-wider     → letter-spacing: 0.05em

  Every focus ring / outline uses Comet black (#000), not wilex-gold.
*/

:root {
  color-scheme: light;
  --ink: #0f172a;
  --line: #cbd5e1;
  --line-soft: #e2e8f0;
  --placeholder: #94a3b8;
  --muted: #64748b;
  --soft: #f8fafc;
  --soft-strong: #1e293b;
  /* Single source of truth for every pill-radius button on Comet.
     Auth views (.btn-primary / .btn-outline), the home-view action
     (.profile-action), the edit-name + edit-bio save buttons
     (.edit-checkmark), and the sidebar avatar circle all read from
     this token so the family's geometry ships in one place. */
  --pill: 999px;
}

@font-face {
  font-family: 'Bierstadt';
  src: local('Bierstadt'), local('Bierstadt Regular');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

* { box-sizing: border-box; }
html, body { min-width: 320px; min-height: 100%; margin: 0; }
/* Lock parent page scroll while any Comet modal (view-post / edit-
   post / auth popup / privacy policy) is open. body becomes a fixed
   scroll context the JS keeps pinned (lockBodyScroll() in script.js
   records scrollY + sets body.style.top = '-scrollY'; unlock restores
   it). position: fixed on body is the iOS-Safari-safe idiom — the
   earlier `overflow: hidden` carried over from desktop silently killed
   touch-scroll on every nested iframe (iOS routes touchmove to a
   blocked ancestor and the embed becomes un-scrollable). With body
   fixed the modal stack above it stays anchored and iframe touch-
   scroll surfaces through normally. We deliberately do NOT set
   `overflow: hidden` on body here — `overflow:hidden` on any ancestor
   of a cross-origin iframe is what iOS uses to drop touchmove. The
   previous-body's scroll was the side effect we wanted to suppress,
   and `position: fixed; inset: 0` already does that (the fixed body
   box is the full viewport, nothing to scroll inside it anyway). */
html.comet-modal-open { overflow: hidden; height: 100%; touch-action: none; overscroll-behavior: contain; }
html.comet-modal-open body { position: fixed; inset: 0; width: 100%; touch-action: none; overscroll-behavior: contain; }
body {
  min-height: 100vh;
  color: var(--ink);
  background: #fff;
  font-family: 'Bierstadt', 'Aptos', 'Segoe UI', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}
button, input { font: inherit; }
button { cursor: pointer; }
button, a { -webkit-tap-highlight-color: transparent; }
a { color: inherit; }

.auth-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;       /* breathing room around the bigger card */
  background: #fff;
}
.auth-card {
  width: 448px;             /* FIXED — does not scale with viewport */
  max-width: 448px;
  padding: 56px;            /* 48 × 1.15 → 55.2 ≈ 56 */
  border: 1px solid var(--line-soft);
  border-radius: 18px;      /* 16 × 1.15 → 18.4 */
  background: #fff;
  box-shadow:
    0 23px 29px -6px rgba(15, 23, 42, 0.10),
    0 9px 11px -8px rgba(15, 23, 42, 0.06);
  position: relative;       /* anchor for the back-arrow */
  flex: 0 0 auto;           /* never grow / shrink inside .auth-shell */
}
.auth-header {
  margin-bottom: 37px;      /* 32 × 1.15 → 36.8 */
  text-align: center;
}
.auth-logo-link {
  display: inline-block;
  text-decoration: none;
}
.auth-logo {
  display: block;
  width: 155px;
  height: auto;
}

#auth-form { margin-bottom: 19px; } /* 16 × 1.15 → 18.4 */

.field { margin-bottom: 21px; }     /* 18 × 1.15 → 20.7 */
.field label {
  display: block;
  margin-bottom: 7px;
  color: var(--ink);
  font-size: 15px;                  /* 13 × 1.15 → 14.95 */
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.field input {
  width: 100%;
  height: 52px;                     /* matches .btn-* so inputs and buttons share vertical height */
  padding: 14px 19px;               /* 12 × 1.15, 16 × 1.15 */
  border: 1px solid var(--line);
  border-radius: var(--pill);       /* same radius as .btn-* — fully pill */
  background: #fff;
  color: var(--ink);
  font-size: 16px;                  /* 14 × 1.15 → 16.1 */
  line-height: 1.3;
  outline: none;
  box-sizing: border-box;
  transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
  -webkit-appearance: none;
  appearance: none;
}
.field input::placeholder { color: var(--placeholder); }
.field input:focus {
  border-color: #000;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.15);
}
.field input:focus:not(:focus-visible) {
  box-shadow: none;
}
.field input:focus-visible {
  border-color: #000;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.15);
}

.field-error {
  margin-top: 10px;
  color: #b91c1c;
  font-size: 14px;                  /* 12 × 1.15 → 13.8 */
  font-weight: 500;
  line-height: 1.4;
}
.field-error:empty { display: none; }

.btn-primary, .btn-outline {
  width: 100%;
  height: 52px;                     /* same vertical height as .field input */
  padding: 14px 19px;               /* 12 × 1.15, 16 × 1.1875 */
  border-radius: var(--pill);       /* same radius as .field input — fully pill */
  font-size: 16px;                  /* 14 × 1.15 → 16.1 */
  font-weight: 600;
  line-height: 1.3;
  box-sizing: border-box;
  transition: background 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}
.btn-primary {
  border: 1px solid #000;
  color: #fff;
  background: #000;
}
.btn-primary:hover {
  background: #1e293b;
  box-shadow: 0 9px 18px rgba(15, 23, 42, 0.14);
  transform: translateY(-1.2px);
}
.btn-primary:active { transform: translateY(0); }
.btn-primary:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}
.btn-outline {
  border: 1px solid var(--line);
  color: var(--ink);
  background: #fff;
}
.btn-outline:hover {
  background: var(--soft);
  border-color: #b3bfca;
  transform: translateY(-1.2px);
}
.btn-outline:active { transform: translateY(0); }
.btn-outline:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}

/* ---------------- Sign-up view additions ---------------- */
.auth-view[hidden] { display: none; }

/* Sign-up header: Comet logo + title, mirrors Instagram "Get started" */
.auth-header--signup {
  margin-bottom: 22px;
  text-align: left;                 /* Comet logo flush left */
}
.auth-header--signup .auth-logo {
  width: 38px;
  height: auto;
  margin: 0 0 24px;                 /* flush left, no auto-center */
}

.auth-title {
  margin: 0 0 10px 0;
  color: var(--ink);
  font-size: 19px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.auth-subtitle {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

/* Back arrow: chevron pinned to the top-left of the card */
.back-arrow {
  position: absolute;
  top: 14px;
  left: 14px;
  width: 36px;
  height: 36px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 999px;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color 160ms ease, background 160ms ease;
  opacity: 0.85;
}
.back-arrow svg { width: 25px; height: 25px; display: block; }
.back-arrow:hover {
  background: rgba(15, 23, 42, 0.05);
  color: var(--ink);
  opacity: 1;
}
.back-arrow:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}

/* Privacy policy link under the auth buttons — same muted color and
   weight as the input placeholders so it reads as secondary chrome. */
.auth-legal-link {
  margin-top: 22px;
  padding: 0;
  border: 0;
  background: none;
  font: 400 9px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: var(--placeholder);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.auth-legal-link:hover { color: var(--muted); }
.auth-legal-link:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: 4px;
}
/* Create-account popup variant: one point smaller than the signup-view
   link so the popup footer reads lighter. */
.auth-popup .auth-legal-link { font-size: 8px; }
/* The privacy-policy link is the last element on the signup view and
   on the create-account popup, so both cards add a bottom pad to
   give the link air. (Selector on the signup view keys off the view's
   real id since the view carries no --signup class.) */
.auth-card:has(> #signup-view:not([hidden])) { padding-bottom: 22px; }
#auth-popup:not([hidden]) > .auth-popup-card { padding-bottom: 22px; }
/* Privacy Policy overlay card content. */
.auth-popup-card--policy { padding: 40px 40px 32px; }
.policy-title {
  margin: 0 0 12px;
  font: 700 18px/1.3 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
}
.policy-body {
  font: 400 14px/1.6 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: var(--muted);
}
.policy-body p { margin: 0 0 12px; }
.policy-body p:last-child { margin-bottom: 0; }

/* "I already have an account" — lighter, matches Instagram secondary outline */
.btn-outline--ghost {
  margin-top: 14px;
  border-color: var(--line-soft);
  color: var(--ink);
  font-weight: 500;
  background: #fff;
}
.btn-outline--ghost:hover {
  background: var(--soft);
  border-color: #b3bfca;
}

/* Signed-out bar (Log in + Sign up) pinned to the home canvas's
   top-right; syncChrome hides it once the visitor signs in. The
   width:100% on btn-primary/btn-outline is overridden so the pair
   sits side-by-side instead of stretching edge to edge. */
.signed-out-bar {
  position: absolute;
  top: 24px;
  right: 24px;
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: 2;
}
/* display:flex above overrides the user-agent [hidden] default, so the
   disabled toggle needs an explicit override to actually go invisible. */
.signed-out-bar[hidden] { display: none; }
.signed-out-bar__btn {
  flex: 1;                   /* equal split of the row so both pills match width */
  height: 40px;              /* same vertical height as before */
  padding: 0 24px;           /* wider inner padding so the longer pill reads as comfortable */
  min-width: 122px;          /* floor so "Sign up" stays on one line instead of wrapping */
  font-size: 14px;
  white-space: nowrap;       /* belt-and-suspenders against any flex-shrink-driven wrap */
  border-radius: 999px;
}
/* .btn-outline--ghost's popup margin-top would leak above the Log in
   pill in a flex row; neutralize it here. */
.signed-out-bar__btn.btn-outline--ghost { margin-top: 0; }

/* Phones and the skinniest laptop window: the bottom nav owns the
   chrome, so the top-right pill pair adds clutter. Keyed off the same
   768px breakpoint that collapses the sidebar so a laptop user with
   their window dragged to its narrowest also gets the bar out of the
   way. Signed-out visitors still reach the auth flow via the + / save
   / follow gates (each one falls through openSignupIfSignedOut). */
@media (max-width: 768px) {
  .signed-out-bar { display: none; }
}

.notice {
  margin: 5px 0 28px;
  padding: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
}
.notice a {
  color: #000;
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid rgba(0, 0, 0, 0.20);
}
.notice a:hover { border-bottom-color: #000; }

/* (No override needed: #signup-password-rules reuses `.field-error` so
   the signup password hint has the exact same visual treatment as
   .field-error on the login view — margin-top, color, font-size,
   line-height, and the :empty display:none all match. Single-line red
   text is the desired shape; no bullet markers here.) */
/* ---------------- Home view ---------------- */
.auth-logo--home { width: 130px; }

@media (max-width: 480px) {
  /* Small viewports may shrink the fixed card down to 100% of the shell
     so it never overflows a phone screen. Desktop (>480px) stays at 448px. */
  .auth-shell { padding: 22px 14px; }
  .auth-card {
    width: 100%;
    max-width: 100%;
    padding: 36px 18px 30px;
    border-radius: 14px;
  }
  .auth-logo { width: 137px; }
  .field { margin-bottom: 18px; }
  .field label { font-size: 14px; }
  .field input { font-size: 15px; padding: 12px 16px; }
  .btn-primary, .btn-outline { font-size: 15px; padding: 12px 16px; }
  .auth-header { margin-bottom: 28px; }
  .auth-header--signup { margin-bottom: 21px; }
  .auth-header--signup .auth-logo { width: 48px; margin: 0 0 24px; }
  .auth-title { font-size: 19px; }
  .auth-subtitle { font-size: 14px; }
  .back-arrow { top: 12px; left: auto; right: 12px; width: 36px; height: 36px; }
  .back-arrow svg { width: 22px; height: 22px; }
  .field-error { font-size: 13px; }
  .auth-logo--home { width: 110px; }
}

/* ---------------- Home view (Instagram-style profile shell) ----------------
   Desktop rule: once signed in (.is-unlocked), the card fills the entire
   viewport edge-to-edge with comfortable padding for the wider canvas.
   The sidebar scales up so it reads at the same visual weight as the
   main column.

   Mobile rule (@max-width: 480px): the same .is-unlocked modifier
   collapses back to a card-shaped panel with rounded corners + shadow,
   and every column shrinks so nothing overflows. */
.auth-card.profile-loading {
  visibility: hidden;
}
.auth-card.is-unlocked {
  width: 100%;
  max-width: 100%;
  min-height: 100vh;
  min-height: 100dvh; /* mobile-grade viewport while avoiding safari url-bar jump */
  padding: 56px clamp(28px, 6vw, 96px);
  border-radius: 0;
  border: 0;
  box-shadow: none;
  background: #fff;
  display: flex;
  flex-direction: column;
}
/* When the shell is wrapping an unlocked card, the shell itself stops
   adding padding — the card is now the page. The login/signup views
   keep their original shell padding. */
.auth-shell:has(.auth-card.is-unlocked) {
  padding: 0;
  align-items: stretch;
  background: #fff;
}

.auth-view--home {
  display: flex;
  flex-direction: column;
}

.home-topbar {
  display: flex;
  align-items: center;
  justify-content: center;
  /* The wordmark sits at the top of the sidebar column, centered
     horizontally to match the three buttons below (sidebar-items are
     `align-items: center` flex columns with no left bias). */
  margin: 0;
  padding: 0;
  margin-bottom: 22px;
}
.home-topbar .auth-logo-link {
  display: inline-flex;
  align-items: center;
}
.home-topbar .auth-logo--home {
  width: 68px;
}

/* Canvas cap: lock the home canvas to IG-desktop content width (935 px)
   regardless of viewport size. Below 935 px the grid still fills
   edge-to-edge as before; above 935 px the canvas centers itself with
   equal whitespace on both sides instead of stretching the column.
   This is the change that fixes the "expanding the window blows the
   layout apart" behavior — fixed-width children (570 px Edit profile
   pill, 260 px post tiles, 168 px avatar) stay anchored to the same
   coordinates while the canvas around them stops growing. */
.home-layout {
  display: grid;
  grid-template-columns: 132px 1fr;
  gap: 0;
  flex: 1;
  align-items: start;
  width: 100%;
  max-width: 935px;       /* IG-desktop content width — same as the IG profile column */
  margin-inline: auto;    /* centers when viewport > 935; no-op below */
}

/* Sidebar (left vertical menu) — scaled for desktop-fullscreen, IG-style.
   No labels (icons only); no border between sidebar and main column. */
.home-sidebar {
  padding: 34px 0 8px 0;
  /* Nudge the whole sidebar column (wordmark + users sparkle + + +
     profile circle) 16 px to the left of the .home-layout grid track
     edge so the menu pixels line up visually even though the three
     buttons paint at the column's center on a 132 px track. Sidebar
     shift is independent of the main-column translateX re-alignment. */
  margin-left: -28px;
  /* Stack above .home-main so the sidebar stays clickable where the
     translated main column would otherwise overlap it. Without this,
     .home-main's `transform: translateX(-148px)` creates a stacking
     context that paints the main column on top of the sidebar on the
     right side, intercepting clicks on the sidebar buttons. */
  position: sticky;
  top: 0;
  height: 100vh;
  z-index: 1;
}
.sidebar-list {
  list-style: none;
  margin: 0;
  /* Push the four icon buttons (sparkle / explore / + / profile)
     well below the comet wordmark on desktop; the bottom nav
     overrides this margin back to 0 at ≤768px. */
  margin-top: 134px;
  padding: 0;
  display: flex;
  flex-direction: column;
  /* Items: wordmark / Users sparkle / + / Profile photo. Each <li>
     stacks vertically on desktop; the gap here controls the spacing
     between them so the column reads with deliberate breathing room
     rather than tightly packed chrome. */
  gap: 20px;
  align-items: stretch;
}
.sidebar-item {
  background: transparent;
  border: 0;
  padding: 8px 0 8px 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  color: var(--ink);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  width: 100%;
  border-radius: 12px;
  transition: background 160ms ease, color 160ms ease;
}
.sidebar-item:hover,
.sidebar-item:focus-visible { background: rgba(15, 23, 42, 0.04); }
.sidebar-item--active { font-weight: 700; }
.sidebar-icon { width: 26px; height: 26px; color: var(--ink); display: block; }
.sidebar-item--active .sidebar-icon { stroke-width: 2.4; }
.sidebar-item--active .sidebar-icon--search { stroke-width: 1.9; }
/* Corner-stars Explore icon is a PNG, not an SVG stroke glyph: keep the
   same 26px footprint, scale it to fit, and let the active-weight rule
   above no-op harmlessly. */
.sidebar-icon--img { object-fit: contain; }
.sidebar-icon--create { width: 28px; height: 28px; }
.sidebar-icon--search { width: 27px; height: 27px; }
/* Sidebar is icons-only, like IG: hide the text label without removing
   the markup (preserves the aria-label semantic for screen readers). */
.sidebar-label { display: none; }
.sidebar-avatar-placeholder {
  display: inline-flex;
  width: 36px;
  height: 36px;
  border-radius: var(--pill);
  overflow: hidden;
  /* #f1f5f9 matches .profile-avatar-large + .users-row-avatar so a
     transparent profile photo renders the same color on every surface.
     The 1px #d6dce6 ring mirrors .profile-avatar-large's perimeter. */
  background: #f1f5f9;
  border: 1px solid #d6dce6;
  flex-shrink: 0;
  position: relative;
}
/* Both imgs fill the circular mask: the gray silhouette default sits
   in flow; the uploaded photo is layered on top. Keeping the uploaded
   img INSIDE the placeholder span means the circle clip (border-radius
   + overflow) applies to it too — previously it filled the whole
   full-width button and rendered as a rounded rectangle. */
.sidebar-avatar-placeholder img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sidebar-avatar-image {
  position: absolute;
  inset: 0;
  z-index: 1;
}
.sidebar-avatar-image[hidden] { display: none; }
.sidebar-avatar-placeholder.has-avatar .sidebar-avatar-default { display: none; }

/* Main profile content — scaled for desktop-fullscreen */
.home-main {
  /* padding-left: 200 keeps the avatar / id / bio / actions / tabs /
     posts pair clear of the left sidebar on every profile view (own +
     other-user's). Sidebar itself is unchanged — only the main
     column shifts rightward. */
  padding: 16px 0 16px 200px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* Shift the whole main column left as a group so the block reads
     visually centered on the canvas. translateX preserves every
     internal alignment (avatar / id-block / bio / pill / tabs /
     posts grid midpoint) untouched — only the outer left+right
     edges of the group move. Sidebar (.home-layout col 0) is
     unaffected, so the menu bar stays put. */
  transform: translateX(-148px);
}

/* Back arrow on a listing-opened profile — same look and on-screen spot
   as the Users topbar arrow. .home-main's translateX(-148px) is the
   containing block, so left:148 + top:20 lands on the same corner at
   any width. Hidden unless that navigation armed it. */
.profile-back-arrow {
  position: absolute;
  top: 20px;
  left: 148px;
  width: 40px;
  height: 40px;
}
/* Narrow widths (sidebar transposed to the bottom bar) don't need the
   back arrow — the profile is one tap from the bottom nav. display:none
   (not the hidden attr) so state is untouched when rotating wider. */
@media (max-width: 768px) {
  .profile-back-arrow { display: none; }
}
/* .back-arrow sets display:inline-flex, which beats the UA [hidden]
   default — same guard the kebab / signed-out bar use. */
.profile-back-arrow[hidden] { display: none; }

/* ---------------- IG-style profile header (2-column: avatar | name/stats) ---------------- */
.profile-header {
  display: grid;
  grid-template-columns: 168px 1fr;
  align-items: center;
  gap: 56px;
  margin-top: 4px;
  padding: 8px 0 24px;
}
.profile-avatar-large {
  display: inline-flex;
  position: relative;
  width: 146px;
  height: 146px;
  border-radius: 999px;
  text-decoration: none;
  flex-shrink: 0;
  cursor: pointer;
  margin-left: 16px;            /* −20px over the prior 36px right shift — only the pictured top elements move; tabs + posts grid stay anchored */
  /* Ring + backing live here, not on .profile-avatar-inner — the
     inner is hidden once a photo loads. */
  background: #f1f5f9;
  border: 1px solid #d6dce6;
}
.profile-avatar-inner {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  overflow: hidden;
  display: block;
}
.profile-avatar-inner img { width: 100%; height: 100%; object-fit: cover; display: block; }

.profile-id-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  margin-left: 16px;            /* matches .profile-avatar-large — keeps the name + stats row aligned with the shifted avatar */
}
/* Desktop only: pull the name/stats column a touch left of the
   avatar's 16px inset so the profile header reads tighter. Skipped on
   skinny/mobile widths, which keep the 16px alignment above. */
@media (min-width: 769px) {
  .profile-id-block { margin-left: 8px; }
}
.profile-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 0;
  margin-bottom: 4px;
}
.profile-kebab-wrap {
  position: relative;
  margin-left: auto;          /* pin the kebab block to the right end of the row */
  display: inline-flex;
  align-items: center;
}
.profile-kebab-wrap[hidden] {
  /* Owner-only chrome: hide the 3-dots while viewing another profile. */
  display: none;
}
.profile-kebab {
  background: transparent;
  border: 0;
  padding: 6px;
  color: var(--ink);
  cursor: pointer;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 160ms ease;
}
.profile-kebab:hover { background: rgba(15, 23, 42, 0.04); }
.profile-kebab:focus-visible { outline: 2px solid #000; outline-offset: 2px; }
.profile-kebab svg { width: 22px; height: 22px; display: block; }

/* Dropdown surface anchored below the kebab. Hidden by default -- the
   [hidden] attribute is flipped by script.js setKebabMenuOpen() on
   click-toggle and Esc/outside-click closure. Single Log out item. */
.profile-kebab-menu {
  position: absolute;
  top: calc(100% + 6px);
  /* Menu opens rightward of the kebab (was leftward). */
  left: 0;
  /* Floor at 130 px so both labels sit single-line. */
  min-width: 130px;
  background: #fff;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  padding: 6px;
  box-shadow:
    0 12px 24px rgba(15, 23, 42, 0.10),
    0 4px 8px rgba(15, 23, 42, 0.06);
  z-index: 5;
}
.profile-kebab-menu[hidden] { display: none; }
.profile-kebab-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  /* Trimmed horizontal padding 12 → 8 px so the menu card is narrower
     even when "edit profile" / "log out" labels pad broad. */
  padding: 10px 8px;
  border-radius: 8px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background 160ms ease;
}
.profile-kebab-item:hover, .profile-kebab-item:focus-visible {
  background: var(--soft);
  outline: none;
}
.profile-kebab-item[hidden] { display: none; }

/* Name line directly under the username. Same sans family, weight,
   and color as .profile-bio so the two read as one block visually. */
/* School under the actual name — shares the name line's font/style and
   its 6px bottom margin + 14px stats padding, so the gap to the stats
   stays constant whether or not a school is listed. */
.profile-name-line,
.profile-school-line {
  margin: 2px 0 6px;
  color: var(--ink);
  font-size: 15px;
  line-height: 1.45;
  font-weight: 400;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.profile-name-line:empty,
.profile-school-line:empty { display: none; }
.profile-username {
  color: var(--ink);
  font-size: 26px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.profile-stats {
  display: flex;
  align-items: center;
  gap: 36px;
  font-size: 16px;
  color: var(--ink);
  font-weight: 400;
  padding: 14px 0 4px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
/* Each stat is a small horizontal group ("2" + "posts"). inline-flex with
   align-items: baseline pins the bold digit and the unit word to the same
   baseline regardless of glyph shape — without that, "2"/"6"/"9" sit
   visually lower than "3"/"0" because their descender swashes dip below. */
.profile-stat {
  display: inline-flex;
  align-items: baseline;
}
.profile-stat strong {
  font-weight: 700;
  color: var(--ink);
  margin-right: 4px;
  font-size: 17px;
  font-variant-numeric: lining-nums tabular-nums;
}
/* The followers / following counts are clickable buttons that open the
   follows list. Reset the button chrome so they read as plain stat text
   (same rhythm as the projects span) with a soft hover affordance. */
.profile-stat--btn {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  border-radius: 4px;
  transition: opacity 140ms ease;
}
.profile-stat--btn:hover { opacity: 0.55; }
.profile-stat--btn:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}

/* ---------------- Bio block ---------------- */
.profile-bio {
  margin-top: 12px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  margin-left: 16px;            /* aligns with the shifted avatar + id-block */
}
.profile-bio:empty { display: none; }
.profile-bio p {
  margin: 0;
  color: var(--ink);
  font-size: 15px;
  line-height: 1.45;
}
.profile-bio p:empty { display: none; }

/* ---------------- Edit profile action button ----------------
   Edit profile | Share | icon-only "Contact" — mirrors IG's three-button footer
   but with the rename for own-profile (Edit profile instead of Follow).

   The current profile layout keeps this action compact at the same width
   as the post pair and translates both by -12 px so their centers land on
   the right of the bio block on the left edge. */ .profile-actions {
  display: block;
  margin-top: 20px;
  margin-bottom: 0;
  margin-left: 16px;                    /* left edge stays on the .profile-bio anchor */
  margin-right: 0;
  padding-top: 4px;
  width: 540px;                         /* trimmed from 546 -> 540 (~6 px smaller right edge) — another "tiny tiny bit" decrease on top of the 546 baseline */
  max-width: calc(100% - 72px);          /* keep a 36 px right gutter inside .home-main */
}
/* Hidden stat mirror retained for the existing live-count observers.
   It no longer sizes .profile-actions; the action row now has its own
   compact 570 px geometry. It stays height:0 + visibility:hidden so it
   never paints or changes the visible layout. */
.profile-action-mirror {
  display: block;
  visibility: hidden;
  pointer-events: none;
  white-space: nowrap;
  height: 0;
  font-size: 16px;
  font-weight: 400;
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.profile-action-mirror strong {
  font-weight: 700;
  color: var(--ink);
  margin-right: 4px;
  font-size: 17px;
}
.profile-action-mirror .profile-action-gap {
  display: inline-block;
  width: 36px;
}
.profile-action {
  display: block;                                  /* fills the .profile-actions row */
  width: 100%;                                      /* full row width — pill now spans the entire home-main column edge to edge */
  padding: 16px 24px;                               /* restored vertical 16 px so the pill reads tall (49 px height) again — userfeedback liked the prior vertical height over the 8 px slimmer variant */
  border-radius: var(--pill);
  font-size: 14px;
  font-weight: 600;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  cursor: pointer;
  transition: background 160ms ease;
  border: 0;
  background: #f1f3f6;
  color: var(--ink);
  text-align: center;                              /* center the label in the long pill to match the reference layout */
}
.profile-action:hover { background: #e7e9ec; }    /* one step darker on hover */
.profile-action:focus-visible { outline: 2px solid #000; outline-offset: 2px; }

/* ---------------- Tab strip + content ---------------- */
.profile-tabs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 40px;             /* tighter horizontal distance between POSTS and SAVED */
  width: 526px;                /* accommodates the slightly larger 260 px photo pair (260 + 6 + 260) */
  max-width: calc(100% - 24px);
  /* Auto-center on the .home-main content midpoint. No translateX — the
     earlier -12 px shift was to align the LEFT edge to .profile-bio, but
     it desynced the tab seam from the photo pair's midpoint (x=311.5 vs
     x=299.5, visible 12 px offset in the screenshot). With both rows
     auto-centered via margin:auto, the pair gap and the tab gap share
     the same vertical line on the page. */
  margin: 28px auto 0;
  /* per-tab ::after paints the symmetric underline — see .profile-tab::after below */
}
.profile-tab {
  background: transparent;
  border: 0;
  padding: 12px 24px;          /* compact tab cell, no longer spans the full row */
  display: flex;               /* fills the grid cell so POSTS and SAVED share the same width */
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  cursor: pointer;
  position: relative;
  transition: color 160ms ease;
}
.profile-tab svg { width: 20px; height: 20px; fill: currentColor; display: block; }
.profile-tab:hover { color: var(--ink); }
.profile-tab--active { color: var(--ink); }
/* Per-tab underline — paints once on each tab so PROJECTS and SAVED always share an equal-length line at the same vertical position. The active tab's bolder override below takes over the same slot. */
.profile-tab::after {
  content: '';
  position: absolute;
  left: -20px;                 /* symmetric extension into the 40 px column-gap so POSTS + SAVED underlines meet in the middle */
  right: -20px;
  bottom: -1.15px;
  height: 1.15px;              /* symmetric base divider (1 × 1.15) */
  background: var(--line-soft);
  border-radius: 1.5px;
  transition: background 160ms ease, height 160ms ease;
}
.profile-tab--active::after {
  height: 2.5px;               /* bolder indicator when selected — clearer but not heavy */
  background: var(--ink);
  border-radius: 3px;          /* matches the taller stroke proportionally */
}

.profile-projects-empty {
  margin: 8px 0 0;
  color: var(--muted);
  text-align: center;
  padding: 56px 24px;
  font-size: 15px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

/* ---- Mobile — full viewport, but everything fits inside the card ----
   At <480px the IG profile shell collapses back to a card-shaped panel:
     - card keeps its rounded corners + subtle shadow + 16–20px margin
       around it so it reads as a card, not a page
     - sidebar shrinks to 64px so the main column gets the most space
     - avatar/stats/buttons/font sizes scale down so nothing overflows
*/
@media (max-width: 480px) {
  .auth-shell { padding: 16px 12px; }
  .auth-card.is-unlocked {
    width: min(100%, 100%);
    max-width: 100%;
    min-height: 0;                    /* don't force 100vh on mobile */
    padding: 22px 16px 22px;
    border-radius: 16px;
    border: 1px solid var(--line-soft);
    box-shadow:
      0 23px 29px -6px rgba(15, 23, 42, 0.10),
      0 9px 11px -8px rgba(15, 23, 42, 0.06);
    display: flex;
    flex-direction: column;
  }
  .auth-shell:has(.auth-card.is-unlocked) { padding: 16px 12px; }
  .home-topbar { margin-bottom: 12px; padding: 0; }
  .home-topbar .auth-logo--home { width: 56px; }
  .home-layout {
    grid-template-columns: 60px 1fr;
    flex: 0 0 auto;
  }
  .home-main {
    /* Drop the inner-edge gutter on phone widths so the profile-header
       avatar / name-stats column / bio / edit-profile pill / tabs /
       posts row sit flush against the canvas-left edge instead of
       floating with right-side empty space. The desktop translateX
       is also stripped so no horizontal-shift compensation is needed. */
    padding: 8px 0 4px 0;
    gap: 14px;
    /* Mobile breakpoint uses its own column-fit geometry; reset the
       desktop translateX so the tightened layout doesn't reflow. */
    transform: none;
  }
  .home-sidebar {
    padding: 6px 0 4px 0;
  }
  .sidebar-list { gap: 12px; }
  .sidebar-item { padding: 6px 0 6px 4px; font-size: 10px; }
  .sidebar-icon { width: 22px; height: 22px; }
  .sidebar-avatar-placeholder { width: 26px; height: 26px; }
  .profile-avatar-large { width: 75px; height: 75px; }
  .profile-username { font-size: 20px; }    .profile-stats { gap: 16px; font-size: 13px; padding: 10px 0; }
    /* Keep the compact action row inside the mobile profile width.
       The seam offset and tile sizing are applied in the mobile media
       rules below. */
    .profile-actions { max-width: 100%; }
    .profile-action { font-size: 13px; padding: 14px 16px; }            /* reading compressed on tablet/phone */
    .profile-action-mirror { font-size: 13px; }
    .profile-action-mirror strong { font-size: 14px; }
    .profile-action-mirror .profile-action-gap { width: 16px; }
    .profile-projects-empty { padding: 28px 12px; font-size: 13px; }
}
/* Mobile overrides for IG-style profile header: collapse the 2-column
   grid to a stacked layout so a 320px viewport still reads cleanly. */
@media (max-width: 600px) {
  .profile-header {
    grid-template-columns: 100px 1fr;
    gap: 24px;
    padding: 4px 0 18px;
    /* Pin both columns to the top of the row so the bolded username
       starts at the same height as the profile picture's top (like the
       normal desktop view), instead of floating above the avatar while
       the stacked stats make the right column taller than it. The row
       height is unchanged, so the bio + edit profile below don't move. */
    align-items: flex-start;
  }
  .profile-avatar-large { width: 100px; height: 100px; }
  /* Font-ascent counter paired with the flex-start above: the 20px
     username glyph sits a few px below its box top, so a −6px nudge
     brings the visible glyph top flush with the visible circle edge
     across the 381–480 px phone band (the 481–600 band is covered by
     its own media query; the ≤380 band tightens it to −4px below). */
  .profile-id-block { margin-top: -6px; }
  .profile-username { font-size: 20px; }    .profile-stats { gap: 18px; font-size: 12px; padding: 8px 0 0; }
    /* Stack the stat on phone so the digit sits on top of the unit
       word ("posts" / "followers" / "following") and the row reads
       vertically. Without this the inline "2 posts  3 followers  3
       following" string overflows the canvas and crops the trailing
       "g" of "following" on viewports < ~460 px. */
    .profile-stat { flex-direction: column; align-items: flex-start; gap: 2px; line-height: 1.05; }
    /* Center the count digit (strong) horizontally inside the stack;
       the "posts" / "followers" / "following" label inherits the
       parent's flex-start so it still sits flush left. */
    .profile-stat strong { font-size: 15px; margin-right: 0; align-self: center; }
    /* Keep the compact action row aligned with the mobile POSTS/SAVED seam. */
    .profile-action-mirror { font-size: 13px; }
    .profile-action-mirror strong { font-size: 14px; }
    .profile-action-mirror .profile-action-gap { width: 16px; }
  .profile-bio p { font-size: 13.5px; }
  .profile-tabs {
    margin-top: 18px;
    column-gap: 24px;
    width: calc(100% - 16px);
    max-width: calc(100% - 16px);
    transform: translateX(-8px);
  }
  .profile-actions {
    margin: 20px 0 0 16px;
    width: calc(100% - 16px);
    max-width: calc(100% - 16px);
  }
  .profile-posts {
    width: calc(100% - 16px);
    max-width: calc(100% - 16px);
    grid-template-columns: repeat(2, minmax(0, 1fr));
    transform: translateX(-4px); /* keep the pair midpoint on the mobile tab seam */
  }
  .profile-tab { padding: 8px 14px; font-size: 11px; gap: 6px; }
  .profile-tab::after { left: -12px; right: -12px; }  /* symmetric extension into the 24 px mobile column-gap */
  .profile-tab svg { width: 16px; height: 16px; }
}
@media (max-width: 380px) {
  .profile-header {
    grid-template-columns: 80px minmax(0, 1fr);
    gap: 16px;
  }
  .profile-avatar-large { width: 84px; height: 84px; }
  .profile-username { font-size: 17px; }    .profile-stats { gap: 12px; font-size: 12px; }
  /* Tighter font-ascent counter on smallest phones — the 17px
     username glyph has smaller ascent than the 20px tablet glyph,
     so a smaller −4px nudge keeps the glyph top lined up with the
     84px circle edge. The 481–768 / ≤600 bands above take care of
     the wider widths. */
  .profile-id-block { margin-top: -3px; }
    /* Drop the icon labels on tabs so "Projects" / "Saved" / "Tagged"
       fit on the smallest phones. */
    .profile-tab svg { display: none; }
    /* Keep the compact action row aligned with the mobile POSTS/SAVED seam. */
    .profile-action { font-size: 12px; padding: 12px 12px; }
    .profile-action-mirror { font-size: 12px; }
    .profile-action-mirror strong { font-size: 13px; }
    .profile-action-mirror .profile-action-gap { width: 12px; }
}
/* Mobile rule: lift the sidebar out of the .home-layout grid and
   pin it to the viewport bottom (IG-style bottom nav). Same three
   items as the desktop sidebar — Users, Create, profile avatar —
   laid out horizontally so the user can tap them after the page has
   scrolled. .home-main gains bottom padding so the last row of posts
   isn't hidden under the bar. Breakpoint matches IG (≤768 px) so the
   sidebar collapses to the bottom on phone widths, not just the
   narrowest. Sidebar rules below override the existing marginal sizing
   without adding any new buttons. */
@media (max-width: 768px) {
  .home-layout {
    grid-template-columns: minmax(0, 1fr);  /* drop the sidebar column */
    padding-bottom: 64px;                  /* room for the fixed bar */
  }
  .home-sidebar {
    position: fixed;
    top: auto;
    height: auto;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 8px 16px calc(8px + env(safe-area-inset-bottom, 0px));
    background: #fff;
    border-top: 1px solid var(--line-soft);
    z-index: 5;
  }
  .sidebar-list {
    flex-direction: row;
    justify-content: space-around;
    gap: 0;
    margin-top: 0;
  }
  .sidebar-item { padding: 6px 0; }
  /* Brand wordmark: redundant once the bottom nav owns the chrome,
     so hide it on phone widths — IG does the same swap. */
  .home-topbar { display: none; }
  /* Whenever the sidebar transposes to the bottom (this breakpoint
     AND all narrower ones), the kebab sits near the right edge of
     the canvas. The desktop menu opens rightward of it (`left: 0`
     from the wrap), which would push a 140-px-min-width menu
     off-screen. Flip the menu's anchor to the kebab's RIGHT edge so
     it grows leftward and stays fully visible. */
  .profile-kebab-menu { left: auto; right: 0; }
}

/* Between 481 and 768 px the bottom nav kicks in; drop the desktop
   gutter/translation and let the avatar grow into the freed-up room.

   The bolded username sits at the same height as the top of the profile
   picture circle, both in this medium band AND in the narrower ≤600
   layouts (where the ≤600 rule below aligns the rows via flex-start).
   That requires (1) flex-start so both columns anchor to the row top
   (no more center-alignment drift in this band), (2) keeping the
   avatar flush at row top (the old `margin-top: -8px` workaround is no
   longer needed), and (3) a small −6px counter on the text column
   that compensates for the username glyph's font ascent so the visible
   glyph top lines up exactly with the visible circle edge, not below
   it. The desktop ≥769 px layout (center-aligned, no nudges) is left
   alone — that's the "perfectly correct" baseline the user approved. */
@media (min-width: 481px) and (max-width: 768px) {
  .home-main { padding-left: 0; transform: none; }
  .profile-header {
    grid-template-columns: 108px minmax(0, 1fr);
    gap: 24px;
    align-items: flex-start;
  }
  .profile-id-block,
  .profile-avatar-large { margin-left: 0; }
  .profile-avatar-large {
    width: 108px;
    height: 108px;
  }
  .profile-id-block { margin-top: -6px; }
}

/* ---------------- Edit profile flow ----------------
   Three sibling views inside .auth-card:
     - .auth-view--edit-profile  (row list, image 1)
     - .auth-view--edit-name     (single-line input, image 2)
     - .auth-view--edit-bio      (multi-line + 34/150 counter, image 3)
   All three use the same .edit-topbar pattern: back arrow on the left,
   centered title, optional action button on the right. The action
   button on edit-name + edit-bio is a black-filled circle with a white
   check (replaces IG's blue check, Comet brand).               */

.auth-view--edit { display: flex; flex-direction: column; }

.edit-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
  min-height: 48px;
  margin-bottom: 20px;
}
.edit-topbar .back-arrow {
  position: static;            /* override the absolute positioning  */
                                   /* used by the sign-up view so the  */
                                   /* row of three icons lines up      */
  top: auto;
  left: auto;
  width: 40px;
  height: 40px;
}
.edit-title {
  margin: 0;
  position: absolute;
  left: 0;
  right: 0;
  text-align: center;
  pointer-events: none;
  color: var(--ink);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.2;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.edit-topbar-spacer { width: 40px; height: 40px; display: inline-block; }

/* Black-filled circular confirmation button on the right side of the
   edit-name + edit-bio top bars (Comet brand — replaces IG blue). */
.edit-checkmark {
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--pill);
  background: #000;
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.edit-checkmark:hover { background: #1e293b; transform: scale(1.02); }
.edit-checkmark:active { transform: scale(0.98); }
.edit-checkmark:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}
.edit-checkmark svg { width: 22px; height: 22px; display: block; }

/* ----- Edit profile (row list) ----- */
.edit-avatar-row {
  display: flex;
  justify-content: center;
  margin: 8px 0 14px;
}
.edit-avatar-circle {
  position: relative;
  display: inline-flex;
  width: 96px;
  height: 96px;
  border-radius: 999px;
  overflow: hidden;
  background: #f1f5f9;
  flex-shrink: 0;
  text-decoration: none;
  cursor: pointer;
  border: 3px solid #fff;
  box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.12);
}
.edit-avatar-circle:hover { box-shadow: 0 0 0 2px rgba(15, 23, 42, 0.20); }
.edit-avatar-svg img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Avatar <img> layered on top of the placeholder SVG. Hidden by default
   (the [hidden] attribute); JS unhides it once a URL is set. Cover-fit
   so portrait or landscape uploads still fill the circle without
   stretching. The `border-radius: inherit` lets the parent circle
   mask the image. z-index: 1 lifts it definitively above the
   placeholder SVG so the visible-on-screen image wins regardless of
   which ancestor establishes stacking context. */
.profile-avatar-image,
.edit-avatar-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  background: #f1f5f9;
  z-index: 1;
}
.profile-avatar-image[hidden],
.edit-avatar-image[hidden] { display: none; }
/* Belt-and-suspenders: once the parent <a> has the .has-avatar class
   (added by the img onload handler), the placeholder silhouette is
   dropped outright. Without this, a stored picture whose <img> goes
   through a slow reload can leave the placeholder visible behind the
   image for the few hundred ms before the image paints. */
.profile-avatar-large.has-avatar .profile-avatar-inner,
.edit-avatar-circle.has-avatar .edit-avatar-svg { display: none; }
.edit-picture-actions {
  margin: 0 0 26px;
  display: flex;
  justify-content: center;
  gap: 10px;
}
.edit-picture-button {
  background: transparent;
  border: 0;
  padding: 6px 8px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  border-radius: 6px;
  transition: background 160ms ease;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.edit-picture-button:hover { background: rgba(15, 23, 42, 0.04); }
.edit-picture-button[hidden] { display: none; }
.edit-picture-button--remove { color: #b91c1c; }
.edit-picture-button--remove:hover { background: rgba(185, 28, 28, 0.06); }

.edit-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}
.edit-rows > li + li { padding-top: 4px; }

.edit-row {
  display: flex;
  align-items: center;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 16px 8px;
  gap: 16px;
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: left;
  transition: background 160ms ease;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.edit-row:hover, .edit-row:focus-visible { background: rgba(15, 23, 42, 0.04); }
.edit-row-label {
  flex: 0 0 110px;
  color: var(--ink);
  font-size: 15px;
  font-weight: 500;
}
.edit-row-value {
  flex: 1;
  color: var(--ink);
  font-size: 15px;
  font-weight: 600;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.edit-row-chev {
  width: 20px;
  height: 20px;
  color: var(--muted);
  flex-shrink: 0;
  display: block;
}

/* ----- Edit name + edit-bio + edit-school forms ----- */
.edit-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.edit-input-wrap {
  position: relative;
  border: 1px solid var(--line);
  /* Full pill to match the edit profile button's shape. */
  border-radius: var(--pill);
  padding: 14px 14px;
  background: #fff;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.edit-input-wrap:focus-within {
  border-color: #000;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.15);
}
.edit-input {
  width: 100%;
  border: 0;
  outline: none;
  background: transparent;
  font: inherit;
  color: var(--ink);
  font-size: 16px;
  line-height: 1.3;
  padding: 0;
  resize: none;
}
.edit-input::placeholder { color: var(--placeholder); }
.edit-input--multi {
  min-height: 88px;
  resize: vertical;
}
.edit-input-wrap--multi {
  padding-top: 12px;
  padding-bottom: 28px;
  /* A full pill would pinch the multi-line Bio box; keep a rounded square. */
  border-radius: 14px;
}
.edit-counter {
  position: absolute;
  right: 12px;
  bottom: 8px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  pointer-events: none;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.edit-counter.is-near-limit { color: #b45309; }
.edit-counter.is-over-limit { color: #b91c1c; }

/* Inline error slot under each edit input. Populated by commitEditName
   / commitEditBio when supabase.auth.updateUser rejects. Sits between
   the input wrap and the helper copy so it never pushes other chrome
   around (the helper copy stays in flow because the slot collapses
   when empty). */
.edit-error {
  margin: 6px 2px 0;
  font-size: 12.5px;
  line-height: 1.35;
  font-weight: 500;
  color: #b91c1c;
}
.edit-error.is-success { color: #16803c; }
.edit-error[hidden] { display: none; }

.edit-helper {
  margin: 0;
  color: var(--ink);
  font-size: 14px;
  line-height: 1.45;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.edit-helper--quiet { color: var(--muted); }
.edit-helper a { color: var(--ink); font-weight: 500; text-decoration: underline; }
.edit-helper a:hover { color: #000; }

/* Mobile cascade shared with the rest of the views */
@media (max-width: 480px) {
  .auth-shell { padding: 16px 12px; }
  .auth-card {
    padding: 22px 16px 22px;
    border-radius: 12px;
    border: 1px solid var(--line-soft);
    box-shadow:
      0 23px 29px -6px rgba(15, 23, 42, 0.10),
      0 9px 11px -8px rgba(15, 23, 42, 0.06);
  }
  .edit-avatar-circle { width: 76px; height: 76px; }
  .edit-row-label { flex-basis: 90px; font-size: 14px; }
  .edit-row-value { font-size: 14px; }
}

/* ---------- For you feed ---------- */
.for-you-main {
  min-width: 0;
  padding: 24px 24px 48px;
}
.for-you-list {
  width: min(620px, 100%);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 28px;
}
.for-you-card {
  overflow: hidden;
  cursor: pointer;
  background: #fff;
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.07);
}
.for-you-card:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}
.for-you-card-author {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  color: var(--ink);
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
}
.for-you-card-author img {
  width: 36px;
  height: 36px;
  border: 1px solid #d6dce6;
  border-radius: 999px;
  object-fit: cover;
  flex: 0 0 auto;
}
.for-you-card-media {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: #f1f5f9;
  cursor: pointer;
}
.for-you-card-media > img,
.for-you-card-media-iframe {
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
  object-fit: cover;
}
.for-you-card-media-iframe {
  pointer-events: none;
  position: absolute;
  left: 50%;
  top: 50%;
  width: 896px;
  height: 896px;
  transform-origin: center;
}
.for-you-card-media-shield {
  display: none;
}
.for-you-card-media-fallback {
  display: grid;
  width: 100%;
  height: 100%;
  place-items: center;
  padding: 24px;
  color: var(--muted);
  font-size: 14px;
  text-align: center;
}
.for-you-card-details {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px 16px 16px;
}
.for-you-card-caption {
  margin: 0;
  color: var(--ink);
  font-size: 15px;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
}
.for-you-card-footer {
  display: flex;
  align-items: center;
  gap: 10px;
}
.for-you-card-footer .for-you-card-caption {
  flex: 1;
  min-width: 0;
}
.for-you-card-footer .view-post-save-btn {
  margin: 0;
}
.for-you-card-date {
  color: var(--muted);
  font-size: 13px;
}
.for-you-status {
  margin: 48px 0;
  color: var(--muted);
  font-size: 14px;
  text-align: center;
}

/* For you is one continuous feed: adjacent posts share the same
   surface instead of reading as separate floating cards. */
.for-you-list {
  gap: 14px;
  overflow: hidden;
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  background: #fff;
}
.for-you-card {
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* ---------- Posts grid (2 columns, FIFO top-left, post #1 in the top-left cell) ---------- */
.profile-posts {
  display: grid;
  /* Same geometry as the .profile-tabs baseline (two 280 px tiles + 10 px gap = 570 px). */
  grid-template-columns: 280px 280px;
  gap: 10px;
  width: 570px;
  max-width: calc(100% - 24px);
  /* Anchors the pair on .profile-bio's margin-left: 16 px column. */
  margin: 12px auto 0 16px;
}
.profile-post {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: #f1f5f9;
  border-radius: 2px;
  cursor: pointer;
}
.profile-post img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Profile-tile link-only iframe (no stored photo). Centered via
   translate(-50%,-50%) scale (set from script.js GRID_VIRTUAL_VIEWPORT)
   and pointer-events:none to forward clicks to the parent <a>. */
.profile-post iframe.profile-post-iframe {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 896px;
  height: 896px;
  border: 0;
  pointer-events: none;
  transform-origin: center center;
}
/* Link-only post tile (no photo): the caption fills the square. */
.profile-post-link {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  text-align: center;
  word-break: break-word;
  font: 500 13px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: #6b7280;
}
/* Editor placeholder for a link-only post with no photo yet. */
.view-post-nophoto {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font: 500 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: #6b7280;
}

/* ---------- New-post create area (rendered inside #profile-projects while data-mode="create") ---------- */
/* ---------- Create-new-post modal ---------- */
/* Modal element reuses .view-post-modal + .edit-post-editor skins (see
   below) so the create + edit flows share one CSS surface. */
.create-post-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
}
.create-post-backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}
.create-post-modal-header {
  position: relative;
  padding: 14px 48px 14px 16px;
  border-bottom: 1px solid var(--line-soft);
  text-align: center;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}
.create-post-modal-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 32px;
  height: 32px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 22px;
  line-height: 1;
  color: var(--ink);
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 160ms ease;
}
.create-post-modal-close:hover { background: #f0f2f5; }
.create-post-modal-close:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
/* Header edit button — top-left of the modal, mirror of the ×-close
   button on the right. Owners see this in the view-post card; clicking
   it opens the editor without going through a dropdown menu. Was
   previously the only item inside the ⋮ kebab, which tended to clip
   against the bottom caption-row at narrow viewports. */
/* ⋯ kebab — top-left of the view-post modal header for the post
   owner's own posts. Circular 32 × 32 chip so its silhouette matches
   the ×-close button on the right (same 32 px height, same 8 px
   corner offset). The 3 dots inside are an inline SVG with currentColor
   so hover/focus tinting works without a CSS filter. */
.create-post-modal-edit {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: var(--ink);
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 160ms ease;
}
.create-post-modal-edit svg { width: 22px; height: 22px; display: block; }
.create-post-modal-edit:hover { background: #f0f2f5; }
.create-post-modal-edit:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
/* View-post header: author chip left, kebab + × right (replaces the
   centered "Post" title). */
.view-post-header {
  display: flex;
  align-items: center;
  /* 6px top pad centers the 36px avatar on the 32px buttons (top:8). */
  padding: 6px 88px 10px 14px;
  text-align: left;
}
.view-post-author {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  /* Clickable chip → the poster's profile (underline on hover). */
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  border-radius: 999px;
  transition: background 160ms ease;
}
.view-post-author:hover { background: #f0f2f5; }
.view-post-author:hover .view-post-author-name { text-decoration: underline; }
/* Poster avatar — same 36px footprint as the sidebar menu avatar. */
.view-post-author-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  flex-shrink: 0;
  background: #f1f5f9;
  border: 1px solid #d6dce6;
}
.view-post-author-name {
  font-weight: 700;
  font-size: 14px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Kebab sits directly left of the × close button (was top-left).
   44 px = 8 px (× offset) + 32 px (× width) + 4 px gap. top:9 = a hair
   below the × (8) so it reads as its own line, card height untouched. */
.view-post-header .create-post-modal-edit {
  left: auto;
  top: 9px;
  right: 44px;
}
/* Posts opened from /explore drop the kebab a hair further (10 vs 9) to
   clear the feed's tighter header rhythm; profile-opened posts stay at 9. */
.view-post-modal--from-explore .create-post-modal-edit {
  top: 10px;
}
/* Created-date line under the caption/link — IG-style 13 px gray. */
.view-post-date {
  font-size: 13px;
  color: #737373;
}
/* The two-pane create-post body + author chip were replaced by the
   vertical-stack layout (.view-post-body / .view-post-text) so the
   create and edit modals share one CSS surface. */
.create-post-dropzone {
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 0;
  background: #000;
  color: #fafbfc;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 24px 18px;
  cursor: pointer;
  text-align: center;
  transition: background 160ms ease;
}
.create-post-dropzone:hover { background: #0b0b0b; }
.create-post-dropzone.is-dragover { background: #1a1a1a; outline: 2px dashed #fafbfc; outline-offset: -12px; }
.create-post-dropzone-icon {
  width: 96px;
  height: 96px;
  display: block;
  color: #fafbfc;
  margin-bottom: 4px;
}
.create-post-dropzone-text {
  font-size: 18px;
  color: #fafbfc;
  font-weight: 400;
}
.create-post-select-btn {
  background: #0095f6;
  color: #fff;
  border: 0;
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  border-radius: 8px;
  cursor: pointer;
  transition: background 160ms ease;
}
.create-post-select-btn:hover { background: #1877f2; }
.create-post-select-btn:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.create-post-preview-img {
  width: 100%;
  height: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
  background: #000;
}
/* Square crop frame for the create/edit preview: the image is sized to
   always cover the square (min 100% each way keeps its aspect) and a
   pointer-drag repositions it, matching the pan baked on submit. */
.create-post-crop-wrap {
  position: relative;
  width: min(100%, 460px);
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 8px;
  background: #000;
  touch-action: none;
  cursor: grab;
}
.create-post-crop-wrap.is-panning { cursor: grabbing; }
.create-post-crop-wrap .create-post-preview-img {
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: none;
  max-height: none;
  object-fit: cover;   /* pre-decode box is 100%×100% — cover matches the baked crop */
  transform: translate(-50%, -50%);
  will-change: transform;
}
.create-post-crop-hint {
  position: absolute;
  left: 50%;
  bottom: 10px;
  transform: translateX(-50%);
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font: 500 12px/1 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  pointer-events: none;
}
/* The editor reuses the square view-post wrap — make it pannable. */
.edit-post-editor .view-post-image-wrap {
  touch-action: none;
  cursor: grab;
}
.edit-post-editor .view-post-image-wrap.is-panning { cursor: grabbing; }
.edit-post-editor .view-post-image {
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: none;
  max-height: none;
  transform: translate(-50%, -50%);
  will-change: transform;
}
.edit-post-change-picture {
  align-self: flex-start;
  font: 600 14px/1 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  padding: 9px 14px;
  border: 0;
  border-radius: var(--pill);
  background: #f0f2f5;
  color: var(--ink);
  cursor: pointer;
  transition: background 160ms ease;
}
.edit-post-change-picture:hover { background: #e4e7eb; }
/* Change picture + Remove picture sit side by side in the editor. */
.edit-post-picture-row {
  display: flex;
  align-self: flex-start;
  gap: 8px;
}
.edit-post-remove-picture {
  font: 600 14px/1 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  padding: 9px 14px;
  border: 0;
  border-radius: var(--pill);
  background: #f0f2f5;
  color: #b91c1c;
  cursor: pointer;
  transition: background 160ms ease;
}
.edit-post-remove-picture:hover { background: rgba(185, 28, 28, 0.08); }
.edit-post-remove-picture[hidden] { display: none; }
.create-post-textarea,
.create-post-input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: var(--pill);
  border: 1px solid var(--line-soft);
  background: var(--soft);
  font: 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  resize: none;
}
/* Caption textarea stays fixed-height while preserving native multiline
   typing and line breaks. */
.create-post-textarea { height: 42px; }
.create-post-textarea:focus,
.create-post-input:focus {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
  border-color: transparent;
}
.create-post-error {
  margin: 0;
  width: 100%;
  font-size: 13px;
  line-height: 1.4;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: #b00020;
  min-height: 18px;
}
.create-post-actions {
  width: 100%;
  display: flex;
  gap: 10px;
  justify-content: space-between;
  align-items: center;
  padding: 12px 20px 16px;
  border-top: 1px solid var(--line-soft);
}
.create-post-actions-spacer { flex: 1 1 auto; }
.create-post-cancel,
.create-post-submit {
  font: 600 14px/1 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  padding: 9px 18px;
  border-radius: var(--pill);
  border: 0;
  cursor: pointer;
  transition: background 160ms ease;
}
.create-post-cancel { background: #f0f2f5; color: var(--ink); }
.create-post-cancel:hover { background: #e4e7eb; }
.create-post-submit { background: #000; color: #fff; }
.create-post-submit:hover { background: #1e293b; }
.create-post-submit[disabled] { opacity: 0.45; cursor: not-allowed; }
.create-post-submit[disabled]:hover { background: #000; }
/* Mobile (max-width: 600px): column tracks fill the page (1fr × 2 +
   10 px gap), the pair shifts -8 px so its centerline stays on the
   POSTS/SAVED seam. Tile aspect stays 1 / 1 — squares at every
   breakpoint. */
@media (max-width: 600px) {
  .for-you-main { padding: 16px 12px 36px; }
  .for-you-list { gap: 12px; }
  .for-you-card-author { padding: 12px; }
  .for-you-card-details { padding: 12px; }
  .profile-posts {
    width: calc(100% - 16px);
    max-width: calc(100% - 16px);
    grid-template-columns: repeat(2, minmax(0, 1fr));
    transform: translateX(-8px); /* align the mobile pair midpoint with the POSTS/SAVED seam */
  }
}

/* Link-bearing Explore and profile previews stay inert until opened.
   Keep this separate from the <=600px layout rules: only the interaction
   shield expands to the bottom-nav breakpoint, not the surrounding feed
   geometry. */
.profile-post-preview-shield {
  display: none;
}
@media (max-width: 768px) {
  .for-you-card-media-shield,
  .profile-post-preview-shield {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
  }
}

/* ------------------------------------------------------------------
   Edit post modal — opens when a .profile-post tile is clicked.
   Reuses the create-post-* skeleton so the chrome (header, close ×,
   image-on-left two-pane body, right caption+link fields, error row,
   action bar) is visually consistent with the create flow.
   The action bar carries a destructive Delete that flips to a
   two-step "Tap delete to confirm" state on first click.
   ------------------------------------------------------------------ */
.edit-post-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
}
.edit-post-backdrop.is-open { opacity: 1; pointer-events: auto; }
.edit-post-modal {
  background: #fff;
  width: min(900px, 92vw);
  max-height: 100vh;
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.35);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.edit-post-actions {
  width: 100%;
  display: flex;
  gap: 10px;
  justify-content: space-between;
  align-items: center;
  padding: 12px 20px 16px;
  border-top: 1px solid var(--line-soft);
}
.edit-post-delete {
  font: 600 14px/1 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  padding: 9px 18px;
  border-radius: var(--pill);
  border: 0;
  cursor: pointer;
  background: #f0f2f5;
  color: var(--ink);
  transition: background 160ms ease;
}
.edit-post-delete:hover { background: #e4e7eb; }
.edit-post-delete.is-confirm-active {
  background: #ed4956;        /* destructive IG red */
  color: #fff;
}
.edit-post-delete.is-confirm-active:hover { background: #000; }
.edit-post-actions.is-confirming-delete .edit-post-delete:not(.is-confirm-active),
.edit-post-actions.is-confirming-delete [data-action="submit-edit"] { display: none; }
@media (max-width: 720px) {
  .edit-post-modal { width: 96vw; max-height: 94vh; }
}

/* ---------------- View-only post card (visiting another user's post) ---
   Clicking a non-owned post paints a read-only card showing the
   image, caption, and an outbound link. No inputs / no Save / no
   Delete — same backdrop + modal shell as the edit modal.

   Centering: with nothing but the inherited .edit-post-backdrop
   (display:flex; justify-content:center) the modal sits dead-center
   on the VIEWPORT. .home-layout centers a 935 px sidebar+main row,
   which puts the home-main / posts-grid centerline 66 px right of
   the viewport center (the 132 px sidebar offset). Without an
   override here the modal reads as visibly-off from the grid below.
   The override below switches the view-post backdrop to a 132 + 1fr
   grid that mirrors .home-layout, so the modal's natural
   justify-items:center lands on the 1fr column center — equals the
   home-main center — equals the posts-grid center — and the modal
   now sits over the grid on every viewport ≥ 935. */
.edit-post-backdrop.view-post-backdrop {
  display: grid;
  /* 116 px (was 280 = sidebar 132 + .home-main padding-left 148) so the
     modal sits over the posts grid after .home-main's
     `transform: translateX(-148px)` shifted it 148 px left. */
  grid-template-columns: 116px 1fr;
  align-items: center;
  justify-items: center;
}
.edit-post-modal.view-post-modal {
  grid-column: 2;
}
/* The post card itself — 880 -> 704 (-20%), then back up 5% to 739,
   then down a hair to 720 so the card reads a touch tighter. The
   microsite inside re-fits geometrically (fitMicrosite: wrap / 1024),
   so the rendering logic is untouched. Mobile keeps its own cap below. */
.view-post-modal { width: min(720px, 94vw); }
/*"Play link" toggle: the wrap substitutes an <iframe> for the static
   <img> on click. Surrounding chrome (header / caption / link) never
   reflows because the wrap and modal sizes are fixed. */
.view-post-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 8px 16px 12px;
  /* Locked: every piece of content fits inside the modal max-height
     (because the wrap self-clamps via the min(...) calc above), so
     we don't need an internal scrollbar. Wheel events that originate
     inside the iframe therefore can't bubble up and drag a modal-body
     scrollbar — they stay inside the iframe's own browsing context. */
  overflow: hidden;
}
.view-post-image-wrap {
  position: relative;
  /* Square (1:1); the width cap shrinks it on short screens instead
     of clipping. flex-shrink: 0 stops the column-flex body from
     compressing it against the modal's max-height. The 64 px
     reserved headroom covers the caption-row + link-row + padding
     + 10 px gap — letting the media fill the rest of the modal so
     the post reads at IG scale, not thumbnail scale. Zero
     border-radius on the wrap so the media meets the modal's
     card edges with no visible inset frame. */
  width: min(100%, calc(100vh - 64px));
  height: auto;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  flex-shrink: 0;
  background: transparent;
  border-radius: 0;
  overflow: hidden;
}
.view-post-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 0;
}
/* Editor gets a smaller square so the caption/link fields + actions fit.
   Desktop-only: the 430 px chrome carve assumes a tall desktop viewport
   (the editor adds caption + link + error row + change-picture pill +
   two-step delete-confirm + share button below the image — a lot of
   vertical chrome). On mobile (<=720 px) we inherit the default
   `.view-post-image-wrap` sizing, which the mobile @media tunes to
   `calc(100dvh - 130px)` so the 1:1 image still reads as the hero.
   Without this @media the editor's wrap would crush the image on phones
   (640 dvh − 430 = 210 px square on a 360 px-wide viewport). */
@media (min-width: 721px) {
  .edit-post-editor .view-post-image-wrap {
    width: min(100%, calc(100vh - 430px));
  }
  .edit-post-editor .view-post-body {
    /* Long errors scroll instead of clipping. */
    overflow-y: auto;
  }
}
/*"Embed link" pill — paints inline within the caption row (right-aligned,
   same baseline as the caption text) so it reads as a row-level affordance
   rather than an image overlay. Visible ONLY when the post has a link. */
.view-post-embed-btn {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 160ms ease, transform 160ms ease;
}
.view-post-embed-btn:hover { background: var(--soft); transform: scale(1.04); }
.view-post-embed-btn:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}
.view-post-embed-btn svg { width: 28px; height: 28px; display: block; }
/* Save-bookmark pill alongside the play button — same shape, just a
   different glyph so the two buttons sit at the same baseline inside
   the caption row. Filled when the post is in the user's saved set. */
.view-post-save-btn {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 160ms ease, color 160ms ease, transform 160ms ease;
  margin-left: 8px;
}
.view-post-save-btn:hover { background: var(--soft); transform: scale(1.04); }
.view-post-save-btn:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}
.view-post-save-btn svg { width: 28px; height: 28px; display: block; }
.view-post-save-btn.is-saved { color: #000; background: var(--soft); }
/* Own-post ⋮ menu in the card header, just left of the close ×. */
/* Editor link row: input stretches, play button flush right. */
.view-post-link-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.view-post-link-row .create-post-input { flex: 1 1 auto; }
/* Caption-row link: bold black, fills the row, breaks long URLs inside
   the card. Clicking it opens the stored URL (target=_blank). */
.view-post-link { flex: 1 1 auto; min-width: 0; }
.view-post-link-row + .view-post-date { margin-top: 1px; }
/* Live-pane iframe — JS (fitMicrosite) sizes this to the linked site's
   design viewport, then scale-fits it to the modal wrap. Native iframe
   scroll stays on so users can reach overflow content (e.g. flappy-bird's
   AI AGENT panel below the canvas). */
.view-post-iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%; /* match the scroller's box; iframe content scrolls inside its own document */
  display: block;
  border: 0;
  background: #fff;
  /* touch-action: pan-y tells iOS Safari "I handle vertical pan
     inside my own scroll context" — without it, parent modal's
     scroll-lock swallows the gesture. The wrapper scroller below
     still owns the iframe DOM's overflow / visible scrollbar. */
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  /* Avoid the `overflow-y: scroll` here — the previous iteration
     declared it on the iframe, but cross-origin iframe documents
     don't expose their INTERNAL scrollbar to parent CSS, so the
     effect was invisible. Document-internal scrolls are handled by
     the iframe's own body; parent-level scroll is handled by the
     wrapper scroller below. */
}
/* Modal microsite scroller — only rendered in the modal (the
   `createEmbedToggler.play` branch wraps the iframe in a
   `.microsite-scroller` div, the only place a custom scrollbar can
   actually be styled/dragged for a cross-origin iframe). Inset:0 of
   `.view-post-image-wrap` (same 1:1 square as `imgWrap`); its
   overflow gives iOS Safari a touchable target for finger-drag
   scroll. `overscroll-behavior: contain` is the iOS-specific
   piece — without it, scrolling on the embed bounces the parent
   modal scroll-lock and the iframe comes back as "un-scrollable". */
.view-post-image-wrap .microsite-scroller {
  position: absolute;
  inset: 0;
  overflow-y: scroll;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  overscroll-behavior: contain;
  /* Firefox + Chromium thin-track fallback; WebKit pseudo-elements
     below handle the rest. */
  scrollbar-width: thin;
  scrollbar-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.06);
}
.view-post-image-wrap .microsite-scroller::-webkit-scrollbar {
  width: 8px;
}
.view-post-image-wrap .microsite-scroller::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.06);
  border-radius: 4px;
}
.view-post-image-wrap .microsite-scroller::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.4);
  border-radius: 4px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.view-post-image-wrap .microsite-scroller::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 0, 0, 0.55);
}
/* "Open in new tab" fallback overlay — painted on top of the wrap when
   the iframe didn't fire its load event inside 8 s (linked site
   blocks embedding, network stalled, or the browser killed it). The
   user still gets a clickable path to the linked site. */
.view-post-fallback {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 6px;
  padding: 20px;
  text-align: center;
  background: rgba(248, 250, 252, 0.95);
  color: var(--ink);
  text-decoration: none;
  font-size: 14px;
  line-height: 1.4;
  word-break: break-all;
}
.view-post-fallback:hover { background: rgba(241, 245, 249, 0.98); text-decoration: underline; }
/*The caption row: caption text on the LEFT (1fr), embed button on the
   RIGHT (auto). When the post has no link, the row collapses to a
   single child and the caption takes the full width. */
.view-post-caption-line {
  display: flex;
  align-items: center;
  gap: 10px;
}
.view-post-caption { flex: 1; }
.view-post-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.view-post-caption {
  margin: 0;
  font: 400 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  white-space: pre-wrap;
  word-wrap: break-word;
}
.view-post-link {
  font: 700 13px/1.3 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  text-decoration: none;
  word-break: break-all;
  align-self: flex-start;
}
.view-post-link:hover { text-decoration: underline; }
/* Mobile view-post modal (<=720 px):
   Viewport-centered + dvh-sized. The desktop rule above uses a grid
   backdrop with a 116 px sidebar column so the modal lands over the
   posts-grid centerline while the 132 px left sidebar is visible.
   That geometry is meaningless once the sidebar has collapsed (60-px
   column or bottom-fixed nav), so on mobile we just flex-center the
   backdrop and let the modal size itself against the visible viewport.
   dvh accounts for the mobile URL bar showing/hiding — 100vh would
   over-allocate and clip the bottom of the link row. */
/* Below 1000 px the desktop grid backdrop (col 1 = 116 px to mirror
   .home-main's translateX(-148px)) no longer fits an 880 px modal in
   col 2 (vw − 116 < 880 below 996 px). Switch to flex-centering so the
   modal stays on-screen through the intermediate 721–1000 px range;
   nested (max-width:720px) tightens to phone-defaults below that. */
@media (max-width: 1000px) {
  .edit-post-backdrop.view-post-backdrop {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .edit-post-modal.view-post-modal { grid-column: auto; }
  .view-post-modal { max-height: 100dvh; }

  @media (max-width: 720px) {
    .view-post-modal { width: min(96vw, 480px); }
    .view-post-body {
      /* Tighter than the 16/16/20 the previous override had — that
         ate ~26 px of vertical chrome on phones that should belong to
         the image. 8/12/10 is closer to desktop's 8/16/12, just with
         narrower side gutters. */
      padding: 8px 12px 10px;
      gap: 4px;
    }
    .view-post-image-wrap {
      /* 1:1 image stays the hero on phone widths; the height cap =
         visible viewport minus mobile chrome:
         ~48 (header) + 8 + 10 (body padding) + 4 (gap to text)
         + ~32 (caption-line) + 2 (text gap) + ~22 (link row) ≈ 126 px.
         We carve 130 px off dvh so the 1:1 image lands inside the
         visible viewport, with a 4 px breathing floor. */
      width: min(100%, calc(100dvh - 130px));
    }
    .view-post-text { gap: 2px; }
    /* Editor (create + edit) carries more chrome below the image than
       the view-post card — caption textarea (42 px) + link input +
       action row (~60 px Cancel + Share, OR Delete-confirm + Save).
       Total non-image chrome on a phone: ~200 px (48 header + 18 body
       padding + 4 gap + 2 text gap + 42 cap textarea + 24 link input +
       60 actions row + 2 px breathing floor). Without this carve the
       image-wrap default (130 px) would force the image to overflow
       the modal max-height. */
    .edit-post-editor .view-post-image-wrap {
      width: min(100%, calc(100dvh - 200px));
    }
    /* Editor body scrolls if a long error message pushes the action row
       past the modal max-height. View-post keeps `overflow: hidden`
       (its iframe microsite needs to keep wheel events inside itself);
       the editor never embeds an iframe. */
    .edit-post-editor .view-post-body {
      overflow-y: auto;
    }
  }
}


/* ---------------- Users view (alphabetical directory) ----------------
   Painted into #users-view by renderUsersView. Each row opens its
   target's profile on the home-view shell with a Follow / Following
   pill replacing Edit profile when viewer != target. */
.auth-view--users {
  /* 576 px mirrors the home-main column family minus gutters so the
     column-edge geometry stays in lockstep with the rest of the chrome. */
  width: 576px;
  max-width: calc(100% - 48px);
  margin: 0 auto;
  padding: 0 0 24px;
}
/* The Users directory rides the same chrome as the home view — the left
   menu bar (Users / Create / profile circle) is present here too. The
   view reuses .auth-view--home + .home-layout; this is the main column
   that keeps the list at its 576px rhythm, centered in the space right
   of the sidebar. */
.users-main {
  padding: 16px 0 24px;
}
.users-main .users-list {
  width: 576px;
  max-width: calc(100% - 48px);
  margin: 0 auto;
}
.users-topbar { margin-bottom: 14px; }
.users-list { display: flex; flex-direction: column; gap: 12px; }
.users-list-empty {
  color: var(--ink-muted);
  font-size: 14px;
  text-align: center;
  padding: 24px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
/* Override display:flex above when the element is marked hidden —
   without this, the inline element attribute does nothing. Both the
   empty placeholder (#users-list-empty) and the skeleton rows are
   toggled off atomically on row arrival so neither leaks past the
   success path. */
.users-list-empty[hidden] { display: none; }
.users-list-empty .users-list-retry {
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink, #111);
  background: #fff;
  border: 1px solid #d8dde3;
  border-radius: var(--pill, 999px);
  padding: 8px 18px;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease;
}
.users-list-empty .users-list-retry:hover { background: #f7f8fa; border-color: #c2c8d0; }
.users-list-empty .users-list-retry:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}
.users-list-empty .users-list-hint {
  display: block;
  max-width: 360px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-muted);
}
.users-list-rows { display: flex; flex-direction: column; gap: 0; }  /* hairline dividers handle the rhythm */
/* Each .users-row is the click target for the directory: avatar +
   username + full-name, anchored to the same column as the home-view
   avatar edge so the cross-page rhythm is preserved. The third grid
   track (auto) only resolves when a .users-row-school child exists
   — empty-school rows stay on the 56px / 1fr pair identity. The
   1fr track uses minmax(0, 1fr) so a long school name can shrink
   the username column rather than overflowing the grid; the auto
   track stays at intrinsic min-content so 'University of Waterloo'
   etc. are never truncated unless row width truly forbids it. */
.users-row {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) minmax(0, auto);
  column-gap: 16px;
  align-items: center;
  padding: 10px 0 12px;
  border: 0;
  background: transparent;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 140ms ease;
  width: 100%;
  border-radius: 0;
  /* Rows are real /<username> hyperlinks — drop the default underline. */
  text-decoration: none;
}
.users-row:hover { background: #f7f8fa; }
.users-row:focus-visible {
  outline: 2px solid #000;
  outline-offset: -2px;
}
.users-row-avatar {
  width: 48px;
  height: 48px;
  border-radius: var(--pill);
  /* Same subtle 1px #d6dce6 perimeter as .profile-avatar-large +
     .sidebar-avatar-placeholder so the directory row circles match
     the menu-bar + profile circles exactly. */
  background: #f1f5f9;
  border: 1px solid #d6dce6;
  overflow: hidden;
  display: block;
}
.users-row-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Initial-letter fallback when profile_picture is null. The hue is
   a deterministic hash of the username so it stays stable across
   reloads (mirrors IG / Twitter convention). */

.users-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.users-row-username {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.users-row-fullname {
  font-size: 13px;
  color: var(--ink-muted);
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* School name on the right edge of the directory row, right-aligned
   like the peer detail it reads as. The grid's minmax(0, auto) third
   track lets it take its full text width when room exists (no 220px
   cap — long names like "University of California, Berkeley" show
   whole) and only ellipsizes when the row genuinely has no room. */
.users-row-school {
  font-size: 14px;
  font-weight: 400;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-left: 16px;
  justify-self: end;
  /* Sit on .users-row-text's second line (the display name), not its center. */
  align-self: center;
  transform: translateY(9px);
  min-width: 0;
}

/* Below 480 px the directory column rhythm resets — the home-main
   flex layout flips to a single track, the avatar shrinks to 40 px,
   and the username column gains all available width. Hide the school
   pill entirely so all three tracks collapse to two; users with schools
   still see it on tablet+ viewports. */
@media (max-width: 480px) {
  .users-row-school { display: none; }
}

/* Skeleton placeholder while dbListUsers is in flight. Three are
   baked into index.html so the column rhythm is right even before
   script.js hydrates. renderUsersView atomically replaces these with
   real rows on success — without ever setting the
   "Loading all users…" copy on the .users-list-empty node. */
.users-row--skeleton {
  display: grid;
  grid-template-columns: 56px 1fr;
  column-gap: 16px;
  align-items: center;
  padding: 10px 0 12px;
  border: 0;
  background: transparent;
  cursor: default;
  pointer-events: none;
  border-radius: 0;
}
.users-row--skeleton:hover { background: transparent; }
.users-row--skeleton .users-row-avatar--skeleton,
.users-row--skeleton .users-row-username--skeleton,
.users-row--skeleton .users-row-fullname--skeleton {
  display: block;
  background: linear-gradient(90deg, #f1f5f9 0%, #e9eef3 40%, #f1f5f9 80%);
  background-size: 200% 100%;
  animation: comet-skeleton-shimmer 1.4s ease-in-out infinite;
  border-radius: 8px;
}
.users-row--skeleton .users-row-avatar--skeleton {
  width: 48px;
  height: 48px;
  border-radius: var(--pill, 999px);
}
.users-row--skeleton .users-row-username--skeleton {
  height: 14px;
  width: 25%;
  max-width: 140px;
  margin: 0;
}
.users-row--skeleton .users-row-fullname--skeleton {
  height: 12px;
  width: 38%;
  max-width: 200px;
  margin-top: 2px;
}
@keyframes comet-skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Profile-action pill color variants for the Follow / Following
   states. Geometry must stay IDENTICAL to .profile-action so the
   Edit-profile / Follow / Following transitions are label + class
   flips, not DOM churn. */
.profile-action--follow,
.profile-action--following {
  display: block;
  width: 100%;
  padding: 16px 24px;
  border-radius: var(--pill);
  font-size: 14px;
  font-weight: 600;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  cursor: pointer;
  transition: background 160ms ease, color 160ms ease, border-color 160ms ease;
  border: 0;
  text-align: center;
}
.profile-action--follow {
  background: #000;           /* black until followed; never flags the un-tapped state with color */
  color: #fff;
}
.profile-action--follow:hover { background: #222; }
.profile-action--following {
  background: #000;
  color: #fff;
}
.profile-action--following:hover { background: #222; }
/* Inline checkmark rendered inside the "following" button by
   paintProfilePillFor. Stroke = currentColor so it inherits the pill's
   #fff text color without an explicit color override. */
.profile-action--following svg.profile-pill-check {
  display: inline-block;
  vertical-align: -2px;
  width: 12px;
  height: 12px;
  margin-left: 6px;
}

/* Comet-global toast host — pinned to body so it survives view
   transitions; mounts only when displayToast() runs. Sits above the
   view-post modal z-index so the error message stays visible while
   the user reads the modal. */
.comet-toast-host {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 2000;
  pointer-events: none;
  width: min(560px, calc(100vw - 32px));
}
.comet-toast {
  pointer-events: auto;
  background: #b91c1c;
  color: #fff;
  border-radius: 10px;
  padding: 12px 16px;
  font: 500 13.5px/1.45 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.30);
}

/* ------------------------------------------------------------------
   Signed-out sign-up popup — a fixed overlay reusing the signup-card
   look (same .auth-card shape / field styles). Rendered on top of
   whatever page a signed-out visitor is on when they try + create,
   follow, or bookmark. The card is a sibling of .auth-card so it can
   float above the home / users views without inheriting the app shell.
   ------------------------------------------------------------------ */
.auth-popup {
  position: fixed;
  inset: 0;
  z-index: 90;                       /* above post modals + app chrome */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(15, 23, 42, 0.45);
}
.auth-popup[hidden] { display: none; }

.auth-popup-card {
  position: relative;                /* anchor for the close button */
  width: 448px;
  max-width: 448px;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  padding: 48px 56px 56px;
  border: 1px solid var(--line-soft);
  border-radius: 18px;
  background: #fff;
  box-shadow:
    0 23px 29px -6px rgba(15, 23, 42, 0.10),
    0 9px 11px -8px rgba(15, 23, 42, 0.06);
}
/* The create-account and step-2 (Username + School) signup cards each
   size to their own content, with a shared min-height floor so the
   shorter step-2 card never looks cramped. The privacy-policy overlay
   card is untouched. */
#auth-popup .auth-popup-card,
#auth-popup-details .auth-popup-card {
  min-height: 515px;
}

.auth-popup-close {
  position: absolute;
  top: 16px;
  right: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.auth-popup-close[hidden] { display: none; }
.auth-popup-close:hover { background: rgba(15, 23, 42, 0.06); color: var(--ink); }
.auth-popup-close svg { width: 18px; height: 18px; }

.share-portfolio-card {
  min-height: 0;
  padding-bottom: 32px;
}
.share-portfolio-header {
  margin-bottom: 24px;
}
.share-portfolio-row {
  display: flex;
  align-items: stretch;
  gap: 10px;
}
.share-portfolio-url,
.share-portfolio-copy {
  height: 52px;
  border-radius: var(--pill);
  font: 600 16px/1.3 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.share-portfolio-url {
  min-width: 0;
  flex: 1;
  padding: 14px 19px;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  font-weight: 400;
  outline: none;
}
.share-portfolio-copy {
  flex: 0 0 auto;
  padding: 14px 20px;
  border: 1px solid #000;
  background: #000;
  color: #fff;
  cursor: pointer;
  transition: background 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}
.share-portfolio-copy:hover {
  background: #1e293b;
  box-shadow: 0 9px 18px rgba(15, 23, 42, 0.14);
  transform: translateY(-1.2px);
}
.share-portfolio-copy:active { transform: translateY(0); }
.share-portfolio-copy:focus-visible,
.share-portfolio-url:focus-visible {
  outline: 2px solid #000;
  outline-offset: 2px;
}
.share-portfolio-status {
  min-height: 20px;
  margin: 12px 0 0;
  color: #15803d;
  font-size: 14px;
  font-weight: 500;
  text-align: center;
}
@media (max-width: 520px) {
  .share-portfolio-card { padding-inline: 24px; }
  .share-portfolio-row { gap: 8px; }
  .share-portfolio-url,
  .share-portfolio-copy { font-size: 14px; }
  .share-portfolio-copy { padding-inline: 16px; }
}

/* Comet styles.css bundle — pinned build tag for cache-bust / debugging.
   The `comet-build-marker` class below is the actual selector other code
   references; the preamble lines are a comment-only tag that changes on
   every rebuild so Vercel CDN invalidation can be observed via a diff. */
.comet-build-marker { content: "comet-css-2026-08-06"; }
