/* ============================================================
   krislocascio.com — draft site system
   Converges the resume palette (navy + sky) with the existing
   Didot display face. Hero keeps the pastel rotation; every
   content surface sits on opaque paper above it.
   ============================================================ */

:root {
  --ink: #072248;
  --ink-soft: #3a4b66;
  --ink-faint: #6b788c;
  --sky: #8fc1e3;
  --sky-deep: #2e6fa3;
  --teal: #1f7a6f;
  --paper: #fbfaf7;
  --surface: #ffffff;
  --surface-2: #f2f0eb;
  --rule: #d8dee7;
  --rule-soft: #e7eaef;

  --display: "linotype-didot", "Playfair Display", Georgia, serif;
  --body: "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "Courier New", Courier, monospace;

  --measure: 66ch;
  --gutter: clamp(20px, 5vw, 64px);
}

* { box-sizing: border-box; }

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

/* Anchored sections stop clear of the top edge instead of flush against it. */
[id] { scroll-margin-top: 28px; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--sky-deep); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { text-decoration: wavy underline var(--sky-deep) 2px; }
a:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }

code {
  font-family: var(--mono);
  font-size: .86em;
  background: var(--surface-2);
  padding: 1.5px 5px;
  border-radius: 3px;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--ink); color: #fff; padding: 12px 18px;
  font-size: 14px; text-decoration: none;
}
.skip:focus { left: 0; }

/* ---------- shared rhythm ---------- */

.shell { max-width: 1120px; margin: 0 auto; padding: 0 var(--gutter); }
.measure { max-width: var(--measure); }

.section { padding: clamp(52px, 8vw, 96px) 0; border-top: 1px solid var(--rule); }
.section:first-of-type { border-top: 0; }

.eyebrow {
  margin: 0 0 16px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--sky-deep);
}

/* Didone f has a long overhang that crashes into a following i, which is what
   the fi ligature exists to fix. Any non-zero letter-spacing disables
   ligatures, since a ligature is one glyph and tracking spaces characters, so
   display type is set without tracking. */
h1, h2, h3 {
  font-family: var(--display);
  font-style: italic;
  font-weight: 400;
  font-variant-ligatures: common-ligatures discretionary-ligatures;
  letter-spacing: normal;
}

h2 {
  margin: 0 0 22px;
  font-size: clamp(28px, 4.4vw, 44px);
  line-height: 1.06;
  text-wrap: balance;
}

h3 {
  margin: 0 0 10px;
  font-size: clamp(21px, 2.6vw, 27px);
  line-height: 1.14;
}

p { margin: 0 0 18px; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

.lede { font-size: clamp(18px, 2.1vw, 21px); line-height: 1.48; color: var(--ink-soft); }

/* ---------- nav ---------- */

.masthead {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 22px 0 0;
}
.masthead__logo { width: 54px; display: block; }
.masthead__logo img { width: 100%; height: auto; display: block; }
.masthead__nav { display: flex; gap: 22px; flex-wrap: wrap; }
.masthead__nav a {
  font-size: 12px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink); text-decoration: none;
}
.masthead__nav a:hover { text-decoration: wavy underline var(--ink) 2px; }

/* ---------- hero: the only rotating surface ---------- */

.hero {
  position: relative;
  background-color: #bae1ff;
  transition: background-color 5s ease;
  padding-bottom: clamp(56px, 9vw, 104px);
}
.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(28px, 5vw, 64px);
  align-items: center; /* vertically centres the name against the taller column */
  padding-top: clamp(46px, 7vw, 84px);
}

.hero__id { align-self: center; }

.hero__name {
  margin: 0 0 10px;
  font-family: var(--display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(38px, 6vw, 76px);
  line-height: .96;
  letter-spacing: -.01em;
  text-transform: uppercase;
  text-wrap: balance;
}
/* The name carries an internal capital. Setting it all-caps would erase
   that, so this one letter opts out of the transform and the real
   spelling survives the uppercase treatment. */
.hero__name .name__lower { text-transform: none; }

.hero__role {
  margin: 0;
  font-size: 11px; font-weight: 700; letter-spacing: .18em;
  text-transform: uppercase; color: var(--ink); opacity: .7;
}
.hero__thesis {
  font-variant-ligatures: common-ligatures discretionary-ligatures;
  margin: 0 0 20px;
  max-width: 28ch;
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(24px, 3.4vw, 40px);
  line-height: 1.08;
  text-wrap: balance;
}
.hero__body { max-width: 58ch; font-size: clamp(15.5px, 1.5vw, 17px); }

.hero__cta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 26px; }

