/* ===========================================================================
   ajvandergugten.com

   This stylesheet implements the system specified in the brand guidelines
   build (~/GitHub/aj-personal-brand/index.html), Rev 2026.07 r2. Tokens, type
   scale, grid rule, motion timings and components are taken from that
   document rather than invented here. Where this file departs from it, the
   reason is stated inline.
   =========================================================================== */

/* Weights are 400, 500 and 700. There is no 600 in this system: asking for one
   gets a synthesized face, which is exactly the muddiness Plex was chosen to
   avoid. */
@font-face {
  font-family: "Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/plex-sans-400.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/plex-sans-500.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/plex-sans-700.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/plex-mono-400.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");
}

:root {
  /* Steel case tones, light theme. Paper is the warmed off-white: the brand's
     biggest perceptual liability is reading cold and foreign in a
     relationship-driven market. Contrast computed, not estimated:
     ink 16.19:1, accent 6.35:1. */
  --ink: #14171c;
  --ink-70: rgba(20, 23, 28, 0.70);
  --ink-45: rgba(20, 23, 28, 0.62); /* 0.45 failed AA at 2.88:1; 0.62 = 4.82:1 */
  --paper: #f4f3f0;
  --paper-alt: #e9e6e1;
  --paper-glass: rgba(244, 243, 240, 0.82);
  --accent: #1d5c8f;
  --accent-strong: #164a74;
  /* Not in the guidelines doc, which has no filled buttons. White on the light
     accent is 7.05:1; on the dark accent it is 3.21:1 and fails AA, so this
     flips with the theme. Computed, not carried over. */
  --accent-on: #ffffff;
  --border: rgba(20, 23, 28, 0.14);
  --border-strong: rgba(20, 23, 28, 0.28);

  --font-sans: "Plex Sans", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "Plex Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --gutter: 56px;
  --gutter-mobile: 24px;
  --rule: 1px solid var(--border);

  /* The guidelines doc sets a 820px content column beside a 240px rail, so the
     page it describes occupies roughly 1170px overall. This site has no rail,
     so the content column widens to keep the same total measure and to leave
     the 5fr column wide enough for the diagram. */
  --measure-page: 1040px;

  --ease-stop: cubic-bezier(0.65, 0, 0.35, 1);   /* mechanical stop */
  --ease-rise: cubic-bezier(0.16, 1, 0.3, 1);    /* rise / reveal */
}

:root[data-theme="dark"] {
  /* Wave-dial tones, dark theme */
  --ink: #eef0f2;
  --ink-70: rgba(238, 240, 242, 0.72);
  --ink-45: rgba(238, 240, 242, 0.52); /* 0.48 was 4.48:1, just under AA */
  --paper: #14171c;
  --paper-alt: #1c2026;
  --paper-glass: rgba(20, 23, 28, 0.82);
  --accent: #4f96c9;
  --accent-strong: #6bacd9;
  --accent-on: #14171c;                /* 5.59:1. White here is 3.21:1 */
  --border: rgba(238, 240, 242, 0.14);
  --border-strong: rgba(238, 240, 242, 0.26);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #eef0f2;
    --ink-70: rgba(238, 240, 242, 0.72);
    --ink-45: rgba(238, 240, 242, 0.52);
    --paper: #14171c;
    --paper-alt: #1c2026;
    --paper-glass: rgba(20, 23, 28, 0.82);
    --accent: #4f96c9;
    --accent-strong: #6bacd9;
    --accent-on: #14171c;
    --border: rgba(238, 240, 242, 0.14);
    --border-strong: rgba(238, 240, 242, 0.26);
  }
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

* {
  box-sizing: border-box;
}

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

html,
body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* No overflow-x here. It was guarding against the diagram, which no longer
   overflows at any width, and it turns body into a scroll container, which is
   the documented way to make position:sticky on the header quietly stop
   working. Horizontal overflow is checked instead: scrollWidth equals
   clientWidth at 1440 and at 390. */
body {
  font-size: 16px;
  line-height: 1.6;
}

/* UA stylesheets give these non-inheriting default fonts and silently drop the
   brand face. Audited element by element rather than trusted to inherit. */
code,
kbd,
samp,
button,
input,
textarea,
select {
  font-family: inherit;
}

code {
  font-family: var(--font-mono);
}

p,
dd,
li {
  text-wrap: pretty;
}

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

