/* ============================================================
   Design tokens
   Change a value here once and it propagates everywhere.
   ============================================================ */
:root {
  /* Colors */
  --color-bg:            #FAFAF7;
  --color-surface:       #FFFFFF;
  --color-text:          #1a1a1a;
  --color-text-muted:    #666;
  --color-text-dim:      #999;
  --color-border:        #EAEAEA;
  --color-border-strong: #D4D4D4;
  --color-border-hover:  #B8B8B8;
  --color-accent:        #C66A2F;   /* Orange — prompt arrow, spinner */
  --color-success:       #1A7F37;   /* Green — done state */
  --color-success-soft:  #2D8F3F;   /* Green — checkmarks */
  --color-error:         #C0392B;   /* Red — failed-tool-call & resume-restricted badge */

  /* Typography */
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;

  /* Sizing */
  --content-width:     560px;
  --max-page-width:    720px;
  --radius-sm:         3px;
  --radius-md:         10px;
  --radius-lg:         12px;

  /* Shadows — warm brown tint (matches --color-bg's cream) instead of pure
     black. The pill row stacks ~6 shadows (musing card + 5 pills) into one
     vertical band; black shadows read as a cool gray cast against the
     warm page background, warm shadows blend invisibly. */
  --shadow-card:       0 4px 14px rgba(60, 50, 30, 0.06);
  --shadow-card-hover: 0 6px 20px rgba(60, 50, 30, 0.08);
  --shadow-avatar:     0 12px 40px rgba(60, 50, 30, 0.10),
                       inset 0 -2px 6px rgba(0, 0, 0, 0.04);

  /* Motion */
  --ease-out:   cubic-bezier(0.2, 0.8, 0.2, 1);
  --t-fast:     0.15s;
  --t-base:     0.2s;
  --t-slow:     0.4s;
}

/* ============================================================
   Reset / base
   ============================================================ */
* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  background: var(--color-bg);
  font-family: var(--font-sans);
  color: var(--color-text);
  -webkit-font-smoothing: antialiased;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 60px 20px 32px;
  animation: body-fade 0.5s ease;
}

main {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  max-width: var(--max-page-width);
  width: 100%;
}

/* ============================================================
   Prompt line ("> Who is skowser?")
   ============================================================ */
.prompt-line {
  width: var(--content-width);
  max-width: 100%;
  padding: 0 22px;
  font-family: var(--font-mono);
  font-size: 32px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.5px;
  min-height: 1.25em;
  text-align: left;
}

.prompt-arrow {
  color: var(--color-accent);
  margin-right: 12px;
  font-weight: 500;
}

.prompt-cursor {
  display: inline-block;
  width: 14px;
  height: 0.85em;
  vertical-align: -2px;
  background: var(--color-text);
  margin-left: 4px;
  animation: cursor-blink 1s steps(2) infinite;
}

.prompt-line.done .prompt-cursor { display: none; }

/* ============================================================
   Musing card (the "thinking → fact" interaction)
   ============================================================ */
.musing {
  position: relative;                 /* anchor for .musing-refresh */
  width: var(--content-width);
  max-width: 100%;
  /* Desktop: 1-line slim card — every fact fits at 560px content width,
     so no need to reserve extra height. The narrow mobile case (where
     facts wrap to 2 lines and would otherwise jitter) overrides this
     with a 2-line floor in the @media block below. The label is clamped
     to 2 lines (.musing-label) so worst case is still bounded. */
  min-height: 64px;
  padding: 21px 22px;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 14px;
  box-shadow: var(--shadow-card);
  transform: translateY(-1px);
  opacity: 0;
  pointer-events: none;
  transition: border-color var(--t-base) ease,
              box-shadow  var(--t-base) ease,
              transform   var(--t-fast) ease,
              opacity     var(--t-slow) ease;
}

.musing.visible {
  opacity: 1;
  pointer-events: auto;
}

.musing:hover {
  border-color: var(--color-border-hover);
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-2px);
}

.musing:active { transform: scale(0.997) translateY(-1px); }

.musing-icon {
  position: relative;                 /* anchor for stacked glyph + refresh */
  flex-shrink: 0;
  width: 18px;
  height: 19.6px;     /* matches text first-line: 14px * 1.4 line-height */
  font-size: 16px;
  line-height: 1;
  color: var(--color-accent);
  transition: color 0.3s ease;
}

