:root {
  --bg: #f2eee9;
  --ink: #02020b;
  --muted: rgba(55, 63, 81, 0.6);
  --accent: #fe5f55;
  --accent-soft: #ffa099;
  --on-accent: #02020b;
  --warn: #c62828;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-weight: 300;
  background: var(--bg);
  color: var(--ink);
}

.cc-app {
  min-height: 100vh;
  position: relative;
  padding: 40px;
  transition: opacity 0.5s ease;
}

/* Pairs with landing.js's own fade-out before navigating: rather than
   the new page just popping in the instant it loads, it starts invisible
   and fades in too, so the whole landing -> intro handoff reads as one
   continuous fade rather than fade-out-then-hard-cut. Scoped to cc-boot
   (see index.html's head script) rather than applied unconditionally, so
   "New journal post" and "Continue conversation" — which already have
   their own established transitions — are completely unaffected. */
.cc-boot .cc-app { opacity: 0; }

/* "New journal post" used to get its own hard-edged wipe here (unconditional
   for every arrival at this page, not just this flow — scoping it by class
   was tried and didn't work reliably, see git history). Replaced by the
   site-wide crossfade in page-transitions.css for consistency across the
   whole app. The ball/mic/status stagger below is unaffected — that's
   plain class-scoped styling on normal DOM elements, reliably gated to
   just this flow, and layers on top of the shared fade unchanged. */

.cc-fresh .cc-ball {
  animation: cc-pop-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.5s both;
}

.cc-fresh .cc-mic {
  animation: cc-fade-up 0.45s ease-out 0.75s both;
}

.cc-fresh .cc-status-lines {
  animation: cc-fade-up 0.45s ease-out 0.9s both;
}

@keyframes cc-pop-in {
  from { opacity: 0; transform: scale(0.7); }
  to { opacity: 1; transform: scale(1); }
}

@keyframes cc-fade-up {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

.hidden { display: none !important; }

/* ---------------------------------------------------------------- exit */

.cc-exit {
  position: absolute;
  top: 40px;
  left: 40px;
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  cursor: pointer;
  color: #000;
  font-family: inherit;
  font-weight: 300;
  font-size: 16px;
  padding: 0;
}

.cc-exit-icon { width: 24px; height: 24px; }
.cc-exit:hover { opacity: 0.7; }

/* ---------------------------------------------------------------- banner */

.cc-banner {
  max-width: 500px;
  margin: 0 auto 16px;
  background: rgba(198, 40, 40, 0.1);
  border: 1px solid var(--warn);
  color: var(--warn);
  padding: 12px 16px;
  border-radius: 8px;
  font-size: 0.9rem;
  text-align: center;
}

/* ------------------------------------------------------------ guest intro */

/* Set synchronously before first paint (index.html's head script) for any
   load that isn't unambiguously "New journal post" or "Continue
   conversation" — closes the gap before init() (app.js) has finished its
   async signed-in/store checks and knows for sure whether to show the
   normal stage or the guest intro. init() removes this class either way
   as soon as it knows. */
.cc-boot .cc-stage { visibility: hidden; }

/* The normal small conversation UI (#ball/#talkBtn/status text) sits in
   the DOM the whole time, underneath the full-screen intro sphere —
   hiding it outright while the intro is active (rather than relying on
   the sphere to visually cover it) means there's never a frame where it
   could show through or flash, regardless of paint timing. See also the
   synchronous cc-boot class in index.html's head script, which hides it
   before this class even exists, closing the gap while init() is still
   figuring out whether this is a guest at all. */
.cc-intro-active .cc-stage { visibility: hidden; }

/* The full-screen liquid sphere a first-time guest sees before the real
   conversation UI appears (Figma node 203:4054). Reuses the exact same
   .cc-ball-outer/.cc-ball-blob markup as the normal small sphere — only
   the box they live in is bigger — so the liquid motion never cuts or
   restarts between this state and the docked-down normal one. 150vmax
   keeps the circle's edge off-screen at any viewport size. Selector is
   .cc-ball.cc-ball-intro (not just .cc-ball-intro) so it always wins over
   the base .cc-ball rules below regardless of stylesheet order — two
   same-specificity single-class selectors would otherwise resolve by
   source order, and .cc-ball is defined later in this file. */
.cc-ball.cc-ball-intro {
  position: fixed;
  top: 50%;
  left: 50%;
  width: 150vmax;
  height: 150vmax;
  transform: translate(-50%, -50%);
  z-index: 20;
  transition: top 0.6s ease, left 0.6s ease, width 0.6s ease, height 0.6s ease;
}

/* #bigQuote, #introStartBtn and #introCaption are each independently
   fixed-positioned (rather than grouped in one wrapper) so #bigQuote can
   stay exactly where it already lives in the DOM and keep working
   unchanged for the normal in-conversation state — only this ancestor
   class repositions it on screen. Offsets match the Figma design's
   relative spacing from center (quote center ~127px above, button ~71px
   below, caption ~151px below), which stays proportionate at other
   viewport heights rather than pinning to the one canvas size Figma
   was authored at. */
.cc-intro-active #bigQuote {
  position: fixed;
  /* Overrides the visibility:hidden inherited from .cc-stage above — that
     rule hides the rest of the stage's normal content, but this element
     is repurposed to show the intro line itself. */
  visibility: visible;
  top: calc(50% - 127px);
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 21;
  /* Display-only text, never interactive — a tall multi-line reveal
     could otherwise reach far enough down to sit over introStartBtn and
     silently block clicks on it, the same bug fixed on .cc-intro-caption. */
  pointer-events: none;
  width: min(670px, calc(100vw - 80px));
  margin: 0;
  max-width: none;
  font-family: 'Hanken Grotesk', 'Plus Jakarta Sans', sans-serif;
  font-size: 35px;
  color: #fff;
}

