/* ==========================================================================
   AppTech Dev — apptechdev.co.za
   Design system implemented from Website Brief v1.0, section 10.
   Every token below maps to a row in that document. Nothing is invented.
   ========================================================================== */

/* --------------------------------------------------------------- 1. fonts */
/* Three families, deliberately none of the six that make up most machine-made
   frontends. Newsreader carries the voice, Public Sans does the work, and
   Plex Mono marks anything that is a measurement. 64KB for all three. */

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/newsreader-500.woff2') format('woff2');
}

@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('/fonts/public-sans.woff2') format('woff2');
}

@font-face {
  font-family: 'Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/plex-mono-500.woff2') format('woff2');
}

/* -------------------------------------------------------------- 2. tokens */

:root {
  /* colour — brief 10.1, contrast measured against white */
  --brand: #2c5690;        /* 7.39:1 */
  --brand-dark: #21406b;   /* 10.45:1 */
  --brand-tint: #eef3fa;
  --ink: #16202e;          /* 16.4:1 */
  --ink-2: #475467;        /* 7.69:1 */
  /* The brief prints #6B7688, measured against white only (4.59:1). Captions
     on this site also sit on --canvas and --brand-tint, where that drops to
     4.35 and 4.12, below AA. Darkened until it passes on all three: 5.51
     white, 5.23 canvas, 4.95 tint. */
  --ink-3: #5e6980;
  --line: #e5e1da;
  --line-strong: #cdc7bd;   /* diagram strokes and dividers — decorative */
  /* WCAG 2.2 SC 1.4.11: anything that identifies a control needs 3:1.
     3.33:1 on white, 3.16:1 on canvas. */
  --line-control: #8a8377;
  --surface: #ffffff;
  /* Warm paper, not the cold blue-grey every dashboard template ships with. */
  --canvas: #f6f3ee;
  --paper: #fbf9f5;
  --accent: #945713;        /* 5.19:1 at worst — one thing at a time */
  --accent-on-dark: #d69a3f;
  --success-text: #07734f;
  --warning-text: #8a5a0b;
  --danger-text: #b02a21;

  /* hero palette — art direction 7.2 */
  --night: #0b1220;
  --night-2: #16202e;
  --amber: #c98a2b;

  /* spacing — 8pt scale, brief 10.3. Nothing outside this scale. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;
  --s-10: 128px;

  /* rhythm */
  --band: clamp(4rem, 2.2rem + 5.2vw, 6rem);      /* 64px mobile -> 96px desktop */
  --gutter: clamp(1.5rem, 0.6rem + 2.6vw, 3rem);  /* 24px -> 48px */
  --content: 1200px;
  --wide: 1440px;
  --measure: 68ch;

  /* radius */
  --r-card: 10px;
  --r-btn: 8px;
  --r-pill: 999px;

  /* elevation — one shadow only, brief 10.3 */
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 1px 3px rgba(16, 24, 40, 0.04);
  --shadow-lift: 0 2px 4px rgba(16, 24, 40, 0.06), 0 8px 24px rgba(16, 24, 40, 0.06);

  /* motion — brief 10.5 */
  --t-fast: 150ms;
  --t-base: 200ms;
  --t-slow: 250ms;
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-both: cubic-bezier(0.4, 0, 0.2, 1);

  /* type */
  --font-display: 'Newsreader', Georgia, 'Times New Roman', serif;
  --font: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* A wider range than before: the big end is bigger, so fewer words carry
     more. Display sizes are serif and set tight. */
  --fs-mega: clamp(2.75rem, 1.3rem + 6.4vw, 5.25rem);
  --fs-display: clamp(2.25rem, 1.3rem + 4.2vw, 3.75rem);
  --fs-h1: clamp(2rem, 1.3rem + 3.1vw, 3rem);
  --fs-h2: clamp(1.625rem, 1.2rem + 1.9vw, 2.25rem);
  --fs-h3: clamp(1.1875rem, 1.05rem + 0.6vw, 1.375rem);
  --fs-lead: clamp(1.1875rem, 1.08rem + 0.5vw, 1.375rem);
  --fs-body: 1.0625rem;
  --fs-small: 0.8125rem;
  --fs-label: 0.6875rem;

  --header-h: 68px;
}

/* --------------------------------------------------------------- 3. reset */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--s-5));
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
}

img,
svg,
video,
picture {
  display: block;
  max-width: 100%;
}

/* An author-level `display: block` above outranks the user agent's
   `[hidden] { display: none }`, so restate it. */
[hidden] { display: none !important; }

