/* The look. Written against docs/design-direction.md, which argues every
 * decision in here and is the place to change one.
 *
 * Three ideas hold the whole file up.
 *
 * ONE SHEET. The background is not a fill, it is a lit surface: a warm radial
 * light falling from above and a fine paper fibre laid over everything,
 * including over the ink. That is the difference between minimal and
 * unfinished, and it is what two previous passes were missing.
 *
 * ONE FRAME. Rail, field, act. The rail and the act never rebuild and never
 * move; only the field changes. So a screen change is content settling inside
 * a standing object rather than one document being swapped for another, and
 * the single Start button can live from the home screen to the end of a call
 * without ever unmounting.
 *
 * ONE ARRIVAL. Everything that appears does the same three things: fades in,
 * de-blurs, and rises six pixels. Nothing in this app slides in from the side.
 * Because every motion is on the block axis or in opacity, the whole thing
 * mirrors for Arabic without one animation being rewritten.
 *
 * Colour is the owner's. What is added is light, not hue. The one genuinely
 * new colour is --amber-ink, and it is an accessibility repair rather than a
 * taste decision: the offered word is amber TEXT, and #D98C1F on this paper is
 * 2.60:1, which is illegible at any size. #A9640C is the same hue at 4.45:1.
 * Amber then appears in exactly one place in the whole app, which is the rule
 * the brand already had and this file now actually keeps.
 *
 * Every box property is logical. One physical value is declared, --from-edge,
 * because a transform-origin cannot be logical, and it is declared once.
 */

:root {
  /* the sheet */
  --paper: #FBFAF8;
  --paper-lit: #FFFEFC;
  --paper-shade: #F6F1E7;
  --card: #FFFDFB;

  /* what is written on it */
  --ink: #12100E;
  --grey: #6E6862;          /* 5.3:1 on paper */
  /* The brand's amber, kept here because it is the brand's and because the
     marketing site sets type in it. Nothing in the app draws with it. */
  --amber: #D98C1F;
  /* What the app actually uses, and the only place amber appears anywhere in
     it: the word the tutor has just put in your mouth. #D98C1F on this paper
     measures 2.60:1, which is illegible at any size; this is the same hue
     darkened to 4.45:1. */
  --amber-ink: #A9640C;
  --hair: #E4E0DA;

  /* Voice is a person speaking. Interface is the app. Note is everything the
     app says quietly. Nothing crosses roles. */
  --voice: 'Newsreader', 'Noto Naskh Arabic', Georgia, serif;
  --ui: 'Archivo', 'IBM Plex Sans Arabic', system-ui, sans-serif;
  --note: 'Public Sans', 'IBM Plex Sans Arabic', system-ui, -apple-system, sans-serif;
  --arabic: 'IBM Plex Sans Arabic', 'Geeza Pro', 'Noto Naskh Arabic', sans-serif;
  --arabic-voice: 'Noto Naskh Arabic', 'Geeza Pro', 'IBM Plex Sans Arabic', serif;

  --gutter: 24px;
  --column: 520px;
  --radius: 14px;

  --ease: cubic-bezier(.2, .8, .2, 1);
  --ease-out: cubic-bezier(.5, 0, .75, 0);
  --t-tap: 140ms;
  --t: 320ms;
  --t-screen: 560ms;

  /* The one physical value in the file. A transform-origin cannot be logical,
     and the ink has to drain out of the button from the edge the reader starts
     at, whichever edge that is. */
  --from-edge: left;
}

/* In the Arabic direction the Arabic faces lead in every role, and the
   numerals stay Western, because Western Arabic numerals are what a Gulf phone
   shows all day and Eastern numerals are what a designer reaches for when they
   want a screen to look Arabic. */
html[dir="rtl"] {
  --voice: 'Noto Naskh Arabic', 'Newsreader', serif;
  --ui: 'IBM Plex Sans Arabic', 'Archivo', system-ui, sans-serif;
  --note: 'IBM Plex Sans Arabic', 'Public Sans', system-ui, sans-serif;
  --from-edge: right;
}

