/* ============================================================
   feelings.css — the affect grid, ported from the app.
   Chrome, type and spacing come from tokens.css + page.css;
   nothing here re-types a colour, a size or a font.
   The one law holds: affect colour is LIGHT (glow), never fill.
   ============================================================ */

/* ---------- Motion the port needs and page.css doesn't carry ---------- */
.affect {
  --ease-app-out: cubic-bezier(0, 0, 0.58, 1);   /* SwiftUI .easeOut */
  --ease-app-in:  cubic-bezier(0.42, 0, 1, 1);   /* SwiftUI .easeIn  */
  /* .spring(response: 0.3, dampingFraction: 0.6) sampled at 50ms.
     Peak overshoot 9.2% at 200ms, settled by 500ms. */
  --spring-cell: linear(0, 0.346, 0.817, 1.056, 1.092, 1.050, 1.009, 0.993, 1.008, 0.999, 1);
  --spring-dur: 500ms;
}
@supports not (transition-timing-function: linear(0, 1)) {
  .affect { --spring-cell: var(--ease-soft); --spring-dur: 300ms; }
}

/* ---------- Section + stage ---------- */
.affect {
  padding: clamp(48px, 8vh, 96px) var(--s-screen-pad) 0;
  text-align: center;
  /* One source of scale — the grid, the dots and the blooms all derive from it.
     The budget must leave room for the two side anchors, or they clip off the
     edge on a phone: grid + 2 dots (0.0507 each) + 2 stage gaps (16px). */
  --grid-size: min(460px, calc((100vw - 2 * var(--s-screen-pad) - 32px) / 1.1014));
}
.affect .stage {
  position: relative;
  display: grid;
  grid-template-columns: auto auto auto;
  grid-template-rows: auto auto auto;
  justify-content: center;
  align-items: center;
  gap: 16px;                       /* AppLayout.gridToDots */
}
.anchor.top    { grid-area: 1 / 2; flex-direction: column; }
.anchor.left   { grid-area: 2 / 1; }
.grid          { grid-area: 2 / 2; position: relative; z-index: 1; }
.anchor.right  { grid-area: 2 / 3; }
.anchor.bottom { grid-area: 3 / 2; flex-direction: column; }

/* ---------- Grid geometry — 7x36 + 6x4 = 276, AppSizes.gridLarge ---------- */
.grid {
  --gap: calc(var(--grid-size) / 69);                         /* keeps the app's 9:1 cell:gap */
  --cell-size: calc((var(--grid-size) - var(--gap) * 6) / 7);
  --hit-inset: min(max(0px, calc((44px - var(--cell-size)) / 2)),
                   calc(var(--gap) / 2));  /* grow toward 44, never into the neighbour */
  --glow-unit: calc(var(--cell-size) * 0.045);                /* intensity 2.25/100, x2 for CSS blur */
  --recede-dur: 450ms;                                        /* checkInRestore — back slow */
  --recede-delay: 0ms;

  display: grid;
  grid-template-columns: repeat(7, var(--cell-size));
  gap: var(--gap);
  width: var(--grid-size);
  margin: 0 auto;
  isolation: isolate;              /* the 68px outer glow raises, never clips */
}
.grid.receding {
  --recede-dur: 250ms;             /* checkInRecede — away fast */
  --recede-delay: 60ms;            /* checkInRecedeDelay: a tap must never trigger it */
}