img,
video {
  height: auto;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

h1, h2, h3, h4, p, ul, ol, figure, blockquote {
  margin: 0;
}

ul, ol {
  padding: 0;
  list-style: none;
}

/* ----------------------------------------------------------- 4. typography */

/* Headings are serif and set at one weight. Hierarchy comes from size and
   space, not from piling on weight. */
h1, h2, h3, h4,
.mega, .display, .h1, .h2 {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.012em;
  text-wrap: balance;
  color: var(--ink);
  font-synthesis-weight: none;
}

.mega {
  font-size: var(--fs-mega);
  line-height: 0.98;
  letter-spacing: -0.028em;
}

.display {
  font-size: var(--fs-display);
  line-height: 1.04;
  letter-spacing: -0.022em;
}

h1, .h1 {
  font-size: var(--fs-h1);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

h2, .h2 {
  font-size: var(--fs-h2);
  line-height: 1.12;
  letter-spacing: -0.016em;
}

/* h3 drops back to the sans: at this size the serif stops earning its keep
   and starts looking like a pull quote. */
h3, .h3 {
  font-family: var(--font);
  font-weight: 600;
  font-size: var(--fs-h3);
  line-height: 1.35;
  letter-spacing: -0.008em;
}

p {
  text-wrap: pretty;
  max-width: var(--measure);
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 54ch;
}

.small {
  font-size: var(--fs-small);
  line-height: 1.5;
}

/* Was an eyebrow with a little dash, on every band. Now a mono label, used
   sparingly and only where a section genuinely needs naming. */
.label {
  display: block;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: var(--s-5);
}

.label-num {
  color: var(--accent);
}

.band-dark .label { color: #8fa0b8; }
.band-dark .label-num { color: var(--accent-on-dark); }

/* numerals and anything measured */
.mono, .stat .sv, .pains .n {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Vertical rhythm. Zero-specificity so any component rule overrides it,
   which is why cards, steps and flex lists keep their own spacing. */
/* The element part stays outside :where() so this keeps enough specificity
   to beat the reset's `p { margin: 0 }`, while any component rule still
   overrides it. */
:where(h1, h2, h3, h4) + p,
:where(h1, h2, h3, h4) + ul,
:where(h1, h2, h3, h4) + ol,
:where(h1, h2, h3, h4) + dl,
:where(h1, h2, h3, h4) + figure,
:where(h1, h2, h3, h4) + blockquote,
p + p {
  margin-top: var(--s-4);
}

.muted { color: var(--ink-2); }
.caption {
  font-size: var(--fs-small);
  color: var(--ink-3);
  line-height: 1.5;
}

a {
  color: var(--brand);
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
  transition: color var(--t-fast) var(--ease-out);
}

a:hover { color: var(--brand-dark); }

/* Links inside body text are underlined at all times — brief 10.4.
   Navigation, cards and buttons are not body text, so they are excluded. */
p a,
dd a,
.answer a,
.prose a,
.ticks a {
  text-decoration: underline;
}

.footer a,
.nav a,
.nav-panel a,
.breadcrumb a { text-decoration: none; }

.footer a:hover { text-decoration: underline; }

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

.hero :focus-visible,
.footer :focus-visible {
  outline-color: #a9c6ee;
}

::selection {
  background: #cfe0f7;
  color: var(--ink);
}

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: var(--s-4);
  z-index: 100;
  padding: var(--s-3) var(--s-4);
  background: var(--brand);
  color: #fff;
  border-radius: var(--r-btn);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform var(--t-fast) var(--ease-out);
}

.skip-link:focus-visible {
  transform: translateY(0);
  color: #fff;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------- 5. layout */

.wrap {
  width: 100%;
  max-width: calc(var(--content) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap-wide {
  max-width: calc(var(--wide) + var(--gutter) * 2);
}

.band {
  padding-block: var(--band);
}

.band-tight { padding-block: calc(var(--band) * 0.66); }
.band-canvas { background: var(--canvas); }
.band-tint { background: var(--brand-tint); }
.band-line { border-top: 1px solid var(--line); }

.band-dark {
  background: var(--night-2);
  color: #dbe3ee;
}

.band-dark h2,
.band-dark h3,
.band-dark .display { color: #fff; }
.band-dark .lead { color: #b6c3d4; }
.band-dark .caption { color: #8fa0b8; }
.band-dark a:not(.btn) { color: #a9c6ee; }
.band-dark a:not(.btn):hover { color: #cfe0f7; }

.stack > * + * { margin-top: var(--s-4); }
.stack-lg > * + * { margin-top: var(--s-5); }

.section-head {
  max-width: 62ch;
  margin-bottom: var(--s-7);
}

.section-head p { margin-top: var(--s-4); }

.grid {
  display: grid;
  gap: var(--s-5);
}

/* auto-fit was quietly giving .grid-2 three columns on a wide screen, which
   left ragged last rows. Each one now caps at the count in its name. */
.grid-2,
.grid-3,
.grid-4 { grid-template-columns: minmax(0, 1fr); }

@media (min-width: 620px) {
  .grid-2,
  .grid-3,
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 940px) {
  .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.split {
  display: grid;
  gap: var(--s-7);
  align-items: start;
}

@media (min-width: 900px) {
  .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s-8); }
  .split-wide { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  .split-narrow { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
  .split-sticky > :first-child { position: sticky; top: calc(var(--header-h) + var(--s-6)); }
}

.rule {
  height: 1px;
  background: var(--line);
  border: 0;
  margin: 0;
}

/* -------------------------------------------------------------- 6. buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: 12px 20px;
  border-radius: var(--r-btn);
  border: 1px solid transparent;
  font-weight: 600;
  font-size: var(--fs-body);
  line-height: 1.25;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--t-fast) var(--ease-out),
    border-color var(--t-fast) var(--ease-out),
    color var(--t-fast) var(--ease-out);
}

.btn svg { flex: none; }

.btn-primary {
  background: var(--brand);
  color: #fff;
  border-color: var(--brand);
}

.btn-primary:hover {
  background: var(--brand-dark);
  border-color: var(--brand-dark);
  color: #fff;
}

.btn-secondary {
  background: transparent;
  color: var(--ink-2);
  border-color: var(--line-control);
}

.btn-secondary:hover {
  color: var(--ink);
  border-color: var(--ink-3);
  background: var(--canvas);
}

.band-dark .btn-secondary,
.hero .btn-secondary {
  color: #dbe3ee;
  border-color: rgba(219, 227, 238, 0.32);
}

.band-dark .btn-secondary:hover,
.hero .btn-secondary:hover {
  color: #fff;
  border-color: rgba(219, 227, 238, 0.6);
  background: rgba(255, 255, 255, 0.06);
}

.btn-sm { min-height: 40px; padding: 8px 16px; font-size: var(--fs-small); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}

/* On a narrow phone a long label such as "Send a short description instead"
   cannot shrink below its own width and pushes the page sideways. Full-width
   buttons fix that and give a bigger tap target at the same time. */
@media (max-width: 460px) {
  .btn-row > .btn {
    width: 100%;
    white-space: normal;
    text-align: center;
  }

  .btn-row > .btn-sm { width: auto; }
}

/* arrow link */
.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-weight: 600;
  text-decoration: none;
  min-height: 44px;
}

.arrow-link svg {
  transition: transform var(--t-fast) var(--ease-out);
}

.arrow-link:hover svg { transform: translateX(3px); }

/* ---------------------------------------------------------------- 7. cards */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: var(--s-5);
  box-shadow: var(--shadow);
}

.card h3 { margin-bottom: var(--s-3); }
.card p { color: var(--ink-2); }


/* numbered step */
.step {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4);
  align-items: start;
}

.step-n {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-pill);
  background: var(--brand-tint);
  color: var(--brand);
  font-weight: 600;
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
  flex: none;
}

.band-dark .step-n {
  background: rgba(169, 198, 238, 0.14);
  color: #a9c6ee;
}

.step h3 { margin-bottom: var(--s-2); }
.step p { color: var(--ink-2); margin: 0; }
.band-dark .step p { color: #b6c3d4; }

/* fact list with ticks */
.ticks { display: grid; gap: var(--s-3); }

.ticks li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-3);
  align-items: start;
  color: var(--ink-2);
}

.ticks svg {
  color: var(--brand);
  margin-top: 4px;
  flex: none;
}

.band-dark .ticks li { color: #b6c3d4; }
.band-dark .ticks svg { color: #a9c6ee; }

/* the "where the time goes" list */
.pains { display: grid; gap: 0; }

.pains li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4);
  align-items: baseline;
  padding-block: var(--s-4);
  border-top: 1px solid var(--line);
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--ink);
}

.pains li:last-child { border-bottom: 1px solid var(--line); }

.pains .n {
  font-size: var(--fs-small);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--brand);
  letter-spacing: 0.04em;
}

/* quote line used on the service page */
.said {
  margin-top: var(--s-5);
  padding-left: var(--s-4);
  border-left: 2px solid var(--brand);
  color: var(--ink);
  font-size: var(--fs-lead);
  line-height: 1.5;
}

.said cite { font-style: normal; }

/* -------------------------------------------------------- 8. screen figures */

.screen {
  margin: 0;
}

.screen img {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--canvas);
}

.screen figcaption {
  margin-top: var(--s-3);
  font-size: var(--fs-small);
  color: var(--ink-3);
  line-height: 1.5;
}

.screen-frame {
  border: 1px solid var(--line);
  border-radius: calc(var(--r-card) + 4px);
  background: var(--surface);
  padding: var(--s-2);
  box-shadow: var(--shadow-lift);
}

.screen-frame img {
  border-radius: 6px;
  border: 1px solid var(--line);
}

/* ------------------------------------------------------------- 9. examples */

.example {
  display: grid;
  gap: var(--s-6);
  padding-block: var(--band);
  border-top: 1px solid var(--line);
}

.example:first-of-type { border-top: 0; padding-top: 0; }

@media (min-width: 960px) {
  .example { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s-8); align-items: start; }
  .example-flip > figure { order: -1; }
}

.beforeafter { display: grid; gap: var(--s-4); margin-top: var(--s-5); }

.beforeafter div {
  display: grid;
  grid-template-columns: 76px 1fr;
  gap: var(--s-4);
  align-items: start;
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}

.beforeafter dt {
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-top: 3px;
}

.beforeafter dd { margin: 0; color: var(--ink-2); }
.beforeafter .now dt { color: var(--brand); }
.beforeafter .now dd { color: var(--ink); }

/* The seven complaints, as evidence rather than cards: one row each, the
   complaint beside the step that answers it. Seven never divides into a card
   grid without leaving a gap. */
.complaints { border-top: 1px solid var(--line); }

.complaints > div {
  display: grid;
  gap: var(--s-2) var(--s-6);
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--line);
}

@media (min-width: 820px) {
  .complaints > div { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
}

.complaints dt {
  font-size: var(--fs-lead);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.014em;
  color: var(--ink);
  text-wrap: balance;
}

.complaints dd {
  margin: 0;
  color: var(--ink-2);
  line-height: 1.6;
}

/* ---------------------------------------------------------------- 10. FAQ */

.faq { border-top: 1px solid var(--line); }

.faq details {
  border-bottom: 1px solid var(--line);
}

.faq summary {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-4);
  align-items: center;
  padding-block: var(--s-5);
  min-height: 44px;
  cursor: pointer;
  font-size: var(--fs-lead);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.014em;
  list-style: none;
  color: var(--ink);
  transition: color var(--t-fast) var(--ease-out);
}

.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--brand); }

.faq summary .chev {
  width: 20px;
  height: 20px;
  color: var(--ink-3);
  transition: transform var(--t-base) var(--ease-out), color var(--t-fast) var(--ease-out);
  flex: none;
}

.faq summary:hover .chev { color: var(--brand); }
.faq details[open] summary .chev { transform: rotate(180deg); color: var(--brand); }

.faq .answer {
  padding-bottom: var(--s-5);
  color: var(--ink-2);
  max-width: var(--measure);
}

.faq details[open] .answer { animation: fade-in var(--t-slow) var(--ease-out); }

@keyframes fade-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------- 11. forms */

.form { display: grid; gap: var(--s-5); }

.field { display: grid; gap: var(--s-2); }

.field label {
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--ink);
}

.field .hint {
  font-size: var(--fs-small);
  color: var(--ink-3);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  border: 1px solid var(--line-control);
  border-radius: var(--r-card);
  background: var(--surface);
  color: var(--ink);
  transition: border-color var(--t-fast) var(--ease-out),
    box-shadow var(--t-fast) var(--ease-out);
}

.field textarea { min-height: 148px; resize: vertical; line-height: 1.6; }

.field input:hover,
.field select:hover,
.field textarea:hover { border-color: var(--ink-3); }

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(44, 86, 144, 0.18);
}

.field input::placeholder,
.field textarea::placeholder { color: var(--ink-3); }

.field .error {
  display: none;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-small);
  color: var(--danger-text);
  font-weight: 600;
}

.field[data-invalid='true'] .error { display: flex; }
.field[data-invalid='true'] input,
.field[data-invalid='true'] textarea,
.field[data-invalid='true'] select { border-color: var(--danger-text); }

.form-row { display: grid; gap: var(--s-5); }

@media (min-width: 620px) {
  .form-row { grid-template-columns: 1fr 1fr; }
}

/* honeypot — hidden from people, visible to naive bots */
.hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-note {
  font-size: var(--fs-small);
  color: var(--ink-3);
  max-width: 54ch;
}

.form-status {
  display: none;
  gap: var(--s-3);
  padding: var(--s-4);
  border-radius: var(--r-card);
  border: 1px solid var(--line);
  background: var(--canvas);
  align-items: flex-start;
}

.form-status[data-state='ok'],
.form-status[data-state='error'] { display: flex; }
.form-status[data-state='ok'] { border-color: #b4dfcc; background: #f0faf5; color: var(--success-text); }
.form-status[data-state='error'] { border-color: #f0c4c0; background: #fdf4f3; color: var(--danger-text); }
.form-status svg { flex: none; margin-top: 2px; }
.form-status p { margin: 0; color: inherit; font-weight: 600; }

/* --------------------------------------------------------------- 12. header */

.header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.88);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-base) var(--ease-out),
    background-color var(--t-base) var(--ease-out);
}

.header[data-scrolled='true'] {
  border-bottom-color: var(--line);
}

.header-inner {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-height: var(--header-h);
}

.brand-link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--ink);
  padding-block: var(--s-2);
  flex: none;
}

/* The AppTech Dev logo: the parent AppTech A and swish, with APPTECH over
   DEV in the parent's own lettering (tools/logo-a.py). One SVG, cached across
   pages. The earlier lambda logo and its blinking cursor are kept in
   assets/brand/apptech-dev-lambda/. */
.brand-logo { display: block; height: 52px; width: auto; }

.footer-brand { margin: 0 0 var(--s-4); }
.footer .brand-logo { height: 62px; }

.nav {
  display: none;
  margin-left: auto;
  align-items: center;
  gap: var(--s-1);
}

.nav a {
  position: relative;
  padding: 10px 12px;
  border-radius: 6px;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--ink-2);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease-out),
    background-color var(--t-fast) var(--ease-out);
}

.nav a:hover { color: var(--ink); background: var(--canvas); }
.nav a[aria-current='page'] { color: var(--brand); font-weight: 600; }