@media (max-width: 760px) {
  .hero__inner { grid-template-columns: 1fr; align-items: start; }
  .hero__id { margin-bottom: 4px; }
  .hero__thesis {
  font-variant-ligatures: common-ligatures discretionary-ligatures; max-width: none; }
}
.btn {
  display: inline-block;
  padding: 11px 20px;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font-family: var(--body);
  font-size: 12px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; text-decoration: none;
  cursor: pointer;
}
.btn:hover { text-decoration: none; background: rgba(7, 34, 72, .06); }
.btn--solid { background: var(--ink); color: #fff; }
.btn--solid:hover { background: #0a2d5c; }

/* ---------- focused fact band ---------- */

.band { background: var(--ink); color: #fff; }
/* Four facts, so the columns are explicit rather than auto-fit: auto-fit
   packed three across and left the fourth stranded on a row of its own. */
.band__facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 22px 36px;
  margin: 0;
  padding: 24px 0 26px;
}
@media (max-width: 1040px) { .band__facts { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .band__facts { grid-template-columns: 1fr; } }
.band__facts > div { min-width: 0; }
.band dt {
  font-size: 10px; font-weight: 700; letter-spacing: .17em;
  text-transform: uppercase; color: var(--sky); margin-bottom: 5px;
}
.band dd { margin: 0; font-size: 14.5px; line-height: 1.4; color: #fff; }

/* ---------- prose devices ---------- */

.more { margin-top: 28px; max-width: var(--measure); border-top: 1px solid var(--rule); }
.more > summary {
  list-style: none;
  cursor: pointer;
  padding: 14px 0 0;
  font-size: 12px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--sky-deep);
  display: flex; align-items: center; gap: 9px;
}
.more > summary::-webkit-details-marker { display: none; }
.more > summary::after {
  content: "+";
  font-family: var(--mono); font-size: 14px; font-weight: 500;
  line-height: 1; color: var(--ink-faint);
}
.more[open] > summary::after { content: "\2013"; }
.more > summary:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.more__body { padding-top: 14px; }
.more__body p { font-size: 15.5px; color: var(--ink-soft); }

/* ---------- work cards, equal height ---------- */

.grid { display: grid; gap: 18px; margin: 32px 0 0; padding: 0; list-style: none; }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(310px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(268px, 1fr)); }
/* Column, not the default row: a card may be followed by an aside link, and
   in row direction the two sat side by side and squeezed the card. */
.grid > li { display: flex; flex-direction: column; }

.card {
  display: flex;
  flex-direction: column;
  flex: 1;
  border: 1px solid var(--rule);
  background: var(--surface);
  padding: 22px 24px 24px;
  color: inherit;
  text-decoration: none;
}
a.card:hover { border-color: var(--ink); text-decoration: none; }
a.card:hover .card__more { color: var(--ink); }
a.card:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }

/* A capture from the work itself, bled to the card edges. These replaced
   abstract diagrams: the case studies now carry real, public-safe screenshots,
   so a drawing standing in front of evidence was the weaker choice. */
.card__thumb {
  margin: -22px -24px 18px;
  border-bottom: 1px solid var(--rule);
  background: var(--surface-2);
  overflow: hidden;
}
.card__thumb img {
  display: block;
  width: 100%;
  /* height: auto is load-bearing. An img carrying width and height attributes
     gets them as presentational hints, and a hinted height beats aspect-ratio,
     so a thumb with those attributes renders at its full pixel height and
     blows the card open. The two thumbs that predate this survived only by
     having no attributes on them. */
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: top center;
}
a.card:hover .card__thumb img { opacity: .92; }


/* The walkthrough sits here rather than in Experiments. It documents this
   page's own plumbing, so the person who just used the assistant is the one
   who wants it; beside case studies it was a scale mismatch. */
.invite__more { margin: 18px 0 0; font-size: 14.5px; }
.invite__more a { color: var(--sky-deep); font-weight: 600; text-decoration: none; }
.invite__more a::after { content: " \2192"; }
.invite__more a:hover { text-decoration: wavy underline var(--sky-deep) 1.5px; text-underline-offset: 3px; }


.card__meta { margin: 0 0 14px; }
.card h3 { font-size: clamp(19px, 2.1vw, 23px); margin: 0 0 10px; }
.card__body { font-size: 15px; color: var(--ink-soft); margin: 0; }
/* pushes the affordance to the baseline so every card ends level */
.card__more {
  margin: 18px 0 0 !important;
  padding-top: 14px;
  border-top: 1px solid var(--rule-soft);
  font-family: var(--body);
  font-size: 11px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--sky-deep);
}
.grid > li .card__more { margin-top: auto !important; }

.tag {
  display: inline-block; padding: 3px 8px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .11em; text-transform: uppercase;
  font-family: var(--body);
}
.tag--live { background: rgba(31, 122, 111, .15); color: var(--teal); }
.tag--far  { background: rgba(46, 111, 163, .14); color: var(--sky-deep); }

