/* As Written: aswritten.grahamcreates.com
   One stylesheet for all three pages. Tokens mirror aswritten/Shared/Colors.swift
   and Typography.swift, which are the source of truth. If the app's palette
   moves, it moves here second, never first. */

/* ---- faces -------------------------------------------------------------
   Plus Jakarta Sans is the app's display face, declared in Info.plist and
   drawn by Typography.swift. The site uses the same face for the same reason
   the app does, so the page and the product are one recognisable thing.
   One variable file covers 400 through 800.

   Bespoke Serif is the serif signal only: quoted scripture and pulled lines,
   never running text. Same rule as the app, same file as grahamcreates.com.
   ---------------------------------------------------------------------- */
@font-face{
  font-family:'Jakarta';
  src:url('/fonts/plus-jakarta-sans-var.woff2') format('woff2-variations');
  font-weight:400 800;
  font-style:normal;
  font-display:swap;
}
@font-face{
  font-family:'Bespoke Serif';
  src:url('/fonts/bespoke-serif-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Bespoke Serif';
  src:url('/fonts/bespoke-serif-400-italic.woff2') format('woff2');
  font-weight:400; font-style:italic; font-display:swap;
}

/* ---- tokens ------------------------------------------------------------ */
:root{
  color-scheme: light dark;

  --ground:      light-dark(#F7F4EF, #0F0D0B);
  --surface:     light-dark(#EDEAE3, #1C1814);
  --ink:         light-dark(#1A1612, #F2EDE6);
  --ink-quiet:   light-dark(#6B5F52, #9B8E82);
  --sienna:      light-dark(#9B4B2A, #C47048);

  /* Two rule tokens on purpose, the same split grahamcreates.com uses.
     --rule only divides. --rule-live is the boundary of something
     interactive and has to clear 3:1 by itself (WCAG 1.4.11). */
  --rule:        light-dark(#DBD4C7, #2A2521);
  --rule-live:   light-dark(#A99C8B, #554C43);

  --space-2xs: 4px;
  --space-xs:  8px;
  --space-sm:  12px;
  --space-md:  16px;
  --space-lg:  24px;
  --space-xl:  32px;
  --space-2xl: 48px;
  --space-3xl: 64px;
  --space-4xl: 96px;

  /* ---- the motion contract ---------------------------------------------
     Shared with grahamcreates.com and deliberately identical there and here.
     The palette and the faces are the app's; the way things move is the
     brand's, and it does not get rebranded per product.

       - Transforms run on --ease-out. Colour runs on `ease`.
         A transform is a thing moving and wants the decelerating curve. A
         colour change has no position to decelerate into, and putting the two
         legs of one hover on different curves makes them arrive apart.
       - Colour 180-200ms, transform 160ms, entrance 520-560ms.
       - Anything pressable takes scale(0.97) on :active. That is the frame the
         press is answered on, before anything else has happened.
       - Every hover effect sits inside (hover:hover) and (pointer:fine).
         Focus effects never do: a touchscreen fires hover on tap, and keyboard
         focus is real intent on any device.
       - Hover intent only guards things that appear out of nothing. A control
         the pointer is already on responds immediately; delaying that reads as
         lag rather than as restraint.
       - Reduced motion means gentler, not absent. Opacity still carries the
         arrival; movement and blur do not.
     ---------------------------------------------------------------------- */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

  --measure: 66ch;
  --gutter: clamp(20px, 6vw, 88px);

  --sans: 'Jakarta', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --serif: 'Bespoke Serif', 'Iowan Old Style', Georgia, serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
}

*,*::before,*::after{ box-sizing:border-box; }

html{ -webkit-text-size-adjust:100%; }

body{
  margin:0;
  background:var(--ground);
  color:var(--ink);
  font-family:var(--sans);
  font-weight:400;
  font-size:17px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* Light type on a dark ground reads lighter than it is, so it gets more
   leading. One declaration, not a second stylesheet. */
@media (prefers-color-scheme: dark){
  body{ line-height:1.68; }
}

a{ color:inherit; }

::selection{ background:var(--sienna); color:var(--ground); }

:focus-visible{
  outline:2px solid var(--sienna);
  outline-offset:3px;
}

.wrap{
  max-width:1180px;
  margin-inline:auto;
  padding-inline:var(--gutter);
}

/* A scripture reference is set in mono because the app sets it in mono. It is
   a citation signal, not a decorative developer tell. */
.ref{
  font-family:var(--mono);
  font-size:0.86em;
  color:var(--sienna);
  white-space:nowrap;
}

/* ---- masthead ---------------------------------------------------------- */
.masthead{ padding-block:var(--space-xl) 0; }
.masthead-inner{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:var(--space-md);
  flex-wrap:wrap;
  padding-bottom:var(--space-sm);
}
/* Two colours, the same split HomeScreen.swift draws: "As" in the primary text
   colour, "Written" in the accent. The app's masthead is the name top left
   above the accent rule, and this is that masthead on the web. Do not set the
   whole thing in one colour; the two-tone form is the wordmark. */
.wordmark{
  font-size:clamp(1.05rem, 1.6vw, 1.2rem);
  font-weight:800;
  letter-spacing:-0.02em;
  margin:0;
  text-decoration:none;
  color:var(--ink);
}
.wordmark span{ color:var(--sienna); }
.status{
  font-size:0.6875rem;
  font-weight:600;
  letter-spacing:0.14em;
  text-transform:uppercase;
  color:var(--ink-quiet);
  margin:0;
}
.accent-rule{
  height:2px;
  background:var(--sienna);
  border:0;
  margin:0;
}

/* ---- hero -------------------------------------------------------------
   The app's first principle is that the headline is the hero and nothing
   competes with it. The landing page keeps that literally: the first thing on
   the page is a real fact from the app, set the way the app sets it. There is
   no product shot, because the app has no imagery to shoot.
   ---------------------------------------------------------------------- */
.hero{ padding-block:clamp(var(--space-2xl), 9vh, var(--space-4xl)) var(--space-3xl); }

.kicker{
  font-size:0.6875rem;
  font-weight:600;
  letter-spacing:0.14em;
  text-transform:uppercase;
  color:var(--sienna);
  margin:0 0 var(--space-lg);
}
.fact-headline{
  font-size:clamp(2.4rem, 7.4vw, 4.6rem);
  font-weight:800;
  line-height:1.03;
  letter-spacing:-0.035em;
  margin:0;
  max-width:19ch;
  text-wrap:balance;
}
.fact-body{
  margin:var(--space-xl) 0 0;
  max-width:var(--measure);
  font-size:clamp(1.0625rem, 1.4vw, 1.1875rem);
  color:var(--ink-quiet);
}
.fact-body strong{ color:var(--ink); font-weight:600; }

/* ---- source block ------------------------------------------------------ */
.sourced{
  margin-top:var(--space-2xl);
  padding-top:var(--space-md);
  border-top:1px solid var(--rule);
  max-width:var(--measure);
  display:grid;
  gap:var(--space-sm);
}
.source-line{
  display:flex;
  align-items:baseline;
  gap:var(--space-sm);
  flex-wrap:wrap;
  font-size:0.875rem;
  color:var(--ink-quiet);
}
.dots{
  display:inline-flex;
  gap:3px;
  align-items:center;
  flex:none;
  translate:0 -1px;
}
.dot{
  width:6px; height:6px;
  border-radius:50%;
  background:var(--sienna);
}
.dot.empty{
  background:transparent;
  box-shadow:inset 0 0 0 1px var(--rule-live);
}
.rating{
  font-size:0.6875rem;
  font-weight:600;
  letter-spacing:0.1em;
  text-transform:uppercase;
  color:var(--sienna);
  flex:none;
}
.cite{ font-style:normal; }

/* ---- the ask, and the launch list -------------------------------------- */
.ask{
  margin-top:var(--space-3xl);
  max-width:34rem;
}
.ask-note{
  margin:0 0 var(--space-md);
  font-size:0.9375rem;
  color:var(--ink-quiet);
  max-width:44ch;
}

/* Off-screen but still announced: the field's label, which the placeholder
   cannot replace, since a placeholder disappears the moment typing starts. */
.notify label, .sr{
  position:absolute;
  inline-size:1px; block-size:1px;
  overflow:hidden; clip-path:inset(50%);
  white-space:nowrap;
}

/* Off-screen rather than display:none, which bots skip. */
.trap{
  position:absolute;
  inset-inline-start:-9999px;
  inline-size:1px; block-size:1px;
  overflow:hidden;
}

.field{
  display:flex;
  gap:var(--space-xs);
  flex-wrap:wrap;
}

/* 1rem, not 0.9375. Under 16px iOS zooms the page on focus and never zooms
   back out. */
.field input{
  flex:1 1 13rem;
  min-inline-size:0;
  font:inherit;
  font-size:1rem;
  color:var(--ink);
  background:var(--ground);
  border:1px solid var(--rule-live);
  border-radius:0;
  padding:0.66em 0.85em;
  /* Colour on `ease`, per the contract. Both legs of this are colour. */
  transition:border-color 200ms ease, box-shadow 200ms ease;
}
.field input::placeholder{ color:var(--ink-quiet); opacity:1; }

/* The ring is drawn inside the control, so focus never changes its footprint
   and the field stays the same height as the button beside it. The transparent
   outline keeps a visible ring in forced-colors mode, where box-shadow is
   discarded. */
.field input:focus-visible{
  outline:2px solid transparent;
  outline-offset:0;
  border-color:var(--sienna);
  box-shadow:inset 0 0 0 1px var(--sienna);
}

.field button{
  flex:0 0 auto;
  font:inherit;
  font-size:0.9375rem;
  font-weight:600;
  letter-spacing:-0.01em;
  padding:0.66em 1.3em;
  background:var(--sienna);
  color:var(--ground);
  border:1px solid var(--sienna);
  border-radius:0;
  cursor:pointer;
  transition:background-color 200ms ease, border-color 200ms ease,
             transform 160ms var(--ease-out);
}
/* Not opacity. This is a solid button, so fading it fades the label with it and
   the result reads as the button going quiet rather than answering: a hover
   state that looks like a disabled state. The ground moves instead and the
   label stays at full strength. Mixed toward --ink rather than set to a second
   token, so it tracks the accent through light and dark without a pair of
   hand-picked values to keep in step. */
@media (hover:hover) and (pointer:fine){
  .field button:hover:not(:disabled){
    background:color-mix(in oklch, var(--sienna) 86%, var(--ink));
    border-color:color-mix(in oklch, var(--sienna) 86%, var(--ink));
  }
}
.field button:active{ transform:scale(0.97); }
.field button:disabled{ opacity:0.5; cursor:default; transform:none; }

.form-msg{
  margin:var(--space-xs) 0 0;
  min-block-size:1.4em;
  font-size:0.875rem;
  color:var(--ink-quiet);
}
.form-msg[data-state="error"]{ color:var(--sienna); }

.notify-done[hidden]{ display:none; }
.notify-done{
  display:flex;
  align-items:flex-start;
  gap:var(--space-sm);
  margin:0;
  font-size:0.9375rem;
  color:var(--ink-quiet);
}
.notify-done svg{
  inline-size:20px; block-size:20px;
  flex:none;
  color:var(--sienna);
  translate:0 2px;
}
/* Drawn on, rather than appearing drawn. 22 is the path length. The tick is the
   one moment on this page worth animating for its own sake: it is the receipt
   for the only thing the page asks anyone to do. */
.notify-done svg path{
  stroke-dasharray:22;
  stroke-dashoffset:22;
  animation:check 420ms var(--ease-out) 120ms forwards;
}
@keyframes check{ to{ stroke-dashoffset:0; } }

/* Its own keyframe rather than reusing .rise, which has no from: .rise carries
   its start state as declared style on the element and this element does not. */
.notify-done[data-enter]{
  animation:enter 340ms var(--ease-out) both;
}
@keyframes enter{
  from{ opacity:0; transform:translateY(6px); }
  to{ opacity:1; transform:none; }
}

/* ---- claims -----------------------------------------------------------
   Deliberately not three equal cards in a row. The three claims do not carry
   equal weight, so they do not get equal space.
   ---------------------------------------------------------------------- */
.claims{
  border-top:1px solid var(--rule);
  padding-block:var(--space-3xl);
  display:grid;
  gap:var(--space-2xl) var(--space-3xl);
  grid-template-columns:1fr;
}
@media (min-width:820px){
  .claims{ grid-template-columns:1.35fr 1fr; align-items:start; }
  .claim:first-child{ grid-row:span 2; }
}
.claim h2{
  font-size:clamp(1.5rem, 2.6vw, 2.1rem);
  font-weight:800;
  line-height:1.12;
  letter-spacing:-0.028em;
  margin:0 0 var(--space-md);
  text-wrap:balance;
}
.claim p{ margin:0; color:var(--ink-quiet); max-width:56ch; }
.claim p + p{ margin-top:var(--space-md); }

.pull{
  font-family:var(--serif);
  font-size:clamp(1.25rem, 2.2vw, 1.6rem);
  line-height:1.4;
  color:var(--ink);
  margin:var(--space-lg) 0 0;
  padding-left:var(--space-lg);
  border-left:1px solid var(--sienna);
}
.pull cite{
  display:block;
  margin-top:var(--space-sm);
  font-family:var(--sans);
  font-size:0.75rem;
  font-style:normal;
  font-weight:600;
  letter-spacing:0.1em;
  text-transform:uppercase;
  color:var(--ink-quiet);
}

/* ---- contents ---------------------------------------------------------- */
.contents{ border-top:1px solid var(--rule); padding-block:var(--space-3xl); }
.contents h2{
  font-size:0.6875rem;
  font-weight:600;
  letter-spacing:0.14em;
  text-transform:uppercase;
  color:var(--ink-quiet);
  margin:0 0 var(--space-xl);
}
.contents ul{
  list-style:none;
  margin:0; padding:0;
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(290px, 1fr));
  column-gap:var(--space-3xl);
}
.contents li{
  padding-block:var(--space-md);
  border-top:1px solid var(--rule);
  display:grid;
  grid-template-columns:2.5ch 1fr;
  gap:var(--space-md);
  align-items:baseline;
}
.num{
  font-family:var(--mono);
  font-size:0.75rem;
  color:var(--sienna);
  font-variant-numeric:tabular-nums;
}
.contents p{ margin:0; }
.contents b{ display:block; font-weight:600; letter-spacing:-0.012em; }
.contents span{ color:var(--ink-quiet); font-size:0.9375rem; }

/* ---- privacy note on the front page ------------------------------------ */
.note{ border-top:1px solid var(--rule); padding-block:var(--space-3xl); }
.note h2{
  font-size:clamp(1.5rem, 2.6vw, 2.1rem);
  font-weight:800;
  letter-spacing:-0.028em;
  line-height:1.12;
  margin:0 0 var(--space-md);
  max-width:22ch;
  text-wrap:balance;
}
.note p{ margin:0; color:var(--ink-quiet); max-width:var(--measure); }
.note p + p{ margin-top:var(--space-md); }

/* ---- documents: privacy and support ------------------------------------ */
.doc{
  padding-block:var(--space-3xl) var(--space-2xl);
  max-width:70ch;
}
.eyebrow{
  font-size:0.6875rem;
  font-weight:600;
  letter-spacing:0.14em;
  text-transform:uppercase;
  color:var(--sienna);
  margin:0 0 var(--space-md);
}
.doc h1{
  font-size:clamp(2.2rem, 6vw, 3.4rem);
  font-weight:800;
  line-height:1.04;
  letter-spacing:-0.035em;
  margin:0;
}
.lede{
  font-family:var(--serif);
  font-size:clamp(1.1875rem, 2vw, 1.4375rem);
  line-height:1.45;
  margin:var(--space-lg) 0 0;
  color:var(--ink);
  max-width:56ch;
}
.doc h2{
  font-size:clamp(1.1875rem, 2vw, 1.4375rem);
  font-weight:800;
  letter-spacing:-0.022em;
  line-height:1.2;
  margin:var(--space-2xl) 0 var(--space-md);
  padding-top:var(--space-md);
  border-top:1px solid var(--rule);
  text-wrap:balance;
}
.doc h3{
  font-size:1.0625rem;
  font-weight:600;
  letter-spacing:-0.012em;
  margin:var(--space-xl) 0 var(--space-xs);
  text-wrap:balance;
}
.doc p{ margin:var(--space-md) 0 0; color:var(--ink-quiet); }
.doc h2 + p, .doc h3 + p{ margin-top:0; }
.doc strong{ color:var(--ink); font-weight:600; }
.doc code{
  font-family:var(--mono);
  font-size:0.86em;
  color:var(--ink);
}
.doc ul{
  margin:var(--space-md) 0 0;
  padding:0;
  list-style:none;
  color:var(--ink-quiet);
  display:grid;
  gap:var(--space-xs);
}
.doc li{
  padding-left:var(--space-lg);
  position:relative;
}
.doc li::before{
  content:'';
  position:absolute;
  left:0;
  top:0.72em;
  width:7px;
  height:1px;
  background:var(--sienna);
}
/* Both legs on `ease` at the same duration. The colour and the rule under it
   are one event; running them on different curves had them arriving apart. */
.doc a, .note a{
  text-decoration:none;
  border-bottom:1px solid var(--rule-live);
  padding-bottom:1px;
  transition:color 180ms ease, border-color 180ms ease;
}
@media (hover:hover) and (pointer:fine){
  .doc a:hover, .note a:hover{ color:var(--sienna); border-bottom-color:var(--sienna); }
}

/* ---- footer ------------------------------------------------------------ */
footer{
  border-top:1px solid var(--rule);
  padding-block:var(--space-xl) var(--space-3xl);
  display:flex;
  align-items:baseline;
  gap:var(--space-sm) var(--space-xl);
  flex-wrap:wrap;
  font-size:0.875rem;
  color:var(--ink-quiet);
}
footer nav{ display:flex; gap:var(--space-lg); flex-wrap:wrap; }
footer a{
  text-decoration:none;
  border-bottom:1px solid var(--rule-live);
  padding-bottom:1px;
  transition:color 180ms ease, border-color 180ms ease;
}
@media (hover:hover) and (pointer:fine){
  footer a:hover{ color:var(--sienna); border-bottom-color:var(--sienna); }
}
/* Pushed to the far end rather than spaced by justify-content, so the offer in
   the middle sits next to the nav it belongs beside instead of floating in the
   gap between two things it is not part of. */
footer p{ margin:0; margin-inline-start:auto; }

/* ---- buy me a coffee ---------------------------------------------------
   A plain anchor, not their widget. The widget is a script from
   cdnjs.buymeacoffee.com that injects an iframe and pulls its artwork from
   their CDN, so it would need script-src, img-src, frame-src and connect-src
   in _headers opened up to a third-party host, on a site whose CSP is
   default-src 'none'. It does not even take the payment in place: the bubble
   collects an amount and a name and then hands off to their checkout. Same
   destination, none of the cost.

   The cup is redrawn to this site's terms rather than copied as artwork: the
   same 24-unit grid and 1.6 stroke as grahamcreates.com, in --sienna, with
   square corners like every other control here. What carries across is the
   behaviour, not the palette.
   ---------------------------------------------------------------------- */
.coffee{
  display:inline-flex;
  align-items:center;
  gap:0.45em;
  color:var(--ink);
  /* The one link in the footer that asks for something rather than answering
     something, so it is the one that carries its rule in the accent at rest.
     The others declare themselves on hover; an offer that only appears on
     hover is not made to anyone on a touch screen. */
  border-bottom-color:var(--sienna);
}
.coffee svg{
  inline-size:16px;
  block-size:16px;
  flex:none;
  fill:none;
  stroke:currentColor;
  stroke-width:1.6;
  stroke-linecap:round;
  stroke-linejoin:round;
  color:var(--sienna);
}
/* Steam is the whole reward, and it is descriptive rather than decorative: it
   is the one motion on this site that says coffee instead of saying motion.
   Held back to hover because the footer is read once and a cup that steams for
   the entire visit is a fidget. */
.coffee .steam{
  opacity:0;
  translate:0 3px;
  transition:opacity 220ms var(--ease-out), translate 220ms var(--ease-out);
}
.coffee .steam:nth-of-type(2){ transition-delay:60ms; }
/* Focus is outside the hover query on purpose: a touch screen fires hover on
   tap, and keyboard focus is real intent on any device. */
.coffee:focus-visible .steam{ opacity:1; translate:0 0; }
@media (hover:hover) and (pointer:fine){
  .coffee:hover .steam{ opacity:1; translate:0 0; }
}

/* ---- load ------------------------------------------------------------
   One orchestrated entrance, staggered, and nothing after it. The page is read
   once. A reader who scrolls back up does not want it performed again.
   ---------------------------------------------------------------------- */
.rise{
  opacity:0;
  transform:translateY(10px);
  animation:rise 520ms var(--ease-out) forwards;
}
/* Gaps of 60ms. Stagger reads as one cascade up to about 80ms and as a queue
   after that, and the last child here starts at 240ms so the page has settled
   by 760ms. Anything longer is the reader waiting on decoration. */
.rise:nth-child(1){ animation-delay:0ms; }
.rise:nth-child(2){ animation-delay:60ms; }
.rise:nth-child(3){ animation-delay:120ms; }
.rise:nth-child(4){ animation-delay:180ms; }
.rise:nth-child(5){ animation-delay:240ms; }
@keyframes rise{
  to{ opacity:1; transform:translateY(0); }
}

/* Gentler, not absent. Everything that moved still arrives, and it arrives by
   opacity alone. The entrance keeps its stagger, because the stagger is what
   says these are separate things; only the travel goes. */
@media (prefers-reduced-motion: reduce){
  .rise{ transform:none; animation-name:fade; animation-duration:280ms; animation-timing-function:ease; }
  @keyframes fade{ to{ opacity:1; } }

  /* The confirmation kept a 6px rise. Redefining the keyframe here drops the
     movement and leaves the fade, which is the part that explains the form
     being replaced. */
  @keyframes enter{
    from{ opacity:0; }
    to{ opacity:1; }
  }

  /* A stroke drawing itself is movement, and this was the last animation on
     the page still running under reduce. The tick arrives finished. */
  .notify-done svg path{ animation:none; stroke-dashoffset:0; }

  /* The rise is the part that moves, so the rise is the part that goes. The
     steam still resolves on hover, by opacity alone. */
  .coffee .steam{ translate:0 0; transition:opacity 140ms linear; }
}