.header-cta { display: none; margin-left: var(--s-3); }

@media (min-width: 1000px) {
  .nav { display: flex; }
  .header-cta { display: inline-flex; }
  .nav-toggle { display: none; }
}

.nav-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-width: 44px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--line-control);
  border-radius: var(--r-btn);
  background: var(--surface);
  color: var(--ink);
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
}

.nav-toggle:hover { border-color: var(--ink-3); }

.nav-toggle svg { transition: transform var(--t-base) var(--ease-out); }
.nav-toggle[aria-expanded='true'] svg { transform: rotate(180deg); }

@media (min-width: 1000px) {
  .nav-toggle { display: none; }
}

.nav-panel {
  display: none;
  border-top: 1px solid var(--line);
  background: var(--surface);
  padding-block: var(--s-4) var(--s-5);
}

.nav-panel[data-open='true'] { display: block; }

@media (min-width: 1000px) {
  .nav-panel { display: none !important; }
}

.nav-panel ul { display: grid; gap: 2px; }

.nav-panel a:not(.btn) {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 0 var(--s-3);
  border-radius: var(--r-btn);
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
}

.nav-panel a:not(.btn):hover { background: var(--canvas); }
.nav-panel a:not(.btn)[aria-current='page'] { color: var(--brand); font-weight: 600; }
.nav-panel .btn { margin-top: var(--s-4); width: 100%; }

/* --------------------------------------------------------------- 13. hero */

.hero {
  position: relative;
  isolation: isolate;
  background: var(--night);
  color: #dbe3ee;
  overflow: hidden;
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: -2;
}

@media (max-width: 639px) {
  /* The phone headline sits over the upper two-thirds of the 9:16 still,
     where the particles are brightest. Measured: without this the headline
     is 1.01:1, which is invisible. With it, 6.9:1. The scrim eases off at the
     bottom so the paper stack still reads. */
  .hero:has(.hero-media img)::before {
    background: linear-gradient(to bottom,
      rgba(11, 18, 32, 0.86) 0%,
      rgba(11, 18, 32, 0.80) 58%,
      rgba(11, 18, 32, 0.70) 72%,
      rgba(11, 18, 32, 0.52) 100%);
  }

  /* the bottom of the fallback ladder, styled so it still reads as lit */
  .hero {
    background:
      radial-gradient(120% 80% at 88% 4%, rgba(70, 116, 184, 0.30) 0%,
        rgba(24, 40, 68, 0.12) 46%, rgba(11, 18, 32, 0) 78%),
      linear-gradient(168deg, #0b1220 0%, #101b2e 62%, #16202e 100%);
  }
}

.hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* keeps the left third dark and readable whatever the media does — AD 7.7 */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(100deg, var(--night) 0%, rgba(11, 18, 32, 0.95) 26%,
      rgba(11, 18, 32, 0.80) 46%, rgba(11, 18, 32, 0.52) 70%, rgba(11, 18, 32, 0.42) 100%),
    linear-gradient(to bottom, rgba(11, 18, 32, 0.55) 0%, rgba(11, 18, 32, 0) 30%,
      rgba(11, 18, 32, 0) 58%, rgba(11, 18, 32, 0.66) 100%);
  pointer-events: none;
}

.hero-inner {
  position: relative;
  display: grid;
  gap: var(--s-8);
  /* the header is sticky and transparent over the hero, so its height has to
     be part of the top padding or the first line hides underneath it */
  padding-block: calc(var(--header-h) + clamp(1.5rem, 1rem + 4vw, 3.5rem))
                 clamp(3rem, 2rem + 4vw, 5rem);
  align-items: center;
}

@media (min-width: 1000px) {
  .hero-inner {
    grid-template-columns: minmax(0, 512px) minmax(0, 1fr);
    gap: var(--s-8);
    min-height: min(78vh, 720px);
  }
}

.hero h1 {
  color: #fff;
  font-size: var(--fs-display);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.hero .lead {
  color: #b6c3d4;
  margin-top: var(--s-5);
  max-width: 46ch;
}

.hero .btn-row { margin-top: var(--s-6); }

.hero-since {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid rgba(219, 227, 238, 0.16);
  font-size: var(--fs-small);
  color: #8fa0b8;
  max-width: 44ch;
}

.hero-since img { width: 104px; height: auto; opacity: 0.9; flex: none; }

/* ------------------------------------------------------------ 14. diagrams */

.dia { width: 100%; height: auto; overflow: visible; }
.dia .static { stroke: var(--line-strong); fill: none; stroke-width: 2; }
.dia .active { stroke: var(--brand); fill: none; stroke-width: 2; }
.dia .fill-tint { fill: var(--brand-tint); stroke: var(--brand); stroke-width: 2; }
.dia .fill-brand { fill: var(--brand); stroke: none; }
.dia .dot { fill: var(--brand); }

.band-dark .dia .static { stroke: rgba(219, 227, 238, 0.28); }
.band-dark .dia .active { stroke: #a9c6ee; }
.band-dark .dia .dot { fill: #a9c6ee; }

.dia-figure { margin: 0; }

.dia-labels {
  display: grid;
  gap: var(--s-4);
  margin-top: var(--s-5);
}

/* DIA-02 · systems connected through one tool.
   Five labelled sources, a wire layer, one target. The wires live in a
   200x320 viewBox whose y positions match the five equal grid rows on the
   left (10%, 30%, 50%, 70%, 90%), so labels and curves cannot drift apart. */
.connect-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(48px, 9vw, 96px) minmax(0, 1.15fr);
  align-items: center;
  min-height: 340px;
}

.connect-sources {
  display: grid;
  grid-template-rows: repeat(5, 1fr);
  align-self: stretch;
  min-height: 340px;
}

.connect-sources li {
  display: flex;
  align-items: center;
  justify-self: stretch;
  align-self: center;
  min-height: 44px;
  padding: 10px 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-card);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 0.9375rem;
  line-height: 1.3;
}

.connect-wires {
  align-self: stretch;
  width: 100%;
  height: 100%;
  min-height: 340px;
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 2;
  stroke-linecap: round;
}

.connect-target {
  padding: var(--s-5);
  border: 2px solid var(--brand);
  border-radius: var(--r-card);
  background: var(--brand-tint);
}

.ct-k {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand);
}

.ct-v {
  display: block;
  margin-top: var(--s-2);
  color: var(--ink);
  line-height: 1.5;
}

/* Below 640px the wires are dropped and the relationship is carried by
   order alone, which is clearer than five curves in a 300px column. */
@media (max-width: 639px) {
  .connect-grid { grid-template-columns: 1fr; gap: var(--s-4); min-height: 0; }
  .connect-sources { grid-template-rows: none; gap: var(--s-2); min-height: 0; }
  .connect-wires { display: none; }
}

/* process line — DIA-03 */
.process { display: grid; gap: var(--s-5); counter-reset: step; }

@media (min-width: 860px) {
  .process { grid-template-columns: repeat(5, 1fr); gap: var(--s-5); }
}

.process li {
  position: relative;
  padding-top: var(--s-6);
}

.process li::before {
  content: '';
  position: absolute;
  top: 5px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--line-strong);
}

/* Each step's rule spans its own cell and bridges the grid gap, so the line
   reads as one continuous track. The last one stops at its own node. */
.process li::before { right: calc(var(--s-5) * -1); }
.process li:first-child::before { left: 6px; border-radius: 2px 0 0 2px; }
.process li:last-child::before { right: calc(100% - 6px); border-radius: 0 2px 2px 0; }

.process li::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--brand);
  box-shadow: 0 0 0 4px var(--surface);
}

.band-canvas .process li::after { box-shadow: 0 0 0 4px var(--canvas); }

@media (max-width: 859px) {
  .process li { padding-top: 0; padding-left: var(--s-6); padding-bottom: var(--s-5); }
  .process li::before { top: 6px; bottom: -24px; left: 5px; right: auto; width: 2px; height: auto; }
  .process li:last-child::before { display: none; }
  .process li:first-child::before { left: 5px; }
}

.process h3 { font-size: 1.0625rem; line-height: 1.35; margin-bottom: var(--s-2); }
.process p { font-size: 0.9375rem; color: var(--ink-2); margin: 0; }

/* Layers of security — DIA-04. Real nesting, so the structure carries the
   idea. No padlock: it is the most-used cliche in security marketing. */
.layers li {
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: var(--s-4) var(--s-5);
  background: transparent;
}

.layers ul { margin-top: var(--s-4); }