.cc-intro-active #bigQuote::before,
.cc-intro-active #bigQuote::after { content: none; }

.cc-intro-btn {
  position: fixed;
  top: calc(50% + 71px);
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 21;
  background: #fff;
  border: 1.5px solid #8d8d8d;
  color: var(--ink);
  font-family: inherit;
  font-weight: 300;
  padding: 10px 20px;
  border-radius: 50px;
  cursor: pointer;
  font-size: 16px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease, background 0.15s ease;
}

.cc-intro-btn.visible { opacity: 1; pointer-events: auto; }
.cc-intro-btn:hover { background: #f2f2f2; }

/* The one required tap (see index.html) — dead-center, since there's no
   quote text on screen yet at this point for it to sit below. No border
   (unlike the shared .cc-intro-btn default). Starts hidden and fades in
   via the same .visible toggle .cc-intro-btn already defines (app.js
   adds it a moment after load) rather than a CSS @keyframes animation —
   an animation's held end-state would keep overriding the plain opacity
   rule this button also needs for hiding again once tapped. */
.cc-intro-begin-btn {
  top: 50%;
  border: none;
  width: 140px;
  height: 140px;
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  white-space: normal;
  line-height: 1.3;
}

.cc-intro-caption {
  position: fixed;
  top: calc(50% + 151px);
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 21;
  width: 210px;
  color: #fff;
  font-size: 12px;
  font-weight: 300;
  line-height: 1.4;
  text-align: center;
  opacity: 0;
  /* Purely informational text, never interactive — without this, its
     invisible (opacity:0) hitbox still sat on top of whatever the normal
     page layout put at this same fixed screen position, silently
     blocking real clicks there (the mic button, depending on viewport
     size) even though nothing was visible to explain why. */
  pointer-events: none;
  transition: opacity 0.4s ease;
}

.cc-intro-caption.visible { opacity: 1; }

/* ---------------------------------------------------------------- stage */

.cc-stage {
  min-height: calc(100vh - 80px);
  display: flex;
  flex-direction: column;
  align-items: center;
}

.cc-top {
  padding-top: 60px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* ------------------------------------------------------------------ ball */

.cc-ball {
  width: 178px;
  height: 178px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Matches the Figma "Companion sphere" asset exactly: a radial gradient
   from solid coral through most of the circle, softening to the lighter
   coral only at the rim (stops at 74.5% / 100%). The liquid motion lives
   entirely on the .cc-ball-blob children (see below), not on this element. */
.cc-ball-outer {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  background: radial-gradient(circle, var(--accent) 74.5%, var(--accent-soft) 100%);
  box-shadow: 0 0 0 0 rgba(254, 95, 85, 0);
  transition: box-shadow 0.3s ease-out;
}

/* Speaking indicator — the sphere glows outward while the companion's
   reply is actually being spoken (S.SPEAKING, toggled via app.js). Applied
   to .cc-ball-outer specifically (not .cc-ball) so the glow follows the
   circle's own border-radius instead of bleeding out as a square. */
.cc-ball.speaking .cc-ball-outer {
  animation: cc-ball-glow 1.8s ease-in-out infinite;
}

@keyframes cc-ball-glow {
  0%, 100% { box-shadow: 0 0 24px 4px rgba(254, 95, 85, 0.35); }
  50% { box-shadow: 0 0 60px 18px rgba(254, 95, 85, 0.65); }
}

@media (prefers-reduced-motion: reduce) {
  .cc-ball.speaking .cc-ball-outer { animation: none; box-shadow: 0 0 40px 10px rgba(254, 95, 85, 0.5); }
}

/* Liquid motion: three oversized blobs of the same rim color, each with a
   solid-color core (not just a soft falloff) so the moving highlight reads
   clearly against the base coral, drifting/rotating/scaling at different
   speeds inside the circular clip. Oversized (180%) so their own edges are
   always clipped off-screen — only the gradient falloff is ever visible. */
.cc-ball-blob {
  position: absolute;
  width: 180%;
  height: 180%;
  top: -40%;
  left: -40%;
  border-radius: 42% 58% 63% 37% / 41% 44% 56% 59%;
  background: radial-gradient(circle at 50% 50%, var(--accent-soft) 0%, var(--accent-soft) 25%, transparent 58%);
  animation: cc-liquid-drift 6s ease-in-out infinite;
}

.cc-ball-blob-1 { animation-duration: 5s; }
.cc-ball-blob-2 { animation-duration: 6.5s; animation-delay: -2s; opacity: 0.8; }
.cc-ball-blob-3 { animation-duration: 8s; animation-delay: -5s; opacity: 0.65; }

@keyframes cc-liquid-drift {
  0%   { transform: translate(0%, 0%) rotate(0deg) scale(1); }
  25%  { transform: translate(32%, -26%) rotate(90deg) scale(1.4); }
  50%  { transform: translate(-24%, 30%) rotate(180deg) scale(0.65); }
  75%  { transform: translate(-32%, -20%) rotate(270deg) scale(1.25); }
  100% { transform: translate(0%, 0%) rotate(360deg) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .cc-ball-blob { animation: none; }
}

/* ----------------------------------------------------------------- quote */

.cc-quote {
  margin: 40px 0 0;
  max-width: 670px;
  font-weight: 700;
  font-size: 25px;
  line-height: 1.35;
  text-align: center;
  color: var(--ink);
}

.cc-quote::before { content: "\201C"; }
.cc-quote::after { content: "\201D"; }

.cc-word {
  display: inline-block;
  animation: cc-word-rise 0.5s cubic-bezier(0.19, 1, 0.22, 1) both;
}

@keyframes cc-word-rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

/* --------------------------------------------------------------- voice */

.cc-voice {
  margin-top: auto;
  padding-top: 60px;
  padding-bottom: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

/* Matches the Figma "Companion sphere" mic asset exactly (same gradient as
   the companion ball, same two brand colors — see .cc-ball-outer). The
   button itself is a fixed 122px box (the tap target never moves or
   resizes) with two independent children: .cc-mic-sphere is the part that
   shrinks/grows, .cc-mic-icon stays a constant size on top of it — the mic
   glyph itself shouldn't visibly scale, only the sphere behind it. */
.cc-mic {
  width: 122px;
  height: 122px;
  border-radius: 50%;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cc-mic:disabled { opacity: 0.5; cursor: default; }
.cc-mic:focus,
.cc-mic:focus-visible { outline: none; }

.cc-mic-sphere {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent) 75%, var(--accent-soft) 100%);
  /* Off by default — visibly shrinks when not listening, and grows back
     to full size the moment audio is actually being captured (.listening,
     toggled in app.js render()). This is the app's only mic animation now
     — it replaces the old always-on ripple pulse. */
  transform: scale(0.72);
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.cc-mic:hover .cc-mic-sphere { transform: scale(0.76); }
.cc-mic.listening .cc-mic-sphere {
  transform: scale(1);
  /* The sound-wave ripple — expanding, fading rings while the mic is
     actively capturing audio. Only animates box-shadow, so it runs
     independently of the transform transition above (no conflict). */
  animation: cc-pulse 1.6s infinite;
}
.cc-mic.listening:hover .cc-mic-sphere { transform: scale(1.03); }

@keyframes cc-pulse {
  0% { box-shadow: 0 0 0 0 rgba(254, 95, 85, 0.35); }
  70% { box-shadow: 0 0 0 22px rgba(254, 95, 85, 0); }
  100% { box-shadow: 0 0 0 0 rgba(254, 95, 85, 0); }
}

.cc-mic-icon {
  position: relative;
  z-index: 1;
  display: block;
  width: 80px;
  height: 100px;
  pointer-events: none;
}

.cc-status-lines { text-align: center; }

.cc-status {
  font-size: 17px;
  color: var(--ink);
  margin: 0 0 4px;
}

.cc-status.error { color: var(--warn); }

.cc-status-sub {
  font-size: 17px;
  color: var(--ink);
  margin: 0;
}

/* --------------------------------------------------------------- buttons */

.cc-btn-row {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
  min-height: 43px;
}

.cc-pill {
  background: var(--accent);
  border: none;
  color: var(--on-accent);
  font-family: inherit;
  font-weight: 300;
  padding: 10px 20px;
  border-radius: 50px;
  cursor: pointer;
  font-size: 0.95rem;
  opacity: 1;
  transition: opacity 1.1s ease-out;
}

/* finishBtn/newEntryBtn fade in place instead of popping into the layout,
   which used to shove the mic button upward when they first appeared. */
#finishBtn.cc-fade-hidden,
#newEntryBtn.cc-fade-hidden {
  opacity: 0;
  pointer-events: none;
}

.cc-pill:hover { background: #ff756c; }
.cc-pill:disabled { opacity: 0.5; cursor: default; }
.cc-pill:disabled:hover { background: var(--accent); }