* { box-sizing: border-box; }

/* The paper is on the root and NOWHERE else. A background on `body` is
   painted after every negative-z descendant of the root stacking context, so
   putting one there hid the light, the ink and the fibre underneath an opaque
   sheet of flat colour. That is one line and it cost a whole design pass. */
html { background: var(--paper); }

body {
  margin: 0;
  /* Bounded, not merely "at least". With min-block-size the flex column could
     grow past the viewport and the way out of a live call would be pushed
     below the fold. */
  block-size: 100dvh;
  max-block-size: 100dvh;
  overflow: hidden;
  color: var(--ink);
  font: 400 17px/1.55 var(--note);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The light. A warm source above and slightly behind the top edge, so the
   sheet is brightest where the eye lands and falls away at the far corners. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -3;
  background:
    radial-gradient(130% 96% at 50% -12%,
      var(--paper-lit) 0%, var(--paper) 44%, var(--paper-shade) 100%),
    var(--paper);
}

/* The fibre. It lies OVER the ink as well as over the paper, because ink soaks
   into a sheet rather than resting on top of it, and that one ordering is most
   of why the stroke reads as written rather than as drawn. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .055;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>");
}

/* The qalam. Transparent, so it can only ever add marks to the sheet and can
   never become a rectangle of not-paper, which is what sank the sphere. */
#ink {
  position: fixed;
  inset: 0;
  z-index: -2;
  inline-size: 100%;
  block-size: 100%;
  display: block;
  pointer-events: none;
}

/* Arabic sits lower and needs more air between lines than Latin at the same
   optical weight, and negative tracking breaks the joins between letters. */
html[dir="rtl"] body { font-size: 17.5px; line-height: 1.75; letter-spacing: 0; }

bdi.ar {
  font-family: var(--arabic);
  font-size: 1.06em;
  line-height: 1.7;
  letter-spacing: 0;
}
html[dir="rtl"] bdi.ar { font-size: 1em; }
/* Inside something somebody said, Arabic gets the voice face too. */
.utterance bdi.ar, .phrase bdi.ar { font-family: var(--arabic-voice); }

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

/* -- the frame ----------------------------------------------------------- */

