/* ==========================================================================
   AKOS International -- base layer
   --------------------------------------------------------------------------
   Structure, layout and behaviour only. Every colour, font and border lives
   in theme-editorial.css, which is the chosen design (2026-08-05) and the
   mirror of the tokens specified in BRAND.md. The split is kept because it
   keeps brand decisions in one file: change a colour there, not here.

   No build step, no framework, no external requests.
   ========================================================================== */

/* --------------------------------------------------------------- webfonts */
/* Self-hosted, so the CSP stays `default-src 'self'` -- a font CDN would break
 * the page outright rather than degrade. Variable fonts, subset to Latin and
 * trimmed to the weight range the theme actually uses (195 KB for all three).
 * Literata carries an opsz axis, so headlines and body get optically correct
 * cuts automatically -- do not set font-optical-sizing: none. */

@font-face {
  font-family: "Literata";
  src: url("fonts/literata-var.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("fonts/literata-italic-var.woff2") format("woff2");
  font-weight: 400 600;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Sans";
  src: url("fonts/ibm-plex-sans-var.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

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

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.65;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { font-family: var(--font-head); line-height: 1.15; margin: 0; font-weight: var(--head-weight); }
p { margin: 0; }
img { max-width: 100%; height: auto; display: block; }

a { color: var(--link); }
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Skip link -- visible only when focused by keyboard. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  padding: .7rem 1rem;
  background: var(--surface);
  color: var(--text);
  z-index: 100;
}
.skip:focus { left: .5rem; top: .5rem; }

.wrap { width: min(100% - 2.5rem, var(--measure-wide)); margin-inline: auto; }
.wrap-narrow { width: min(100% - 2.5rem, var(--measure)); margin-inline: auto; }

/* ------------------------------------------------------------------ header */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--header-bg);
  border-bottom: 1px solid var(--rule);
  backdrop-filter: saturate(180%) blur(8px);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: var(--header-h);
}

.brand {
  text-decoration: none;
  color: inherit;
  display: flex;
  align-items: center;
  gap: .7rem;
}

/* Same letterform as favicon.ico, so the browser tab and the masthead agree.
 * Colours come from --accent / --on-accent rather than being hard-coded, which
 * is what lets dark mode invert the square instead of hiding navy on near-black. */
.brand-mark { flex: none; display: block; width: 2.2rem; height: 2.2rem; }
.brand-mark .field  { fill: var(--accent); }
.brand-mark .letter { fill: var(--on-accent); }

.brand-text { display: block; min-width: 0; }
.brand-name {
  display: block;
  font-family: var(--font-head);
  font-weight: var(--brand-weight);
  font-size: var(--text-brand);
  letter-spacing: var(--brand-tracking);
  color: var(--heading);
}
.brand-tag {
  display: block;
  font-size: .74rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: .12rem;
}