/* Spinner glyph (✻ etc.) — owns the slot during thinking, fades out
   once the fact lands so the refresh icon can occupy the bullet
   position cleanly. */
.musing-glyph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.3s ease;
}

.musing:not(.thinking) .musing-glyph { opacity: 0; }

.musing-label {
  flex: 1;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;       /* bounds card growth to 2 lines worst case */
  -webkit-box-orient: vertical;
  transition: color 0.25s ease;
}

.musing.thinking .musing-label { color: var(--color-text-muted); }

.musing-timer {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--color-text-dim);
  font-variant-numeric: tabular-nums;
  min-width: 32px;
  text-align: right;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.musing.thinking .musing-timer { opacity: 1; }

/* Refresh affordance — sits in the icon slot as the "bullet" for the
   fact. Hidden during thinking (the spinner glyph owns the slot then);
   fades in once the fact lands as a visual cue that the whole card is
   clickable for another fact. Card's existing click handler covers it. */
.musing-refresh {
  position: absolute;
  inset: 0;
  margin: auto;                       /* center within .musing-icon */
  width: 13px;
  height: 13px;
  color: var(--color-text-dim);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease,
              color var(--t-base) ease,
              transform 0.4s var(--ease-out);
}

.musing:not(.thinking) .musing-refresh { opacity: 1; }
.musing:not(.thinking):hover .musing-refresh {
  color: var(--color-text);
  transform: rotate(90deg);
}

/* Inline logo icons used inside fact text */
.fact-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin: 0 1px;
  vertical-align: -3px;
  border-radius: var(--radius-sm);
  object-fit: contain;
}

/* ============================================================
   Avatar
   ============================================================ */
/* Handwritten "psst… / click me…" nudge tucked inside the avatar circle,
   beside the bitmoji head. Lives inside .avatar-circle so it scales with
   the circle on small viewports — no responsive gymnastics needed. */
.avatar-whisper {
  position: absolute;
  right: 8%;
  top: 30%;
  transform: translateY(-50%) rotate(-5deg);
  font-family: 'Patrick Hand', 'Comic Sans MS', cursive;
  font-size: 18px;
  font-weight: 400;
  letter-spacing: 0.3px;
  color: var(--color-text-muted);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  z-index: 2;                                 /* above the avatar layers */
  transition: opacity 0.7s var(--ease-out);
}

.avatar-whisper.visible { opacity: 0.85; }

.avatar-circle {
  /* Vertical shift applied to BOTH avatar layers so the desk's bottom
     edge lines up with the circle's bottom (the source PNG has ~8%
     transparent padding below the figure). */
  --avatar-shift-y: 8%;
  /* Eye-tracking knobs — how far the eyes shift to "look at the laptop"
     in the non-thinking state. */
  --eye-shift-y: 0.65%;
  --eye-shift-x: 0.4%;
  /* Cursor-tracked delta added on top of the laptop-look pose. JS
     writes these on mousemove (idle, non-thinking state only); they
     default to 0% so the eyes rest on the laptop when the cursor is
     absent or off-screen. Keep the radius small — see EyeTracking. */
  --eye-track-x: 0%;
  --eye-track-y: 0%;

  position: relative;
  width: 320px;
  height: 320px;
  border-radius: 50%;
  overflow: hidden;
  background: linear-gradient(180deg, #F0EDE5 0%, #E8E3D7 100%);
  box-shadow: var(--shadow-avatar);
  cursor: pointer;
  transition: transform var(--t-base) ease, box-shadow var(--t-base) ease;
}

.avatar-circle:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 44px rgba(60, 50, 30, 0.12),
              inset 0 -2px 6px rgba(0, 0, 0, 0.04);
}

.avatar-circle:active { transform: scale(0.998); }
.avatar-circle:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 4px;
}

.avatar-circle img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  transform: translate(-50%, calc(-50% + var(--avatar-shift-y)));
  object-fit: contain;
  pointer-events: none;
}

/* Eye tracking: pupils (with catchlight highlights baked into the PNG)
   ride on top of the base layer and translate down to "look at the
   laptop" when the musing card is NOT in its thinking state. The
   exported pupil position = looking up (thinking pose); the default
   transform is inherited from `.avatar-circle img`. */
.avatar-eyes {
  transition: transform 0.55s var(--ease-out);
  will-change: transform;
}