h1,
h2,
h3 {
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

p {
  margin: 0;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

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

::selection {
  background: var(--accent);
  color: var(--accent-on);
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */

.wrap {
  max-width: var(--measure-page);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* One token, redefined at the breakpoint, rather than a second padding rule
   carrying a second source of truth for the page inset. */
@media (max-width: 900px) {
  :root {
    --gutter: var(--gutter-mobile);
  }
}

/* Hairlines are back, on a rule rather than by feel: a line marks where one
   kind of content ends and another begins, never between like items in a
   list. So sections get one, and the fact rails do not, because the mono
   label already marks where each row starts. Six lines on the page, against
   the thirty-six this started with. */
section {
  padding: 76px 0;
}

section + section {
  border-top: var(--rule);
}

/* The contact block already changes background, which is a stronger break
   than a hairline. Two of them reads as a double rule. */
.contact {
  border-top: none;
}

/* The sticky header would otherwise sit on top of an anchor target. */
section[id] {
  scroll-margin-top: 88px;
}

/* THE GRID RULE, s06, learned the hard way and locked: every major section is
   either full width or a 7/5 split with real content on both sides. A lone
   narrow column inside a wide page reads as broken, not as an intentional
   edit. Nothing here uses any other ratio. */
.split {
  display: grid;
  grid-template-columns: 7fr 5fr;
  gap: 40px;
}

@media (max-width: 860px) {
  .split {
    grid-template-columns: 1fr;
    gap: 24px;
  }
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 12px 20px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
}

.skip-link:focus {
  left: 0;
}

/* The single mono meta style. */
.meta {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-45);
}

/* --------------------------------------------------------------------------
   Header. Sticky, and lineless until it is actually stuck: a bar with content
   scrolling underneath needs an edge, a bar sitting at the top of the page
   does not. The theme toggle sits in the content flow, per s08, never
   floating.
   -------------------------------------------------------------------------- */

.header-sentinel {
  height: 1px;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  /* Opaque by default. The glass version is opt-in below, because a
     translucent bar with no blur behind it is just text over text. */
  background: var(--paper);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s var(--ease-stop);
}

@supports ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .site-header {
    background: var(--paper-glass);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    backdrop-filter: saturate(180%) blur(14px);
  }
}

/* The role label sits at --ink-45 everywhere else on the page, against flat
   paper. Here the background is whatever happens to be scrolling underneath,
   so it is computed against the worst case: a solid ink block under 82% glass.
   --ink-45 lands at 4.19:1 light and 3.84:1 dark and fails; --ink-70 lands at
   5.23:1 and 5.76:1. The wordmark is plain --ink and never drops below 9:1. */
.site-header .meta {
  color: var(--ink-70);
}

.site-header.stuck {
  border-bottom-color: var(--border);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-top: 20px;
  padding-bottom: 20px;
}

.header-id {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.wordmark {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  text-decoration: none;
  color: var(--ink);
  white-space: nowrap;
}

.theme-toggle {
  position: relative;
  width: 28px;
  height: 28px;
  flex: none;
  border-radius: 50%;
  border: var(--rule);
  background: var(--paper);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.theme-toggle svg {
  width: 14px;
  height: 14px;
}

/* The 28px circle is the visual. The tap target must still clear 44x44
   (WCAG 2.5.5), so a pseudo-element expands it without enlarging the mark. */
.theme-toggle::after {
  content: "";
  position: absolute;
  inset: -8px;
}

.theme-toggle .sun { display: none; }
:root[data-theme="dark"] .theme-toggle .moon { display: none; }
:root[data-theme="dark"] .theme-toggle .sun { display: block; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .moon { display: none; }
  :root:not([data-theme="light"]) .theme-toggle .sun { display: block; }
}

/* --------------------------------------------------------------------------
   Hero. The name is already the wordmark in the header, so the headline is
   the positioning line instead of repeating it.
   -------------------------------------------------------------------------- */

.hero {
  padding-top: 104px;
  padding-bottom: 88px;
}

.hero-headline {
  position: relative;
  font-size: clamp(30px, 3.4vw, 42px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  font-weight: 700;
  max-width: 20ch;
  margin: 0 0 32px;
}

/* Signature moment, s07: a chronograph-hand line sweeps across on load with a
   mechanical stop and slight overshoot. Used once, under the headline. */
.hero-rule {
  height: 2px;
  background: var(--accent);
  transform-origin: left center;
  transform: scaleX(1);
  margin-bottom: 40px;
}

.js .hero-rule {
  animation: sweep 0.9s var(--ease-stop) both;
}

@keyframes sweep {
  0%   { transform: scaleX(0); }
  70%  { transform: scaleX(1.02); }
  85%  { transform: scaleX(0.995); }
  100% { transform: scaleX(1); }
}

.hero-sub {
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-70);
  max-width: 56ch;
  margin: 0 0 32px;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* --------------------------------------------------------------------------
   Fact rows, the 5fr companion column. Never empty, never padding. Separated
   by space rather than rules: the mono label already marks where a row starts,
   so the border was saying a second time what the type was saying once.
   -------------------------------------------------------------------------- */

.facts > div + div {
  margin-top: 28px;
}

.facts dt {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-45);
  margin-bottom: 6px;
}

.facts dd {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-70);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 13px 22px;
  border: 1px solid transparent;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.25s var(--ease-stop),
    border-color 0.25s var(--ease-stop), color 0.25s var(--ease-stop);
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-on);
}

.btn-secondary {
  border-color: var(--border-strong);
  color: var(--ink);
  background: none;
}

/* --------------------------------------------------------------------------
   Credentials strip, s08: full width, three columns. No frame and no rule.
   -------------------------------------------------------------------------- */

.credentials {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
}

.hero .credentials {
  margin-top: 56px;
  padding-top: 40px;
  border-top: var(--rule);
}

@media (max-width: 760px) {
  .credentials {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

.credentials dt {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-45);
  margin-bottom: 4px;
}

.credentials dd {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-70);
}

/* --------------------------------------------------------------------------
   Sections and prose
   -------------------------------------------------------------------------- */

.section-title {
  font-size: clamp(22px, 2.2vw, 27px);
  line-height: 1.3;
  letter-spacing: -0.01em;
  font-weight: 700;
  max-width: 26ch;
  margin: 0 0 40px;
}

.prose {
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-70);
  max-width: 58ch;
}

.prose + .prose {
  margin-top: 20px;
}

.lede {
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink);
  max-width: 56ch;
  margin: 0 0 20px;
}

