/* ==========================================================================
   Alfred's writing surface.

   Every number in this file comes from docs/EDITOR_EXPERIENCE.md §4 and §5.
   Those values were tuned by typing into a prototype, not chosen to look
   reasonable — so change one because typing feels wrong, not because it looks
   arbitrary. Where a value is load-bearing the comment says why.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Tokens (§5)

   Three theme states, not two: bare :root is light; the system-preference
   block is guarded with :not([data-theme="light"]) so an explicit light choice
   still wins; and [data-theme="dark"] repeats the dark palette so an explicit
   dark choice wins over a light system preference.
   -------------------------------------------------------------------------- */

:root {
  --ink:        #16233F;
  --ink-soft:   #4A5773;
  --ink-faint:  #97A0B2;
  --brass:      #A9834A;
  --brass-lift: #C0A06B;
  --paper:      #F8F7F5;
  --raised:     #FFFFFF;
  --rule:       #E3DFD7;

  /* One easing for everything that moves (§4.1). It decelerates hard and
     settles without overshoot — the surface is paper and ink, not a toy. */
  --curve: cubic-bezier(.22, .61, .36, 1);

  /* The writing measure, and the margin column Alfred writes into. The column
     is reserved on the container even when empty (§5) — otherwise the measure
     visibly shifts off-axis the first time a note appears. */
  --measure:    min(38rem, 92vw);
  --margin-col: 13rem;
  --gutter:     2.5rem;

  /* Writing-surface line-height as a unitless number, because the caret needs
     it as a number to compute its own height (§4.2) and duplicating 1.95 in
     two places is how they drift apart. */
  --leading: 1.95;

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink:        #EFEDE8;
    --ink-soft:   #B6BECD;
    --ink-faint:  #6E788D;
    --brass:      #D2B27C;
    --brass-lift: #E0C696;
    --paper:      #121B30;
    --raised:     #18243D;
    --rule:       #2B3853;
  }
}

:root[data-theme="dark"] {
  --ink:        #EFEDE8;
  --ink-soft:   #B6BECD;
  --ink-faint:  #6E788D;
  --brass:      #D2B27C;
  --brass-lift: #E0C696;
  --paper:      #121B30;
  --raised:     #18243D;
  --rule:       #2B3853;
}

/* --------------------------------------------------------------------------
   Page
   -------------------------------------------------------------------------- */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--paper);
  color: var(--ink);
}

body {
  font-family: Lora, ui-serif, Georgia, "Times New Roman", serif;
  /* No text cursor: the caret is ours (§4.2), and a native I-beam alongside a
     hand-placed caret reads as two cursors. */
  cursor: default;
  -webkit-font-smoothing: antialiased;
}

/* The masthead sets the identity and then gets out of the way. Playfair mixes
   roman and italic in one line, as the Avant-Garde identity does (§5). */
.masthead {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 3;
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: 1.4rem var(--gutter);
  background: linear-gradient(var(--paper) 62%, transparent);
  pointer-events: none;
}

.masthead__name {
  font-family: "Playfair Display", ui-serif, Georgia, serif;
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: .01em;
}

.masthead__name em { font-style: italic; color: var(--brass); }

.masthead__label {
  font-family: Karla, ui-sans-serif, system-ui, sans-serif;
  font-size: .625rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   Surface and document

   .surface is the viewport. .doc is what the typewriter translates (§4.5), and
   it is also the caret's positioning context — the caret is measured against
   .doc's own rect, so the translate cancels out of the arithmetic and the caret
   stays glued to its character while the page slides underneath.
   -------------------------------------------------------------------------- */

.surface {
  position: relative;
  height: 100vh;
  overflow: hidden;
}

.doc {
  position: relative;
  will-change: transform;
  /* Slower than the caret on purpose (§4.5): the page settling should trail
     the cursor, not race it. */
  transition: transform .38s var(--curve);
}

/* Reserving the margin column here rather than positioning notes over the page
   is what keeps the measure from jumping when Alfred first writes something. */
.page {
  display: grid;
  grid-template-columns: var(--measure) var(--margin-col);
  column-gap: var(--gutter);
  width: max-content;
  margin-inline: auto;
  padding-block: 1px;
}

@media (max-width: 68rem) {
  /* Below this the margin column cannot coexist with the measure. The note
     still exists in the DOM for screen readers; it just stops being a column. */
  .page { grid-template-columns: var(--measure); }
  .margin-note { display: none; }
}

/* --------------------------------------------------------------------------
   Blocks and characters (§3)

   Text lives in the block model, not in the DOM. Each character is its own
   span so that the ink animation (§4.3) can address exactly one of them and
   the caret can be placed from a real rect.
   -------------------------------------------------------------------------- */

.block {
  position: relative;
  grid-column: 1;
  font-size: 1.185rem;
  line-height: var(--leading);
  letter-spacing: .002em;
  /* Long words must wrap rather than widen the measure — a rect measured
     outside the measure would place the caret outside it too. */
  overflow-wrap: break-word;
  /* Spotlight (§4.6). .42 is tuned; .3 was tried and reads as broken rather
     than focused. */
  opacity: .42;
  transition: opacity .5s ease;
}

.block.is-active { opacity: 1; }

/* An empty block still has to have height, or the caret has no line to sit on
   and the block collapses to zero. */
.block:empty::after,
.block .run:empty::after { content: "\200b"; }

.ch { white-space: pre-wrap; }

/* Ink (§4.3). Only the character just inserted carries this class; the tag
   moves on the next render. Animating a whole block on every keystroke looks
   like a bug. */
.ch.is-fresh { animation: ink .26s var(--curve) both; }

@keyframes ink {
  from { opacity: 0; filter: blur(1.1px); transform: translateY(-.055em); }
  to   { opacity: 1; filter: blur(0);     transform: none; }
}

/* Block birth (§4.4). Enter splits a block; the new one arrives.

   max-height needs an explicit end value — with only a `from` keyframe the
   browser would interpolate toward the computed `none`, which is not a length.
   20rem is roughly ten lines: enough that a split tail is never clipped, and
   the class is removed on the next render so nothing stays clamped. */
.block.is-born { animation: birth .3s var(--curve) both; }

@keyframes birth {
  from { opacity: 0; max-height: 0;     transform: translateY(-.35rem); }
  to   { opacity: 1; max-height: 20rem; transform: none; }
}

/* --------------------------------------------------------------------------
   The caret (§4.2)
   -------------------------------------------------------------------------- */

.caret {
  position: absolute;
  top: 0;
  left: 0;
  width: 2px;
  border-radius: 1px;
  background: var(--brass);
  /* Barely perceptible; makes the caret feel lit rather than drawn. */
  box-shadow: 0 0 6px color-mix(in srgb, var(--brass) 55%, transparent);
  pointer-events: none;
  /* Replaces the native hard blink. A blink is a metronome; this is a pulse. */
  animation: breathe 1.7s ease-in-out infinite;
}

/* THE distinction that makes this feel luxurious rather than broken (§4.2):
   the caret glides to a new position on cursor MOVEMENT, and must not glide on
   character insertion. Typing has to feel instant.

   It is expressed as two classes rather than one toggled inline style because
   the transition must be part of the same style commit as the transform —
   the browser starts a transition from the after-change style, so committing
   `transition: none` together with the new transform reliably suppresses it. */
.caret.is-still  { transition: none; }
.caret.is-gliding { transition: transform .13s var(--curve), height .13s ease; }

@keyframes breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: .22; }
}

