/* ═══════════════════════════════════════════════════════════════════════
   fressocafe.com / Contact — the form that replaced the printed address.

   Three lines and a button: a name, an email to reply to, the message. No
   boxes. Each field is a hairline; its label sits on the line where the
   words will go and lifts out of their way the moment there is something to
   say. The field being written in draws an ink line in from the left, the
   same gesture as a .link under the pointer, and the line retreats to the
   right when the reader moves on. That line IS the focus ring: nothing else
   is drawn round the control.

   Errors are ink, never red. The site keeps its one accent for the moment
   the message is safely with us: the F petal, drawn on in green above
   "Thank you".

   Without a script the form still posts. The server answers with a 303 to
   /contact#sent or /contact#trouble, and :target puts that block where the
   form was (the end of this file). This file loads after site.css.
   ═══════════════════════════════════════════════════════════════════════ */

.contact-page .contact {
  padding-top: calc(var(--header-h) + clamp(48px, 9vw, 120px));
  padding-bottom: var(--section);
}

/* ─── Layout ──────────────────────────────────────────────────────────────
   A phone reads it top to bottom: the greeting, the form, then the other
   ways to reach us. A wide screen puts the greeting on its own and starts
   the form and those other ways on one line, so the first field and the
   first hairline of the aside are level. */
.contact-grid { display: grid; }
/* One measure for everything in the single column, the aside included, so
   on a tablet its rules stop where the form's do. */
.contact-intro, .contact-body, .contact-aside { max-width: 34rem; }
.contact-intro .lede { margin-top: 20px; }
.contact-body { margin-top: clamp(34px, 7vw, 56px); }
.contact-aside { margin-top: clamp(64px, 16vw, 96px); border-top: 1px solid var(--line); }

@media (min-width: 1000px) {
  .contact-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
    grid-template-areas: "intro ." "body aside";
    column-gap: clamp(64px, 8vw, 140px);
    align-items: start;
  }
  .contact-intro { grid-area: intro; }
  .contact-body { grid-area: body; }
  .contact-aside { grid-area: aside; max-width: none; margin-top: clamp(34px, 7vw, 56px); }
}
/* A long message can outgrow the aside, which then stays in view beside it —
   but only where the whole aside fits under the bar. Pinned in a shorter
   window, its last column would be out of reach until the page ended. */
@media (min-width: 1000px) and (min-height: 760px) {
  .contact-aside { position: sticky; top: calc(var(--header-h) + 32px); }
}

/* ─── Fields ────────────────────────────────────────────────────────────── */

.contact-form { position: relative; display: grid; gap: 10px; }

/* The hairline, and the air above it that the lifted label moves into. */
.field-line {
  position: relative;
  padding-top: 22px;
  border-bottom: 1px solid var(--line);
  transition: border-color .4s var(--ease-soft);
}
/* The ink line. transform-origin is NOT transitioned, which is the whole
   trick: focus sets it to the left and the line grows out of the left edge;
   blur sets it back to the right and the line withdraws into the right. */
.field-line::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: var(--ink);
  transform: scaleX(0); transform-origin: right;
  transition: transform .6s var(--ease);
  pointer-events: none;
}
.field-line:focus-within::after { transform: scaleX(1); transform-origin: left; }

.field input,
.field textarea {
  display: block; width: 100%; margin: 0;
  border: 0; border-radius: 0; background: none; box-shadow: none;
  padding: 10px 0 14px;
  font: inherit; letter-spacing: inherit; color: var(--ink);
  outline: none;
  -webkit-appearance: none; appearance: none;
}
/* The message field is one line until there is more to say, then it grows
   a line at a time (contact.js, or field-sizing where the browser has it).
   Its leading is a little tighter than the page's; the extra pixel of
   padding on each side puts its first line exactly where the label sits and
   keeps it the same height as the two fields above it. */
.field textarea {
  line-height: 1.5;
  padding: 11px 0 15px;
  min-height: calc(1.5em + 26px);
  resize: none; overflow: hidden;
  field-sizing: content;
}
/* Pressed-in autofill colours would put a pale box behind one field. */
.field input:-webkit-autofill,
.field input:-webkit-autofill:hover,
.field input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 60px var(--cream) inset;
  box-shadow: 0 0 0 60px var(--cream) inset;
  caret-color: var(--ink);
}

/* The label is the placeholder: same size, same line, a quieter ink. It
   lifts with a transform (never a font-size), so the move is one composited
   step and the type does not reflow on the way. */