/* ---------- the prototype field ---------- */

.field { margin: 34px 0 0; }
.field__body {
  display: grid;
  grid-template-columns: 310px minmax(0, 1fr);
  gap: 30px;
  align-items: start;
}
.field__marks {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 7px;
}
.field .m {
  aspect-ratio: 1;
  border-radius: 1px;
  cursor: crosshair;
  transition: transform .14s ease, box-shadow .14s ease;
}
.field .m:hover,
.field .m.is-active {
  transform: scale(1.35);
  box-shadow: 0 0 0 1.5px var(--paper), 0 0 0 3px var(--ink);
  position: relative;
  z-index: 1;
}

/* The readout is a fixed-height slot so naming a square never
   nudges the caption or the section below it. */
.field__readout {
  margin: 0;
  min-height: 62px;
  display: flex;
  align-items: center;
  padding-left: 22px;
  border-left: 1px solid var(--rule);
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(20px, 2.4vw, 27px);
  line-height: 1.15;
  color: var(--ink);
}
.field__hint {
  font-family: var(--body);
  font-style: normal;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Screen readers get the full list; the grid itself is decorative. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 700px) {
  .field__body { grid-template-columns: 1fr; gap: 18px; }
  .field__marks { max-width: 310px; }
  .field__readout {
    padding-left: 0; border-left: 0;
    border-top: 1px solid var(--rule); padding-top: 14px; min-height: 54px;
  }
}
.m--d { background: var(--ink); }
.m--f { background: var(--sky-deep); }
.m--m { background: var(--sky); }
.m--c { background: var(--teal); opacity: .55; }
.field figcaption {
  margin-top: 14px;
  font-size: 13px;
  color: var(--ink-faint);
}

/* Two columns rather than auto-fit: four items across three tracks
   orphans the last one onto a row of its own. A 2x2 block reads as
   a deliberate set at every width above the stack. */
.qgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 26px 40px;
  margin: 34px 0 0; padding: 0; list-style: none;
  max-width: 760px;
}
@media (max-width: 620px) {
  .qgrid { grid-template-columns: 1fr; gap: 22px; }
}
.qgrid > li { min-width: 0; }
.qgrid .key {
  display: inline-block; width: 10px; height: 10px;
  border-radius: 1px; margin-bottom: 10px;
}
.key--d { background: var(--ink); }
.key--f { background: var(--sky-deep); }
.key--m { background: var(--sky); }
.key--c { background: var(--teal); opacity: .55; }
.qgrid h3 {
  font-family: var(--body); font-style: normal; font-weight: 700;
  font-size: 12px; letter-spacing: .13em; text-transform: uppercase;
  margin: 0 0 7px;
}
.qgrid .q {
  font-family: var(--display); font-style: italic;
  font-size: 18px; line-height: 1.26; margin: 0; color: var(--ink-soft);
}

/* ---------- blogroll ---------- */

/* The line leads; the title attributes it. Every quote is verbatim from the
   post, so nothing here is a summary of Kris's writing written by anyone else. */

.quotes { margin: 30px 0 0; padding: 0; list-style: none; }
.quotes > li { padding: 26px 0; border-top: 1px solid var(--rule); }
.quotes > li:last-child { border-bottom: 1px solid var(--rule); }

.quotes blockquote {
  font-variant-ligatures: common-ligatures discretionary-ligatures;
  margin: 0 0 12px;
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(22px, 3vw, 32px);
  line-height: 1.16;
  max-width: 30ch;
  text-wrap: pretty;
}
.quotes__source {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sky-deep);
  text-decoration: none;
}
.quotes__source:hover { text-decoration: wavy underline var(--sky-deep) 1.5px; text-underline-offset: 4px; }
.quotes__source:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.quotes time {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-faint);
  margin-left: 10px;
}
.more-link { margin-top: 22px; font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }

/* ---------- invite ---------- */

.section--invite { background: var(--surface-2); }
.invite__cta { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 26px; }
.invite__note { font-size: 14px; color: var(--ink-faint); }

/* ---------- contact ---------- */

/* The form is the last thing on the homepage and the only outbound path
   that does not depend on a microphone, so it gets the paper ground and
   the solid button rather than the voice invite above it. */
.section--contact { background: var(--paper); border-top: 1px solid var(--rule); }
.contact { margin-top: 30px; max-width: 640px; }

/* One vertical rhythm for the whole form. The row used to carry the spacing
   between fields on its own, which left no gap between the row and the
   textarea under it: the last field's margin sat inside the grid track
   rather than after it. Spacing now belongs to the form, so every gap is
   the same one. */
.contact > * + * { margin-top: 20px; }
.contact__row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }

/* align-content matters here. A grid item stretches to its track by default,
   and a two-row auto grid absorbs the extra height into both rows, so a
   field beside a taller one grew its label row and its input by 10px each.
   Two inputs in the same row then sat at different heights. Start-aligning
   keeps every field the size of its content. */
/* Only the bottom margin is reset here. Using the `margin` shorthand would
   also zero margin-top and beat the rhythm rule above it, which is how the
   textarea ended up flush against the row. */
.contact__field { margin-bottom: 0; display: grid; gap: 7px; align-content: start; }
.contact__field label {
  font-size: 11.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint);
}
.contact__field input,
.contact__field textarea {
  box-sizing: border-box; width: 100%;
  padding: 11px 13px; border: 1px solid var(--rule);
  background: #fff; color: var(--ink);
  font-family: var(--body); font-size: 15.5px; line-height: 1.45;
}
.contact__field textarea { resize: vertical; min-height: 130px; }
.contact__field input:focus-visible,
.contact__field textarea:focus-visible { outline: 3px solid var(--sky-deep); outline-offset: 1px; border-color: var(--ink); }
.contact__field--bad input,
.contact__field--bad textarea { border-color: #8b1e1e; }
.contact__error { margin: 0; font-size: 13px; color: #8b1e1e; }

/* The honeypot is positioned off-canvas rather than hidden with display or
   the hidden attribute. A bot that reads the stylesheet skips anything it
   can see is invisible; this one it has to fill in to be caught by. It is
   aria-hidden and out of the tab order, so nobody reaches it by accident. */
.contact__trap {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden;
  margin-top: 0;
}

.contact__actions { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }

/* The submit sits directly under a 46px input, so it matches it rather than
   using the shorter default button height. */
.contact__actions .btn { min-height: 46px; padding-top: 0; padding-bottom: 0; display: inline-flex; align-items: center; }
.contact__note { font-size: 14px; color: var(--ink-faint); }
.contact__status { font-size: 15px; line-height: 1.45; }
.contact__status:empty { margin-top: 0; }
.contact__status--ok { color: #1d6b45; }
.contact__status--bad { color: #8b1e1e; }
.contact.is-sending .btn { opacity: .55; pointer-events: none; }

@media (max-width: 620px) {
  .contact__row { grid-template-columns: 1fr; }
}

/* ---------- bio: portrait beside the arc ---------- */

.bio {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  gap: 34px;
  align-items: start;
  padding: 26px;
  margin: 0 -26px;
  background: transparent;
  transition: background-color .45s ease;
}
.bio__aside { min-width: 0; }
.bio__portrait { margin: 0; }
.bio__portrait img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
}
.bio__text > p:last-child { margin-bottom: 0; }
.bio .more { margin-top: 22px; }

/* ---------- the fact, and the flip ----------
   The one place the page drops its shoulders. Clicking inverts the
   bio block and puts the fact where the CV was. */

.fact__btn, .fact__back {
  margin-top: 14px;
  padding: 8px 12px;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  font-family: var(--body);
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase;
  transition: color .2s ease, border-color .2s ease;
}
.fact__btn:hover, .fact__back:hover { color: var(--ink); border-color: var(--ink); }
.fact__btn:focus-visible, .fact__back:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.fact__back[hidden] { display: none; }

.fact__out { display: none; }

/* --- inverted state --- */

.bio.is-flipped { background: var(--ink); }
.bio.is-flipped .bio__text { display: none; }
.bio.is-flipped .fact__out {
  display: block;
  align-self: center;
  margin: 0;
  max-width: 24ch;
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(24px, 3.2vw, 38px);
  line-height: 1.12;
  color: #fff;
  animation: factIn .5s cubic-bezier(.22,.61,.36,1) both;
}
.bio.is-flipped .bio__portrait img { border-color: rgba(255, 255, 255, .28); }
.bio.is-flipped .fact__btn,
.bio.is-flipped .fact__back {
  border-color: rgba(255, 255, 255, .3);
  color: rgba(255, 255, 255, .72);
}
.bio.is-flipped .fact__btn:hover,
.bio.is-flipped .fact__back:hover { color: #fff; border-color: #fff; }
.bio.is-flipped .fact__btn:focus-visible,
.bio.is-flipped .fact__back:focus-visible { outline-color: var(--sky); }

@keyframes factIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .bio { transition: none; }
  .bio.is-flipped .fact__out { animation: none; }
}
@media (max-width: 700px) {
  .bio { margin: 0 -18px; padding: 18px; }
}

@media (max-width: 700px) {
  .bio { grid-template-columns: 1fr; gap: 24px; }
  .bio__portrait { max-width: 150px; }
}

/* ---------- colophon ---------- */

.colophon { background: var(--paper); border-top: 1px solid var(--rule); padding: 44px 0 56px; }
.colophon p { font-size: 14px; color: var(--ink-faint); }
.colophon p.colophon__legal { margin-top: 10px; font-size: 12.5px; }
.colophon a { font-weight: 600; }
.colophon__links { display: flex; gap: 20px; flex-wrap: wrap; margin: 0 0 14px; font-size: 13px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; }
.colophon__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 26px 40px;
  align-items: start;
}
.colophon__aside { margin: 0; }
.linkish {
  border: 0; background: none; padding: 0;
  font-family: var(--body); font-size: 13px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--sky-deep); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.linkish:hover { color: var(--ink); }
.linkish:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.colophon__fact {
  display: block; margin-top: 10px; min-height: 1.4em;
  font-family: var(--display); font-style: italic;
  font-size: 17px; line-height: 1.35; color: var(--ink);
  max-width: 40ch;
}

/* ---------- voice assistant ---------- */

/* The assistant is a character in the corner. It stacks, top to bottom: a
   speech bubble when it has something to say, then the character with the
   call controls beside it while a call is live. There is no panel; the bubble
   carries the words and the controls carry the rest.

   The character is a strip of nine frames (assets/imgs/mascot-bubble.webp)
   drawn on ::before. The strip is a background image, so with no script it
   simply rests, and the button works as soon as voice-chat.js loads.
   assets/js/mascot.js chooses the frame through --f. A frame k of 9 sits at
   k/8 of the way along the strip, hence 12.5%.

   Geometry, as fractions of a frame: the tail tip is 16% up from the bottom
   and the body spans roughly 15% to 85% across, so the button is cut to the
   body (the empty frame around it must not swallow clicks meant for the page)
   and the frame hangs 16% below the button, which puts the tail tip exactly
   on the button's bottom edge. */
.voice {
  --mascot: 150px;
  position: fixed; right: 16px; bottom: 12px; z-index: 900;
  width: min(360px, calc(100vw - 32px));
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "bubble bubble" "bar stage";
  align-items: end;
}
.voice__launcher {
  grid-area: stage;
  position: relative; display: block; padding: 0; border: 0; background: none; cursor: pointer;
  width: calc(var(--mascot) * .62); height: calc(var(--mascot) * .64);
  margin-right: calc(var(--mascot) * .19);
}
.voice__launcher::before {
  content: ""; position: absolute; left: 50%; bottom: calc(var(--mascot) * -.16);
  width: var(--mascot); height: var(--mascot);
  background: url(../imgs/mascot-bubble.webp) no-repeat 0 0 / 900% 100%;
  background-position: calc(var(--f, 0) * 12.5%) 0;
  transform: translateX(-50%);
  transform-origin: 50% 84%;
  animation: mascot-breathe 3.6s ease-in-out infinite;
  pointer-events: none;
}
.voice__launcher:focus-visible,
.voice button:focus-visible { outline: 3px solid var(--sky-deep); outline-offset: 3px; }
.voice__launcher:focus-visible { border-radius: 18px; }
@keyframes mascot-breathe {
  0%, 100% { transform: translateX(-50%) scale(1, 1); }
  50% { transform: translateX(-50%) scale(1.012, .985); }
}
@media (prefers-reduced-motion: reduce) {
  .voice__launcher::before { animation: none; }
}

/* The speech bubble. Square and ruled like the panel it replaces as the main
   surface; only the character is round. */
.voice__bubble {
  grid-area: bubble; justify-self: end;
  position: relative; max-width: min(300px, 100%);
  margin: 0 6px 12px 0; padding: 12px 14px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--ink);
  box-shadow: 0 8px 24px rgba(7, 34, 72, .16);
}
.voice__bubble[hidden] { display: none; }
.voice__bubble::after {
  content: ""; position: absolute; bottom: -7px; width: 12px; height: 12px;
  right: calc(var(--mascot) * .5 - 12px);
  background: var(--surface);
  border-right: 1px solid var(--ink); border-bottom: 1px solid var(--ink);
  transform: rotate(45deg);
}
.voice__heard { margin: 0 0 6px; font-size: 12.5px; line-height: 1.35; color: var(--ink-faint); }
.voice__caption, .voice__note { margin: 0; font-size: 15px; line-height: 1.4; }
.voice__heard[hidden], .voice__caption[hidden], .voice__note[hidden] { display: none; }
.voice__consent { display: flex; gap: 8px; margin-top: 12px; }
.voice__consent[hidden] { display: none; }

/* Controls that exist only while a call is live: nothing to stop otherwise. */
.voice__bar {
  grid-area: bar; display: none; flex-direction: column; align-items: flex-end; gap: 4px;
  align-self: center; margin-right: calc(var(--mascot) * .08 + 6px);
}
.voice[data-voice-state]:not([data-voice-state="idle"]) .voice__bar { display: flex; }
.voice__bar-btn {
  padding: 6px 9px; border: 1px solid var(--ink); background: var(--surface); color: var(--ink);
  cursor: pointer; font-family: var(--body); font-size: 10.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
}
.voice__bar-btn[aria-pressed="true"] { background: var(--ink); color: #fff; }

/* Where the assistant has just pointed. The same wavy underline the site
   uses for hover, so "here" looks like the rest of the page's language. */
.is-pointed { text-decoration: wavy underline var(--sky-deep) 2px; text-underline-offset: 6px; }
.tryout a.is-pointed { text-decoration: none; }
.tryout a.is-pointed .tryout__name { text-decoration: wavy underline var(--ink) 2px; text-underline-offset: 5px; }
.tryout a.is-pointed .tryout__go { color: var(--ink); transform: translateX(3px); }

.voice__primary, .voice__secondary {
  min-height: 38px; padding: 9px 14px; border: 1px solid var(--ink);
  background: var(--ink); color: #fff; cursor: pointer;
  font-family: var(--body); font-size: 11.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
}
.voice__secondary { background: transparent; color: var(--ink); }
/* On a phone the character is a persistent overlay on top of the work
   cards, so it is smaller. */
@media (max-width: 700px) {
  .voice { --mascot: 112px; }
}

@media (max-width: 480px) {
  .voice { right: 10px; bottom: 10px; width: calc(100vw - 20px); }
}

/* ---------- the creative reel ----------
   A password box, and once unlocked the player takes its place. The field
   reuses the contact form's, which is why it carries those class names. */
.reel { padding: clamp(26px, 4vw, 44px) 0 clamp(40px, 6vw, 72px); }
.reel__gate { max-width: 420px; display: grid; gap: 16px; }
.reel__gate .btn { justify-self: start; }
.reel__gate[hidden], .reel__player[hidden] { display: none; }
.reel__player { margin: 0; }

/* The Taste case study leads with its film. A lead figure runs the full
   shell rather than the 860px a mid-page shot gets: it is the first thing
   on the page and the argument the prose then qualifies. */
.cs-lead { padding: clamp(26px, 4vw, 44px) 0 0; }
.shot.shot--lead { max-width: none; margin: 0; }
.shot.shot--lead figcaption { margin-top: 13px; }

/* ---------- case study: wide figures, sequence, switcher ---------- */

/* A product screenshot goes illegible at the reading measure, and 860px is
   still tight for a screen with a side panel in it. A wide figure breaks out
   of the shell to the viewport, capped so it never touches the edge. The
   100vw is reduced by more than the visual gutter because 100vw includes the
   scrollbar and would otherwise force a horizontal scroll. */
.shot.shot--wide {
  width: min(1180px, calc(100vw - 64px));
  max-width: none;
  margin-left: 50%;
  transform: translateX(-50%);
}

/* The five stages, as labels on a rule rather than five cards. Cards would
   promise five destinations; this is one sequence the page then walks. */
.seq {
  display: flex; flex-wrap: wrap; gap: 10px 0;
  margin: 26px 0 34px; padding: 0;
  list-style: none;
  border-top: 1px solid var(--rule);
}
.seq li {
  position: relative;
  flex: 1 1 auto; min-width: 140px;
  margin: 0; padding: 14px 18px 0 0;
  font-size: 12.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft);
}
.seq li::before {
  content: ""; position: absolute; top: -1px; left: 0;
  width: 28px; height: 2px; background: var(--teal);
}

/* The switcher. Both figures are in the document and both are readable with
   no script at all; the controls are display:none until the script adds
   is-enhanced, because a button that switches nothing is worse than no
   button. Nothing here is hidden by script that script has to reveal. */
.switch { margin: 30px 0; }
.switch__controls { display: none; }
.switch.is-enhanced .switch__controls {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px;
}
.switch__btn {
  border: 1px solid var(--rule); background: var(--surface);
  padding: 9px 15px; cursor: pointer;
  font-family: var(--body); font-size: 11.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-soft);
}
.switch__btn:hover { border-color: var(--ink); color: var(--ink); }
.switch__btn[aria-pressed="true"] {
  background: var(--teal); border-color: var(--teal); color: #fff;
}
.switch__btn:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.switch.is-enhanced .switch__panel { margin-top: 0; }