.musing:not(.thinking) ~ .avatar-circle .avatar-eyes {
  /* Snappier follow when cursor-tracking is driving the transform —
     0.55s would lag visibly behind a moving cursor. The thinking ↔
     not-thinking transition still reads as smooth at 0.2s. */
  transition: transform 0.2s var(--ease-out);
  transform: translate(-50%, calc(-50% + var(--avatar-shift-y)))
             translate(calc(var(--eye-shift-x) + var(--eye-track-x)),
                       calc(var(--eye-shift-y) + var(--eye-track-y)));
}

/* Eyebrow tracking: each brow PNG is full-canvas with the brow in its
   natural resting position. When the eyes look down at the laptop
   (not-thinking state) the inner ends of both brows tilt downward into
   a concentration V. Each brow pivots around its OUTER end so only the
   inner end drops. transform-origin x≈ outer-edge canvas coord, y≈
   centroid + the object-fit:contain top padding (~7.35/320 = 2.3%). */
.avatar-brow {
  transition: transform 0.55s var(--ease-out);
  will-change: transform;
}

.avatar-brow-left  { transform-origin: 47% 36%; --brow-rotate:  8deg; }
.avatar-brow-right { transform-origin: 64% 37%; --brow-rotate: -8deg; }

.musing:not(.thinking) ~ .avatar-circle .avatar-brow {
  transform: translate(-50%, calc(-50% + var(--avatar-shift-y)))
             rotate(var(--brow-rotate));
}

/* ============================================================
   Action pills (Launches, Resume, LinkedIn, Blogs)
   Horizontal row, no wrap. Centered when all pills fit; on narrower
   viewports the row becomes horizontally scrollable (scrollbar hidden).
   Fades in once the first fact resolves — see script.js.
   ============================================================ */
.buttons {
  width: 100%;
  max-width: var(--content-width);
  /* Pull the pill row closer to the musing card — they read as a single
     "chat input + tool palette" group, so a tighter gap than main's
     default 32px works better here. */
  margin-top: -14px;
  padding: 0 0 4px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;                 /* graceful fallback on ultra-narrow viewports */
  justify-content: space-between;  /* spread pills to span card width */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

.buttons.visible {
  opacity: 1;
  pointer-events: auto;
}

/* While the first fact is generating, the pill outlines + icons are
   visible but the labels are hidden, and a light-to-dim sweep travels
   across each pill's background (left-to-right) like a skeleton-loader
   shine. The .shimmering class is removed the moment the first fact
   lands; labels fade back in + pills become clickable. Each pill's
   sweep is staggered by 120ms so the row reads as a wave. */
/* Lock + sweep applies to NON-active pills only — when a pill is the
   one being revealed (.active), it stays solid + clickable (so the
   user can close it mid-animation). During boot's intro shimmer no
   pill is active, so all five shimmer. */
.buttons.shimmering .btn:not(.active) {
  pointer-events: none;
  cursor: default;
  background: linear-gradient(
    90deg,
    var(--color-surface) 0%,
    var(--color-surface) 35%,
    #e8e8e8 50%,
    var(--color-surface) 65%,
    var(--color-surface) 100%
  );
  background-size: 200% 100%;
  animation: pill-bg-shine 1.8s linear infinite;
}
.buttons.shimmering .btn:not(.active) .btn-label {
  opacity: 0;             /* hide text while loading */
}
.buttons.shimmering .btn:not(.active) svg {
  opacity: 0.55;          /* icon dimmed but visible */
}
.buttons.shimmering .btn:not(.active):nth-child(1) { animation-delay: 0s; }
.buttons.shimmering .btn:not(.active):nth-child(2) { animation-delay: 0.12s; }
.buttons.shimmering .btn:not(.active):nth-child(3) { animation-delay: 0.24s; }
.buttons.shimmering .btn:not(.active):nth-child(4) { animation-delay: 0.36s; }

.btn-label { transition: opacity 0.25s ease; }
.btn svg   { transition: opacity 0.3s ease; }

@keyframes pill-bg-shine {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;         /* center icon + label inside the pill */
  gap: 7px;
  padding: 9px 18px;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);   /* match musing card stroke */
  border-radius: 999px;                            /* pill */
  box-shadow: var(--shadow-card);                  /* same gentle lift as musing */
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;             /* never wrap pill label */
  flex: 1 1 0;                     /* all pills share the row evenly */
  transition: all var(--t-base) ease;
}