/* ---------- The cell ---------- */
.cell {
  position: relative;
  appearance: none; -webkit-appearance: none;
  border: 0; padding: 0; margin: 0;
  width: var(--cell-size);
  height: var(--cell-size);
  background: var(--cell);         /* #232323 — AppColors.cellInactive. NEVER --c */
  border-radius: calc(var(--cell-size) * var(--r-cell-ratio));
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  --glow-core: color-mix(in srgb, var(--c) 40%, #fff);  /* color.lerp(to:.white, t:0.6) */
  transition:
    opacity var(--recede-dur) var(--ease-app-out) var(--recede-delay),
    transform var(--spring-dur) var(--spring-cell),
    background-color 300ms var(--ease-soft);
}
/* 44x44 hit target without touching the visual frame */
.cell::before {
  content: ""; position: absolute;
  inset: calc(-1 * var(--hit-inset));
  border-radius: inherit;
}
/* Six-layer Figma glow on its own layer, so one opacity fades the whole stack.
   FigmaGlow.multipliers [1,2,7,14,24,42]; inner three 60% whitened. */
.cell::after {
  content: ""; position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--spring-dur) var(--spring-cell);
  box-shadow:
    0 0 calc(var(--glow-unit) *  1) var(--glow-core),
    0 0 calc(var(--glow-unit) *  2) var(--glow-core),
    0 0 calc(var(--glow-unit) *  7) var(--glow-core),
    0 0 calc(var(--glow-unit) * 14) var(--c),
    0 0 calc(var(--glow-unit) * 24) var(--c),
    0 0 calc(var(--glow-unit) * 42) var(--c);
}
.cell.is-lit { background: #fff; transform: scale(1.15); z-index: 2; }
.cell.is-lit::after { opacity: 1; }
.cell:focus-visible { outline: 1.5px solid rgba(255, 255, 255, 0.6); outline-offset: 4px; }

/* The one-shot invitation pulse — renders exactly like a real selection */
.cell.demo { animation: cell-demo 3.35s var(--ease-soft) 1 both; }
.cell.demo::after { animation: cell-demo-glow 3.35s var(--ease-soft) 1 both; }
@keyframes cell-demo {
  0%, 100% { background: var(--cell); transform: scale(1); }
  33%, 67% { background: #fff; transform: scale(1.15); }
}
@keyframes cell-demo-glow { 0%, 100% { opacity: 0 } 33%, 67% { opacity: 0.9 } }

/* ---------- Anchor dots — quadrant-aware, incommensurate breath ---------- */
.anchor {
  display: flex; align-items: center; justify-content: center;
  gap: 4px;
  transform: scale(1);
  transition: opacity 250ms var(--ease-app-out), transform 250ms var(--ease-app-out);
}
.anchor .dot {
  position: relative;
  --dot: calc(var(--grid-size) * 0.0507);   /* circumplexDot 14 / gridLarge 276 */
  --dg: calc(var(--dot) * 0.04);            /* 4-layer glow, scale 2.0 */
  width: var(--dot); height: var(--dot); border-radius: 50%;
  background: var(--c);
  animation: dot-breathe var(--period) ease-in-out var(--phase) infinite;
}
.anchor .dot::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  --g: 1;
  box-shadow:
    0 0 calc(var(--dg) *  1 * var(--g)) var(--c),
    0 0 calc(var(--dg) *  2 * var(--g)) var(--c),
    0 0 calc(var(--dg) *  7 * var(--g)) var(--c),
    0 0 calc(var(--dg) * 14 * var(--g)) var(--c);
  animation: dot-glow var(--period) ease-in-out var(--phase) infinite;
  transition: box-shadow 250ms var(--ease-app-out);
}
@keyframes dot-breathe { 0%, 100% { transform: scale(0.97) } 50% { transform: scale(1.02) } }
@keyframes dot-glow    { 0%, 100% { opacity: 0.74 }          50% { opacity: 1 } }

/* four synced dots read as a heartbeat — the periods are incommensurate */
.anchor.top    { --c: var(--orange); --period: 3.4s; --phase:  0s;     }
.anchor.bottom { --c: var(--blue);   --period: 4.6s; --phase: -0.805s; }
.anchor.right  { --c: var(--pink);   --period: 3.9s; --phase: -1.428s; }
.anchor.left   { --c: var(--umber);  --period: 4.2s; --phase: -0.401s; }

.affect.touching .anchor      { opacity: 0.50; }
.affect.touching .anchor.lead { opacity: 1; transform: scale(1.16); }
.affect.touching .anchor.lead .dot::after { --g: 1.7; }

/* ---------- Ambient blooms — gradients, not a heavy blur layer ---------- */
.blooms {
  --bloom: calc(var(--grid-size) * 0.797);   /* 220 / 276 */
  --off:   calc(var(--grid-size) * 0.543);   /* 150 / 276 */
  position: absolute; inset: calc(var(--grid-size) * -0.652);
  pointer-events: none;
  opacity: 0.06;
  transition: opacity 450ms var(--ease-app-out);
  background:
    radial-gradient(var(--bloom) var(--bloom) at 50% calc(50% - var(--off)), var(--orange), transparent 70%),
    radial-gradient(var(--bloom) var(--bloom) at 50% calc(50% + var(--off)), var(--blue),   transparent 70%),
    radial-gradient(var(--bloom) var(--bloom) at calc(50% + var(--off)) 50%, var(--pink),   transparent 70%),
    radial-gradient(var(--bloom) var(--bloom) at calc(50% - var(--off)) 50%, var(--umber),  transparent 70%);
}
.affect.touching .blooms { opacity: 0.10; }

/* ---------- The label — one element, swapped in place ---------- */
.readout { margin-top: var(--s-grid-label); min-height: 112px; }   /* 24px, AppSpacing.xxl */
.readout .phrase {
  font-size: clamp(22px, 3vw, 28px);
  font-weight: 600; letter-spacing: -0.015em; line-height: 1.28;
  color: var(--c, var(--text));
  transition: filter 420ms var(--ease-app-out);   /* back slow */
}
.readout .phrase.swapping { filter: blur(3px); transition: filter 180ms var(--ease-app-in); }
.readout .phrase.arriving { filter: blur(11px); transition: none; }
.readout .explain {
  font-size: var(--t-body); line-height: 1.6;
  /* a quieter version of the phrase's own colour, not neutral grey.
     40% is the ceiling: at 50% the umber cell falls to 4.34:1 on black,
     under AA for 17px text. At 40% the worst of the 45 is 4.64:1. */
  color: color-mix(in srgb, var(--c) 40%, var(--text-2));
  margin-top: 10px; max-width: 42ch; margin-left: auto; margin-right: auto;
}

/* no JS: the 49 phrases are the page. With JS: one live element. */
#phrase, #axes { display: none; }
.js #phrase, .js #axes { display: block; }
.js .ro { display: none; }
.ro + .ro { margin-top: 20px; }

.hint { font-size: var(--t-micro); color: var(--text-3); margin-top: 14px; }
.hint.enter { --enter-delay: 60ms; }

/* ---------- Poster lede + closer ---------- */
.lede {
  max-width: 780px; margin: 0 auto;   /* the site's one measure, as .manifesto */
  font-size: var(--t-statement);
  font-weight: 550; letter-spacing: -0.015em; line-height: 1.28;
  color: var(--text);
}
.lede .dim { color: var(--text-2); }
.lede.enter { --enter-delay: 0ms; }   /* first in flow, and the LCP element */
/* Borrow the home page's own closer rhythm rather than inventing one.
   A flat 32px gap was --s-screen-pad's value (a horizontal token) used
   vertically, and it stacked with .cta-note's 12px into a 44px gap under the
   pill. page.css sets .cta-row { margin-top: clamp(72px,12vh,130px) } and
   .cta-note { margin-top: 12px } — a big approach to the button, then the
   note tucked right beneath it. Same two values here, no grid gap to stack. */
.closer-stage { display: grid; justify-items: center; max-width: 780px; }
.closer-stage .pill { margin-top: clamp(72px, 12vh, 130px); }
/* page.css centres .closer's content in 46svh, so the space beneath it is
   (46svh - contentHeight)/2. The home page's content is the 55px pill alone,
   giving 23svh - 28px; ours is 283px tall (statement + pill + note), which
   left only 65px. State the home page's own figure explicitly so the gap above
   the footer matches at every viewport height, not just one. */
.closer { padding-bottom: calc(23svh - 28px); }

/* ---------- Prose — mirrors .manifesto's 780px measure ---------- */
.prose { padding: clamp(96px, 14vh, 160px) var(--s-screen-pad); }
.prose .stage { max-width: 780px; margin: 0 auto; }
.prose h2 {
  font-size: 24px; font-weight: 600; letter-spacing: -0.015em; line-height: 1.28;
  color: var(--text);
  margin: var(--s-checkin) 0 12px;
}
.prose h2:first-child { margin-top: 0; }
.prose p { font-size: var(--t-body); line-height: 1.6; color: var(--text-2); }
.prose p + p { margin-top: 24px; }
.prose strong, .prose em { color: var(--text); }
.prose strong { font-weight: 600; }
.prose a {
  color: var(--text);
  border-bottom: 0.5px solid rgba(255, 255, 255, 0.25);
  transition: border-color 0.25s ease;
}
.prose a:hover { border-bottom-color: var(--text); }

/* ---------- Grid entrance — one mass, gated on visibility not load ---------- */
.affect.will-enter .stage {
  opacity: 0; transform: scale(0.94); filter: blur(8px);
}
.affect.will-enter.in .stage {
  opacity: 1; transform: none; filter: blur(0);
  transition: opacity 700ms var(--ease-soft), transform 700ms var(--ease-soft),
              filter 700ms var(--ease-soft);
}

/* ---------- Narrow ---------- */
@media (max-width: 640px) {
}

/* ---------- Reduce, don't delete ---------- */
@media (prefers-reduced-motion: reduce) {
  .affect { --spring-cell: var(--ease-app-out); --spring-dur: 220ms; }
  .anchor .dot, .anchor .dot::after { animation: none; }
  .anchor .dot { transform: scale(0.995); }
  .anchor .dot::after { opacity: 0.87; }
  .affect.will-enter .stage { opacity: 0; transform: none; filter: none; }
  .affect.will-enter.in .stage { opacity: 1; transition: opacity 0.3s ease; }
  .cell.demo, .cell.demo::after { animation: none; }
}