/* A figure nested in a disclosure stays inside the reading column: it is
   supporting proof, not a chapter of its own. */
.more__body .shot.shot--wide,
.more__body .shot { width: auto; max-width: 100%; margin: 18px 0 0; transform: none; margin-left: 0; }

.colophon__note {
  margin-top: 40px; padding-top: 16px;
  border-top: 1px solid var(--rule);
  font-size: 13.5px; color: var(--ink-faint);
}

@media (max-width: 700px) {
  .shot.shot--wide { width: calc(100vw - 36px); }
  .seq li { min-width: 50%; }
}

/* ---------- case study ---------- */

.cs-hero { border-bottom: 1px solid var(--rule); padding: clamp(40px, 6vw, 72px) 0 clamp(34px, 5vw, 56px); }
.cs-hero h1 {
  margin: 0 0 20px;
  font-size: clamp(32px, 5.4vw, 56px);
  line-height: 1.03;
  text-wrap: balance;
}
.back { display: inline-block; margin-bottom: 26px; font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }

.facts {
  display: flex;
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--rule);
  margin: 34px 0 0;
  padding: 0;
}
.facts > div {
  flex: 1 1 165px;
  padding: 15px 18px;
  border-right: 1px solid var(--rule);
}
.facts > div:last-child { border-right: 0; }
.facts dt { font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 4px; }
.facts dd { margin: 0; font-size: 14.5px; font-weight: 600; line-height: 1.35; }