/* The keystroke sink (§3). Visually hidden, but it holds the real text of the
   active block so a screen reader has something to read and a future clipboard
   implementation has something to copy. Never `display: none` — that cannot
   take focus. */
.sink {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  outline: none;
  resize: none;
  opacity: 0;
  overflow: hidden;
  /* Not clip-path/clip: the element must still be focusable and must not cause
     the viewport to scroll when focus lands on it. */
  white-space: pre;
}

/* --------------------------------------------------------------------------
   Alfred's mark (§4.7) — approved, and the whole point
   -------------------------------------------------------------------------- */

/* A brass underline that draws in left-to-right beneath a detected commitment.
   A background gradient rather than a border or an underline so it can never
   affect layout — the sentence must not reflow when Alfred notices it. */
.run--mark {
  background-image: linear-gradient(var(--brass-lift), var(--brass-lift));
  background-repeat: no-repeat;
  background-position: 0 calc(100% - .08em);
  background-size: 0% 1.5px;
  /* .55s is slow on purpose. The underline should arrive at the edge of
     notice — you catch it having happened rather than watch it happen. */
  transition: background-size .55s var(--curve);
}

.run--mark.is-drawn { background-size: 100% 1.5px; }

.margin-note {
  grid-column: 2;
  align-self: start;
  font-family: Karla, ui-sans-serif, system-ui, sans-serif;
  font-size: .68rem;
  font-weight: 600;
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: .13em;
  color: var(--brass);
  opacity: 0;
  transform: translateX(.5rem);
  transition: opacity .45s ease, transform .45s var(--curve);
}

.margin-note.is-drawn { opacity: 1; transform: none; }

.margin-note__what {
  display: block;
  margin-top: .25rem;
  font-size: .72rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: .01em;
  color: var(--ink-soft);
}

.margin-note__due {
  display: block;
  margin-top: .2rem;
  font-size: .62rem;
  letter-spacing: .13em;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   The edit log

   Not a debug panel that happens to be visible — the log is this app's actual
   output (apps/web will drain it at TF-094), and watching it while typing is
   how you confirm the model and the render have not drifted apart. IBM Plex
   Mono is deliberately clinical against the writing surface (§5).
   -------------------------------------------------------------------------- */

.log {
  position: fixed;
  right: var(--gutter);
  bottom: 1.1rem;
  z-index: 3;
  /* An explicit width rather than max-width. With only `right` set the box is
     shrink-to-fit, so its width tracks whatever the longest entry happens to
     be and the panel visibly changes width as you type. A fixed width keeps it
     still, and gives text-overflow a stable column to elide into. */
  width: min(22rem, 38vw);
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .66rem;
  line-height: 1.7;
  text-align: right;
  color: var(--ink-faint);
  pointer-events: none;
}

/* Each entry is one line, clipped rather than wrapped. A wrapped edit log grows
   upward as you type and turns into a second column of text competing with the
   note; an elided one stays exactly eight lines tall. */
.log > div {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.log__title {
  font-family: Karla, ui-sans-serif, system-ui, sans-serif;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--ink-faint);
  opacity: .7;
}

.hint {
  position: fixed;
  left: var(--gutter);
  bottom: 1.1rem;
  z-index: 3;
  font-family: Karla, ui-sans-serif, system-ui, sans-serif;
  font-size: .62rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--ink-faint);
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Reduced motion (§4.8)

   Every behaviour above degrades to its end state. Nothing depends on an
   animation having run: the caret is placed by arithmetic, the underline's
   drawn state is a class, and the ink tag only ever adds an animation.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
}