.nav ul {
  display: flex;
  align-items: center;
  gap: .35rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav a {
  display: block;
  padding: .5rem .8rem;
  text-decoration: none;
  color: var(--muted);
  font-size: .9rem;
  font-weight: 600;
  letter-spacing: .02em;
  border-radius: var(--radius-sm);
}
.nav a:hover { color: var(--heading); background: var(--surface-alt); }
.nav a[aria-current="page"] { color: var(--accent); }

.nav-toggle {
  display: none;
  font: inherit;
  font-size: .85rem;
  font-weight: 600;
  padding: .45rem .75rem;
  color: var(--heading);
  background: var(--surface-alt);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* --------------------------------------------------------------- sections */

main { display: block; }

.section { padding-block: var(--section-gap); }
.section + .section { border-top: 1px solid var(--rule); }

/* Section tone -------------------------------------------------------------
 * A long near-monochrome page reads as one undifferentiated scroll. These give
 * it rhythm. Budget: at most ONE inverted band per page -- it is the anchor,
 * and two of them cancel each other out -- with tint alternating around it.
 *
 * --invert-* are real tokens rather than overrides of --accent, because a rule
 * like `background: var(--accent)` inside a block that also redefines --accent
 * resolves to the NEW value and silently paints the wrong colour. */

.section--tint { background: var(--surface-alt); }

.section--invert {
  background: var(--invert-bg);
  color: var(--invert-text);
  --heading: var(--invert-heading);
  --text: var(--invert-text);
  --muted: var(--invert-muted);
  --rule: var(--invert-rule);
  --rule-strong: var(--invert-rule-strong);
  --accent: var(--invert-accent);
  --counter-deep: var(--invert-accent);
  /* --link is easy to forget here and fails silently: the light-mode link
   * colour IS the band's background, so an un-remapped link renders navy on
   * navy and disappears entirely. */
  --link: var(--invert-accent);
}

/* The change of background already separates these, so the hairline doubles up. */
.section--tint, .section--invert,
.section--tint + .section, .section--invert + .section { border-top: none; }

.section-head { max-width: var(--measure); margin-bottom: 2.5rem; }

.eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  /* Brass, not navy -- this is the one element that recurs on every section, so
   * it is where the accent goes furthest. --counter-deep, not --counter: at
   * .74rem this is small text, and --counter is a graphics tone. */
  color: var(--counter-deep);
  margin-bottom: .8rem;
}

.section-title { font-size: var(--text-h2); color: var(--heading); }

.lede { font-size: var(--text-lede); color: var(--muted); margin-top: 1rem; }

/* ------------------------------------------------------------------- hero */

.hero { padding-block: var(--hero-gap); }
.hero-inner { max-width: var(--measure); }
.hero-title { font-size: var(--text-h1); color: var(--heading); letter-spacing: var(--head-tracking); }
.hero-lede { font-size: var(--text-lede); color: var(--muted); margin-top: 1.5rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2.25rem; }

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-block;
  padding: .8rem 1.5rem;
  font-family: var(--font-body);
  font-size: .95rem;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-strong); }
.btn-secondary { background: transparent; color: var(--heading); border-color: var(--rule-strong); }
.btn-secondary:hover { background: var(--surface-alt); }

/* -------------------------------------------------------------- expertise */

.expertise { display: grid; gap: var(--card-gap); }
@media (min-width: 900px) { .expertise { grid-template-columns: repeat(3, 1fr); } }

.expertise-item { display: flex; flex-direction: column; }
.expertise-title { font-size: var(--text-h3); color: var(--heading); }
.expertise-claim { margin-top: .9rem; color: var(--text); font-weight: 500; }
.expertise-body { margin-top: .9rem; color: var(--muted); font-size: .95rem; }

.questions { margin: 1.1rem 0 0; padding: 0; list-style: none; display: grid; gap: .5rem; }
.questions li {
  position: relative;
  padding-left: 1.5rem;
  font-size: .95rem;
  color: var(--text);
}
.questions li::before {
  content: "";
  position: absolute;
  left: .35rem;
  top: .68em;
  width: .4rem;
  height: .4rem;
  border-radius: 50%;
  background: var(--accent);
}

/* Four practice areas read better as a 2x2 than as a row of three plus an
 * orphan. Declared after .expertise so it wins at the same specificity. */
@media (min-width: 900px) { .expertise--pairs { grid-template-columns: repeat(2, 1fr); } }

/* ---------------------------------------------------------------- results */