.prose { padding: clamp(40px, 6vw, 72px) 0; }
.prose h2 { font-size: clamp(24px, 3.4vw, 34px); margin-top: 44px; }
.prose h2:first-child { margin-top: 0; }
.prose ul { max-width: var(--measure); padding-left: 22px; }

/* The three modes. A definition list because that is what they are, with the
   name in the display face so the set reads as a framework rather than as
   three more paragraphs. */
.modes { max-width: var(--measure); margin: 24px 0 0; }
.modes > div {
  padding: 16px 0;
  border-top: 1px solid var(--rule-soft);
}
.modes > div:last-child { border-bottom: 1px solid var(--rule-soft); }
.modes dt {
  font-family: var(--display);
  font-size: 20px; line-height: 1.2;
  color: var(--ink);
  margin-bottom: 6px;
}
.modes dd { margin: 0; color: var(--ink-soft); }
.modes dd a { font-weight: 600; text-decoration: none; color: var(--sky-deep); }
.modes dd a:hover { text-decoration: wavy underline var(--sky-deep) 1.5px; text-underline-offset: 3px; }

/* Used by the ACE case study, which groups eight stages under three
   questions. Numerals sit in the margin so the text lines up with the
   paragraphs around it. */
.steps { max-width: var(--measure); margin: 22px 0 0; padding: 0; list-style: none; counter-reset: step; }
.steps > li {
  position: relative;
  counter-increment: step;
  padding-left: 34px;
  margin-bottom: 14px;
  color: var(--ink-soft);
}
.steps > li::before {
  content: counter(step);
  position: absolute; left: 0; top: .1em;
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--sky-deep);
}
.steps b { color: var(--ink); }
.steps p { margin: 4px 0 0; }