.client-name {
  color: var(--accent);
  margin-bottom: 12px;
}

/* --------------------------------------------------------------------------
   Work rows
   -------------------------------------------------------------------------- */

.work-row + .work-row {
  margin-top: 48px;
  padding-top: 48px;
  border-top: var(--rule);
}

.work-row h3 {
  font-size: 17px;
  font-weight: 700;
  margin-bottom: 4px;
}

.work-row .meta {
  margin-bottom: 12px;
}

/* The live site for a piece of work, at the end of its block rather than
   inline. A link inside a sentence pulls the eye out of the prose; here it is
   an endnote you take once you have read the story. */
.work-link {
  display: inline-block;
  margin-top: 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   Diagram
   -------------------------------------------------------------------------- */

/* Unboxed and unfilled: the diagram sits on the page like the rest of the
   content instead of inside a panel. Everything in it is hairline weight. */
.diagram {
  margin: 40px 0 0;
}

.diagram svg {
  width: 100%;
  height: auto;
}

/* The drawing and the stacked flow carry the same architecture, and exactly
   one of them is ever rendered. display:none keeps the hidden one out of the
   accessibility tree, so a screen reader is never read both. Below 760px the
   720-wide drawing renders at roughly 6px type and clips its right-hand
   column, which is not a diagram any more. */
.flow {
  display: none;
}

@media (max-width: 760px) {
  .diagram-svg {
    display: none;
  }

  .flow {
    display: block;
  }
}

.flow-head {
  margin-bottom: 20px;
}

.flow-step {
  padding-left: 16px;
  border-left: 1px solid var(--border-strong);
}

/* The accent rule replaces the accent box: same signal, no frame. */
.flow-step.mine {
  border-left-color: var(--accent);
  border-left-width: 2px;
  padding-left: 15px;
}

.flow-gap {
  margin-top: 36px;
}

.flow-name {
  font-size: 14px;
  font-weight: 700;
  line-height: 1.4;
}

.flow-sub {
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-70);
}

/* The edge between two steps. The arrow is the only place on the page a glyph
   does structural work, and it is aria-hidden nowhere because it reads fine
   aloud as a step marker. */
.flow-edge {
  padding: 10px 0 10px 16px;
}

.flow-edge::before {
  content: "\2193";
  margin-right: 8px;
}