.results { display: grid; gap: var(--card-gap); }
@media (min-width: 640px)  { .results { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .results { grid-template-columns: repeat(3, 1fr); } }

/* Two results since 2026-08-11, when the utilization card came off. Left on the three-column
 * rule the pair sits in columns one and two with an empty third, which reads as a card that
 * failed to load rather than as a deliberate pair. Capped as well as halved, because two
 * cards stretched across the full 74rem measure are wider than their two lines of body copy
 * can carry. Same reasoning as .expertise--pairs above, and declared after .results so it
 * wins at equal specificity. */
@media (min-width: 1000px) {
  .results--pair { grid-template-columns: repeat(2, 1fr); max-width: 52rem; }
}

.result { padding-top: 1.15rem; border-top: 2px solid var(--accent); }
.result-figure {
  font-family: var(--font-head);
  font-size: var(--text-h3);
  font-weight: var(--head-weight);
  line-height: 1.1;
  color: var(--heading);
  /* Lining and tabular: these are financial figures sitting in a grid, so they
   * must align column to column and not drop below the baseline. */
  font-variant-numeric: lining-nums tabular-nums;
}
.result-body { margin-top: .7rem; color: var(--muted); font-size: .95rem; }

/* ------------------------------------------------------------------ prose */
/* Running narrative, capped at the reading measure. Used where the page needs
 * to make an argument rather than list something. */

.prose { max-width: var(--measure); }

/* A short assertion standing on its own line at the end of a prose block, used where the
 * sentence IS the argument rather than a summary of it. Sized between body and h3 and set
 * in the heading colour, which is enough separation without resorting to bold: bold inside
 * running prose reads as shouting on a page that is otherwise quiet.
 *
 * Deliberately not a blockquote. There is no source and nobody is being quoted. */
.prose-claim {
  margin-top: 1.4rem;
  font-family: var(--font-head);
  font-size: 1.18rem;
  line-height: 1.4;
  color: var(--heading);
}
.prose p { margin-top: 1.15rem; color: var(--text); }
.prose p:first-child { margin-top: 0; }
.prose strong { color: var(--heading); font-weight: 600; }

/* --------------------------------------------------------------- projects */

.projects { display: grid; gap: var(--card-gap); }
@media (min-width: 860px) { .projects { grid-template-columns: repeat(3, 1fr); } }

.project { display: flex; flex-direction: column; }
.project-icon { color: var(--accent); margin-bottom: 1rem; }
.project-icon svg { width: 2rem; height: 2rem; display: block; }
.project-title { font-size: var(--text-h3); color: var(--heading); }
/* Ties each tool back to the engagement type on the home page, so the portfolio
 * reads as an output of the offering rather than as a separate list of software.
 * --counter-deep, not --counter: at .82rem this is small text. */
.project-meta {
  margin-top: .45rem;
  font-size: .82rem;
  font-weight: 600;
  color: var(--counter-deep);
}
.project-outcome { margin-top: .8rem; color: var(--text); font-weight: 500; }
.project-body { margin-top: .8rem; color: var(--muted); font-size: .95rem; }
.project-body + .project-body { margin-top: .7rem; }

.project-status {
  margin-top: auto;
  padding-top: 1.1rem;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --------------------------------------------------------------- approach */

.approach { display: grid; gap: var(--card-gap); counter-reset: step; }
@media (min-width: 780px) { .approach { grid-template-columns: repeat(3, 1fr); } }

.approach-step { counter-increment: step; }
.approach-step::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  font-family: var(--font-head);
  font-size: 1.6rem;
  font-weight: var(--head-weight);
  color: var(--accent);
  margin-bottom: .6rem;
}
.approach-title { font-size: 1.1rem; color: var(--heading); }
.approach-body { margin-top: .5rem; color: var(--muted); font-size: .95rem; }

/* -------------------------------------------------------------------- cta */

.cta { text-align: center; }
.cta .section-head { margin-inline: auto; text-align: center; }
.cta-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: .75rem; }

/* -------------------------------------------------------------------- bio */

.bio { display: grid; gap: var(--card-gap); align-items: start; }
@media (min-width: 860px) { .bio { grid-template-columns: minmax(0, 18rem) minmax(0, 1fr); } }

.bio-photo {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  margin-bottom: 1.1rem;
}

/* Single column below the grid breakpoint, where a full-width portrait would
 * be enormous on a phone. */
@media (max-width: 859px) { .bio-card { max-width: 15rem; } }

.bio-name { font-size: var(--text-h3); color: var(--heading); }
.bio-role { color: var(--muted); font-size: .95rem; margin-top: .3rem; }

.bio-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 1rem; }
.bio-list li { position: relative; padding-left: 1.75rem; }
.bio-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .62em;
  width: 1rem;
  height: 1px;
  background: var(--accent);
}

.credentials { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--rule); }

/* Standing on their own rather than beside the portrait, the blocks cap at the
 * reading measure -- a two-column dl stretched across --measure-wide leaves the
 * definition column absurdly long for values that are one line each. The first
 * block already sits under the section-head hairline, so it draws no second one. */
