/* Countabil — stylesheet.
   Written against docs/design/DESIGN_BRIEF.md. Every rule below solves a named
   problem, stated in the comment above it. A rule with no stated problem is
   decoration and does not belong here. */

@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/fraunces-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/inter-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/inter-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Colours taken from the wordmark (#0c1e7f) and the mark's mid-green, so the
   palette is derived from the logo rather than chosen alongside it. */
:root {
  --bg: #fdfcfa;
  --text: #16181a;
  --muted: #55595c;
  --rule: #ddd8d0;
  --ink: #0c1e7f;
  --proven: #1d6b42;
  --failed: #96341f;
  --measure: 68ch;
}

/* Declared, not implied: this site is designed for light only. Saying so stops
   the browser inventing a dark rendering we have never inspected. */
:root { color-scheme: light; }

* { box-sizing: border-box; margin: 0; padding: 0; }

/* Problem: text touching the viewport edge is unreadable, and an unbounded
   measure defeats the eye's return sweep. Four constants, from the survey:
   measure 60-80ch, leading >= 1.4, body >= 16px, never touch the edge. */
body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

.shell {
  max-width: 76ch;
  margin: 0 auto;
  padding: 2rem 1.25rem 5rem;
}

/* Problem: headings set at body leading do not read as headings. Fraunces at a
   tight measure carries the hierarchy; nothing else is needed to signal it. */
h1, h2, h3 {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  /* Problem: a headline that strands its last word on a line of its own reads
     as an accident. text-wrap: balance evens the line lengths instead, which is
     what a typesetter would do by hand. Browsers without it simply wrap
     normally, so there is nothing to fall back to. */
  text-wrap: balance;
}
/* 26ch, not 22ch: at 22ch the About page's opening question dropped its last
   word onto a line of its own. A headline that ends in a widow reads as an
   accident. */
h1 { font-size: 2.1rem; max-width: 26ch; }
h2 { font-size: 1.4rem; margin-bottom: 0.5rem; }
h3 { font-size: 1.08rem; margin-bottom: 0.35rem; }

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

/* Problem: a link the reader cannot identify as a link is not a link.
   brutalist-web.design rule 3 -- hyperlinks are underlined. */
a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}
a:hover { text-decoration-thickness: 2px; }

/* Problem: keyboard users cannot see where they are if focus is styled away. */
a:focus-visible, button:focus-visible, summary:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* Navigation. Problem: the reader needs to know where they are and where else
   they can go, in as few words as possible. Nav is not a site map. */
.nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 2.5rem;
}
.logo { text-decoration: none; display: inline-flex; }
.logo img { display: block; width: 168px; height: auto; }
.nav-links { display: flex; flex-wrap: wrap; gap: 1.1rem; font-size: 0.95rem; }
.nav-links a { color: var(--text); text-decoration: none; }
.nav-links a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
/* The current page is stated, not merely coloured -- colour alone is not a
   signal for anyone who cannot distinguish these two. */
.nav-links a.active { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 0.2em; }

/* Hero. Problem: the reader must be able to tell within one screen whether
   there is anything real here. No image: there is no photograph that carries
   this information, and a photograph that carries none is decoration. */
.hero { margin-bottom: 3rem; }
.hero h1 { margin-bottom: 1rem; }
.hero .lede { font-size: 1.1rem; max-width: 62ch; }

/* Problem: two actions is the purposeful range; five is the decorative one. */
.actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.5rem; }

/* Problem: a button must look like a button (brutalist rule 3) and be large
   enough to hit on a phone (>= 44px tall). */
.btn {
  display: inline-block;
  font: inherit;
  font-weight: 600;
  padding: 0.7rem 1.15rem;
  min-height: 44px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--bg);
  text-decoration: none;
  cursor: pointer;
}
.btn:hover { text-decoration: underline; text-underline-offset: 0.2em; }
.btn-secondary { background: transparent; color: var(--ink); }

section { margin-bottom: 3rem; }

/* Problem: a section boundary needs to be visible without a coloured panel, a
   shadow or a card. One rule of one pixel says the same thing, and putting it
   in CSS rather than in markup means no page can forget it. */
.hero + section, section + section { border-top: 1px solid var(--rule); padding-top: 2.5rem; }

/* Problem: a label above a heading tells the reader what kind of thing follows.
   Set at 0.85rem -- the smallest size on the site, and still 13.6px. */
