/* ============================================================
   paused.studio — portfolio layout
   ============================================================ */

:root {
  --bg: #efefef;
  --card-bg: #ffffff;
  --text: #000000;
  --text-muted: rgba(0, 0, 0, 0.5);
  --placeholder: #d9d9d9;
  --field-bg: #efefef;
  --content-w: 800px;
  --radius-card: 12px;
  --radius-img: 8px;
  --gap: 10px;
  /* Submit button on the login form — black in light, white in dark. */
  --submit-bg: #000;
  --submit-fg: #fff;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000000;
    --card-bg: #141414;
    --text: #ffffff;
    --text-muted: rgba(255, 255, 255, 0.5);
    --placeholder: #2a2a2a;
    --field-bg: #1a1a1a;
    --submit-bg: #ffffff;
    --submit-fg: #000000;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

html {
  /* No horizontal scroll, no visible scrollbar. `overflow-x: clip`
     constrains horizontal overflow without making the element a scroll
     container — so vertical scrolling and position:sticky keep working. */
  overflow-x: clip;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge legacy */
}

html::-webkit-scrollbar {
  display: none; /* WebKit */
}

/* Scroll lock during page transitions. page-transitions.js toggles
   html.transitioning at startRun → finishRun. The incoming overlay
   body's inline overflow is set to hidden in JS (scrollTop is still
   programmatically assignable, but wheel/touch can't move it). */
html.transitioning,
html.transitioning body {
  overflow: hidden !important;
  overscroll-behavior: none;
  touch-action: none;
}

body.portfolio {
  background: var(--bg);
  color: var(--text);
  font-family:
    "Figtree",
    system-ui,
    -apple-system,
    sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

/* Global keyboard-focus ring. Uses currentColor so it adapts to both light
   and dark themes automatically. */
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: inherit;
}
.work-card:focus-visible,
.profile-menu__trigger:focus-visible {
  outline-offset: 4px;
}
/* Auth inputs: the input itself sits inside a pill, so put the focus
   indicator on the pill (subtle, follows its radius) and clear the ring
   on the input element so we don't get a double outline. */
.auth-field__input:focus,
.auth-field__input:focus-visible {
  outline: none;
}
.auth-field:focus-within {
  outline: 1.5px solid var(--text);
  outline-offset: 2px;
}

/* Single column rule — header, profile, main all sit on the same 800px
   centered column with identical 24px gutters. */
.portfolio-header__inner,
.profile-block,
.portfolio-main {
  width: 100%;
  max-width: var(--content-w);
  margin: 0 auto;
  padding-left: 24px;
  padding-right: 24px;
}

/* ---------- Header ---------- */

.portfolio-header {
  width: 100%;
  padding: 20px 0;
  position: sticky;
  top: 0;
  z-index: 50;
  overflow: visible;
  /* Smooth color flip when scrolling over dark imagery */
  transition: color 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.portfolio-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
  z-index: 1;
}

/* Progressive blur stack — eight layers stacked, each with progressively
   heavier blur and a vertical mask that places its contribution in a
   different band. The result is a smooth blur gradient: minimal at the top
   edge, peak near the bottom, fading to no-blur where the header meets the
   page so the transition is clean. Pattern: each blur doubles, masks
   overlap so contributions cross-fade. */
.progressive-blur {
  position: absolute;
  /* Covers the whole header strip — heavy frost at the top fades cleanly to
     transparent right where the profile name text starts below. */
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

.progressive-blur > div {
  position: absolute;
  inset: 0;
}

/* Heaviest blur in the top band, lightest in the bottom band. Each layer's
   mask spans a narrow vertical slice that overlaps its neighbors so the
   bands cross-fade smoothly. */
.progressive-blur > div:nth-child(1) {
  backdrop-filter: blur(64px);
  -webkit-backdrop-filter: blur(64px);
  mask: linear-gradient(to bottom, #000 0%, #000 12.5%, transparent 25%);
  -webkit-mask: linear-gradient(
    to bottom,
    #000 0%,
    #000 12.5%,
    transparent 25%
  );
}
.progressive-blur > div:nth-child(2) {
  backdrop-filter: blur(32px);
  -webkit-backdrop-filter: blur(32px);
  mask: linear-gradient(
    to bottom,
    transparent 0%,
    #000 12.5%,
    #000 25%,
    transparent 37.5%
  );
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 0%,
    #000 12.5%,
    #000 25%,
    transparent 37.5%
  );
}
.progressive-blur > div:nth-child(3) {
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  mask: linear-gradient(
    to bottom,
    transparent 12.5%,
    #000 25%,
    #000 37.5%,
    transparent 50%
  );
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 12.5%,
    #000 25%,
    #000 37.5%,
    transparent 50%
  );
}
.progressive-blur > div:nth-child(4) {
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  mask: linear-gradient(
    to bottom,
    transparent 25%,
    #000 37.5%,
    #000 50%,
    transparent 62.5%
  );
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 25%,
    #000 37.5%,
    #000 50%,
    transparent 62.5%
  );
}
.progressive-blur > div:nth-child(5) {
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  mask: linear-gradient(
    to bottom,
    transparent 37.5%,
    #000 50%,
    #000 62.5%,
    transparent 75%
  );
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 37.5%,
    #000 50%,
    #000 62.5%,
    transparent 75%
  );
}
.progressive-blur > div:nth-child(6) {
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  mask: linear-gradient(
    to bottom,
    transparent 50%,
    #000 62.5%,
    #000 75%,
    transparent 87.5%
  );
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 50%,
    #000 62.5%,
    #000 75%,
    transparent 87.5%
  );
}
.progressive-blur > div:nth-child(7) {
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
  mask: linear-gradient(
    to bottom,
    transparent 62.5%,
    #000 75%,
    #000 87.5%,
    transparent 100%
  );
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 62.5%,
    #000 75%,
    #000 87.5%,
    transparent 100%
  );
}
.progressive-blur > div:nth-child(8) {
  backdrop-filter: blur(0.5px);
  -webkit-backdrop-filter: blur(0.5px);
  mask: linear-gradient(to bottom, transparent 75%, #000 87.5%, #000 100%);
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 75%,
    #000 87.5%,
    #000 100%
  );
}

