/* Homepage profile (templates/profile.html). Separate from rapture.css on purpose: the Art Deco
   pages at /lattice keep their own theme untouched. */
/* Palette and type come from tokens.css (loaded first). */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}
img { max-width: 100%; }
.wrap { max-width: 44rem; margin: 0 auto; padding: 0 24px 3rem; }
@media (max-width: 560px) {
  /* Phone width is the binding constraint: the wider 24px gutter only rewraps text here. */
  .wrap { padding-left: 20px; padding-right: 20px; }
  /* The shared top bar's phone gutter is 24px (topbar.css); on this page the content column is 20px,
     and the bar's edges should line up with it. Loaded after topbar.css, so it wins here only. */
  .topbar__inner { --gutter: 20px; }
}

/* ---- availability banner (templates/_availability.html; same look as rapture.css's, which this
   page does not load) ---- */
.availability {
  background: var(--teal-deep);
  color: var(--on-teal);
  border-bottom: 1px solid var(--brass);
  padding: 7px 20px;
  text-align: center;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  line-height: 1.35;
}

/* ---- masthead ---- */
.masthead {
  background: var(--teal);
  color: var(--on-teal);
  padding: 2.75rem 24px 2.5rem;
  border-bottom: 2px solid var(--brass);
  position: relative;
  isolation: isolate;  /* the constellation sits behind the text, inside this band */
}
/* Faint constellation behind the masthead and the contact band: a mask (static/img/constellation.svg)
   coloured by a token, so light and dark both work. It drifts: `cover` always overflows the box on
   one axis, and the mask position slides across that overflow, so nothing is transformed and the
   band cannot widen the page. TASK-20 removed an earlier twinkle; slow motion is back because Kevin
   asked for it, and it is cancelled under prefers-reduced-motion below. Both bands share these
   declarations and ONE @keyframes; only opacity is per band. Decorative: no
   pointer events, behind the content, and `inset: 0` keeps it inside the band.
   No ramp: ease-in-out from rest looked frozen for the first seconds (0.07px in the first second at
   375px), so each cycle starts HALF-WAY through (a negative delay of half the duration), where the
   eased speed is at its peak. One duration at every width, so resizing across a breakpoint never
   makes the drift jump to another phase. */
.masthead::before, .contact::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: var(--on-teal);
  -webkit-mask: url("../img/constellation.svg") no-repeat center / cover;
  mask: url("../img/constellation.svg") no-repeat center / cover;
  animation: constellation-drift 40s ease-in-out -20s infinite alternate;
}
/* Opacity stays at 0.12 or under on the masthead and 0.10 on the contact band: brighter lines cost
   contrast on the on-teal text (the 60%-opacity footnote is the limit; tests recompute it). */
.masthead::before { opacity: 0.12; }
.contact::before { opacity: 0.10; }
/* The drift keyframes live in topbar.css (loaded by this page): one copy for every band on the site. */
@media (prefers-reduced-motion: reduce) {
  .masthead::before { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
  .contact::before { animation: none; }
}
.masthead-inner {
  max-width: calc(44rem - 48px);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 9.5rem 1fr;
  gap: 2.5rem;
  align-items: center;
}
.masthead-inner--no-portrait { grid-template-columns: 1fr; }
/* height: auto because the tag carries height="900", which would otherwise become the CSS height. */
.portrait {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 50%;
  border: 2px solid var(--brass-warm);
  display: block;
}
.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--brass-warm);
  margin: 0 0 0.9rem;
}
h1 {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(2.6rem, 8vw, 3.9rem);
  line-height: 1.02;
  letter-spacing: 0.04em;
  margin: 0 0 1.1rem;
  text-wrap: balance;
}
.claim { font-size: 1.06rem; line-height: 1.55; margin: 0; opacity: 0.93; }
.claim strong { font-weight: 600; color: var(--brass-warm); }
/* One quiet line under the claim, at full --on-teal (contrast listed in the tokens.css header), so it needs no opacity to be read. */
.subline { font-size: 0.95rem; line-height: 1.5; margin: 0.6rem 0 0; color: var(--on-teal); }
@media (max-width: 640px) {
  .masthead-inner { grid-template-columns: 1fr; gap: 1.5rem; }
  .portrait { width: 8.5rem; }
}