.btn:hover {
  /* Border + shadow already match base — only the lift is new on hover. */
  transform: translateY(-1px);
}

.btn:active        { transform: scale(0.98); }
.btn:focus-visible { outline: 2px solid var(--color-text); outline-offset: 2px; }
.btn svg           { width: 15px; height: 15px; color: #555; }

.btn.active {
  background: var(--color-text);
  border-color: var(--color-text);
  color: #fff;
}
.btn.active:hover { border-color: var(--color-text); }
.btn.active svg   { color: #fff; }

/* ============================================================
   Reveal sections (Launches, Resume, Blogs)
   All three buttons reveal a section below the button row using
   the same animation. Shared base, three IDs for content.
   ============================================================ */
.reveal-section {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  max-height: 0;
  margin-top: 0;
  overflow: hidden;
  transition: max-height 0.5s var(--ease-out),
              margin-top var(--t-slow) ease;
}

.reveal-section.open {
  max-height: 2000px;     /* large enough to fit all content */
  margin-top: 24px;
}

.tool-header {
  width: var(--content-width);
  max-width: 100%;
  margin-top: -16px;       /* tighten to the prompt line above */
  padding: 0 22px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-text-muted);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.tool-header.visible { opacity: 1; transform: translateY(0); }
.tool-header .check    { color: var(--color-success-soft); font-size: 14px; }
.tool-header .meta-dim { color: var(--color-text-dim); }

/* Error variant — used by Resume + Blogs tool calls */
.tool-header--error .check { color: var(--color-error); }
.tool-header--error .error-text {
  color: var(--color-error);
  font-family: var(--font-mono);
}

.projects-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.project-card {
  width: var(--content-width);
  max-width: 100%;
  padding: 14px 20px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  gap: 14px;
  cursor: pointer;
  opacity: 0;
  transform: translateY(8px);
  transition: border-color var(--t-base) ease,
              box-shadow  var(--t-base) ease,
              transform   var(--t-fast) ease;
}

.project-card.visible {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.35s ease,
              transform 0.35s var(--ease-out),
              border-color var(--t-base) ease,
              box-shadow var(--t-base) ease;
}

.project-card:hover {
  border-color: var(--color-border-strong);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.05);
  transform: translateY(-1px);
}

/* Cards without an external link aren't clickable — drop the hover affordances. */
.project-card.no-link { cursor: default; }
.project-card.no-link:hover {
  border-color: var(--color-border);
  box-shadow: none;
  transform: translateY(0);
}

.project-check {
  flex-shrink: 0;
  width: 16px;
  font-size: 13px;
  color: var(--color-success-soft);
  text-align: center;
}

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

.project-title-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 2px;
}

.project-name {
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text);
}

.project-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-dim);
  letter-spacing: 0.2px;
}

.project-outcome {
  font-size: 13px;
  color: #555;
  line-height: 1.4;
}

/* Inline redaction — used for confidential employer metrics.
   The text inside is a deliberate placeholder ("XX%") so even an
   inspect-element view doesn't reveal real numbers. */
.redacted {
  display: inline-block;
  filter: blur(2.2px);
  user-select: none;
  cursor: not-allowed;
  color: var(--color-text);
  font-weight: 600;
  letter-spacing: 0.3px;
  transition: filter 0.2s ease;
}

.redacted:hover { filter: blur(1.8px); }   /* tiny tease on hover */

.project-link-icon {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  color: #BBB;
  transition: color var(--t-base) ease;
}

.project-card:hover .project-link-icon { color: var(--color-text-muted); }

/* ============================================================
   Resume — PDF-viewer chrome with scrollable blurred page
   ============================================================ */
.resume-card {
  position: relative;
  width: var(--content-width);
  max-width: 100%;
  height: 620px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--t-slow) ease,
              transform 0.35s var(--ease-out);
}

.resume-card.visible {
  opacity: 1;
  transform: translateY(0);
}

/* Toolbar — like macOS Preview / Chrome PDF viewer */
.resume-pdf-toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  background: linear-gradient(180deg, #F4F2EE 0%, #ECEAE5 100%);
  border-bottom: 1px solid #D8D5CE;
  font-family: var(--font-mono);
  font-size: 12px;
  color: #555;
}

.resume-pdf-icon {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  color: #888;
}