.shell {
  position: relative;
  z-index: 1;
  block-size: 100dvh;
  max-inline-size: calc(var(--column) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: calc(16px + env(safe-area-inset-top)) calc(18px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
}

.rail {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-block-end: 4px;
}

.wordmark {
  font: 600 30px/1 var(--ui);
  letter-spacing: -.035em;
  margin: 0;
  text-align: start;
}
html[dir="rtl"] .wordmark { letter-spacing: 0; font-size: 27px; }

.rail-act {
  background: none;
  border: 0;
  padding: 4px 0;
  margin: 0;
  color: var(--grey);
  font: 400 .93rem/1.3 var(--note);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--t-tap) ease;
}
.rail-act:hover { color: var(--ink); }
.rail-act[hidden] { display: none; }

.field {
  flex: 1 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  position: relative;
}

.act {
  flex: 0 0 auto;
  margin-block-start: 12px;
}
.under {
  min-block-size: 26px;
  margin-block-start: 12px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* -- screens ------------------------------------------------------------- */

 /* Every screen is its own scroll container, and the call is the one
    exception. A previous pass locked the body to 100dvh to keep "End the call"
    on screen and silently truncated the record and the after screen: 169px of
    the record was unreachable at 390x844 and 458px at 320x568, with no gesture
    that could reach it. The frame fixes this structurally rather than with a
    rule: the act bar lives outside the field, so the way out is always on
    screen, and the field scrolls underneath it. */
.screen {
  display: none;
  flex: 1 1 auto;
  min-block-size: 0;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.screen.on { display: flex; }
/* Content that runs under the act bar fades into it rather than being sliced
   off mid-word, so a cut line reads as "there is more below" instead of as a
   rendering fault. */
.screen {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 26px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 26px), transparent);
}
/* The call is viewport locked. Her words are anchored and the ink needs the
   air under them to stay where it was put. */
#s-call { overflow: hidden; -webkit-mask-image: none; mask-image: none; }

.col { inline-size: 100%; }

/* Ink settles. It does not slide. Every arrival in this app is this one
   gesture, and there is no second one. */
@keyframes settle {
  from { opacity: 0; filter: blur(4px); transform: translateY(6px); }
  to   { opacity: 1; filter: blur(0);   transform: none; }
}
@keyframes lift {
  from { opacity: 1; filter: blur(0);   transform: none; }
  to   { opacity: 0; filter: blur(4px); transform: translateY(-6px); }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* Leaving is quick, except at the end of a call, which is allowed to be
   unhurried. Beginnings are 320ms and that ending is 460ms on purpose. */
.screen.leaving { animation: lift 220ms var(--ease-out) both; }
.screen.leaving.slow { animation-duration: 460ms; }
.screen.arriving > *,
.screen.arriving > .col > * { animation: settle var(--t) var(--ease) backwards; }
.screen.arriving > .col > *:nth-child(2) { animation-delay: 70ms; }
.screen.arriving > .col > *:nth-child(3) { animation-delay: 140ms; }
.screen.arriving > .col > *:nth-child(4) { animation-delay: 210ms; }
.screen.arriving > .col > *:nth-child(5) { animation-delay: 280ms; }
.screen.arriving > .col > *:nth-child(6) { animation-delay: 350ms; }

/* The page arriving for the first time. The sheet resolves, then the mark,
   then the sentence, then the button, which is where the thumb is going. */
/* Hidden until the copy is in, because every string on every screen is
   injected by app.js and an empty hero under an empty button is worse than a
   quarter second of paper. The keyframe is the safety net: if the module never
   arrives, the shell reveals itself anyway rather than leaving a blank app. */
body.booting .shell { opacity: 0; animation: reveal 1ms 2.5s forwards; }
@keyframes reveal { to { opacity: 1; } }
body.woke .rail { animation: settle 460ms var(--ease) 60ms backwards; }
body.woke .field { animation: settle 520ms var(--ease) 200ms backwards; }
body.woke .act { animation: rise 560ms var(--ease) 320ms backwards; }
body.woke .shell { opacity: 1; transition: opacity 260ms ease; }

/* -- type roles ---------------------------------------------------------- */

.hero {
  font: 600 clamp(33px, 9vw, 44px)/1.04 var(--ui);
  letter-spacing: -.04em;
  margin: 0;
  max-inline-size: 15ch;
  text-align: start;
  text-wrap: balance;
}
html[dir="rtl"] .hero {
  letter-spacing: 0;
  line-height: 1.3;
  font-size: clamp(29px, 7.6vw, 38px);
  max-inline-size: 17ch;
}

h2.head {
  font: 600 clamp(23px, 6vw, 27px)/1.2 var(--ui);
  letter-spacing: -.025em;
  margin: 0 0 18px;
  text-align: start;
}
html[dir="rtl"] h2.head { letter-spacing: 0; line-height: 1.45; }

.label {
  font: 600 11px/1 var(--ui);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--grey);
  display: block;
  margin: 0 0 14px;
}
html[dir="rtl"] .label { letter-spacing: .06em; font-size: 12px; }

.lede { font-size: 1.06rem; margin: 18px 0 0; color: var(--grey); text-align: start; }
.note { color: var(--grey); font-size: .91rem; line-height: 1.5; margin: 0; text-align: start; }

.rule { border: 0; border-block-start: 1px solid var(--hair); margin-block: 32px; }

/* -- the one button ------------------------------------------------------ */

.btn {
  position: relative;
  isolation: isolate;
  inline-size: 100%;
  padding-block: 20px;
  padding-inline: 18px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--paper);
  font: 500 1.06rem/1 var(--ui);
  cursor: pointer;
  overflow: hidden;
  transition: transform var(--t-tap) var(--ease),
              color var(--t-tap) ease var(--t-tap);
}
/* The fill is a layer of its own, so it can be drained out of the button
   without the button moving, changing size or re-rendering its label. */