.field label {
  position: absolute; left: 0; top: 32px;
  color: var(--ink-3);
  white-space: nowrap;
  pointer-events: none;
  transform-origin: left top;
  transition: transform .5s var(--ease), color .4s var(--ease-soft);
}
.field-line:focus-within label,
.field input:not(:placeholder-shown) + label,
.field textarea:not(:placeholder-shown) + label {
  transform: translateY(-24px) scale(.78);
  color: var(--ink-2);
}
/* Kept apart: a browser that does not know this selector drops the rule it
   is in, and the one above must survive that. Autofill can paint a value
   before the field reports one; the label must already be out of its way. */
.field input:-webkit-autofill + label {
  transform: translateY(-24px) scale(.78);
  color: var(--ink-2);
}

/* ─── What is wrong, and where ─────────────────────────────────────────────
   Ink, never red: the rule turns to ink, and one short sentence opens
   underneath. A lifted label turns to ink with them. A label still resting
   on the line only darkens a step: in full ink, at full size, "Email" reads
   as though somebody had typed it. The sentence opens through the grid row
   (the FAQ's trick, so there is nothing to measure) and fades down into its
   place, so the fields below slide rather than jump. */
.field.is-invalid .field-line { border-bottom-color: var(--ink); }
.field.is-invalid label { color: var(--ink-2); }
.field.is-invalid .field-line:focus-within label,
.field.is-invalid input:not(:placeholder-shown) + label,
.field.is-invalid textarea:not(:placeholder-shown) + label { color: var(--ink); }
.field-note {
  display: grid; grid-template-rows: 0fr;
  font-size: .8125rem; line-height: 1.45; font-weight: 500; color: var(--ink);
  transition: grid-template-rows .5s var(--ease);
}
.field-note > span {
  display: block; overflow: hidden; min-height: 0;
  opacity: 0; transform: translateY(-4px);
  transition: opacity .35s var(--ease-soft), transform .5s var(--ease);
}
/* The space above the words is INSIDE the row that opens. As padding on the
   row itself it would count toward the row's smallest size, and every field
   would carry eight empty pixels under its rule while nothing was wrong. */
.field-note > span::before { content: ''; display: block; height: 8px; }
.field.is-invalid .field-note { grid-template-rows: 1fr; }
.field.is-invalid .field-note > span { opacity: 1; transform: none; transition-delay: .06s; }

/* The honeypot. Off the page rather than display:none, which some bots read
   as "leave this one empty". tabindex="-1" and aria-hidden keep it away from
   every person, and contact.js sends whatever is in it for the server to
   judge. */
.hp { position: absolute; left: -10000px; top: auto; width: 1px; height: 0; overflow: hidden; opacity: 0; }

/* ─── Sending ─────────────────────────────────────────────────────────────
   The one button, with one extra state. While the message is on its way the
   label cross-fades to "Sending…" (contact.js swaps the words while they are
   invisible), the button rests at a lower opacity and stops listening. The
   width is held while the words change, so the rectangle never twitches. */
.contact-actions { margin-top: clamp(30px, 5vw, 44px); display: grid; justify-items: start; }
.contact-actions .btn { transition: color .45s var(--ease), opacity .35s var(--ease-soft); }
[data-submit-label] { transition: opacity .25s var(--ease-soft); }
.contact-form.is-swapping [data-submit-label] { opacity: 0; }
.contact-form.is-sending .btn { pointer-events: none; }
.contact-actions .btn[disabled] { opacity: .6; cursor: default; }

/* A failure that belongs to no one field (too many messages, the server
   could not keep it, the connection dropped) is said once, under the
   button, opening the same way a field's note does. */
.contact-note {
  display: grid; grid-template-rows: 0fr;
  color: var(--ink); max-width: 30rem;
  transition: grid-template-rows .55s var(--ease);
}
.contact-note > span {
  display: block; overflow: hidden; min-height: 0;
  opacity: 0; transform: translateY(-4px);
  transition: opacity .4s var(--ease-soft), transform .55s var(--ease);
  text-wrap: pretty;
}
.contact-note > span::before { content: ''; display: block; height: 18px; }
.contact-note.is-in { grid-template-rows: 1fr; }
.contact-note.is-in > span { opacity: 1; transform: none; transition-delay: .08s; }

/* ─── Sent ────────────────────────────────────────────────────────────────
   The form lifts away, and in its place the thank-you rises a little way
   while the petal draws itself: four strokes, one after another, the F
   last, beginning once the words have landed. contact.js keeps the page
   below from jumping by easing the height across the swap. */
.contact-form.is-done {
  opacity: 0; transform: translateY(-10px);
  transition: opacity .45s var(--ease), transform .45s var(--ease);
}
.contact-form.is-gone { display: none; }
.contact-body.is-sizing { transition: height .6s var(--ease); }