.flow-legend {
  margin-top: 28px;
  color: var(--accent);
}

.diagram figcaption {
  margin-top: 24px;
  padding-top: 20px;
  border-top: var(--rule);
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-45);
}

/* Diagram type is set here, not in SVG presentation attributes: var() inside a
   presentation attribute is inconsistently supported, and these have to
   re-resolve on every theme change. */
.diagram .host {
  fill: none;
  stroke: var(--border);
  stroke-dasharray: 3 3;
}

.diagram .node {
  fill: none;
  stroke: var(--border);
  stroke-width: 1;
}

.diagram .mine {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1;
}

.diagram .wire {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 1;
}

.diagram .d-name {
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 700;
  fill: var(--ink);
}

.diagram .d-item {
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  fill: var(--ink);
}

.diagram .d-sub {
  font-family: var(--font-sans);
  font-size: 11.5px;
  font-weight: 400;
  fill: var(--ink-70);
}

.diagram .d-tag {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.8px;
  fill: var(--ink-45);
}

.diagram .d-tag.on-accent {
  fill: var(--accent);
}

/* --------------------------------------------------------------------------
   Spec rows
   -------------------------------------------------------------------------- */

.spec > div {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 32px;
  padding: 20px 0;
  border-top: var(--rule);
}

.spec > div:last-child {
  border-bottom: var(--rule);
}

@media (max-width: 640px) {
  .spec > div {
    grid-template-columns: 1fr;
    gap: 8px;
  }
}

.spec dt {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-45);
  padding-top: 2px;
}

.spec dd {
  margin: 0;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-70);
}

/* --------------------------------------------------------------------------
   Contact
   -------------------------------------------------------------------------- */

.contact {
  background: var(--paper-alt);
  border-bottom: none;
}

.contact-mail {
  display: inline-block;
  margin-top: 8px;
  font-size: clamp(20px, 2.4vw, 27px);
  font-weight: 500;
  letter-spacing: -0.01em;
  word-break: break-word;
}

/* --------------------------------------------------------------------------
   Footer title block, s08: the technical-drawing convention.
   -------------------------------------------------------------------------- */

.site-footer {
  padding: 40px 0 64px;
}

/* Unboxed. The earlier version was a bordered grid with vertical dividers,
   and fixed tracks were needed to stop wrapping flex fields from carrying a
   stray left border. With the box and the dividers gone, the fields are just
   mono lines that wrap, and the contact block's background change above is
   what separates the footer. */
.title-block {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 44px;
  padding-top: 24px;
  border-top: var(--rule);
}

.title-block .field {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.03em;
  line-height: 1.5;
  min-width: 0;
}

.title-block .field-label {
  color: var(--ink-45);
  text-transform: uppercase;
  margin-right: 10px;
}

.title-block .field-value {
  color: var(--ink-70);
}

/* --------------------------------------------------------------------------
   Links. s07: an underline grows in from the left. s10: this treatment is for
   real links only and never for static text.
   -------------------------------------------------------------------------- */

a:not(.btn):not(.skip-link):not(.wordmark) {
  color: var(--ink);
  text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size 0.25s var(--ease-stop), color 0.25s var(--ease-stop);
}

a:not(.btn):not(.skip-link):not(.wordmark):focus-visible {
  color: var(--ink);
  background-size: 100% 1px;
}

/* Never a bare :hover. On touch and hybrid devices it sticks to the last
   tapped element and reads as a stuck state. :focus-visible above stays
   unconditional, since keyboard focus applies regardless of pointer type. */
@media (hover: hover) and (pointer: fine) {
  a:not(.btn):not(.skip-link):not(.wordmark):hover {
    color: var(--ink);
    background-size: 100% 1px;
  }

  .btn-primary:hover {
    background: var(--accent-strong);
    border-color: var(--accent-strong);
  }

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

  .theme-toggle:hover {
    border-color: var(--border-strong);
  }
}

/* --------------------------------------------------------------------------
   Scroll reveals. The hidden state applies only once JS has confirmed it is
   running, via .js on the root, so a blocked script or a headless renderer
   ships content visible rather than blank.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.6s var(--ease-rise), transform 0.6s var(--ease-rise);
  }

  .js .reveal.in {
    opacity: 1;
    transform: none;
  }
}

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

  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  .hero-rule {
    transform: scaleX(1) !important;
  }

  .reveal {
    opacity: 1 !important;
    transform: none !important;
  }
}