.kicker {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

/* Problem: three parallel offers compared side by side is a comparison, and a
   comparison should be easy to make. 12rem is the widest minimum at which three
   columns still fit the measure -- at 15rem the third wrapped and broke the
   comparison. Stacks on narrow screens. */
.grid { display: grid; gap: 1.75rem; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.grid p, .grid h3 { max-width: none; }
.grid > * { border-top: 2px solid var(--text); padding-top: 0.75rem; }

/* Problem: four items in a three-column grid leaves one orphaned on its own
   row, which reads as a mistake rather than a comparison. Two and four both
   want two columns. */
.grid.pair { grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }

/* Problem: a secondary remark beside the hero is not a section and must not
   look like one. A single rule and indent is enough to mark it as an aside. */
.note {
  border-left: 3px solid var(--rule);
  padding-left: 1.15rem;
  margin-top: 2rem;
  max-width: var(--measure);
}
.note h3 { margin-bottom: 0.25rem; }
.note p { max-width: none; }

/* Problem: a list of observed patterns is not prose and should not be set as
   prose -- the reader scans it. */
.list { list-style: none; margin: 1rem 0; max-width: var(--measure); }
.list li { padding: 0.6rem 0 0.6rem 1.5rem; border-bottom: 1px solid var(--rule); position: relative; }
.list li::before { content: "\2014"; position: absolute; left: 0; color: var(--muted); }
.list li:last-child { border-bottom: 0; }

/* The demonstration block. Problem: this is the one claim on the site the
   reader can verify for themselves, so it gets the emphasis the verdict block
   gets on the reader page itself. */
.demo { border-left: 3px solid var(--text); padding: 0.25rem 0 0.25rem 1.15rem; }

/* Problem: a limitation set in grey small print is a limitation the writer
   hopes you will not read. This is the same colour and size as every other
   claim on the page, deliberately -- it is the most important paragraph here. */
.caveat { margin-top: 1.25rem; max-width: var(--measure); }

/* Problem: the email fallback is a real alternative to the booking link, not a
   footnote to it, but it should not compete with the button either. */
.contact-alt { margin-top: 1rem; }

/* Problem: stacked on a phone, the portrait sat flush against the line above
   it, so the photograph read as part of the sentence. */
.portrait { display: block; margin-top: 1.75rem; }

.founder-photo {
  display: block;
  width: 100%;
  max-width: 22rem;
  height: auto;
  margin-bottom: 1.5rem;
  border: 1px solid var(--rule);
}

/* Problem: the portrait should sit beside the opening question, and the prose
   beside it must still hold a 60-80 character measure -- the one typographic
   rule the research found actually survives.

   An earlier version widened this section past the shell to buy that room. It
   worked typographically and failed visually: every other element on the site
   shares one left edge, and breaking that spine for a single section reads as a
   mistake rather than a decision. The fix is a narrower photograph, not a wider
   container. At 13rem the text column measures ~61ch, inside the band, with
   nothing escaping the shell.

   Below 52rem there is no room for two columns, so it stacks and the question
   still comes first. */
@media (min-width: 52rem) {
  .founder-hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 17rem;
    column-gap: 2.5rem;
    align-items: start;
  }
  /* The question spans both columns. It is the hook for 1,100 words and needs
     room to land in two lines, not three -- the owner reported that the wider
     setting was measurably easier to commit to reading, and that is the only
     thing this page is for. Only the shorter lede shares a row with the
     photograph, which is what lets the photograph stay large. */
  .founder-hero > .kicker,
  .founder-hero > h1 { grid-column: 1 / -1; }
  .founder-hero > h1 { max-width: 30ch; }
  .founder-hero .founder-text { grid-column: 1; }
  .founder-hero .founder-text p { max-width: none; }
  .founder-hero .portrait { grid-column: 2; grid-row: 3; margin-top: 0; }
  .founder-hero .founder-photo { max-width: none; margin-bottom: 0; }
}
}

blockquote {
  margin: 1.5rem 0;
  padding-left: 1.15rem;
  border-left: 3px solid var(--rule);
  max-width: var(--measure);
}

footer {
  margin-top: 4rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  font-size: 0.9rem;
  color: var(--muted);
}
footer a { color: var(--muted); }

/* Problem: at 375px the measure and the padding must still hold, and a 2.1rem
   headline stacks into too many lines to read as one thought. */
@media (max-width: 34rem) {
  h1 { font-size: 1.7rem; max-width: none; }
  .shell { padding: 1.5rem 1.1rem 4rem; }
  .nav { gap: 0.75rem 1rem; }
  .logo img { width: 148px; }
}

/* ---- The proof illustration -------------------------------------------
   Two panels: the same three statement lines, read two ways. Text, not an
   image -- so it is selectable, readable by a screen reader, translatable,
   and a few hundred bytes rather than a few megabytes. It also means the
   figures stay sharp at any zoom, which a GIF of a table does not.

   Steps carry data-at and are revealed by proof.js. Without that file they
   are all visible, which is the static fallback. */

.proof { margin: 2.5rem 0; }
.proof-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
/* Problem: if one panel grows taller than the other their captions land at
   different heights and the pair stops reading as a matched pair. Stretching
   both bodies to the row height fixes it whatever the content does. */
.proof figure { margin: 0; display: flex; flex-direction: column; }
.proof .panel-body { flex: 1; }

/* Problem: the two panels are a comparison, so they must be the same size and
   sit on the same baseline. A border does that without a card or a shadow. */
.panel-body {
  border: 1px solid var(--rule);
  padding: 0.9rem 1rem;
  min-height: 11.5rem;
  background: #fff;
}