.resume-pdf-filename {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.resume-pdf-meta {
  color: var(--color-text-dim);
  font-variant-numeric: tabular-nums;
}

/* Wrap = positioning context for the overlay (which sits on top of viewport) */
.resume-pdf-viewport-wrap {
  position: relative;
  flex: 1;
  min-height: 0;             /* needed so flex child can shrink */
}

/* The actual scrollable area — gray gutter like a PDF viewer canvas */
.resume-pdf-viewport {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  background: #5A5755;
  padding: 18px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* Per-page "paper" card */
.resume-page {
  width: 92%;
  background: white;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  overflow: hidden;
  border-radius: 2px;
}

.resume-page__img {
  display: block;
  width: 100%;
  height: auto;
  filter: blur(1.5px) contrast(1.05);
  user-select: none;
  pointer-events: none;
}

/* Centered "restricted" badge — sits on top of the viewport, but with
   pointer-events: none so the user can still scroll the document
   underneath. The email link re-enables pointer-events.

   Starts hidden; animates in 500ms after the card becomes visible
   (so the blurred page lands first, then the badge pops over it). */
.resume-overlay {
  position: absolute;
  left: 50%;
  top: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 22px 28px;
  gap: 8px;
  max-width: 85%;
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(0, 0, 0, 0.06);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.96);
}

.resume-overlay a { pointer-events: auto; }

.resume-card.visible .resume-overlay {
  animation: resume-overlay-in 0.4s var(--ease-out) 0.5s forwards;
}

@keyframes resume-overlay-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.96); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

.resume-overlay__title {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  color: var(--color-error);
  letter-spacing: 0.6px;
  text-transform: uppercase;
}

.resume-overlay__msg {
  font-size: 14px;
  color: var(--color-text);
  line-height: 1.5;
  max-width: 360px;
}

.resume-overlay__msg a {
  font-family: var(--font-mono);
  color: var(--color-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.resume-overlay__msg a:hover { color: var(--color-accent); }

/* ============================================================
   Reveals stack — container for all reveal sections. JS reorders
   children inside this so click order = display order.
   ============================================================ */
.reveals-stack {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* ============================================================
   Blogs card — "no posts" state
   ============================================================ */
.blogs-card {
  width: var(--content-width);
  max-width: 100%;
  padding: 22px 24px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  gap: 8px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--t-slow) ease,
              transform 0.35s var(--ease-out);
}

.blogs-card.visible {
  opacity: 1;
  transform: translateY(0);
}

.blogs-card__title {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text);
  letter-spacing: 0.3px;
}

.blogs-card__msg {
  font-size: 14px;
  line-height: 1.55;
  color: var(--color-text-muted);
}

/* ============================================================
   Footer
   ============================================================ */
footer { padding-top: 40px; text-align: center; }
footer .location  { font-size: 13px; color: var(--color-text-muted); margin-bottom: 4px; }
footer .copyright { font-size: 12px; color: var(--color-text-dim); }

/* ============================================================
   Animations
   ============================================================ */
@keyframes body-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes cursor-blink {
  0%, 50%      { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 640px) {
  .avatar-circle { width: 240px; height: 240px; }
  .musing, .prompt-line, .tool-header, .project-card { width: 100%; }
  .prompt-line, .tool-header { padding: 0 16px; }
  .musing       { padding: 19px 16px; min-height: 78px; }   /* 2 lines @ 13px */
  .musing-label { font-size: 13px; }
  .prompt-line  { font-size: 22px; }
  .project-card { padding: 12px 16px; }
  /* Force a clean 2×2 layout on mobile — natural flex-wrap was packing
     3 pills on row 1 and 1 orphan on row 2 at common phone widths.
     Each pill stretches to half the row so the two-pill rows span the
     same width as the musing card above. */
  .buttons      {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 10px;
    row-gap: 8px;
  }
  .btn          {
    padding: 7px 12px;
    font-size: 12px;
    width: 100%;                /* fill the grid cell */
    justify-content: center;    /* center icon + label in the wider pill */
  }
  .btn svg      { width: 13px; height: 13px; }

  /* Force the title/meta row to stack on narrow widths so every project
     card has the same 3-row rhythm (title → meta → outcome). The inline
     "Title | Meta" only fits when the title is short, which produced an
     inconsistent layout from card to card. */
  .project-title-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    margin-bottom: 4px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