/* ---- figures ---- */
/* Flex, not grid: a short last row stretches to fill instead of leaving an empty ruled cell. */
.figures {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  margin-block: 2.25rem;
}
.fig { background: var(--surface); padding: 1.15rem 1.3rem; flex: 1 1 11rem; }
/* The measured figures (buildstats) carry a warm edge, so the 3/2 split reads as "these two are
   live" rather than as a row that happened to come up short. The mark is deliberately small: an
   edge, never a filled cell.
   The edge takes --brass-text, not --brass-warm: these cells sit on --surface (a pale blue in
   light mode), where --brass-warm measures 2.19 and washes out. --brass-text is the on-paper
   value of the same accent and holds at 5.21 (both measured 2026-10-13, tokens.css). */
.fig--live { box-shadow: inset 3px 0 0 var(--brass-text); }
.fig-n {
  font-family: var(--font-mono);
  font-size: 1.55rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--brass-text);
  line-height: 1.1;
}
.fig-l { font-size: 0.78rem; color: var(--muted); margin-top: 0.4rem; line-height: 1.35; }
.fig-note { font-size: 0.7rem; color: var(--muted); margin-top: 0.3rem; line-height: 1.35; font-style: italic; }

h2 {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 2.25rem 0 1.2rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--rule);
}
p { margin: 0 0 1.05rem; max-width: 36rem; }

/* ---- story ---- */
.story p:first-of-type::first-letter {
  font-family: var(--font-display);
  font-size: 3.1rem;
  line-height: 0.82;
  float: left;
  padding: 0.16em 0.12em 0 0;
  color: var(--brass-text);
}
.pull {
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.34;
  border-left: 2px solid var(--brass);
  padding: 0.2rem 0 0.2rem 1.15rem;
  margin: 1.75rem 0;
  max-width: 34rem;
}

/* ---- roles ---- */
.role { padding-block: 1.05rem; border-bottom: 1px solid var(--rule); }
.role:last-of-type { border-bottom: 0; }
.role-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 0.9rem; }
.role-title { font-weight: 600; }
.role-org { color: var(--brass-text); font-weight: 500; }
.role-when {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--muted);
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}
.role-bullets { margin: 0.45rem 0 0; padding-left: 1.1rem; max-width: 36rem; }
.role-bullets li { font-size: 0.93rem; color: var(--muted); margin-bottom: 0.25rem; }
.role-bullets li::marker { color: var(--brass-text); }

/* ---- systems ---- */
.systems { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
.sys { background: var(--surface); padding: 1.4rem; }
.sys-name { font-weight: 600; margin-bottom: 0.3rem; }
.sys-name a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--brass); padding-bottom: 1px; }
.sys-name a:hover { color: var(--brass-text); }
/* Project status pill (siteflags.PROJECT_STATUS), same look as rapture.css's .status-tag; this page
   loads profile.css alone. Tokens only. */
.status-tag {
  display: inline-block;
  margin-left: 0.55rem;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid currentColor;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  vertical-align: 0.1em;
  color: var(--muted);  /* a status without its own modifier below still reads as a pill */
}
.status-tag--live { color: var(--ok); }
.status-tag--beta { color: var(--brass-text); }
.sys-desc { font-size: 0.92rem; color: var(--muted); margin: 0 0 0.6rem; }
.sys-figs { list-style: none; margin: 0 0 0.6rem; padding: 0; display: flex; flex-wrap: wrap; gap: 0.2rem 1.2rem; font-size: 0.85rem; color: var(--muted); }
.sys-fig-n { font-family: var(--font-mono); font-weight: 600; color: var(--ink); }
.sys-note { font-size: 0.8rem; color: var(--muted); margin: 0.6rem 0 0; }
.sys-meta {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--brass-text);
  letter-spacing: 0.04em;
}