/* Problem: figures that do not line up cannot be compared. Tabular numerals
   give every digit the same width so the columns hold. */
/* Problem: a comparison the eye cannot scan across is not a comparison. Both
   panels get a header of the same height and a status column of the same
   width, so the three rows and their amounts land on identical lines. Without
   this the chat bubble pushes the left rows down and the two sides can only be
   compared by memory. */
.panel-head { font-size: 0.92rem; line-height: 1.5; padding: 0.5rem 0.7rem; margin: 0; max-width: none; min-height: 2.6rem; }
.panel-head.plain { background: transparent; padding-left: 0; padding-right: 0; border-bottom: 1px solid var(--rule); color: var(--muted); }

.ledger { font-variant-numeric: tabular-nums; font-size: 0.85rem; margin-top: 0.6rem; }
/* Four columns, because three was a lie. The check is
   balance[i] - balance[i-1] == amount[i], so a panel that shows only dates and
   amounts is asserting a proof rather than showing one -- the reader has no way
   to redo the arithmetic. The opening balance is here for the same reason: it
   is what makes the FIRST row provable instead of unverifiable. */
.lrow { display: grid; grid-template-columns: 2.9rem 1fr 1fr 3.6rem; gap: 0.4rem; align-items: baseline; padding: 0.26rem 0; }
.lrow .amt { text-align: right; }

/* Problem: a column of figures with no header is a column of unknowns -- the
   reader cannot tell a movement from a balance. */
.lhead { font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); border-bottom: 1px solid var(--rule); padding-bottom: 0.35rem; margin-bottom: 0.15rem; }
.lrow .lbl { color: var(--muted); font-style: italic; }

/* The failed row highlights only once its verdict has actually landed --
   otherwise the illustration gives away the answer before it checks. */
.lrow:has(.mark.bad.is-on),
.proof:not(.is-animated) .lrow:has(.mark.bad) { background: #fbe6e0; box-shadow: inset 3px 0 0 var(--failed); }
.lrow .mark { font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase; font-weight: 600; }
.mark.ok { color: var(--proven); }
.mark.bad { color: var(--failed); }

/* Problem: this is a chat message and needs to read as one, without
   impersonating any particular product's interface. */
.bubble { background: #f0ece4; }

/* Problem: the reason a row failed is the whole point of the right panel, and
   it is longer than the row. It sits under the row it explains. */
.why { font-size: 0.8rem; color: var(--muted); padding: 0.35rem 0 0 3.3rem; max-width: none; line-height: 1.45; }

/* Problem: the two captions are different lengths, so one wraps to two lines
   and the other does not -- which pushes the panel bodies to unequal heights
   and knocks the rows out of line. Reserving two lines in both keeps the pair
   matched however the wording changes. */
.proof figcaption { font-size: 0.9rem; margin-top: 0.6rem; min-height: 2.9rem; }

/* Problem: an illustration that claims to be a recording must say which it
   is. This line is where that provenance goes, and it is deliberately not
   grey fine print. */
.proof-source { font-size: 0.85rem; margin-top: 1.25rem; }

/* Revealed by proof.js. The hiding lives inside .is-animated so that no
   JavaScript means no hiding. */
.proof [data-at] { transition: opacity 0.35s ease, transform 0.35s ease; }
.proof.is-animated [data-at] { opacity: 0; transform: translateY(6px); }
.proof.is-animated [data-at].is-on { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .proof [data-at] { transition: none; }
}

/* ---- Reveal on scroll --------------------------------------------------
   Problem: on a long page there is no signal that the next section is a new
   thought rather than a continuation, and the About page runs to ~1,100 words.
   A short fade as each section arrives gives the page a rhythm and marks the
   boundaries the horizontal rules already imply.

   Being straight about it: this is the first thing on the site that is closer
   to atmosphere than to information. It is kept to 10px and half a second so
   that it registers as pacing rather than as an effect -- you should notice its
   absence more than its presence.

   Everything here is scoped to .js-reveal, which only reveal.js adds. No
   JavaScript, no hiding. */

.js-reveal .shell > section,
.js-reveal .shell > footer {
  opacity: 0;
  transform: translateY(10px);
}

.js-reveal .shell > section.is-in,
.js-reveal .shell > footer.is-in {
  opacity: 1;
  transform: none;
  /* ease-out, because things that decelerate as they arrive read as physical.
     Linear reads as mechanical and ease-in reads as sluggish. */
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

/* Belt and braces: reveal.js already returns before adding .js-reveal when
   reduced motion is set, so this should never be reachable. It is here so that
   a future change to the script cannot silently reintroduce motion. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .shell > section,
  .js-reveal .shell > footer {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Problem: on the service pages every section heading sat at h3 directly under
   the h1, with no h2 between -- a broken document outline for a screen reader
   and for a crawler. They are now h2, which is the correct level, but a heading
   level is a statement about structure and not about size. This keeps them
   looking exactly as they did. */
h2.as-h3 { font-size: 1.08rem; margin-bottom: 0.35rem; }