.btn::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--ink);
  transform-origin: var(--from-edge) center;
  transition: transform 420ms var(--ease), opacity var(--t-tap) ease;
}
.btn:hover::before { opacity: .9; }
.btn:active { transform: scale(.988); }
.btn:disabled { cursor: default; }
.btn:disabled::before { opacity: .45; }

/* The ink leaves the button and becomes the line. */
.btn.draining { color: var(--ink); border-color: var(--hair); }
.btn.draining::before { transform: scaleX(0); }
.btn.draining:disabled::before { opacity: 1; }

/* A heartbeat, not a progress bar. There is no progress to report and there is
   no spinner anywhere in this app. */
.btn.waiting::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1px solid var(--ink);
  opacity: .2;
  animation: beat 2.4s ease-in-out infinite;
  pointer-events: none;
}
@keyframes beat { 0%, 100% { opacity: .1; } 50% { opacity: .32; } }

.btn .lbl {
  display: inline-block;
  transition: opacity 130ms ease, filter 130ms ease;
}
.btn .lbl.out { opacity: 0; filter: blur(3px); }

.btn.quiet {
  color: var(--ink);
  border-color: var(--hair);
  padding-block: 16px;
  font-weight: 400;
}
.btn.quiet::before { background: none; }
.btn.small { padding-block: 14px; font-size: 1rem; }

/* -- home ---------------------------------------------------------------- */

#s-home .col { padding-block-start: 6vh; }

#record-link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  inline-size: 100%;
  padding: 3px 0;
  background: none;
  border: 0;
  color: var(--grey);
  font: 400 .95rem/1.4 var(--note);
  text-align: start;
  cursor: pointer;
  transition: color var(--t-tap) ease;
}
#record-link:hover { color: var(--ink); }
#record-link[hidden], #peek[hidden] { display: none; }
#record-link .chev { opacity: .5; }
html[dir="rtl"] #record-link .chev { transform: scaleX(-1); }

/* The microphone is the only permission this product spends, so it is spent on
   the one button and explained one sentence before the sheet appears, on the
   first run and never again. */
#mic-note {
  opacity: 0;
  transition: opacity .3s ease;
  block-size: 0;
  overflow: hidden;
}
#mic-note.on { opacity: 1; block-size: auto; margin-block-end: 8px; }

/* -- sign in ------------------------------------------------------------- */

#s-signin .col { padding-block-start: 6vh; }
#handle {
  font: 400 1.15rem/1 var(--ui);
  inline-size: 100%;
  padding: .95em .9em;
  margin-block-start: 10px;
  border-radius: 10px;
  border: 1px solid var(--hair);
  background: var(--card);
  color: var(--ink);
  letter-spacing: .02em;
}
/* The same ring as everything else. An amber ring would have been the second
   thing in the app wearing the colour that means "say this". */