ul.plain { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.45rem; }
ul.plain li { font-size: 0.93rem; color: var(--muted); padding-left: 1.1rem; position: relative; }
ul.plain li::before { content: "\2014"; position: absolute; left: 0; color: var(--brass-text); }

.demo-video { width: 100%; height: auto; display: block; margin-top: 1rem; border: 1px solid var(--rule); }

/* ---- two paths under the masthead, and the closing "You might want to:" row ---- */
.paths { padding: 1.4rem 24px 0; }
/* The 24px side padding sits OUTSIDE on .paths and .contact, so these boxes give up 48px to match
   .wrap, whose 44rem includes its own padding. All three then share one content width. */
.paths-inner {
  max-width: calc(44rem - 48px);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
.path {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-top: 2px solid var(--brass);
  padding: 1rem 1.2rem;
  min-width: 0;
}
/* An h2 (the outline goes h1 -> h2), restyled: it overrides the page's small uppercase section h2. */
.path__title {
  margin: 0 0 0.5rem;
  padding: 0;
  border: 0;
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
}
.path__links { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1.1rem; font-size: 0.95rem; }
.path__note { color: var(--muted); }
/* The one primary action in the hero paths: a filled button. --on-teal on --teal is 13.1:1. The
   brass border keeps its silhouette in dark mode, where --teal is close to the page ground. */
.path__primary {
  display: inline-block;
  background: var(--teal);
  color: var(--on-teal);
  border: 1px solid var(--brass);
  padding: 0.5rem 1rem;
  font-weight: 600;
  text-decoration: none;
}
.path__primary:hover { background: var(--teal-deep); text-decoration: underline; }
.path__primary:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* "See my work" is its own outlined box, in the card colour, so it reads as separate from the
   dark band below it (surface against paper is 1.10:1; the border carries the rest). */
.next-steps {
  margin: 2.5rem 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1.2rem;
  font-size: 0.95rem;
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
  padding: 0.9rem 1.1rem;
  max-width: none;  /* the page-wide <p> measure would stop the box short of the column */
}
.next-steps__label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
@media (max-width: 560px) {
  .paths { padding: 1.2rem 20px 0; }
  .paths-inner { grid-template-columns: 1fr; }
}

/* ---- timeline: alternating left/right around a centre line; one column below 560px ---- */
.timeline {
  list-style: none;
  margin: 1.4rem 0 0;
  padding: 0;
  position: relative;
}
.timeline::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: var(--rule-strong);
}
.timeline__item {
  position: relative;
  width: 50%;
  box-sizing: border-box;
  padding: 0 1.8rem 1.6rem 0;
  text-align: right;
}
.timeline__item--right {
  margin-left: 50%;
  padding: 0 0 1.6rem 1.8rem;
  text-align: left;
}
.timeline__item::before {
  content: "";
  position: absolute;
  top: 0.35rem;
  right: -5px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--brass-warm);
  box-shadow: 0 0 0 3px var(--paper);
}
.timeline__item--right::before { right: auto; left: -4px; }
.timeline__when {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.timeline__title { display: block; margin-top: 0.2rem; font-size: 0.97rem; color: var(--ink); }
.timeline__org { display: block; font-size: 0.88rem; color: var(--muted); }
@media (max-width: 640px) {
  .timeline::before { left: 4px; }
  .timeline__item,
  .timeline__item--right {
    width: auto;
    margin-left: 0;
    padding: 0 0 1.4rem 1.6rem;
    text-align: left;
  }
  .timeline__item::before,
  .timeline__item--right::before { right: auto; left: 0; }
}

/* ---- off hours ---- */
.offhours {
  display: grid;
  grid-template-columns: 11rem 1fr;
  gap: 2rem;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: 1.6rem;
  margin-top: 1rem;
}
.offhours + .offhours { margin-top: 1px; }
.offhours--no-photo { grid-template-columns: 1fr; }
/* Both photos render in the same square box. Their sources differ (kevin-dog.jpg 900x1189,
   kevin-beach.jpg 900x638, two people), so each is cropped by object-position: the dog photo
   keeps the head and the dog from the top, the landscape one slides left so both faces stay in
   (checked by eye in Chrome, 2026-10-13). The bank's `landscape` flag now only selects that crop;
   the box is the same for every photo, and a new photo may need its own object-position. */
.offhours img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: center 15%;
  display: block;
}
.offhours img.landscape { object-position: 25% center; }
.offhours p { margin: 0; font-size: 0.95rem; color: var(--muted); }
@media (max-width: 560px) {
  .offhours { grid-template-columns: 1fr; gap: 1.6rem; }
  .lines { gap: 0.7rem; }
  .offhours img { max-width: 11rem; }
}