.contact-sent {
  display: none;
  opacity: 0; transform: translateY(14px);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
.contact-sent.is-shown, .contact-sent:target { display: block; }
.contact-sent.is-in, .contact-sent:target { opacity: 1; transform: none; }
/* It takes focus so a screen reader lands on it; it is not a control, so
   it draws no ring. */
.contact-sent:focus { outline: none; }
.contact-sent .lede { margin-top: 14px; max-width: 30rem; }
.contact-sent > .link { margin-top: 26px; }
[data-sent-email] { color: var(--ink); overflow-wrap: anywhere; }
/* Without a script the span still holds "your email", which is not an address and should not be set like one. */
.contact-sent:target [data-sent-email] { color: inherit; }

.sent-mark {
  width: clamp(64px, 12vw, 88px); height: auto;
  color: var(--green);
  margin-bottom: 26px;
  /* The top petal's stroke sits a hair outside the drawing's own box. */
  overflow: visible;
}
/* Each stroke is one dash the length of its own path (pathLength="1" on
   every path makes that the same number for all four), parked a length
   back. Not 1 and 1: parked at exactly its own length, the dash ends ON the
   start of the path, and Chromium paints that end as a round cap sitting on
   nothing, four green specks before a line is drawn (story.css met the same
   thing in WebKit). Parked at 1.06 behind a 1.02 dash and a 1.4 gap, the end
   is off the path until the pen actually moves, and the dash still overruns
   the finish, so the finished mark is whole. */
.sent-mark path {
  stroke-dasharray: 1.02 1.4; stroke-dashoffset: 1.06;
  transition: stroke-dashoffset 1.6s var(--ease);
}
.contact-sent.is-in .sent-mark path,
.contact-sent:target .sent-mark path { stroke-dashoffset: 0; }
.sent-mark path:nth-child(1) { transition-delay: .25s; }
.sent-mark path:nth-child(2) { transition-delay: .45s; }
.sent-mark path:nth-child(3) { transition-delay: .65s; }
.sent-mark path:nth-child(4) { transition-delay: .85s; }

/* No script, and /contact#sent or /contact#trouble: that block, in place of
   the form, already finished. (:has() is the only way CSS can hide the form
   from the block that follows it; a browser without it shows both.) */
.contact-form:has(~ .contact-sent:target) { display: none; }

/* ─── Other ways to reach us ──────────────────────────────────────────────
   The Visit section's columns, always stacked: hairline, a heading in New
   Spirit, the one or two lines under it, hairline. The Visit column's own
   padding, which on a wide screen also sets "Call or text" on exactly the
   baseline of "Your name" across the page. */
.contact-col { padding: 28px 0 32px; border-bottom: 1px solid var(--line); }
.contact-col h2 {
  font-family: var(--display); font-weight: 300; letter-spacing: -.03em;
  font-size: 1.5rem; line-height: 1.2;
  margin-bottom: 14px;
}
.contact-col address { font-style: normal; }
.contact-col .status { margin: 14px 0 0; }
.contact-col address ~ .link { margin-top: 14px; }
.contact-col .link + .small { margin-top: 8px; }
/* The aside is on the first screen on a wide one, and its first hairline
   peeks into it on a phone. Revealed the moment the page loads, it would
   arrive before the heading; held back, it follows the form. contact.js
   lifts the hold once the page has arrived, so a column still waiting for a
   scroll keeps the quick cascade every other section has. */
.contact-aside [data-reveal] { transition-delay: calc(var(--lag, 600ms) + var(--d, 0) * 1ms); }

/* ─── Reduced motion ──────────────────────────────────────────────────────
   site.css already makes every transition instant; these are the states the
   motion would have ended in, stated so nothing waits on it. */
@media (prefers-reduced-motion: reduce) {
  .contact-form.is-done { transform: none; }
  .contact-sent { transform: none; }
  .sent-mark path { stroke-dashoffset: 0; }
  .field-note > span, .contact-note > span { transform: none; }
}

/* Without JavaScript nothing waits to be drawn, and the message field has
   room to be written in without a script to grow it. */
.no-js .sent-mark path { stroke-dashoffset: 0; }
.no-js .field textarea { min-height: calc(1.5em * 4 + 26px); overflow: auto; }
/* …and landing on #sent or #trouble scrolls that block up to the bar. With
   no site.js to take the bar out of its clear top-of-page state, the end of
   the greeting would pass under the wordmark; the bar keeps its cream. */
.no-js body:has(.contact-sent:target) .header::before { opacity: 1; }
/* A breath under the bar, so the aside's first rule is not a second line
   drawn right under the bar's own. */
.contact-sent:target { scroll-margin-top: 28px; }