#handle:focus { outline: 2px solid var(--ink); outline-offset: 2px; border-color: var(--grey); }
.err { color: #8C3B27; font-size: .9rem; margin: 12px 0 0; text-align: start; }
.err[hidden] { display: none; }

/* -- the call ------------------------------------------------------------ */

/* Her words are bottom anchored, so the newest line always sits in the same
   place and the eye never hunts for it, and the air under them is where the
   ink is written. The padding is that air. */
.stage {
  flex: 1 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-block-end: min(31vh, 268px);
  overflow: hidden;
  /* A line long enough to overflow the top of the stage fades out rather than
     being sliced through the middle of its letters. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 22px);
}

/* A serif, because this is somebody talking. It is the only serif in the app
   and it is never used for a heading. */
.utterance {
  font: 400 clamp(23px, 6.2vw, 28px)/1.42 var(--voice);
  letter-spacing: -.004em;
  margin: 0 0 18px;
  text-align: start;
  text-wrap: pretty;
  transition: opacity .6s ease;
  animation: settle .5s var(--ease) backwards;
}
html[dir="rtl"] .utterance { letter-spacing: 0; line-height: 1.75; font-size: clamp(21px, 5.6vw, 25px); }
/* .58 and not lower. At .52 an earlier version measured 3.76:1, which clears
   AA large and misses AA normal. A line she said thirty seconds ago is still a
   line somebody has to be able to read. */
.utterance.past { opacity: .58; }

/* The recast, brought into the call. The word arrives, fades in, de-blurs and
   is simply there. It never replaces anything, because the learner's own words
   are not on this screen to be replaced. See web/call.js for the argument. */
bdi.said, span.said { color: var(--amber-ink); font-weight: 500; }
bdi.arriving, span.arriving { animation: arrive .5s .1s ease both; }
@keyframes arrive {
  from { opacity: 0; filter: blur(3px); }
  to   { opacity: 1; filter: blur(0); }
}

#peek {
  align-self: flex-start;
  background: none;
  border: 0;
  padding: 3px 0;
  color: var(--grey);
  font: 400 .93rem/1.4 var(--note);
  text-align: start;
  cursor: pointer;
  transition: color var(--t-tap) ease;
}
#peek:hover { color: var(--ink); }

#slot {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.card {
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 16px;
  padding: 18px;
  box-shadow: 0 1px 2px rgba(18, 16, 14, .04), 0 12px 30px rgba(18, 16, 14, .06);
  animation: settle .4s var(--ease) both;
}
.card[hidden] { display: none; }
.card h2 { margin: 0 0 4px; font: 500 1.06rem/1.35 var(--ui); letter-spacing: -.015em; }
.card p { margin: 0 0 14px; color: var(--grey); font-size: .92rem; }
.card .btn + .btn { margin-block-start: 8px; }

/* -- what was said ------------------------------------------------------- */

#sheet {
  position: fixed;
  inset: 0;
  background: rgba(18, 16, 14, .18);
  z-index: 20;
  display: none;
}
#sheet.on { display: block; animation: fade .22s ease both; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
#sheet .panel {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  max-block-size: 82dvh;
  overflow-y: auto;
  background: var(--card);
  border-start-start-radius: 22px;
  border-start-end-radius: 22px;
  padding: 26px var(--gutter) calc(20px + env(safe-area-inset-bottom));
  animation: up .34s var(--ease) both;
  max-inline-size: calc(var(--column) + var(--gutter) * 2);
  margin-inline: auto;
}
@keyframes up { from { transform: translateY(18px); } to { transform: none; } }
.turn { margin: 0 0 20px; }
.turn .who {
  display: block;
  font: 600 11px/1 var(--ui);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--grey);
  margin-block-end: 6px;
}
html[dir="rtl"] .turn .who { letter-spacing: .06em; }
.turn .words { font: 400 1.05rem/1.5 var(--voice); }
.turn.you .words { color: var(--grey); font-family: var(--note); font-size: 1rem; }
#sheet .btn { margin-block-start: 10px; }

/* -- after the call ------------------------------------------------------ */

#s-after .col { padding-block-start: 6vh; }
.said-item { margin-block-end: 30px; animation: settle .46s var(--ease) backwards; }
.said-item:nth-child(2) { animation-delay: 90ms; }
.said-item:nth-child(3) { animation-delay: 180ms; }
.said-item:nth-child(4) { animation-delay: 270ms; }
.said-item .phrase {
  font: 400 clamp(28px, 8vw, 34px)/1.3 var(--voice);
  margin: 0;
  text-align: start;
}
.said-item .gloss { margin: 6px 0 0; color: var(--grey); font-size: .95rem; }
#after-more { margin-block-start: 6px; }
#after-more[hidden] { display: none; }