/* Bottom progressive-blur strip — same eight-layer stack as the header,
   flipped so the heaviest blur sits at the bottom edge and the fade
   travels upward. Fixed at the bottom of the viewport; opacity tracks
   "distance from end of document" so it backs off as you reach the foot
   of the page. */
.bottom-blur-strip {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 200px;
  pointer-events: none;
  z-index: 50;
}
.progressive-blur--bottom {
  position: absolute;
  inset: 0;
}
/* Three stacked layers with VERY wide soft masks. Lighter blurs reach
   further up the strip so the boundary between "no blur" and "blur" is a
   long, smooth ramp instead of a visible seam. */
.progressive-blur--bottom > div:nth-child(1) {
  backdrop-filter: blur(40px);
  -webkit-backdrop-filter: blur(40px);
  mask: linear-gradient(to top, #000 0%, transparent 50%);
  -webkit-mask: linear-gradient(to top, #000 0%, transparent 50%);
}
.progressive-blur--bottom > div:nth-child(2) {
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  mask: linear-gradient(to top, #000 0%, transparent 75%);
  -webkit-mask: linear-gradient(to top, #000 0%, transparent 75%);
}
.progressive-blur--bottom > div:nth-child(3) {
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  mask: linear-gradient(to top, #000 0%, transparent 100%);
  -webkit-mask: linear-gradient(to top, #000 0%, transparent 100%);
}
/* Layers 4–8 from the markup go unused for the bottom strip. */
.progressive-blur--bottom > div:nth-child(n + 4) {
  display: none;
}

/* Contrast invert — toggled by header-contrast.js when the imagery under
   the strip would clash with the default text color. In light mode that
   means a dark image is scrolling under and we need white text; in dark
   mode it means a light image is scrolling under and we need black text. */
body.header-invert .portfolio-header__twitter,
body.header-invert .portfolio-header__twitter svg,
body.header-invert .portfolio-header__twitter svg path {
  color: #fff;
  stroke: #fff;
}
@media (prefers-color-scheme: dark) {
  body.header-invert .portfolio-header__twitter,
  body.header-invert .portfolio-header__twitter svg,
  body.header-invert .portfolio-header__twitter svg path {
    color: #000;
    stroke: #000;
  }
}

.portfolio-header__avatar {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  /* No fixed size — the img provides the 40px circle, the optional
     name label expands the row width on post pages. */
}

.portfolio-header__avatar-img {
  display: block;
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  border-radius: 9999px;
  object-fit: cover;
}

.portfolio-header__avatar-img--empty {
  background: var(--placeholder);
}

.portfolio-header__twitter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 12px;
  border-radius: 9999px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.portfolio-header__twitter svg {
  display: block;
}

/* ---------- Profile block ---------- */

.profile-block {
  padding-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.profile-block__row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.profile-block__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.profile-block__date {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
}

.profile-block__bio {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

/* ---------- Main + grid ---------- */

.portfolio-main {
  padding-top: 10px;
  padding-bottom: 40px;
}

.work-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
}

/* Desktop grid order differs from DOM order. DOM order is the mobile
   vertical reading order (stamps → apple-store → app-icons → red-panda
   → moon-music → tokyo → watches → streetlights). On the 2-column
   desktop layout, we want the original pairings:
     Row 1: stamps      | apple-store
     Row 2: red-panda   | app-icons
     Row 3: moon-music  | tokyo
     Row 4: streetlights| watches
   CSS `order` rearranges grid items without touching markup. */
@media (min-width: 641px) {
  .work-card[data-id="stamps"] {
    order: 1;
  }
  .work-card[data-id="apple-store"] {
    order: 2;
  }
  .work-card[data-id="red-panda"] {
    order: 3;
  }
  .work-card[data-id="app-icons"] {
    order: 4;
  }
  .work-card[data-id="moon-music"] {
    order: 5;
  }
  .work-card[data-id="tokyo"] {
    order: 6;
  }
  .work-card[data-id="streetlights"] {
    order: 7;
  }
  .work-card[data-id="watches"] {
    order: 8;
  }
}

/* ---------- Card ---------- */

.work-card {
  display: block;
  background: var(--card-bg);
  border-radius: var(--radius-card);
  /* keep card height stable regardless of media presence */
  aspect-ratio: 16 / 11;
  overflow: hidden;
}

.work-card__media,
.work-card__placeholder {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--placeholder);
}

/* Inset hairline stroke layered on top of every media container — matches
   the figma `inner stroke 1px white @ 20%` treatment. `::after` inherits
   the parent's border-radius so the stroke follows the rounded clip
   (cards, post-blocks) or the full circle (avatars). `inset` shadow
   draws on the inner edge so it doesn't expand the layout. */
.work-card,
.post-block,
.profile-menu__avatar {
  position: relative;
}
.work-card::after,
.post-block::after,
.profile-menu__avatar::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
  pointer-events: none;
  z-index: 2;
}
/* The FLIP morph clone is a <div> wrapper containing the cloned <img>.
   The stroke is painted by a ::after pseudo on top of the image (an
   inset box-shadow on the wrapper itself sits behind child content and
   gets hidden by the img). Same approach as .work-card::after. */
.flip-clone__img,
.flip-clone img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}
.flip-clone::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
  pointer-events: none;
  z-index: 2;
}
@media (prefers-color-scheme: dark) {
  .work-card::after,
  .post-block::after,
  .profile-menu__avatar::after,
  .flip-clone::after {
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
  }
}
/* No backdrop blur on avatars. The inset stroke alone is the
   decoration; even a 0.5px blur softens the portrait and reads as a
   loss of sharpness. */

/* ---------- Profile menu (logged-in) ---------- */

.profile-menu {
  position: relative;
}

.profile-menu__trigger {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  height: 40px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Pill that fits whatever's inside — just the 40px circle on the home
     page; circle + name label on a post page. */
  border-radius: 9999px;
  color: var(--text);
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.profile-menu__name,
.portfolio-header__name {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  white-space: nowrap;
}

.profile-menu__avatar {
  display: block;
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  border-radius: 9999px;
  overflow: hidden;
}

.profile-menu__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.profile-menu__avatar--empty {
  background: var(--placeholder);
  display: block;
  width: 100%;
  height: 100%;
}

.profile-menu__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  background: var(--card-bg);
  border-radius: 10px;
  padding: 10px 40px 10px 10px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* hidden by default; data-open=true reveals it */
  opacity: 0;
  transform: translateY(-4px) scale(0.98);
  transform-origin: top left;
  pointer-events: none;
  transition:
    opacity 160ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 100;
  white-space: nowrap;
}

.profile-menu[data-open="true"] .profile-menu__panel {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

/* Signed-out visitors: the dropdown panel is never reachable. JS already
   short-circuits the trigger click to navigate home, but defense in
   depth — even if JS misfires, the panel can't open. */
body:not(.signed-in) .profile-menu__panel {
  display: none;
}

.profile-menu__item {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
}

.profile-menu__item svg {
  display: block;
  flex: 0 0 auto;
}

.profile-menu__item--danger {
  color: #ff383c;
}

.profile-menu__item--danger button {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: #ff383c;
  cursor: pointer;
}

/* View/edit mode toggling. Both sets of menu items are rendered in the
   DOM; CSS picks which ones are visible based on body.edit-mode so we
   never reload the page on toggle. */
.profile-menu__edit-only {
  display: none;
}
body.edit-mode .profile-menu__view-only {
  display: none;
}
body.edit-mode .profile-menu__edit-only {
  display: inline-flex;
}

/* ---------- Edit-mode grid background ----------
   Activated by `body.edit-mode`. 80px square cells, 10% #0066EE lines. */

body.edit-mode {
  background-image:
    linear-gradient(to right, rgba(0, 102, 238, 0.1) 1px, transparent 1px),
    linear-gradient(to top, rgba(0, 102, 238, 0.1) 1px, transparent 1px);
  background-size: 40px 40px;
  background-position: 0 0;
}

/* ---------- Edit-mode media handle ----------
   Glass dot + drag-pill under every media block. The dot is a remove
   button that expands to an X on hover; the bar is the SortableJS drag
   handle. */

.media-handle {
  display: none;
}

/* In edit mode, cards expose overflow so the handle can sit below them. */
body.edit-mode .work-card,
body.edit-mode .post-block {
  overflow: visible;
  position: relative;
}
/* Explicit radius on the inner media because `border-radius: inherit`
   doesn't cross the <picture> wrapper — children of <picture> would
   inherit from <picture> (which has no radius) and the corners would
   stay sharp. */
body.edit-mode .work-card picture,
body.edit-mode .post-block picture {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-card);
  overflow: hidden;
}
body.edit-mode .work-card img,
body.edit-mode .post-block img,
body.edit-mode .work-card .work-card__placeholder,
body.edit-mode .post-block .post-block__placeholder {
  border-radius: var(--radius-card);
}

/* Kill the entry cascade entirely in edit mode. The page-reload that
   takes us into edit mode shouldn't re-play the slow blur-in
   choreography — the editor wants immediate, settled state to work in. */
body.edit-mode .portfolio-header__avatar,
body.edit-mode .portfolio-header__twitter,
body.edit-mode .profile-block__name,
body.edit-mode .profile-block__date,
body.edit-mode .profile-block__bio,
body.edit-mode .work-card,
body.edit-mode .post-header__title-row,
body.edit-mode .post-header__date,
body.edit-mode .post-body > * {
  animation: none !important;
  opacity: 1 !important;
  filter: none !important;
  transform: none !important;
}

body.edit-mode .media-handle {
  /* Fixed-size box. Children are absolutely positioned at known anchor
     points so the bar stays put when the dot expands. */
  display: block;
  position: absolute;
  left: 50%;
  bottom: -22px;
  width: 87px;
  height: 18px;
  transform: translateX(-50%);
  pointer-events: auto;
}

/* Dot — anchored at x=9px (its center), grows from its center via the
   transform so the bar never shifts. Hover → expands to 18×18, X fades
   in slightly delayed (secondary action). */
body.edit-mode .media-handle__dot {
  appearance: none;
  border: 0;
  margin: 0;
  padding: 0;
  position: absolute;
  left: 9px;
  top: 50%;
  width: 7px;
  height: 7px;
  border-radius: 9999px;
  background: rgba(0, 0, 0, 0.25);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transform: translate(-50%, -50%);
  transition:
    width 280ms cubic-bezier(0.16, 1, 0.3, 1),
    height 280ms cubic-bezier(0.16, 1, 0.3, 1);
}
body.edit-mode .media-handle__dot svg {
  display: block;
  width: 18px;
  height: 18px;
  opacity: 0;
  transition: opacity 160ms cubic-bezier(0.16, 1, 0.3, 1) 80ms;
}
body.edit-mode .media-handle__dot:hover,
body.edit-mode .media-handle__dot:focus-visible {
  width: 18px;
  height: 18px;
}
body.edit-mode .media-handle__dot:hover svg,
body.edit-mode .media-handle__dot:focus-visible svg {
  opacity: 1;
}

/* Bar — original 68×5 size. Fixed at x=19px, immune to dot expansion. */
body.edit-mode .media-handle__bar {
  position: absolute;
  left: 19px;
  top: 50%;
  width: 68px;
  height: 5px;
  border-radius: 2.5px;
  background: rgba(0, 0, 0, 0.25);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  cursor: grab;
  transform: translateY(-50%);
  transition: background 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
body.edit-mode .media-handle__bar:hover {
  background: rgba(0, 0, 0, 0.4);
}
body.edit-mode .media-handle__bar:active {
  cursor: grabbing;
}

/* Dark-mode handle colors — the default rgba(0,0,0,0.25) disappears
   against a dark page bg. Use a translucent white instead. Hover/active
   bump the alpha higher. */
@media (prefers-color-scheme: dark) {
  body.edit-mode .media-handle__dot {
    background: rgba(255, 255, 255, 0.35);
  }
  body.edit-mode .media-handle__bar {
    background: rgba(255, 255, 255, 0.35);
  }
  body.edit-mode .media-handle__bar:hover {
    background: rgba(255, 255, 255, 0.55);
  }
  body.edit-mode .media-handle--inert .media-handle__bar:hover {
    background: rgba(255, 255, 255, 0.35);
  }
}

/* Sortable.js drag states. Tuned for "weight" — long, soft ease on pickup
   so the card visibly lifts; bigger shadow + scale so it reads as held
   above the page; the leftover ghost fades quietly. The 560ms reflow
   animation (set in SORTABLE_OPTS) handles the rest. */
.work-card.sortable-chosen,
.post-block.sortable-chosen {
  transition:
    transform 380ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 380ms cubic-bezier(0.22, 1, 0.36, 1);
  transform: scale(1.045);
  box-shadow:
    0 2px 8px rgba(0, 0, 0, 0.06),
    0 28px 60px rgba(0, 0, 0, 0.22);
  z-index: 5;
}
.work-card.sortable-drag,
.post-block.sortable-drag {
  cursor: grabbing;
  opacity: 0.96;
}
.work-card.sortable-ghost,
.post-block.sortable-ghost {
  opacity: 0.18;
  transform: scale(0.96);
  transition:
    opacity 360ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 360ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Inline link/code styling that applies in both view and edit modes,
   so what you author in the toolbar matches what visitors see. Links
   keep the surrounding text color — only an underline marks them. */
.profile-block__bio a,
.post-text a,
.post-header__title a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.profile-block__bio code,
.post-text code,
.post-header__title code {
  font-family: "SF Mono", ui-monospace, Menlo, monospace;
  font-size: 0.92em;
  background: rgba(0, 0, 0, 0.07);
  padding: 1px 5px;
  border-radius: 4px;
}
@media (prefers-color-scheme: dark) {
  .profile-block__bio code,
  .post-text code,
  .post-header__title code {
    background: rgba(255, 255, 255, 0.1);
  }
}

/* Floating selection toolbar. Appears below the user's selection when
   inside any contenteditable in edit mode (see public/js/text-toolbar.js).
   `transform: translateX(-50%)` centers the toolbar on the selection
   midpoint that JS writes to `left`. */
.text-toolbar {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1000;
  display: none;
  align-items: center;
  gap: 2px;
  background: #1a1a1a;
  color: #fff;
  border-radius: 10px;
  padding: 4px;
  transform: translateX(-50%);
  box-shadow:
    0 2px 8px rgba(0, 0, 0, 0.15),
    0 16px 36px rgba(0, 0, 0, 0.22);
  white-space: nowrap;
  user-select: none;
  -webkit-user-select: none;
}
.text-toolbar--visible {
  display: inline-flex;
}
.text-toolbar button {
  appearance: none;
  border: 0;
  background: transparent;
  color: #fff;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 140ms cubic-bezier(0.22, 1, 0.36, 1);
}
.text-toolbar button:hover {
  background: rgba(255, 255, 255, 0.12);
}
.text-toolbar button:active {
  background: rgba(255, 255, 255, 0.18);
}

/* ---------- Edit-mode inline text editing ---------- */
body.edit-mode [contenteditable="true"] {
  outline: none;
  cursor: text;
  border-radius: 4px;
  transition:
    box-shadow 180ms cubic-bezier(0.16, 1, 0.3, 1),
    background 180ms cubic-bezier(0.16, 1, 0.3, 1);
}
body.edit-mode [contenteditable="true"]:hover {
  box-shadow: 0 0 0 4px rgba(0, 102, 238, 0.08);
}
body.edit-mode [contenteditable="true"]:focus {
  box-shadow: 0 0 0 4px rgba(0, 102, 238, 0.15);
  background: rgba(0, 102, 238, 0.04);
}

/* Extra breathing room below cards so the handle sits clearly outside. */
body.edit-mode .work-grid {
  row-gap: 32px;
}
body.edit-mode .post-body {
  gap: 32px;
}
body.edit-mode .post-block-row {
  row-gap: 32px;
}

/* Hero handle + "+ image" / "+ text" reveal buttons.
   The hero shows the same visual handle as other blocks but it's inert —
   not draggable, not a remove button. Hovering the handle (or the area
   immediately to its right) reveals the two glass buttons next to it. */
.hero-actions,
.hero-add {
  display: none;
}
body.edit-mode .hero-actions {
  display: block;
  position: absolute;
  left: 50%;
  bottom: -22px;
  transform: translateX(-50%);
  width: 87px;
  height: 18px;
}
/* Invisible bridge to the right of the handle so the cursor can travel
   from the dot/bar onto the reveal buttons without dropping :hover. Sized
   to JUST the gap — wider would overlay the buttons (::after renders above
   children in source order) and eat their clicks. */
body.edit-mode .hero-actions::after {
  content: "";
  position: absolute;
  left: 100%;
  top: -8px;
  width: 12px;
  height: 34px;
}
body.edit-mode .hero-actions .media-handle {
  /* Override absolute positioning from the default rule — the wrapper
     now owns the position; the handle just fills it. */
  position: relative;
  left: auto;
  bottom: auto;
  transform: none;
  width: 87px;
  height: 18px;
  pointer-events: auto;
}
/* Inert hero handle: dot doesn't expand on hover, bar isn't a grab cursor. */
body.edit-mode .media-handle--inert .media-handle__dot {
  cursor: default;
}
body.edit-mode .media-handle--inert .media-handle__dot:hover,
body.edit-mode .media-handle--inert .media-handle__dot:focus-visible {
  width: 7px;
  height: 7px;
}
body.edit-mode .media-handle--inert .media-handle__bar {
  cursor: default;
}
body.edit-mode .media-handle--inert .media-handle__bar:hover {
  background: rgba(0, 0, 0, 0.25);
}
body.edit-mode .hero-add {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: 12px;
  height: 18px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
body.edit-mode .hero-actions:hover .hero-add,
body.edit-mode .hero-actions:focus-within .hero-add {
  opacity: 1;
  pointer-events: auto;
}
.hero-add__btn {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  width: 18px;
  height: 18px;
  transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
.hero-add__btn:hover {
  transform: scale(1.08);
}
.hero-add__btn svg {
  display: block;
}

/* Text blocks get their own positioning context in edit mode so the
   media-handle inside them anchors correctly. */
body.edit-mode .post-text-block {
  position: relative;
  overflow: visible;
}
body.edit-mode .post-text-block .post-text {
  margin: 0;
}

/* ---------- Login ---------- */

.login-main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Push the card to roughly the vertical middle of the viewport below the
     header (header is ~80px tall, footer area gives breathing room). */
  min-height: calc(100dvh - 80px);
  padding: 40px 24px 80px;
}

.auth-card {
  background: var(--card-bg);
  border-radius: 24px;
  padding: 12px;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.auth-card__title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
  padding: 4px 0;
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 240px;
}

.auth-field {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px;
  background: var(--field-bg);
  border-radius: 12px;
}

.auth-field__icon {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--text-muted);
}

.auth-field__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  outline: none;
  padding: 0;
}

.auth-field__input::placeholder {
  color: var(--text-muted);
}

.auth-submit {
  appearance: none;
  border: 0;
  background: var(--submit-bg);
  color: var(--submit-fg);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 12px;
  border-radius: 12px;
  cursor: pointer;
  transition: opacity 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

.auth-submit:hover {
  opacity: 0.85;
}

.auth-submit:active {
  opacity: 0.7;
}

.auth-submit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.auth-error {
  font-size: 12px;
  font-weight: 600;
  color: #c0392b;
  text-align: center;
}

/* ---------- Post page ---------- */

.post-main {
  width: 100%;
  max-width: var(--content-w);
  margin: 0 auto;
  padding: 0 24px 60px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.post-header {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.post-header__title-row {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
}

.post-header__title-row svg {
  display: block;
}

.post-header__title {
  line-height: 1;
}

.post-header__date {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
}

.post-body {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.post-block {
  background: var(--card-bg);
  border-radius: var(--radius-card);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.post-block__media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.post-block__placeholder {
  width: 100%;
  height: 100%;
  background: var(--placeholder);
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* "Upload media" affordance shown inside empty placeholders (work-card +
   post-block). Two grey glyphs above a small label, vertically stacked. */
.upload-media {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}

.upload-media__icons {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.upload-media__icons svg {
  display: block;
}

.upload-media__label {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: rgba(0, 0, 0, 0.45);
}

.work-card__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Edit-mode upload affordance. Empty placeholders accept clicks (file
   picker) and drag-and-drop of image/video files. */
body.edit-mode .post-block__placeholder,
body.edit-mode .work-card__placeholder {
  cursor: pointer;
  transition:
    background 200ms cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
body.edit-mode .post-block__placeholder:hover,
body.edit-mode .work-card__placeholder:hover {
  background: rgba(0, 0, 0, 0.08);
}
body.edit-mode .upload-dragover {
  background: rgba(0, 102, 238, 0.1) !important;
  box-shadow: inset 0 0 0 2px rgba(0, 102, 238, 0.55);
}

/* Side-drop indicator. While a .post-block is being dragged toward the
   left or right 25% of another block, a thick blue rail shows on that
   edge of the target. On drop, the two blocks wrap into a .post-block-row. */
body.edit-mode .post-block.row-drop-left,
body.edit-mode .post-block.row-drop-right {
  position: relative;
}
body.edit-mode .post-block.row-drop-left::before,
body.edit-mode .post-block.row-drop-right::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 4px;
  border-radius: 2px;
  background: rgba(0, 102, 238, 0.85);
  z-index: 6;
  pointer-events: none;
}
body.edit-mode .post-block.row-drop-left::before {
  left: -8px;
}
body.edit-mode .post-block.row-drop-right::before {
  right: -8px;
}

.post-block-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
}

/* Inside a 2-up row, each block keeps a 16:9 footprint so a side-by-side
   pair reads as a balanced pair regardless of the heights the blocks had
   when they were full-width. Override the inline `height` set by
   post-block / edit-drag. */
.post-block-row .post-block {
  height: auto !important;
  aspect-ratio: 16 / 9;
}

.post-text {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--text);
}

@media (max-width: 640px) {
  .post-main {
    padding-left: 16px;
    padding-right: 16px;
  }
  .post-block-row {
    grid-template-columns: 1fr;
  }
  /* On phones cards + hero are both 16:9, matching shapes so the FLIP
     morph is a uniform translate+scale instead of a stretch. The hero's
     inline `height` from post-block is overridden so aspect-ratio drives
     the size. */
  .work-card {
    aspect-ratio: 16 / 9;
  }
  .post-block--hero {
    height: auto !important;
    aspect-ratio: 16 / 9;
  }
}

/* ---------- Entry cascade ----------
   Top-down stagger using transform + opacity. Curve below is a soft settle
   (ease-out with gentle decel) so each element has slow-in/slow-out and a
   bit of follow-through without overshooting. Disabled under
   prefers-reduced-motion. */

@keyframes fade-up {
  from {
    opacity: 0;
    filter: blur(22px);
  }
  to {
    opacity: 1;
    filter: blur(0);
  }
}

:root {
  /* Same near-instant start as before, but the second control point is
     pushed far right so the deceleration tail is much longer — elements
     zip into motion fast and settle into place slowly. */
  --enter-curve: cubic-bezier(0.05, 1, 0.5, 1);
  --enter-dur: 2100ms;
  --enter-step: 160ms;
}

/* Hide-then-reveal. Each element overrides --d for its delay. */
.portfolio-header__avatar,
.portfolio-header__twitter,
.profile-block__name,
.profile-block__date,
.profile-block__bio,
.work-card,
.post-header__title-row,
.post-header__date,
.post-body > * {
  opacity: 0;
  will-change: transform, opacity;
  animation: fade-up var(--enter-dur) var(--enter-curve) both;
  animation-delay: var(--d, 0ms);
}

/* Arrived via a FLIP transition. Persistent chrome (avatar, twitter) stays
   put. The morphing hero stays at opacity 1 (it's animated by JS). Other
   text + blocks "melt in" with a subtle translate + fade timed so they
   settle right as the hero morph finishes — feels like a single piece
   of motion rather than a page swap. */

@keyframes melt-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

body.vt-incoming .portfolio-header__avatar,
body.vt-incoming .portfolio-header__twitter,
body.vt-incoming .work-card,
/* The morphing hero — animated by JS via FLIP, never via the cascade. */
body.vt-incoming .post-body > :first-child {
  opacity: 1;
  animation: none;
  transform: none;
}

/* Surround fade is JS-driven (page-transitions.js primeFade/playFade) so
   it starts in the exact same paint as the morph. To let inline opacity
   win, kill any running CSS animation from the base entry cascade. */
body.vt-incoming .post-header__title-row,
body.vt-incoming .post-header__date,
body.vt-incoming .post-body > *:not(:first-child),
body.vt-incoming .profile-block__name,
body.vt-incoming .profile-block__date,
body.vt-incoming .profile-block__bio,
body.vt-incoming .work-card {
  animation: none !important;
}

/* Staging — homepage cascade. Delays bumped to spread the motion out
   over a longer timeline for a more dramatic first-load. */
.portfolio-header__avatar {
  --d: 0ms;
}
.portfolio-header__twitter {
  --d: 120ms;
}
.profile-block__name {
  --d: 280ms;
}
.profile-block__date {
  --d: 380ms;
}
.profile-block__bio {
  --d: 500ms;
}
.work-card {
  --d: calc(700ms + var(--i, 0) * var(--enter-step));
}

/* Staging — post page cascade */
.post-header__title-row {
  --d: 200ms;
}
.post-header__date {
  --d: 280ms;
}
.post-body > *:nth-child(1) {
  --d: 380ms;
}
.post-body > *:nth-child(2) {
  --d: 460ms;
}
.post-body > *:nth-child(3) {
  --d: 540ms;
}
.post-body > *:nth-child(4) {
  --d: 620ms;
}
.post-body > *:nth-child(5) {
  --d: 700ms;
}
.post-body > *:nth-child(n + 6) {
  --d: 780ms;
}

/* Cleanup — drop the will-change once animation settles */
.work-card,
.portfolio-header__avatar,
.portfolio-header__twitter,
.profile-block__name,
.profile-block__date,
.profile-block__bio {
  animation-fill-mode: both;
}

@media (prefers-reduced-motion: reduce) {
  .portfolio-header__avatar,
  .portfolio-header__twitter,
  .profile-block__name,
  .profile-block__date,
  .profile-block__bio,
  .work-card,
  .post-header__title-row,
  .post-header__date,
  .post-body > * {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}

/* ---------- Mobile ---------- */

@media (max-width: 640px) {
  .work-grid {
    grid-template-columns: 1fr;
  }
  .work-card {
    aspect-ratio: 16 / 9;
  }
  .portfolio-header__inner,
  .profile-block,
  .portfolio-main {
    padding-left: 16px;
    padding-right: 16px;
  }
}