.prose li { margin-bottom: 9px; }
.prose li::marker { color: var(--sky-deep); }

/* ---------- screenshots in a case study ---------- */

/* Prose is held to 66ch for reading. A screenshot of an interface is not
   read that way and goes illegible at that width, so a shot is allowed to
   run wider than the column it sits in and stops at the shell. */
.shot { margin: 34px 0; max-width: 860px; }
.shot img,
.shot video {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--rule); background: #fff;
}

/* A screen recording is letterboxed against its own dark interface, so the
   white ground under an image would show as a border on two sides. */
.shot video { background: #000; }
.shot figcaption {
  margin-top: 11px; max-width: var(--measure);
  font-size: 14px; line-height: 1.45; color: var(--ink-faint);
}

/* Until a capture exists the figure holds its own space and says what is
   missing, rather than collapsing or showing a broken image. Replacing the
   div with an img and dropping the modifier is the whole edit. Each one has
   a TODO in the page naming the file it wants. */
.shot--pending .shot__frame {
  display: grid; place-items: center; aspect-ratio: 16 / 10;
  border: 1px dashed var(--rule); background: var(--surface-2);
  color: var(--ink-faint); font-size: 12px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; text-align: center;
  padding: 20px;
}

.pull { border-left: 3px solid var(--ink); padding: 2px 0 2px 22px; margin: 30px 0; }
.pull p { font-family: var(--display); font-style: italic; font-size: clamp(19px, 2.5vw, 25px); line-height: 1.3; margin: 0; }

.note {
  background: var(--surface-2);
  border-left: 3px solid var(--sky-deep);
  padding: 18px 22px;
  margin: 30px 0;
}
.note p { font-size: 15px; color: var(--ink-soft); }

/* ---------- scroll reveal ----------
   Hiding only happens once JS has added .has-reveal to <html>, so with
   JavaScript off — or blocked — every element stays visible. Nothing is
   ever hidden by something that might not run. */

.has-reveal .reveal {
  opacity: 0;
  transform: translateY(16px);
  will-change: opacity, transform;
}

.has-reveal .reveal.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity .62s cubic-bezier(.22, .61, .36, 1),
              transform .62s cubic-bezier(.22, .61, .36, 1);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: auto;
}