.layers .lk {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.layers .lv {
  display: block;
  color: var(--ink-2);
  font-size: 0.9375rem;
  line-height: 1.5;
  margin-top: 3px;
}

.layers .core {
  background: var(--brand-tint);
  border-color: var(--brand);
}

.layers .core .lk { color: var(--brand); }
.layers .core .lv { color: var(--ink); }

:is(.band-dark, .page-head) .layers li { border-color: rgba(219, 227, 238, 0.18); }
:is(.band-dark, .page-head) .layers .lk { color: #8fa0b8; }
:is(.band-dark, .page-head) .layers .lv { color: #b6c3d4; }

:is(.band-dark, .page-head) .layers .core {
  background: rgba(169, 198, 238, 0.09);
  border-color: rgba(169, 198, 238, 0.48);
}

:is(.band-dark, .page-head) .layers .core .lk { color: #a9c6ee; }
:is(.band-dark, .page-head) .layers .core .lv { color: #fff; }

@media (max-width: 520px) {
  .layers li { padding: var(--s-4); }
  .layers ul { margin-top: var(--s-3); }
}

/* --------------------------------------------------------------- 15. misc */

.stat-row {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}

.stat .sv {
  font-size: var(--fs-h2);
  font-weight: 600;
  letter-spacing: -0.024em;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.band-dark .stat .sv { color: #a9c6ee; }
.stat .sk { color: var(--ink-2); margin-top: var(--s-2); font-size: 0.9375rem; }
.band-dark .stat .sk { color: #b6c3d4; }

.portrait {
  width: 160px;
  height: 160px;
  border-radius: var(--r-card);
  object-fit: cover;
  border: 1px solid var(--line);
  background: var(--canvas);
  flex: none;
}

.portrait-placeholder {
  display: grid;
  place-items: center;
  width: 160px;
  height: 160px;
  border-radius: var(--r-card);
  border: 1px dashed var(--line-strong);
  background: var(--canvas);
  color: var(--ink-3);
  font-size: var(--fs-small);
  text-align: center;
  padding: var(--s-3);
  flex: none;
}

.person {
  display: flex;
  gap: var(--s-5);
  align-items: flex-start;
  flex-wrap: wrap;
}

.person-body { flex: 1 1 320px; }

.contact-list { display: grid; gap: var(--s-4); }

/* Direct children only: the inner span holds the label and the value, and
   must not become a two-column grid itself. */
.contact-list > a,
.contact-list > span {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-3);
  align-items: start;
  min-height: 44px;
  align-content: center;
  text-decoration: none;
  color: var(--ink);
}

.contact-list > a:hover { color: var(--brand); }
.contact-list > * > svg,
.contact-list > svg { color: var(--brand); margin-top: 2px; flex: none; }
.contact-list .ck { font-size: var(--fs-small); color: var(--ink-3); display: block; }

.prose h2 { margin-top: var(--s-8); margin-bottom: var(--s-4); }
.prose h3 { margin-top: var(--s-6); margin-bottom: var(--s-3); }
.prose p, .prose ul { margin-bottom: var(--s-4); color: var(--ink-2); }
.prose > :first-child { margin-top: 0; }
.prose ul { list-style: disc; padding-left: var(--s-5); max-width: var(--measure); }
.prose li { margin-bottom: var(--s-2); }
.prose li::marker { color: var(--ink-3); }

.page-head { padding-block: clamp(3rem, 2rem + 4vw, 5rem) 0; }
.page-head .lead { margin-top: var(--s-5); }

.breadcrumb {
  font-size: var(--fs-small);
  color: var(--ink-3);
  margin-bottom: var(--s-4);
}

.breadcrumb a { color: var(--ink-3); text-decoration: none; }
.breadcrumb a:hover { color: var(--brand); text-decoration: underline; }
.breadcrumb span { margin-inline: 6px; }

/* ------------------------------------------------------------- 16. footer */

.footer {
  background: var(--night-2);
  color: #8fa0b8;
  padding-block: var(--s-9) var(--s-6);
  font-size: 0.9375rem;
}

.footer-grid {
  display: grid;
  gap: var(--s-7);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}

@media (min-width: 900px) {
  .footer-grid { grid-template-columns: 1.6fr 1fr 1fr 1.2fr; gap: var(--s-6); }
}

.footer h2 {
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #dbe3ee;
  margin-bottom: var(--s-4);
}

.footer ul { display: grid; gap: 2px; }

.footer li a,
.footer address a,
.footer address span {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  color: #8fa0b8;
  text-decoration: none;
}

.footer li a:hover,
.footer address a:hover { color: #fff; text-decoration: underline; }

.footer address { font-style: normal; display: grid; gap: 0; }
.footer .blurb { color: #8fa0b8; max-width: 34ch; line-height: 1.6; }

.footer-base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  justify-content: space-between;
  align-items: center;
  margin-top: var(--s-8);
  padding-top: var(--s-5);
  border-top: 1px solid rgba(219, 227, 238, 0.12);
  font-size: var(--fs-small);
  color: #8494ab;   /* 5.32:1 on --night-2 */
}

.footer-base ul { display: flex; flex-wrap: wrap; gap: var(--s-5); }
.footer-base a { color: #8494ab; }
.footer-base a:hover { color: #dbe3ee; }

/* --------------------------------------------------------------- 17. motion */

/* Hidden only when there is JavaScript to reveal it again. If a script is
   blocked, or fails, every element is simply visible — the page never
   depends on JavaScript to show its content. */
@media (scripting: enabled) {
  [data-reveal] {
    opacity: 0;
    transform: translateY(8px);
  }
}

[data-reveal][data-shown='true'] {
  opacity: 1;
  transform: none;
  transition: opacity var(--t-base) var(--ease-out),
    transform var(--t-base) var(--ease-out);
  transition-delay: var(--delay, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  [data-reveal],
  .build [data-at] {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ---------------------------------------------------------------- 18. print */

@media print {
  .header, .footer, .hero-media, .nav-panel, .btn { display: none !important; }
  body { color: #000; font-size: 11pt; }
  .hero { background: #fff; color: #000; }
  .hero h1, .hero .lead { color: #000; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 9pt; color: #444; }
  .band { padding-block: 12pt; }
}

/* ==========================================================================
   19. Utilities
   A closed set. These exist so no element on the site carries a style
   attribute, which is what lets the Content Security Policy stay strict.
   Nothing here invents a value: every one comes from the token scale.
   ========================================================================== */

/* stagger delays — Art Direction section 10, 40ms steps */
.d0   { --delay: 0ms; }
.d40  { --delay: 40ms; }
.d60  { --delay: 60ms; }
.d80  { --delay: 80ms; }
.d100 { --delay: 100ms; }
.d120 { --delay: 120ms; }
.d140 { --delay: 140ms; }
.d160 { --delay: 160ms; }
.d180 { --delay: 180ms; }
.d200 { --delay: 200ms; }
.d240 { --delay: 240ms; }
.d280 { --delay: 280ms; }

/* spacing */
.mt-4  { margin-top: var(--s-4); }
.mt-5  { margin-top: var(--s-5); }
.mt-6  { margin-top: var(--s-6); }
.mt-7  { margin-top: var(--s-7); }
.mt-8  { margin-top: var(--s-8); }
.mt-9  { margin-top: var(--s-9); }
.mt-10px { margin-top: 10px; }
.mb-1  { margin-bottom: var(--s-1); }
.mb-4  { margin-bottom: var(--s-4); }
.mb-5  { margin-bottom: var(--s-5); }
.mb-6  { margin-bottom: var(--s-6); }
.gap-6 { gap: var(--s-6); }
.p-6   { padding: var(--s-6); }

/* type */
.h3-sm { font-size: 1.0625rem; line-height: 1.35; letter-spacing: -0.012em; }
.t-sm  { font-size: 0.9375rem; }
.measure-tight { max-width: 62ch; }
.w-narrow { max-width: 920px; }
.w-720 { max-width: 720px; }

/* colour and decoration */
.c-brand { color: var(--brand); }
.icon-brand { color: var(--brand); }
.eyebrow-light { color: #a9c6ee; }
.accent-left { border-left: 3px solid var(--brand); }
.svc-icon { color: var(--brand); margin-bottom: var(--s-4); }
.items-start { align-items: start; }
.sprite { position: absolute; }

/* ==========================================================================
   20. The picker
   Radio inputs plus :checked. No JavaScript, keyboard-operable by default,
   no ARIA needed because the semantics are already right.
   ========================================================================== */

.possible { background: var(--paper); }

.possible-head { margin-bottom: var(--s-8); }

.picker fieldset { border: 0; margin: 0; padding: 0; }

.picker-list {
  display: grid;
  gap: 0;
}

@media (min-width: 920px) {
  .picker-list {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--s-9);
    align-items: start;
  }
}

/* the inputs themselves are never seen, but stay focusable */
.picker-list input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.picker-list label {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4);
  align-items: baseline;
  padding: var(--s-5) var(--s-4) var(--s-5) 0;
  border-top: 1px solid var(--line);
  font-size: var(--fs-lead);
  line-height: 1.4;
  color: var(--ink-2);
  cursor: pointer;
  transition: color var(--t-fast) var(--ease-out);
}

.picker-list label:last-of-type { border-bottom: 1px solid var(--line); }
.picker-list label:hover { color: var(--ink); }

.picker-n {
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  color: var(--ink-3);
  transition: color var(--t-fast) var(--ease-out);
}

/* selected */
.picker-list input:checked + label {
  color: var(--ink);
}

.picker-list input:checked + label .picker-n { color: var(--accent); }

/* a rule that marks the choice, drawn rather than boxed */
.picker-list input:checked + label {
  box-shadow: inset 2px 0 0 0 var(--accent);
  padding-left: var(--s-5);
}

.picker-list input:focus-visible + label {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
  border-radius: 3px;
}

/* --- the panels ----------------------------------------------------------- */

.picker-panels {
  grid-row: 1 / 20;
  grid-column: 2;
  position: sticky;
  top: calc(var(--header-h) + var(--s-6));
}

@media (max-width: 919px) {
  .picker-panels {
    grid-row: auto;
    grid-column: 1;
    position: static;
    margin-top: var(--s-7);
  }
}

.panel { display: none; }

.picker-list input:nth-of-type(1):checked ~ .picker-panels #panel-1,
.picker-list input:nth-of-type(2):checked ~ .picker-panels #panel-2,
.picker-list input:nth-of-type(3):checked ~ .picker-panels #panel-3,
.picker-list input:nth-of-type(4):checked ~ .picker-panels #panel-4,
.picker-list input:nth-of-type(5):checked ~ .picker-panels #panel-5,
.picker-list input:nth-of-type(6):checked ~ .picker-panels #panel-6 {
  display: block;
  animation: panel-in var(--t-slow) var(--ease-out);
}

@keyframes panel-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.panel-k {
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: var(--s-4);
}

.panel-v {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--fs-h2);
  line-height: 1.18;
  letter-spacing: -0.014em;
  color: var(--ink);
  max-width: 22ch;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-5);
}

.chips li {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.06em;
  color: var(--ink-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  padding: 5px 11px;
  white-space: nowrap;
}

.panel-note {
  margin-top: var(--s-5);
  color: var(--ink-2);
  font-size: 0.9375rem;
  max-width: 44ch;
}

.panel-shot { margin-top: var(--s-5); }

.panel-shot img {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}

.panel-shot figcaption {
  margin-top: var(--s-3);
  font-size: var(--fs-small);
  color: var(--ink-3);
}

.picker-foot {
  margin-top: var(--s-8);
  color: var(--ink-2);
  max-width: none;
}

/* ==========================================================================
   21. The statement band
   One idea, set large. No cards, no icons, no three-across.
   ========================================================================== */

.statement-line {
  max-width: 20ch;
  margin-bottom: var(--s-8);
  color: #fff;
}

.statement-grid { display: grid; gap: var(--s-6); }

@media (min-width: 820px) {
  .statement-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-8); }
}

.statement-grid p { color: #b6c3d4; max-width: 46ch; }

/* ==========================================================================
   22. Proof strip
   Three screens at a glance. Deliberately not a card grid: no borders, no
   shadows, no repeated headings — the images and one line each.
   ========================================================================== */

.proof { background: var(--canvas); }

.proof-head { max-width: 58ch; margin-bottom: var(--s-8); }
.proof-head .lead { margin-top: var(--s-4); }
.proof-head p:last-child { margin-top: var(--s-5); }

.proof-strip {
  display: grid;
  gap: var(--s-6);
  grid-template-columns: minmax(0, 1fr);
}

/* Two-up, not four-up. The band claims four systems, so all four have to be
   here — but at a quarter of the content width a screenshot is a grey smudge
   and proves nothing. Half width is the narrowest they stay legible at. */
@media (min-width: 800px) {
  .proof-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6); }
}

.proof-strip img {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface);
}

.proof-strip span {
  display: block;
  margin-top: var(--s-3);
  font-size: var(--fs-small);
  color: var(--ink-2);
}

/* ==========================================================================
   23. Hero adjustments for the new type
   ========================================================================== */

.hero-label { color: #8fa0b8; margin-bottom: var(--s-6); }
.hero h1 { color: #fff; }
.hero .lead { color: #c3cedd; margin-top: var(--s-5); max-width: 42ch; }

.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: #dbe3ee;
  padding-left: var(--s-3);
  padding-right: var(--s-3);
}

.btn-ghost:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }


/* ==========================================================================
   24. The index
   Replaces seven near-identical pitch sections. A numbered editorial list:
   name, what it does, and the sentence a buyer says out loud. No icons, no
   cards, no four-bullet lists repeated seven times.
   ========================================================================== */

.index { counter-reset: none; }

.index > li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-5);
  padding-block: var(--s-6);
  border-top: 1px solid var(--line);
  align-items: start;
}

.index > li:last-child { border-bottom: 1px solid var(--line); }

@media (min-width: 860px) {
  .index > li { grid-template-columns: 4rem minmax(0, 1fr); gap: var(--s-8); }
  .index-body {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: var(--s-8);
    row-gap: var(--s-4);
    align-items: start;
  }
  .index-body :is(h2, h3) { grid-column: 1 / -1; margin-bottom: 0; }
}

.index-n {
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  color: var(--accent);
  padding-top: 0.55em;
}

.index-body :is(h2, h3) {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--fs-h2);
  line-height: 1.14;
  letter-spacing: -0.016em;
  margin-bottom: var(--s-4);
}

.index-body p { color: var(--ink-2); max-width: 46ch; }

/* On the 404 the row heading is the link. Underline only on hover, because a
   column of four permanently underlined display-size headings is noise. */
.index-body :is(h2, h3) a {
  color: inherit;
  text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}

.index-body :is(h2, h3) a::after {
  content: ' \2192';
  color: var(--line-control);
  transition: color var(--t-fast) var(--ease-out);
}

.index-body :is(h2, h3) a:hover,
.index-body :is(h2, h3) a:focus-visible { color: var(--brand); }
.index-body :is(h2, h3) a:hover::after,
.index-body :is(h2, h3) a:focus-visible::after { color: var(--brand); }

/* the quoted line, set apart without a box */
.said-inline {
  font-family: var(--font-display);
  font-size: 1.125rem;
  line-height: 1.45;
  color: var(--ink);
  border-left: 2px solid var(--accent);
  padding-left: var(--s-4);
  max-width: 34ch;
}

@media (max-width: 859px) {
  .said-inline { margin-top: var(--s-4); }
}

.ct-k { font-family: var(--font-mono); }

/* ==========================================================================
   25. Specs
   Eight decisions, as rules and type. Was a 2x4 grid of bordered cards, which
   is the most over-used shape on the web and said nothing the type does not.
   ========================================================================== */

.specs {
  display: grid;
  gap: 0;
  border-top: 1px solid var(--line);
}

@media (min-width: 780px) {
  .specs { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-8); }
  .specs > div:nth-child(2) { border-top: 0; }
}

.specs > div {
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--line);
}

@media (min-width: 780px) {
  .specs > div:nth-last-child(-n+2) { border-bottom: 0; }
}

.specs dt {
  font-weight: 600;
  font-size: 1.0625rem;
  line-height: 1.35;
  color: var(--ink);
  margin-bottom: var(--s-2);
}

.specs dd {
  margin: 0;
  color: var(--ink-2);
  font-size: 0.9375rem;
  line-height: 1.6;
  max-width: 48ch;
}

/* A line break that only applies once there is room for it. */
.br-wide { display: none; }

@media (min-width: 700px) {
  .br-wide { display: inline; }
}

/* A filled button owns its text colour. Context link rules must never win —
   that is how the CTA band ended up with light blue text on blue, and the
   mobile menu with ink on blue. */
.btn-primary,
.btn-primary:hover,
.btn-primary:visited { color: #fff; }

/* ==========================================================================
   26. The founder
   A 4:5 portrait given real size. A 160px square was treating the only
   photograph on the site as an avatar.
   ========================================================================== */

.person-layout { display: grid; gap: var(--s-7); align-items: start; }

@media (min-width: 900px) {
  .person-layout {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--s-9);
  }
}

.person-figure { margin: 0; }

.person-figure .portrait {
  width: 100%;
  height: auto;
  max-width: 460px;
  border-radius: var(--r-card);
  background: var(--canvas);
  display: block;
}

.person-figure figcaption {
  margin-top: var(--s-4);
  max-width: 460px;
  font-size: var(--fs-small);
  color: var(--ink-3);
  line-height: 1.5;
}

.person-figure figcaption .mono {
  color: var(--ink);
  font-size: var(--fs-label);
  letter-spacing: 0.06em;
}

.person-body > * + * { margin-top: var(--s-4); }

/* ==========================================================================
   27. Hero motion
   Scroll choreography with CSS scroll-driven animations — no JavaScript, no
   library, and browsers without support simply show the settled state.
   ========================================================================== */

/* --- headline: a masked line reveal -------------------------------------- */

.reveal-lines span { display: block; }

.reveal-lines > span {
  overflow: hidden;
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
}

@media (scripting: enabled) {
  .reveal-lines > span > span {
    transform: translateY(105%);
    animation: line-up 900ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }
  .reveal-lines > span:nth-child(2) > span { animation-delay: 110ms; }
}

@keyframes line-up { to { transform: none; } }

/* --- the scroll cue ------------------------------------------------------ */

.hero-scrollcue {
  position: absolute;
  left: var(--gutter);
  bottom: var(--s-6);
  display: none;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #7e8ca1;
}

@media (min-width: 1000px) { .hero-scrollcue { display: flex; } }

.hero-scrollcue-line {
  width: 54px;
  height: 1px;
  background: linear-gradient(90deg, #7e8ca1, rgba(126, 140, 161, 0));
  transform-origin: left;
  animation: cue 2.6s var(--ease-both) infinite;
}

@keyframes cue {
  0%, 100% { transform: scaleX(0.4); opacity: 0.5; }
  50% { transform: scaleX(1); opacity: 1; }
}

/* --- scroll-linked depth -------------------------------------------------
   Each card drifts at its own rate as the hero leaves. Progressive
   enhancement: without animation-timeline nothing moves and nothing breaks. */

@supports (animation-timeline: view()) {
  @media (min-width: 1000px) and (prefers-reduced-motion: no-preference) {
    /* the copy settles back and dims as the picker takes over */
    .hero-copy {
      animation-name: hero-recede;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: exit 0% exit 90%;
    }

    @keyframes hero-recede {
      to { opacity: 0.25; translate: 0 -40px; }
    }
  }
}

/* ==========================================================================
   28. Entrances
   Different content moves differently. A single fade-up applied to everything
   is what made the first build feel templated.

   Triggered by a class from an IntersectionObserver rather than
   `animation-timeline: view()`. The CSS version is spec-correct and would cost
   no JavaScript, but it cannot be verified in every engine today, and an
   entrance that silently never runs is worse than 700 bytes of observer.
   Continuous effects (the hero drift, the progress rule) stay scroll-driven
   because those fail safe to a static state.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {

  /* images arrive like a print being pulled */
  .anim-img { --anim: wipe-up; --dur: 900ms; }

  @keyframes wipe-up {
    from { clip-path: inset(14% 0 0 0); opacity: 0; transform: scale(1.03); }
    to { clip-path: inset(0 0 0 0); opacity: 1; transform: none; }
  }

  /* index rows come from the side, the number leading */
  .anim-row { --anim: row-in; --dur: 700ms; }

  @keyframes row-in {
    from { opacity: 0; transform: translateX(-20px); }
    to { opacity: 1; transform: none; }
  }

  .anim-row .index-n {
    opacity: 0;
    letter-spacing: 0.4em;
  }

  .anim-row.is-in .index-n {
    animation: num-in 700ms 120ms ease-out both;
  }

  @keyframes num-in {
    from { opacity: 0; letter-spacing: 0.4em; }
    to { opacity: 1; letter-spacing: 0.1em; }
  }

  /* spec rows lift a little */
  .anim-spec { --anim: spec-in; --dur: 600ms; }

  @keyframes spec-in {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: none; }
  }

  /* the big serif headings rise from behind their own edge */
  .anim-head { --anim: head-rise; --dur: 800ms; }

  @keyframes head-rise {
    from { opacity: 0; transform: translateY(18px); }
    to { opacity: 1; transform: none; }
  }

  /* the statement tightens as it lands */
  .anim-statement { --anim: statement-in; --dur: 1000ms; }

  @keyframes statement-in {
    from { opacity: 0.25; letter-spacing: 0.02em; }
    to { opacity: 1; letter-spacing: -0.022em; }
  }

  /* the process line draws itself */
  .anim-line::before {
    transform: scaleX(0);
    transform-origin: left;
  }

  .anim-line.is-in::before {
    animation: line-draw 900ms cubic-bezier(.22,.61,.36,1) both;
  }

  @keyframes line-draw {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
  }

  /* Only hide things once we know a script is there to reveal them. */
  @media (scripting: enabled) {
    .anim-img, .anim-row, .anim-spec, .anim-head, .anim-statement { opacity: 0; }
  }

  .anim-img.is-in,
  .anim-row.is-in,
  .anim-spec.is-in,
  .anim-head.is-in,
  .anim-statement.is-in {
    animation-name: var(--anim);
    animation-duration: var(--dur, 700ms);
    animation-delay: var(--stagger, 0ms);
    animation-timing-function: cubic-bezier(0.22, 0.61, 0.36, 1);
    animation-fill-mode: both;
  }
}

/* ==========================================================================
   29. Continuous scroll effects
   These stay scroll-driven: with no support they simply never move, which is
   a perfectly good resting state.
   ========================================================================== */

.read-progress { display: none; }

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .read-progress {
      display: block;
      position: fixed;
      inset: var(--header-h) 0 auto 0;
      height: 2px;
      background: var(--accent);
      transform-origin: 0 50%;
      transform: scaleX(0);
      z-index: 60;
      animation-name: progress;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: scroll(root);
    }

    @keyframes progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }
  }
}

/* the "before" still that pairs with each picker answer */
.panel-before { margin: 0 0 var(--s-5); }

.panel-before img {
  width: 100%;
  border-radius: var(--r-card);
  border: 1px solid var(--line);
  display: block;
}

.panel-before figcaption {
  margin-top: var(--s-3);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* the Security opener: one wide image, no frame, bleeding into the band */
.blueprint-band { padding-top: var(--s-7); }

.blueprint { margin: 0; }

.blueprint img {
  width: 100%;
  height: auto;
  max-height: 460px;
  object-fit: cover;
  object-position: center 45%;
  border-radius: var(--r-card);
  display: block;
}

/* ==========================================================================
   30. The aura
   A light that follows the pointer across the dark bands and reveals a
   blueprint grid underneath. It is not decoration: the whole argument of this
   company is that there is infrastructure under the software, so the light
   uncovers structure rather than just glowing.

   Pointer only, fine pointers only, and off entirely under reduced motion.
   ========================================================================== */

.aura {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/* the structure that the light finds */
.aura::before,
.aura::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 600ms var(--ease-out);
}

.aura::before {
  background-image:
    linear-gradient(to right, rgba(169, 198, 238, 0.30) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(169, 198, 238, 0.30) 1px, transparent 1px),
    linear-gradient(to right, rgba(169, 198, 238, 0.14) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(169, 198, 238, 0.14) 1px, transparent 1px);
  background-size: 96px 96px, 96px 96px, 24px 24px, 24px 24px;
  -webkit-mask-image: radial-gradient(
    240px circle at var(--ax, 50%) var(--ay, 50%),
    #000 0%, rgba(0, 0, 0, 0.55) 45%, transparent 72%);
  mask-image: radial-gradient(
    240px circle at var(--ax, 50%) var(--ay, 50%),
    #000 0%, rgba(0, 0, 0, 0.55) 45%, transparent 72%);
}

/* the light itself, warm, so it reads as a lamp and not a UI highlight */
.aura::after {
  background:
    radial-gradient(380px circle at var(--ax, 50%) var(--ay, 50%),
      rgba(214, 154, 63, 0.10) 0%,
      rgba(90, 138, 201, 0.07) 38%,
      transparent 70%);
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .aura[data-aura='on']::before { opacity: 1; }
  .aura[data-aura='on']::after { opacity: 1; }
}

/* content has to sit above the light */
.aura > * { position: relative; z-index: 1; }

/* ==========================================================================
   31. Magnetic primary action
   The main button leans very slightly toward the pointer. Three pixels, not
   fifteen: enough to feel responsive, not enough to look like a toy.
   ========================================================================== */

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .btn-primary {
    transition: background-color var(--t-fast) var(--ease-out),
      border-color var(--t-fast) var(--ease-out),
      translate 400ms var(--ease-out);
  }

  .btn-primary[data-magnet] {
    translate: calc(var(--mx, 0) * 3px) calc(var(--my, 0) * 3px);
  }
}

/* ==========================================================================
   32. Before and after
   The Work page pairs the situation with the screen that replaced it. The
   tag is what makes the pair legible at a glance.
   ========================================================================== */

.before-after { margin-bottom: var(--s-5); }

.before-after img {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  display: block;
}

.ba-tag {
  display: inline-block;
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid currentColor;
  border-radius: var(--r-pill);
  padding: 3px 9px;
  margin-right: var(--s-2);
}

.ba-now { color: var(--brand); }

/* --- How we work --------------------------------------------------------- */

.stages-figure { margin: var(--s-8) 0 0; }

.stages-figure img {
  width: 100%;
  border-radius: var(--r-card);
  display: block;
}

.stages-figure figcaption,
.scope-figure figcaption {
  margin-top: var(--s-3);
  font-size: var(--fs-small);
  color: var(--ink-3);
}

.scope-figure { margin: 0 0 var(--s-6); }

.scope-figure img {
  width: 100%;
  border-radius: var(--r-card);
  border: 1px solid var(--line);
  display: block;
}

/* the pair is one grid child, or the screen wraps to its own row */
.ba-stack { display: grid; gap: var(--s-5); }
.ba-stack .before-after { margin-bottom: 0; }

/* ==========================================================================
   33. FAQ and Contact figures
   The FAQ was thirteen accordions in a narrow column and nothing else. It is
   now a sticky rail carrying the three group names, so a long page reads as
   three short ones, with the still underneath it as the page's only image.
   ========================================================================== */

@media (min-width: 900px) {
  .split.faq-layout { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); }
}

.faq-rail .label { margin-bottom: var(--s-3); }

.faq-nav {
  list-style: none;
  margin: 0;
  padding: 0;
  border-left: 1px solid var(--line-strong);
}

.faq-nav a {
  display: block;
  padding: var(--s-2) 0 var(--s-2) var(--s-4);
  margin-left: -1px;
  border-left: 2px solid transparent;
  color: var(--ink-2);
  text-decoration: none;
  font-size: var(--fs-small);
  transition: color var(--t-fast) var(--ease-out),
    border-color var(--t-fast) var(--ease-out);
}

.faq-nav a:hover,
.faq-nav a:focus-visible {
  color: var(--brand);
  border-left-color: var(--brand);
}

.faq-figure {
  margin: var(--s-7) 0 0;
}

.faq-figure img,
.contact-figure img {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}

.faq-figure figcaption {
  margin-top: var(--s-3);
  font-size: var(--fs-small);
  color: var(--ink-3);
}

/* The rail is decoration below the breakpoint where it can sit beside the
   answers, and the answers are the page. */
@media (max-width: 899px) {
  .faq-figure { display: none; }
}

.contact-figure { margin-inline: 0; }

/* ==========================================================================
   34. The hero, 2027
   The previous hero showed four status cards from AppTech's own tools, which
   answered "what have you built for yourselves". This one answers "what could
   you build for me": code on the left of the build writes the interface on
   the right while the visitor watches, over a render of software being
   assembled in mid-air.
   ========================================================================== */

/* --- the still ----------------------------------------------------------- */

.hero-still {
  transform-origin: 74% 50%;
}

/* The render breathes: a slow push-in and drift, long enough that nobody
   sees it move, only that the scene is not a flat photograph. */
@media (prefers-reduced-motion: no-preference) {
  .hero-still { animation: hero-breathe 24s var(--ease-both) infinite alternate; }

  .hero-media picture {
    display: block;
    height: 100%;
    translate: calc(var(--hx, 0) * -16px) calc(var(--hy, 0) * -10px);
    transition: translate 1200ms var(--ease-out);
  }
}

@keyframes hero-breathe {
  from { transform: scale(1.07); }
  to { transform: scale(1) translate3d(-1.2%, 0.6%, 0); }
}

/* live threads of light over the render, drawn by hero.js */
.hero-field {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
}

/* The new render is dark on the left by composition, so the scrim no longer
   has to crush the right half as well. The left third, where the headline
   sits, is as dark as before; the right half keeps its light. */
.hero::before {
  background:
    linear-gradient(100deg, var(--night) 0%, rgba(11, 18, 32, 0.92) 24%,
      rgba(11, 18, 32, 0.62) 44%, rgba(11, 18, 32, 0.16) 68%, rgba(11, 18, 32, 0.04) 100%),
    linear-gradient(to bottom, rgba(11, 18, 32, 0.5) 0%, rgba(11, 18, 32, 0) 26%,
      rgba(11, 18, 32, 0) 64%, rgba(11, 18, 32, 0.8) 100%);
}

/* a light that follows the pointer across the hero */
.hero-glow {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  mix-blend-mode: screen;
  background:
    radial-gradient(560px circle at var(--gx, 70%) var(--gy, 45%),
      rgba(90, 138, 240, 0.16) 0%, rgba(224, 160, 74, 0.05) 42%, transparent 70%);
  transition: opacity 800ms var(--ease-out);
}

.hero[data-glow='on'] .hero-glow { opacity: 1; }

/* --- the build ------------------------------------------------------------ */

.build {
  position: relative;
  width: 100%;
  max-width: 560px;
  aspect-ratio: 560 / 420;
  margin-inline: auto;
  font-size: 12px;
  transform-style: preserve-3d;
  transform: perspective(1400px)
    rotateY(calc(-5deg + var(--tx, 0) * 6deg))
    rotateX(calc(3deg - var(--ty, 0) * 5deg));
  transition: transform 1100ms var(--ease-out);
}

@media (min-width: 1000px) {
  .build { margin-right: 0; font-size: 13px; }
}

/* the panes: glass, over the render */
.build-editor,
.build-app {
  position: absolute;
  border-radius: 14px;
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 30px 80px rgba(2, 6, 14, 0.62), inset 0 1px 0 rgba(255, 255, 255, 0.07);
}

.build-editor {
  top: 0;
  left: 0;
  width: 84%;
  background: rgba(7, 13, 24, 0.78);
  border: 1px solid rgba(143, 179, 232, 0.22);
  overflow: hidden;
}

.build-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(143, 179, 232, 0.12);
  background: rgba(255, 255, 255, 0.02);
}

.build-dots { display: inline-flex; gap: 6px; flex: none; }
.build-dots i { width: 9px; height: 9px; border-radius: 50%; background: rgba(143, 160, 184, 0.35); }

.build-tabs { display: flex; gap: 2px; min-width: 0; overflow: hidden; }

.build-tab {
  font-family: var(--font-mono);
  font-size: 0.85em;
  color: #6f8199;
  padding: 3px 9px;
  border-radius: 6px;
  white-space: nowrap;
  transition: color 400ms var(--ease-out), background-color 400ms var(--ease-out);
}

.build-tab[data-active='true'] { color: #dbe3ee; background: rgba(143, 179, 232, 0.12); }

/* Whitespace between the line spans collapses, and each line keeps its own
   indentation, so the markup can be written one line per line. */
.build-code {
  margin: 0;
  padding: 14px 16px 18px 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 1em;
  line-height: 1.75;
  color: #c9d6e8;
  white-space: normal;
  counter-reset: ln;
  min-height: calc(6 * 1.75em + 32px);
}

.build-code .ln {
  display: block;
  white-space: pre;
  padding-right: 8px;
  transition: background-color 300ms var(--ease-out);
}

.build-code .ln::before {
  counter-increment: ln;
  content: counter(ln);
  display: inline-block;
  width: 3.2em;
  padding-right: 1.2em;
  text-align: right;
  color: #3f4f66;
}

.build-code .ln[data-current] { background: rgba(143, 179, 232, 0.07); }
.build-code .ln[data-pending]::before { visibility: hidden; }

/* syntax: brand blue keywords, amber calls, sand strings, muted comments */
.build-code .k { color: #8fb3e8; }
.build-code .f { color: #e0a04a; }
.build-code .s { color: #d9c4a0; }
.build-code .c { color: #62748c; font-style: italic; }
.build-code .t { color: #a9c6ee; }
.build-code .p { color: #c9d6e8; }

.build-caret {
  display: inline-block;
  width: 0.55em;
  height: 1.2em;
  margin-left: 1px;
  vertical-align: -0.22em;
  background: #e0a04a;
  animation: caret 1s steps(1) infinite;
}

@keyframes caret { 50% { opacity: 0; } }

/* the app it builds */
.build-app {
  right: 0;
  bottom: 0;
  width: 58%;
  padding: 14px 14px 16px;
  background: rgba(15, 25, 44, 0.8);
  border: 1px solid rgba(143, 179, 232, 0.3);
  color: #dbe3ee;
  transform: translateZ(46px);
}

.bs-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.8em;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8fa0b8;
  margin-bottom: 10px;
}

.bs-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: #a9c6ee;
  letter-spacing: 0.06em;
}

.bs-live::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #5a8af0;
  box-shadow: 0 0 0 3px rgba(90, 138, 240, 0.2);
  animation: live 1.8s var(--ease-both) infinite;
}

@keyframes live { 50% { box-shadow: 0 0 0 6px rgba(90, 138, 240, 0); } }

.bs-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid rgba(143, 179, 232, 0.12);
  font-size: 0.95em;
}

.chip-ok,
.chip-look {
  font-size: 0.82em;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  white-space: nowrap;
}

.chip-ok { color: #a9c6ee; background: rgba(90, 138, 240, 0.16); }
.chip-look { color: #f0c27f; background: rgba(224, 160, 74, 0.16); }

.bs-flow {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 0 12px;
}

.bs-node {
  flex: none;
  font-size: 0.9em;
  font-weight: 600;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid rgba(143, 179, 232, 0.34);
  background: rgba(90, 138, 240, 0.08);
}

.bs-wire {
  position: relative;
  flex: 1;
  height: 2px;
  background: linear-gradient(90deg, rgba(143, 179, 232, 0.5), rgba(224, 160, 74, 0.6));
  border-radius: 2px;
}

.bs-wire i {
  position: absolute;
  top: 50%;
  left: 0;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 10px 2px rgba(143, 179, 232, 0.8);
  opacity: 0;
}

.bs-wire[data-on] i { animation: packet 1.6s linear infinite; }
.bs-wire[data-on] i:nth-child(2) { animation-delay: 0.53s; }
.bs-wire[data-on] i:nth-child(3) { animation-delay: 1.06s; }

@keyframes packet {
  0% { left: 0; opacity: 0; }
  12%, 88% { opacity: 1; }
  100% { left: 100%; opacity: 0; }
}

.bs-count,
.bs-kpi {
  font-size: 0.95em;
  color: #b6c3d4;
}

.bs-count strong,
.bs-kpi strong {
  font-size: 1.9em;
  font-weight: 600;
  color: #fff;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  margin-right: 6px;
  vertical-align: -0.12em;
}

.bs-trend {
  display: block;
  width: 100%;
  height: 52px;
  margin-top: 8px;
  color: #8fb3e8;
  overflow: visible;
}

.bs-trend path {
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

.build-toast {
  position: absolute;
  right: 3%;
  top: -5%;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px 7px 10px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.85em;
  color: #0b1220;
  background: #e0a04a;
  box-shadow: 0 12px 30px rgba(224, 160, 74, 0.28);
  transform: translateZ(80px);
}

/* --- the build, running ---------------------------------------------------
   Without JavaScript only the first scene shows, finished, and the toast
   says it was deployed. hero.js sets data-running and takes it from there. */

.build-code:not([data-build-code='0']),
.build-scene:not([data-build-scene='0']) { display: none; }

.build[data-running] .build-code,
.build[data-running] .build-scene { display: none; }

.build[data-running] .build-code[data-shown],
.build[data-running] .build-scene[data-shown] { display: block; }

.build[data-running] [data-at] {
  transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
}

.build[data-running] [data-at]:not([data-on]) {
  opacity: 0;
  transform: translateY(6px);
}

.build[data-running] .bs-trend:not([data-on]) path { stroke-dashoffset: 1; }
.build[data-running] .bs-trend path { transition: stroke-dashoffset 1400ms var(--ease-out); }

.build[data-running] .build-toast {
  transition: opacity 450ms var(--ease-out), translate 450ms cubic-bezier(0.16, 1, 0.3, 1);
}

.build[data-running] .build-toast:not([data-on]) {
  opacity: 0;
  translate: 0 10px;
}

/* the app pane dims for a moment as it changes scene */
.build[data-running] .build-app { transition: opacity 350ms var(--ease-out); }
.build[data-running][data-swap] .build-app { opacity: 0.2; }

/* --- layout -------------------------------------------------------------- */

@media (max-width: 999px) {
  .hero-inner { gap: var(--s-7); }
  .build { max-width: 520px; margin-top: var(--s-2); }
}

/* On a phone the build keeps its code and its toast and loses the tilt: at
   that width a 3D pane only makes the code harder to read. */
@media (max-width: 639px) {
  .build {
    aspect-ratio: auto;
    height: 300px;
    font-size: 10.5px;
    transform: none;
  }
  .build-editor { width: 94%; }
  .build-app { width: 66%; transform: none; }
  .build-toast { transform: none; }
}

/* --- the marquee --------------------------------------------------------- */

.marquee {
  background: var(--night-2);
  border-top: 1px solid rgba(219, 227, 238, 0.1);
  color: #8fa0b8;
  font-size: var(--fs-small);
  overflow: hidden;
}

.marquee-inner {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  padding-block: var(--s-5);
}

.marquee-lead {
  flex: none;
  margin: 0;
  max-width: none;
  line-height: 1.5;
}

.marquee-lead strong,
.marquee-we { color: #dbe3ee; font-weight: 600; }

.marquee-we::after { content: ' \2192'; color: #e0a04a; }

.marquee-window {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}

/* Each item carries its own trailing space rather than the track using gap,
   so the track is exactly two copies wide and -50% lands on the seam. */
.marquee-track {
  display: flex;
  width: max-content;
}

.marquee-track span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-5);
  padding-right: var(--s-5);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: #b6c3d4;
  white-space: nowrap;
}

.marquee-track span::after {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #e0a04a;
  opacity: 0.75;
}

@media (prefers-reduced-motion: no-preference) {
  .marquee-track { animation: marquee 46s linear infinite; }
  .marquee-window:hover .marquee-track { animation-play-state: paused; }
}

@keyframes marquee { to { transform: translateX(-50%); } }

@media (max-width: 767px) {
  .marquee-inner {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-4);
  }
}

/* ==========================================================================
   35. Motion everywhere
   build.mjs tags every page with these classes as it renders, so every
   heading, lead, list, label and image has an entrance without anyone
   remembering to add one. site.js decides when each one plays.
   ========================================================================== */

/* --- the failsafe ---------------------------------------------------------
   Anything that starts hidden is shown by CSS alone if site.js has not
   marked the page within three seconds. A blocked or failed script must
   never leave a page blank. site.js adds .js-motion to <html> the moment it
   runs, which switches this off and hands the reveals to the observer. */

@keyframes failsafe-show {
  to { opacity: 1; transform: none; clip-path: none; filter: none; }
}

@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  html:not(.js-motion) :is(.anim-img, .anim-row, .anim-spec, .anim-head, .anim-statement,
    .anim-words, .anim-fade, .anim-label, .anim-stagger > *, .anim-layers li) {
    animation: failsafe-show 1ms 3s both;
  }
}

@media (prefers-reduced-motion: no-preference) {

  @media (scripting: enabled) {
    .anim-words:not([data-split]),
    .anim-fade,
    .anim-label,
    .anim-stagger > *,
    .anim-layers li { opacity: 0; }
  }

  /* headings: each word rises from behind its own baseline */
  .anim-words .w {
    display: inline-block;
    overflow: hidden;
    vertical-align: top;
    padding-bottom: 0.12em;
    margin-bottom: -0.12em;
  }

  .anim-words .wi {
    display: inline-block;
    transform: translateY(108%);
  }

  .anim-words.is-in .wi {
    animation: word-rise 850ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(var(--i, 0) * 45ms);
  }

  @keyframes word-rise {
    from { transform: translateY(108%); }
    to { transform: none; }
  }

  /* leads come into focus */
  .anim-fade.is-in {
    animation: fade-focus 950ms cubic-bezier(0.22, 0.61, 0.36, 1) 140ms both;
  }

  @keyframes fade-focus {
    from { opacity: 0; transform: translateY(14px); filter: blur(6px); }
    to { opacity: 1; transform: none; filter: none; }
  }

  /* labels settle their tracking, like a readout locking on */
  .anim-label.is-in { animation: label-in 900ms ease-out both; }

  @keyframes label-in {
    from { opacity: 0; letter-spacing: 0.42em; }
    to { opacity: 1; letter-spacing: 0.14em; }
  }

  /* lists cascade, one item after another */
  .anim-stagger.is-in > * {
    animation: item-in 650ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
    animation-delay: calc(var(--i, 0) * 70ms + 60ms);
  }

  @keyframes item-in {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: none; }
  }

  /* the security layers draw in from the outside to the core */
  .anim-layers.is-in li { animation: layer-in 700ms cubic-bezier(0.22, 0.61, 0.36, 1) both; }
  .anim-layers.is-in li li { animation-delay: 200ms; }
  .anim-layers.is-in li li li { animation-delay: 400ms; }
  .anim-layers.is-in li li li li { animation-delay: 600ms; }

  @keyframes layer-in {
    from { opacity: 0; transform: scale(0.96); }
    to { opacity: 1; transform: none; }
  }
}

/* --- page heads: dark, and alive -----------------------------------------
   Every inner page now opens the way the home page does: on the night blue,
   with slow light moving behind the headline and pulses running along two
   lines at its foot. The h1 itself never moves; it is the largest paint. */

.page-head {
  background: var(--night);
  color: #c3cedd;
  padding-block: clamp(3rem, 2rem + 4vw, 5.5rem) clamp(3.5rem, 2.5rem + 4vw, 6rem);
}

.page-head h1 { color: #fff; }
.page-head .lead { color: #c3cedd; }
.page-head .label { color: #8fa0b8; }
.page-head .caption { color: #97a6ba; }
.page-head .breadcrumb { color: #8fa0b8; }
.page-head .breadcrumb a { color: #b6c3d4; }
.page-head .breadcrumb a:hover { color: #fff; }

.page-head > .ph-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(143, 179, 232, 0.06) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(143, 179, 232, 0.06) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: linear-gradient(100deg, transparent 18%, #000 64%);
  mask-image: linear-gradient(100deg, transparent 18%, #000 64%);
}

.ph-orb {
  position: absolute;
  right: -10%;
  top: -40%;
  width: min(62vw, 820px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(58, 104, 172, 0.55), rgba(44, 86, 144, 0.18) 55%, transparent);
}

.ph-orb-2 {
  right: 26%;
  top: 30%;
  width: min(34vw, 460px);
  background: radial-gradient(closest-side, rgba(224, 160, 74, 0.18), transparent);
}

/* The lines live in the head's bottom padding and fade out under the text
   column: a bright pulse passing behind a sentence pulls the eye off it. */
.ph-lines {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 34%;
  overflow: visible;
  -webkit-mask-image: linear-gradient(90deg, transparent 8%, #000 42%);
  mask-image: linear-gradient(90deg, transparent 8%, #000 42%);
}

.ph-lines path {
  fill: none;
  stroke: rgba(143, 179, 232, 0.2);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.ph-lines .ph-pulse {
  stroke: #a9c6ee;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 0.05 0.95;
  stroke-dashoffset: 1;
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .ph-orb { animation: orb-drift 19s var(--ease-both) infinite alternate; }
  .ph-orb-2 { animation: orb-drift 24s var(--ease-both) infinite alternate-reverse; }
  .page-head > .ph-field { animation: grid-drift 40s linear infinite; }

  .ph-lines .ph-pulse {
    opacity: 1;
    animation: pulse-run 6.5s linear infinite;
  }
  .ph-lines .ph-pulse + .ph-pulse {
    stroke: #e0a04a;
    animation-duration: 9s;
    animation-delay: -3s;
  }
}

@keyframes orb-drift {
  to { transform: translate3d(-9%, 12%, 0) scale(1.14); }
}

@keyframes grid-drift {
  to { background-position: 56px 56px, 56px 56px; }
}

@keyframes pulse-run {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: -0.05; }
}

/* --- the renders: depth, light and a little life --------------------------
   The generated stills sit in a dark frame, drift slowly as they cross the
   viewport, lean in under the pointer, and catch a sweep of light. */

picture.render {
  display: block;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  border-radius: var(--r-card);
  background: var(--night);
  box-shadow: 0 18px 50px rgba(11, 18, 32, 0.18);
}

picture.render img {
  display: block;
  width: 100%;
  border: 0;
  border-radius: 0;
  transition: scale 1400ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* a hairline of light around the frame, over the image */
picture.render::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(143, 179, 232, 0.16);
  pointer-events: none;
}

/* the sweep of light, played on hover */
picture.render::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -70%;
  width: 55%;
  z-index: 1;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.13), transparent);
  transform: skewX(-16deg);
  pointer-events: none;
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  picture.render:hover img { scale: 1.045; }
  picture.render:hover::after { animation: sweep 1200ms var(--ease-out); }
}

@keyframes sweep {
  from { left: -70%; }
  to { left: 130%; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    picture.render img {
      animation-name: render-drift;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
  }
}

@keyframes render-drift {
  from { transform: scale(1.14) translateY(-3.5%); }
  to { transform: scale(1.06) translateY(3.5%); }
}

/* --- spotlights -------------------------------------------------------------
   Rows and cards light up under the pointer, from where the pointer is. */

:is(.index > li, .faq details, .card, .contact-list > *)[data-spot] {
  background-image: radial-gradient(460px circle at var(--sx, 50%) var(--sy, 50%),
    rgba(44, 86, 144, 0.08), transparent 62%);
}

/* --- the primary action catches the light --------------------------------- */

.btn-primary {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.btn-primary::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -70%;
  width: 50%;
  z-index: -1;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.3), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .btn-primary:hover::after { animation: sweep 850ms var(--ease-out); }
}

/* --- the footer sign ------------------------------------------------------
   The wordmark again, very large, outlined, with a band of amber light
   travelling through it and the cursor blinking at the end. It only animates
   while the footer is on screen. */

.footer-sign {
  margin: var(--s-8) 0 0;
  max-width: none;
  font-family: var(--font);
  font-weight: 700;
  font-size: clamp(3.5rem, 1rem + 12.5vw, 12.5rem);
  line-height: 0.9;
  letter-spacing: -0.045em;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1px rgba(143, 179, 232, 0.22);
  background: linear-gradient(100deg,
    rgba(143, 179, 232, 0.05) 0%, rgba(143, 179, 232, 0.05) 42%,
    rgba(224, 160, 74, 0.5) 50%,
    rgba(143, 179, 232, 0.05) 58%, rgba(143, 179, 232, 0.05) 100%);
  background-size: 260% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  user-select: none;
  overflow: hidden;
}

.footer-sign .fs-dev {
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: -0.02em;
  margin-left: 0.18em;
}

.footer-sign .fs-caret {
  display: inline-block;
  width: 0.42em;
  height: 0.12em;
  margin-left: 0.08em;
  background: #e0a04a;
  vertical-align: baseline;
}

@media (prefers-reduced-motion: no-preference) {
  .footer[data-inview] .footer-sign { animation: sign-sweep 7s linear infinite; }
  .footer[data-inview] .fs-caret { animation: caret 1.05s steps(1) infinite; }
}

@keyframes sign-sweep {
  from { background-position: 100% 0; }
  to { background-position: -160% 0; }
}

/* --- counting figures ------------------------------------------------------ */

.stat .sv { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   36. Index rows with a render
   What we build was seven rows of text and nothing to look at. Each row now
   carries its own render at its right edge; below 860px it sits under the
   text at full width.
   ========================================================================== */

.index-media .index-pic { grid-column: 2; margin-top: var(--s-2); }

@media (min-width: 860px) {
  .index-media > li { grid-template-columns: 4rem minmax(0, 1fr) 280px; }
  .index-media .index-pic { grid-column: 3; grid-row: 1; margin-top: 0; }
  .index-media .index-body { grid-template-columns: minmax(0, 1fr); }
  .index-media .said-inline { margin-top: var(--s-2); }
}

.reach-figure { margin: var(--s-8) 0 0; }

/* ==========================================================================
   37. Links to the parent company
   build.mjs links every visible "AppTech" and "Application Technology" to
   apptech.co.za. They are meant to be found, not noticed: the text keeps its
   own colour and weight, with a hairline underline that firms up on hover.
   Declared last so it wins over the context rules for footer and band links.
   ========================================================================== */

a.parent-link {
  color: inherit;
  font-weight: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
  transition: text-decoration-color var(--t-fast) var(--ease-out);
}

a.parent-link:hover,
a.parent-link:focus-visible {
  color: inherit;
  text-decoration-color: currentColor;
}