/* -- the record ---------------------------------------------------------- */

/* The graph and the headline figure are one object. The line rises and it
   terminates in the number, so the number reads as where the line got to
   rather than as a score in a box. It is drawn with the same nib as the ink on
   the call screen: during a call the ink evaporates, and this is the one line
   that survives. */
.growth { margin-block-start: 8px; }
.growth .fig {
  font: 600 clamp(56px, 16vw, 74px)/1 var(--ui);
  letter-spacing: -.05em;
  text-align: end;
  font-variant-numeric: tabular-nums;
  margin: 0;
}
.growth svg { inline-size: 100%; block-size: 132px; display: block; margin-block-start: -6px; }
html[dir="rtl"] .growth svg { transform: scaleX(-1); }
/* Filled, not stroked: it is a ribbon swept by a nib, and its width carries
   the direction it is travelling. The hairline stroke on top is the floor a
   real nib has, so the line can never break in two. */
.growth path {
  fill: var(--ink);
  stroke: var(--ink);
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.growth .axis {
  display: flex;
  justify-content: space-between;
  color: var(--grey);
  font: 600 11px/1 var(--ui);
  letter-spacing: .14em;
  text-transform: uppercase;
  margin-block-start: 10px;
}
html[dir="rtl"] .growth .axis { letter-spacing: .04em; font-size: 11px; }
.growth-label { margin-block-start: 14px; font-size: 1rem; }

.cando { margin: 0; padding: 0; list-style: none; }
.cando li {
  position: relative;
  padding-inline-start: 22px;
  margin-block-end: 14px;
  font: 400 1.02rem/1.5 var(--note);
}
.cando li::before {
  content: '';
  position: absolute;
  inset-inline-start: 2px;
  inset-block-start: .62em;
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--ink);
  opacity: .3;
}

.scoreline { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.scoreline .label { margin: 0; }
.scoreline .n {
  font: 600 36px/1 var(--ui);
  letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
}
#score-note { margin-block: 14px 24px; }
.stat {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 18px;
  padding-block: 13px;
  border-block-start: 1px solid var(--hair);
  font-size: 1rem;
}
.stat b {
  font: 500 1.05rem/1.3 var(--ui);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* -- bigger than a phone ------------------------------------------------- */

@media (min-width: 600px) {
  :root { --gutter: 28px; }
  .utterance { font-size: clamp(25px, 3.2vw, 30px); }
  .shell { padding-block: 34px 30px; }
  #s-home .col, #s-signin .col, #s-after .col { padding-block-start: 9vh; }
}
@media (min-width: 900px) {
  /* A wider sheet once there is a desk to put it on. The ink still bleeds the
     whole viewport behind it, so the page is a crop of a larger sheet rather
     than a framed one. */
  :root { --column: 560px; }
}

/* -- motion off ---------------------------------------------------------- */

/* The app still has to look designed with this on, and it does, because the
   design is in the substrate, the type and the composition. What is kept is
   the two things that are feedback rather than decoration: the stroke still
   appears when she speaks and the sheet still opens when you speak, at reduced
   amplitude, because removing them would take away the only signal that
   anything is listening. web/ink.js handles that half. */
@media (prefers-reduced-motion: reduce) {
  .screen.arriving > *,
  .screen.arriving > .col > *,
  .screen.leaving,
  .said-item, .utterance, .card,
  #sheet.on, #sheet .panel,
  body.woke .rail, body.woke .field, body.woke .act {
    animation: none !important;
  }
  bdi.arriving, span.arriving { animation: fade .2s ease both !important; }
  .btn.waiting::after { animation: none; opacity: .2; }
  .btn, .btn::before, .btn .lbl, body.woke .shell { transition-duration: 1ms !important; }
}

/* Digits stay left-to-right; the block sits where the eye already is. */
html[dir="rtl"] #handle { text-align: right; }