/* ---------- responsive ---------- */

@media (max-width: 700px) {
  body { font-size: 16px; }
  .masthead__nav { gap: 14px; }
  .masthead__nav a { font-size: 11px; }
  .hero__thesis {
  font-variant-ligatures: common-ligatures discretionary-ligatures; max-width: none; }

  /* Stacked credentials read as a list, so the inline separators
     would otherwise strand themselves at the start of each line. */
  .proof ul { display: grid; gap: 7px; }
  .proof li + li::before { content: none; padding: 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero { transition: none; }
  .has-reveal .reveal { opacity: 1; transform: none; }
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ---------- behind the code: file browser ---------- */

.btc {
  display: grid;
  grid-template-columns: 270px minmax(0, 1fr);
  gap: 20px;
  margin: 30px 0 0;
  align-items: start;
}

.btc__tree {
  display: grid;
  gap: 4px;
  border: 1px solid var(--rule);
  background: var(--surface);
  padding: 12px;
  position: sticky;
  top: 20px;
}
.btc__file {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  border-left: 3px solid transparent;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  text-align: left;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.btc__file:hover { background: var(--surface-2); }
.btc__file[aria-current="true"] {
  border-left-color: var(--ink);
  background: var(--surface-2);
  color: var(--ink);
}
.btc__file:focus-visible { outline: 3px solid var(--ink); outline-offset: -1px; }
.btc__file-kind {
  display: block;
  margin-top: 3px;
  font-family: var(--body);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.btc__viewer { border: 1px solid var(--rule); background: var(--surface); min-width: 0; }
.btc__head { padding: 22px 24px 20px; border-bottom: 1px solid var(--rule); }
.btc__path {
  margin: 0 0 10px;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-faint);
}
.btc__viewer h3 {
  font-variant-ligatures: common-ligatures discretionary-ligatures; margin: 0 0 8px; font-size: clamp(20px, 2.4vw, 26px); }
.btc__summary { margin: 0; font-size: 15px; color: var(--ink-soft); max-width: 62ch; }

.btc__snippet {
  margin: 0;
  max-height: 400px;
  overflow: auto;
  padding: 20px 24px;
  background: var(--ink);
  color: #fff;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  white-space: pre;
}
/* The global `code` rule gives inline code a light pill and .86em. Inside the
   navy snippet block that painted a near-white background under white text,
   which is invisible, and compounded the size down to under 10px. A block of
   code is not inline code, so it takes none of it. */
.btc__snippet code {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  color: inherit;
  background: none;
  padding: 0;
  border-radius: 0;
}

.btc__notes { padding: 20px 24px 22px; border-top: 1px solid var(--rule); }
.btc__label {
  margin: 0 0 10px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.btc__notes ul { margin: 0; padding-left: 20px; max-width: 62ch; }
.btc__notes li { margin-bottom: 8px; font-size: 15px; color: var(--ink-soft); }
.btc__notes li::marker { color: var(--sky-deep); }

.facts .linkish { font-size: 14.5px; letter-spacing: 0; text-transform: none; font-weight: 600; }

@media (max-width: 820px) {
  .btc { grid-template-columns: 1fr; }
  .btc__tree { position: static; }
  .btc__snippet { font-size: 11.5px; max-height: 320px; }
}

/* ---------- live demos ----------
   Deliberately not cards. A thing you can use is a different offer from a
   thing you can read about, and the two should not look interchangeable. */

.tryout { margin: 34px 0 0; border-top: 1px solid var(--rule); padding-top: 22px; }
.tryout__label {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--sky-deep);
}
.tryout ul { margin: 0; padding: 0; list-style: none; }
.tryout > ul > li { border-bottom: 1px solid var(--rule-soft); }
.tryout a {
  display: grid;
  gap: 4px;
  padding: 18px 0;
  color: inherit;
  text-decoration: none;
}
.tryout a:hover { text-decoration: none; }
.tryout a:hover .tryout__name { text-decoration: wavy underline var(--ink) 2px; text-underline-offset: 5px; }
.tryout a:hover .tryout__go { color: var(--ink); transform: translateX(3px); }
.tryout a:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.tryout__name {
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(20px, 2.4vw, 26px);
  line-height: 1.15;
}
.tryout__what {
  font-size: 15px;
  color: var(--ink-soft);
  max-width: 62ch;
}
.tryout__go {
  /* Sits after the last word of the title, on its baseline. */
  display: inline-block;
  width: .78em; height: .78em;
  margin-left: .3em;
  vertical-align: -.04em;
  color: var(--sky-deep);
  transition: transform .16s ease, color .16s ease;
}
@media (prefers-reduced-motion: reduce) {
  .tryout a:hover .tryout__go { transform: none; }
}