.credentials-group { max-width: var(--measure); }
.credentials:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.credentials dl { margin: 0; display: grid; gap: .9rem; }
@media (min-width: 640px) { .credentials dl { grid-template-columns: 10rem 1fr; gap: .9rem 1.5rem; } }
.credentials dt {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: .15rem;
}
.credentials dd { margin: 0; color: var(--text); }

/* -------------------------------------------------------------------- viz */
/* One chart, on the home page, carrying the argument the whole site makes.
 * Inline SVG rather than an image so it is theme-aware and needs no request --
 * the CSP is default-src 'self' and there is no chart library anywhere.
 *
 * Sized deliberately small. Scaled much past 30rem the type inside grows
 * larger than the body copy around it, and it starts reading as decoration. */

/* 28rem against a 440-unit viewBox renders the SVG's type at roughly its stated
 * size, just under the body copy. Widen the box and the chart's labels grow
 * larger than the prose around it, which makes it read as decoration. */
.viz { margin: 0; max-width: 28rem; }
.viz-svg {
  width: 100%;
  height: auto;
  display: block;
  font-family: var(--font-body);
  font-variant-numeric: lining-nums tabular-nums;
}
/* Row labels sit in a fixed-width column, so they run a size below the values
 * to keep the longest one inside it. */
.viz-name  { fill: var(--heading); font-size: 14px; }
.viz-value { fill: var(--heading); font-size: 15px; font-weight: 600; }
.viz-note  { fill: var(--muted); font-size: 13px; }
.viz-bar   { fill: var(--viz-1); }
.viz-bar--flag { fill: var(--counter); }
.viz-zero { stroke: var(--rule-strong); stroke-width: 1; }
.viz-caption { margin-top: 1.1rem; font-size: .82rem; color: var(--muted); max-width: 30rem; }
/* The caption and the paragraph that follows are both small and muted, so
 * without this they read as one run-on block. */
.viz + .prose { margin-top: 2rem; }

/* -------------------------------------------------------------- shots */
/* Captures of the working market P&L engine running on generated data.
 * Raster, unlike .viz above, because these are photographs of a real
 * application rather than drawings: there is nothing to redraw as SVG.
 *
 * Two rules came out of the review that produced them on 2026-08-10, and
 * both are enforced in demo-data/capture_figures.py rather than here.
 * They are regions, never whole application windows, because at the width
 * this page renders a full window is texture and the eye has nowhere to
 * land. And they stop above the expanded P&L, whose row labels are a
 * former employer's chart of accounts.
 *
 * The application is light-themed, so in dark mode these are bright blocks
 * on a dark page. Deliberately not filtered or dimmed to suit the page:
 * that would misrepresent the artifact, which is the one thing a figure
 * claiming to be a real screenshot cannot do. The frame gives the eye an
 * edge instead. */

.shots { display: grid; gap: 2.75rem; }
.shot { margin: 0; }
.shot-frame {
  border: 1px solid var(--rule-strong);
  border-radius: 5px;
  overflow: hidden;
}
.shot-frame img { width: 100%; }
/* Held to the reading measure rather than the image width. A caption set
 * across 74rem is a different document from the prose above it. */
.shot figcaption { margin-top: .9rem; max-width: var(--measure); }
.shot-label {
  display: block;
  font-size: .74rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--counter-deep);
  margin-bottom: .3rem;
}
.shot-note { font-size: .88rem; color: var(--muted); }
.shot-note + .shot-note { margin-top: .6rem; }
.shot-hint { display: none; margin-top: .55rem; font-size: .74rem; color: var(--muted); }

/* Below the width where the capture stops being legible the figure becomes a
 * pannable frame rather than a shrunk-to-fit strip. At 375px a full-width image
 * renders the application's own type at roughly 40% of its authored size, which
 * is exactly the failure the rejected full-window captures had: texture instead
 * of evidence, at a different scale. 44rem holds it near 90%.
 *
 * The frame scrolls, never the page. Checked at 375px: documentElement
 * scrollWidth stays equal to the viewport. */