/* ---- contact ---- */
/* The bottom twin of the masthead: a 2px brass rule at its top edge (like the top bar's rule above
   the masthead), the lines inside the box (its ::before is inset 0, so it sits inside the border and
   never crosses the rule), and below it the footer bar, whose own 2px top border is the single rule
   between the two (.contact + .site-footer in topbar.css drops the footer's margin). */
.contact { margin-top: 3rem; background: var(--teal); color: var(--on-teal); padding: 2.4rem 24px;
  border-top: 2px solid var(--brass);
  position: relative; isolation: isolate;  /* the constellation sits behind the text, inside this band */ }
.contact-inner { max-width: calc(44rem - 48px); margin: 0 auto; }
/* Section ornament (same glyph as rapture.css's .section-heading::before; this page loads
   profile.css alone). A mask coloured by --brass-warm (the token for marks larger than a hairline),
   static, invisible to screen readers. Only the section headings that are direct children of
   .wrap: not the contact band and not the "temporarily unavailable" notice. */
.wrap > h2::before {
  content: "";
  display: block;
  width: 34px;
  height: 18px;
  margin-bottom: 8px;
  background-color: var(--brass-warm);
  -webkit-mask: url("../img/lattice-glyph.svg") no-repeat left center / contain;
  mask: url("../img/lattice-glyph.svg") no-repeat left center / contain;
}
.contact h2 { color: var(--brass); border-color: color-mix(in srgb, var(--brass) 30%, transparent); margin-top: 0; }
.lines { display: grid; gap: 0.85rem; }
.line {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem 0.8rem;
  font-family: var(--font-mono); font-size: 0.9rem;
}
.line-k {
  font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--brass); min-width: 5rem;
}
.line-v { word-break: break-word; min-width: 0; }
.line-v.resume-note { opacity: 0.8; }
/* Every line stacks label above value, so short values don't sit beside their label while
   long ones wrap below it. Kept below the base .line rule, which would otherwise win. */
@media (max-width: 560px) {
  .line { flex-direction: column; align-items: flex-start; gap: 0.2rem; }
  /* The phone gutter for the two bands. It lives here, below their base rules, because their
     `padding` shorthand would otherwise win over it. */
  .masthead, .contact { padding-left: 20px; padding-right: 20px; }
}
/* The footnote is its own marked-off block under the contact rows: a clear brass rule and more room. */
.footnote {
  margin: 2.75rem 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--brass);  /* the 0.6 opacity below makes it a 60% rule */
  font-size: 0.88rem;
  opacity: 0.6;
  max-width: 34rem;
}
a { color: var(--brass-text); text-decoration-thickness: 1px; text-underline-offset: 3px; }
.contact a { color: var(--on-teal); }
a:focus-visible, .line-v:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