@media (max-width: 46rem) {
  .shot-frame { overflow-x: auto; overflow-y: hidden; }
  .shot-frame img { min-width: 44rem; }
  .shot-hint { display: block; }
}

.shots + .prose { margin-top: 2.75rem; }

/* ---------------------------------------------------------------- listing */
/* Label on the left, what it means on the right. Deliberately not a card grid:
 * entries run to very different lengths, and a grid punishes that by padding
 * every short one out to match the tallest. It also breaks the page's rhythm of
 * card grids, which is the point -- Home already carries two of those.
 *
 * Used for the engagement types on Home and the record on About, so the class
 * names stay generic. Capped narrower than --measure-wide so the body column
 * lands near the reading measure. */

.listing { display: grid; gap: 2.5rem; max-width: 60rem; }

.listing-item { display: grid; gap: .9rem; }
@media (min-width: 900px) {
  .listing-item { grid-template-columns: minmax(0, 18rem) minmax(0, 1fr); gap: 2.5rem; }
}

.listing-label { font-size: var(--text-h3); color: var(--heading); }
.listing-meta { margin-top: .5rem; font-size: .82rem; color: var(--muted); }
.listing-body { color: var(--muted); font-size: .95rem; }
.listing-body p + p { margin-top: .8rem; }

.listing + .prose { margin-top: 2.5rem; }

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

.contact-grid { display: grid; gap: var(--card-gap); align-items: start; }
@media (min-width: 880px) { .contact-grid { grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); } }

.contact-block + .contact-block { margin-top: 1.75rem; }
.contact-label {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .3rem;
}
.contact-value { color: var(--text); }
.contact-value a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 3px; }

.form { display: grid; gap: 1.1rem; }
.field { display: grid; gap: .4rem; }
.field label { font-size: .85rem; font-weight: 600; color: var(--heading); }
.field .req { color: var(--accent); }

.field input, .field textarea {
  font: inherit;
  font-size: .95rem;
  padding: .7rem .85rem;
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-sm);
  width: 100%;
}
.field input:focus, .field textarea:focus { border-color: var(--accent); }
.field textarea { resize: vertical; min-height: 8rem; }

/* Honeypot -- hidden from people, tempting to bots. Not display:none, since
 * some bots skip those; off-screen with aria-hidden is more effective. */
.hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px;
  overflow: hidden;
}

.form-note { font-size: .85rem; color: var(--muted); }

.notice { padding: .9rem 1.1rem; border-radius: var(--radius-sm); border: 1px solid; font-size: .93rem; }
.notice-ok { background: var(--ok-bg); border-color: var(--ok-border); color: var(--ok-text); }
.notice-bad { background: var(--bad-bg); border-color: var(--bad-border); color: var(--bad-text); }

/* ----------------------------------------------------------------- footer */

.site-footer {
  border-top: 1px solid var(--rule);
  padding-block: 2.5rem;
  margin-top: var(--section-gap);
  font-size: .88rem;
  color: var(--muted);
}
.site-footer .wrap { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; justify-content: space-between; }
.site-footer a { color: var(--muted); }

/* ----------------------------------------------------------------- mobile */

/* Raised from 660px to 820px on 2026-08-10, when Engagements became a fifth nav
 * item. Five items measure 440px against the brand lockup's 226px, so below about
 * 720px the two no longer fit on one line and the brand name and descriptor each
 * wrap, pushing the header from 81px to 97px. iPad portrait is 768px and sat inside
 * that band. Measured, not guessed: at 820px the pair needs 682px of the 780px
 * available, which leaves enough slack that a longer nav label will not reopen this.
 * If an item is ever removed, this can come back down. */
@media (max-width: 820px) {
  .nav-toggle { display: block; }

  .brand { gap: .55rem; }
  .brand-mark { width: 1.9rem; height: 1.9rem; }

  .nav ul {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: .4rem;
    background: var(--surface);
    border-bottom: 1px solid var(--rule);
    box-shadow: var(--shadow);
  }
  .nav ul.open { display: flex; }
  .nav a { padding: .75rem .9rem; font-size: 1rem; }

  .site-header .wrap { position: relative; }
  .site-footer .wrap { flex-direction: column; }
}
