/* ==========================================================================
   Tyo — portfolio site stylesheet
   One stylesheet for the whole site. Sections are grouped and labelled below.
   Colours and sizes come from the design tokens in brief/README.md.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Colour */
  --ground:      #f8f7f3;   /* page background */
  --ink:         #171614;   /* primary text */
  --sage:        #6b7f5e;   /* accent */
  --sage-dark:   #55684a;   /* accent, hover/active */
  --green-panel: #bfcfbb;   /* timezone bar + contact block */
  --band-cool:   #f2efe9;   /* experience band on About */
  --image-well:  #eceae4;   /* behind screenshots */
  --white:       #ffffff;   /* cards */

  /* Muted text on the green panel. The usual 50%/55% ink tints are too faint
     to read against #bfcfbb, so these two are darker on purpose. */
  --on-green-strong: rgba(23, 22, 20, .82);
  --on-green-muted:  rgba(23, 22, 20, .68);

  /* Hairlines and text tints, all built from the ink colour */
  --hairline:      rgba(23, 22, 20, .1);
  --hairline-card: rgba(23, 22, 20, .14);
  --hairline-soft: rgba(23, 22, 20, .12);
  --ink-72:        rgba(23, 22, 20, .72);
  --ink-70:        rgba(23, 22, 20, .7);
  --ink-68:        rgba(23, 22, 20, .68);
  --ink-65:        rgba(23, 22, 20, .65);
  --ink-60:        rgba(23, 22, 20, .6);
  --ink-55:        rgba(23, 22, 20, .55);
  --ink-50:        rgba(23, 22, 20, .5);
  --ink-45:        rgba(23, 22, 20, .45);

  /* Type */
  --sans:  "Helvetica Neue", Helvetica, Arial, sans-serif;
  --serif: Newsreader, Georgia, "Times New Roman", serif;

  /* Page safe area — 88px desktop, stepped down on smaller screens */
  --inset: 88px;
}


/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  /* Anchor links glide to their target instead of jumping. Turned off further
     down for anyone who has asked for reduced motion. */
  scroll-behavior: smooth;
}

/* The nav and timezone bar are sticky, so anything an anchor link points at
   has to stop short of them. script.js measures the header into --header-h. */
[id] { scroll-margin-top: calc(var(--header-h, 104px) + 16px); }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

a { color: inherit; }

h1, h2, h3, p { margin: 0; }

/* --- Reveal ---------------------------------------------------------------
   Anything marked data-reveal in the HTML fades in and drifts up a few
   pixels. Whatever is on screen when the page opens comes in straight away;
   everything further down waits until you scroll to it.

   Safety: the hidden state only applies under `.js`, a class added by a small
   script in each page's <head>. If script.js never runs, that same script
   takes the class back off and the whole page simply shows, so a failed
   script can never leave content invisible.
   -------------------------------------------------------------------------- */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity   .8s cubic-bezier(.22, .61, .36, 1) var(--rd, 0s),
    transform .8s cubic-bezier(.22, .61, .36, 1) var(--rd, 0s);
}

.js [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* Neighbours arrive one after another rather than all at once. */
.js [data-reveal]:nth-child(2)   { --rd: .07s; }
.js [data-reveal]:nth-child(3)   { --rd: .14s; }
.js [data-reveal]:nth-child(4)   { --rd: .21s; }
.js [data-reveal]:nth-child(n+5) { --rd: .28s; }

/* The header only fades in — it must not drift, or it would fight
   position:sticky. But this rule must not out-rank the hide-on-scroll rule
   below it, hence the explicit .is-hidden pairing. */
.js .site-header[data-reveal] { transform: none; }
.js .site-header[data-reveal].is-hidden { transform: translateY(-100%); }

/* On paper there is no scrolling, so nothing would ever be revealed.
   Print every block at full opacity. */
@media print {
  .js [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* Keyboard focus ring. Deliberately visible. */
:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Visible only to screen readers */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link, first thing in the tab order */
.skip-link {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 100;
  background: var(--sage);
  color: #fff;
  font: 500 13px/1 var(--sans);
  padding: 12px 16px;
  border-radius: 7px;
  text-decoration: none;
  transition: top .15s ease;
}

.skip-link:focus { top: 12px; }


/* --------------------------------------------------------------------------
   3. Shared button styles
   -------------------------------------------------------------------------- */

.btn {
  display: inline-block;
  font: 500 14px/1 var(--sans);
  text-decoration: none;
  border-radius: 7px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease;
}

.btn-primary {
  background: var(--sage);
  color: #fff;
  padding: 15px 26px;
}

.btn-primary:hover { background: var(--sage-dark); }

.btn-secondary {
  background: transparent;
  color: var(--ink);
  padding: 14px 24px;
  border-color: rgba(23, 22, 20, .35);
}

.btn-secondary:hover { border-color: rgba(23, 22, 20, .55); }

/* Underlined text link, used beside the contact button */
.text-link {
  font: 500 13.5px/1 var(--sans);
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: rgba(23, 22, 20, .3);
}

.text-link:hover { text-decoration-color: var(--ink); }


/* --------------------------------------------------------------------------
   4. Top navigation
   -------------------------------------------------------------------------- */

/* Nav + timezone bar travel down the page together, and slide out of the way
   when you scroll down. Scrolling back up brings them straight back.
   script.js adds .is-hidden. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--ground);
  transition: transform .4s cubic-bezier(.22, .61, .36, 1);
}

.site-header.is-hidden { transform: translateY(-100%); }

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px var(--inset);
  border-bottom: 1px solid var(--hairline);
}

.nav-wordmark {
  font: 600 20px/1 var(--serif);
  letter-spacing: .01em;
  text-decoration: none;
}

/* Everything on the right-hand side of the nav */
.nav-right {
  display: flex;
  align-items: center;
  gap: 28px;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 28px;
}

/* Hovering lifts the label a little and draws an underline in from the left,
   so it is obvious which one you are pointing at. */
.nav-link {
  position: relative;
  font: 400 13.5px/1 var(--sans);
  color: var(--ink-60);
  text-decoration: none;
  padding: 4px 0;
  transition: color .18s ease, transform .18s ease;
}

.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .28s cubic-bezier(.22, .61, .36, 1);
}

.nav-link:hover,
.nav-link:focus-visible {
  color: var(--ink);
  transform: translateY(-2px);
}

.nav-link:hover::after,
.nav-link:focus-visible::after { transform: scaleX(1); }

/* The page you are currently on */
.nav-link[aria-current="page"] { color: var(--ink); }

.nav-cta {
  font: 500 13px/1 var(--sans);
  background: var(--sage);
  color: #fff;
  padding: 11px 18px;
  border-radius: 7px;
  text-decoration: none;
  transition: background-color .15s ease;
}

.nav-cta:hover { background: var(--sage-dark); }

/* Hamburger — hidden on desktop, shown below 760px */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 44px;
  height: 44px;
  margin-left: -12px;
  padding: 0 12px;
  background: none;
  border: 0;
  cursor: pointer;
}

.nav-toggle span {
  display: block;
  width: 20px;
  height: 1.5px;
  background: var(--ink);
}

/* The panel the hamburger opens */
.nav-menu {
  display: none;
  flex-direction: column;
  padding: 8px 24px 20px;
  background: var(--ground);
  border-bottom: 1px solid var(--hairline);
}

.nav-menu[data-open="true"] { display: flex; }

.nav-menu a {
  font: 400 16px/1 var(--sans);
  color: var(--ink);
  text-decoration: none;
  padding: 15px 0;
  border-bottom: 1px solid var(--hairline-soft);
}


/* --------------------------------------------------------------------------
   5. Timezone bar
   Sits directly under the nav on every page. The clock is live — see the
   script at the bottom of index.html.
   -------------------------------------------------------------------------- */

.timezone-bar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 9px var(--inset);
  background: var(--green-panel);
  /* The rule matches the fill, so the bar reads as one solid block. */
  border-bottom: 1px solid var(--green-panel);
}

.timezone-clock {
  font: 500 11.5px/1 var(--sans);
  color: var(--on-green-strong);
}

.timezone-divider {
  flex: none;
  width: 1px;
  height: 11px;
  background: rgba(23, 22, 20, .28);
}

.timezone-note {
  font: 400 11.5px/1 var(--sans);
  color: var(--on-green-muted);
}

/* How far the visitor sits from Jakarta, e.g. "(12h behind)". Quieter than the
   time itself, so it reads as a footnote to it rather than a second fact. */
.timezone-offset { color: var(--on-green-muted); }

/* The two label wordings. Long form on desktop, short form on mobile. */
.tz-short { display: none; }

/* JS adds this when the visitor is already on GMT+7, or their browser
   reports no timezone at all. The "Your time" half then disappears. */
.timezone-you[hidden] { display: none; }


/* --------------------------------------------------------------------------
   6. Hero — Direction A
   -------------------------------------------------------------------------- */

.hero { padding: 88px var(--inset) 64px; }

.hero-eyebrow {
  font: 700 10.5px/1.4 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sage);
}

.hero-headline {
  margin-top: 26px;
  font: 400 46px/1.08 var(--serif);
  letter-spacing: -.015em;
  max-width: 820px;
  text-wrap: pretty;
}

.hero-subline {
  margin-top: 26px;
  font: 400 17px/1.6 var(--sans);
  color: var(--ink-72);
  max-width: 34em;
}

.hero-buttons {
  display: flex;
  gap: 14px;
  margin-top: 34px;
}

/* Stat strip — three ruled columns */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin-top: 48px;
  max-width: 900px;
  border-top: 1px solid rgba(23, 22, 20, .16);
  border-bottom: 1px solid rgba(23, 22, 20, .16);
}

.stat {
  padding: 28px 32px;
  border-left: 1px solid var(--hairline-card);
}

.stat:first-child {
  padding-left: 0;
  border-left: 0;
}

.stat:last-child { padding-right: 0; }

.stat-number {
  font: 400 34px/1 var(--serif);
  color: var(--sage);
}

.stat-label {
  margin-top: 10px;
  font: 400 10.5px/1.4 var(--sans);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-55);
}


/* --------------------------------------------------------------------------
   7. Scroll cue + What I actually do
   -------------------------------------------------------------------------- */

.scroll-cue {
  display: flex;
  justify-content: center;
  padding: 0 0 40px;
}

.scroll-cue a {
  font: 400 10.5px/1 var(--sans);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(23, 22, 20, .4);
  text-decoration: none;
  padding: 6px 8px;
  transition: color .15s ease;
}

.scroll-cue a:hover { color: var(--ink-68); }

.approach {
  padding: 80px var(--inset);
  border-top: 1px solid var(--hairline);
}

.approach-intro {
  margin: 16px auto 0;
  font: 400 17px/1.6 var(--sans);
  color: var(--ink-72);
  max-width: 38em;
  text-align: center;
}

.approach-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 34px 48px;
  margin: 44px auto 0;
  max-width: 900px;
}

/* Each point is a ruled text block. No cards, no numbering, no left accent. */
.approach-item {
  padding-top: 22px;
  border-top: 1px solid rgba(23, 22, 20, .2);
}

.approach-heading { font: 700 17px/1.35 var(--sans); margin: 0; }

.approach-text {
  margin-top: 10px;
  font: 400 14.5px/1.6 var(--sans);
  color: var(--ink-70);
}


/* --------------------------------------------------------------------------
   8. Featured work
   -------------------------------------------------------------------------- */

.work {
  padding: 80px var(--inset);
  border-top: 1px solid var(--hairline);
}

.work-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
}

.section-title {
  font: 400 38px/1.15 var(--serif);
  letter-spacing: -.01em;
}

.section-title-centred { text-align: center; }

.work-all {
  flex: none;
  font: 500 13.5px/1 var(--sans);
  color: var(--sage);
  text-decoration: none;
  border-bottom: 1px solid rgba(107, 127, 94, .4);
  padding-bottom: 3px;
}

.work-all:hover { border-bottom-color: var(--sage); }

.work-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 26px;
  margin-top: 36px;
}

/* --- Project card. The whole card is one link. --- */
.card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--hairline-card);
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  transition:
    transform .28s cubic-bezier(.22, .61, .36, 1),
    box-shadow .28s cubic-bezier(.22, .61, .36, 1),
    border-color .2s ease;
}

/* Hover: the card lifts off the page and the banner eases in a little
   behind it, so the whole thing reads as popping forward. The band already
   clips its image, so the zoom stays inside the frame. */
.card:hover {
  transform: translateY(-4px);
  border-color: rgba(23, 22, 20, .28);
  box-shadow: 0 10px 24px rgba(23, 22, 20, .1);
}

.card:hover .card-image img,
.card:hover .card-placeholder { transform: scale(1.05); }

.card-image {
  /* Locked to 2:1, so a 1600 x 800 export drops in with nothing cropped.
     The height follows the card's width instead of being a fixed number. */
  aspect-ratio: 2 / 1;
  background: var(--image-well);
  border-bottom: 1px solid var(--hairline-soft);
  overflow: hidden;
}

.card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 0 0;
  transition: transform .45s cubic-bezier(.22, .61, .36, 1);
}

/* For a banner whose artwork is composed centrally rather than reading from
   the top-left. The crop then takes evenly from top and bottom. */
.card-image-center img { object-position: 50% 50%; }

/* Placeholder band for a project whose screenshot has not been supplied yet.
   Replace the whole .card-placeholder block with a .card-image block once
   the real image arrives. */
.card-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 2 / 1;
  padding: 0 14px;
  transition: transform .45s cubic-bezier(.22, .61, .36, 1);
  text-align: center;
  background: repeating-linear-gradient(45deg, #e9e5dd 0 6px, #f3f0ea 6px 12px);
  border-bottom: 1px solid var(--hairline-soft);
}

.card-placeholder span {
  font: 400 10px/1.5 ui-monospace, Menlo, monospace;
  letter-spacing: .06em;
  color: var(--ink-45);
}

.card-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 22px 22px 24px;
}

.card-title { font: 700 17px/1.3 var(--sans); }

.card-text {
  flex: 1;
  margin-top: 10px;
  font: 400 14px/1.6 var(--sans);
  color: var(--ink-70);
}

.card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 18px;
}

.tag {
  font: 400 10.5px/1 var(--sans);
  letter-spacing: .04em;
  padding: 6px 9px;
  border: 1px solid rgba(23, 22, 20, .2);
  border-radius: 5px;
  color: var(--ink-65);
}


/* --------------------------------------------------------------------------
   9. Contact block
   -------------------------------------------------------------------------- */

.contact {
  padding: 88px var(--inset);
  background: var(--green-panel);
  /* The rule matches the fill, so the block reads as one solid colour. */
  border-top: 1px solid var(--green-panel);
  text-align: center;
}

.contact-title {
  font: 400 36px/1.2 var(--serif);
}

/* The lighter sage disappears against the green panel, so the highlighted
   phrase uses the darker sage from the palette instead. */
.contact-title strong {
  font-weight: 600;
  color: var(--sage-dark);
}

.contact-text {
  margin: 16px auto 0;
  font: 400 16.5px/1.6 var(--sans);
  color: var(--on-green-strong);
  max-width: 38em;
}

/* On the green panel the underline needs a little more presence. */
.contact .text-link { text-decoration-color: rgba(23, 22, 20, .45); }

.contact-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin-top: 30px;
}


/* --------------------------------------------------------------------------
  10. Footer
   -------------------------------------------------------------------------- */

.footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 26px var(--inset);
  border-top: 1px solid var(--hairline);
}

.footer-copyright {
  font: 400 12px/1 var(--sans);
  color: var(--ink-45);
}

.footer-links {
  display: flex;
  gap: 22px;
}

.footer-links a {
  font: 400 12.5px/1 var(--sans);
  color: var(--ink);
  text-decoration: none;
}

.footer-links a:hover { text-decoration: underline; text-underline-offset: 3px; }


/* --------------------------------------------------------------------------
  11. About page
   -------------------------------------------------------------------------- */

.about-intro {
  padding: 76px var(--inset) 0;
  max-width: 1000px;
  margin: 0 auto;
  text-align: center;
}

.about-title {
  font: 400 62px/1.04 var(--serif);
  letter-spacing: -.02em;
}

.about-title span { color: var(--sage); }

.about-subline {
  margin-top: 18px;
  font: 400 18px/1.6 var(--sans);
  color: rgba(23, 22, 20, .62);
}

/* The portrait stands flush on the top edge of the sage bar — no gap,
   no frame, no panel, no rounded corners. */
.about-portrait {
  display: flex;
  justify-content: center;
  padding: 40px var(--inset) 0;
}

.about-portrait img {
  height: 560px;
  width: auto;
}

/* --- Keyword marquee --- */
.keyword-bar {
  background: var(--sage);
  overflow: hidden;
  padding: 22px 0;
}

.keyword-track {
  display: flex;
  width: max-content;
  animation: marquee 90s linear infinite;
}

/* Pause while someone is reading it, by mouse or by keyboard. */
.keyword-bar:hover .keyword-track,
.keyword-bar:focus-within .keyword-track {
  animation-play-state: paused;
}

/* Two identical copies sit side by side; shifting exactly one copy's width
   and starting over reads as one continuous loop. */
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.keyword-group {
  display: flex;
  align-items: center;
  gap: 40px;
  padding-right: 40px;
  white-space: nowrap;
}

.keyword-group span {
  display: flex;
  align-items: center;
  gap: 40px;
  font: 500 19px/1 var(--sans);
  letter-spacing: .02em;
  color: #fff;
}

/* The small rotated square between keywords */
.keyword-group span::after {
  content: "";
  display: block;
  width: 6px;
  height: 6px;
  background: rgba(255, 255, 255, .55);
  transform: rotate(45deg);
}

/* --- About Me --- */
.about-body {
  max-width: 760px;
  margin: 0 auto;
  padding: 72px var(--inset);
}

.about-body h2 {
  font: 400 38px/1.15 var(--serif);
  letter-spacing: -.01em;
  text-align: center;
}

.about-body p {
  margin-top: 22px;
  font: 400 16.5px/1.75 var(--sans);
  color: rgba(23, 22, 20, .78);
}

.about-body p:first-of-type { margin-top: 28px; }

/* --- How I work --- */
.how-i-work {
  background: #f4f1ea;
  padding: 72px var(--inset) 76px;
}

.how-i-work-inner {
  max-width: 960px;
  margin: 0 auto;
}

.how-i-work h2 {
  font: 400 34px/1.15 var(--serif);
  text-align: center;
}

.principles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 36px 56px;
  margin-top: 40px;
}

/* No numbering, no card boxes, no left accent border. */
.principle-title {
  font: 400 21px/1.32 var(--serif);
  text-wrap: pretty;
}

.principle-rule {
  width: 30px;
  height: 2px;
  background: var(--sage);
  margin: 13px 0 11px;
}

.principle-text {
  font: 400 14.5px/1.7 var(--sans);
  color: var(--ink-68);
}

/* --- Work experience --- */
.experience {
  background: var(--band-cool);
  border-top: 1px solid var(--hairline);
  padding: 48px var(--inset) 56px;
}

.experience-inner {
  max-width: 936px;
  margin: 0 auto;
}

.meta-label {
  font: 700 10.5px/1 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-45);
}

.exp-list {
  margin-top: 20px;
  border-bottom: 1px solid var(--hairline-card);
}

/* One row per company, not per role. */
.exp-row {
  display: grid;
  grid-template-columns: 210px 1fr;
  gap: 28px;
  padding: 24px 0;
  border-top: 1px solid var(--hairline-card);
}

.exp-company { font: 400 22px/1.15 var(--serif); }

.exp-years {
  margin-top: 7px;
  font: 400 10.5px/1 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-45);
}

.exp-roles {
  display: flex;
  flex-direction: column;
  gap: 12px;
  border-left: 2px solid var(--sage);
  padding-left: 24px;
}

.exp-role {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
}

.exp-role-name { font: 500 14.5px/1.4 var(--sans); }

.exp-role-dates {
  font: 400 12.5px/1.4 var(--sans);
  color: var(--ink-50);
  white-space: nowrap;
}

/* --- Education + Tools --- */
/* Education stands on its own across the full width, now that Tools has
   moved up into the running keyword bar. */
.about-meta {
  margin-top: 48px;
}

.edu-list { margin-top: 18px; }

.edu-item {
  padding: 11px 0;
  border-top: 1px solid var(--hairline-soft);
}

.edu-item:last-child { border-bottom: 1px solid var(--hairline-soft); }

.edu-title { font: 500 13.5px/1.5 var(--sans); }

.edu-where {
  font: 400 12.5px/1.5 var(--sans);
  color: var(--ink-55);
}

.tool-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 14px;
}

/* Square-cornered, unlike the rounded tag chips on the project cards. */
.tool-chips span {
  font: 400 11.5px/1 var(--sans);
  padding: 7px 10px;
  border: 1px solid rgba(23, 22, 20, .2);
  color: var(--ink-70);
}


/* --------------------------------------------------------------------------
  12. Projects index
   -------------------------------------------------------------------------- */

.page-header { padding: 72px var(--inset) 40px; }

.page-title {
  font: 400 56px/1.05 var(--serif);
  letter-spacing: -.015em;
}

.page-intro {
  margin-top: 20px;
  font: 400 17px/1.6 var(--sans);
  color: var(--ink-70);
  max-width: 40em;
}

.project-group { padding: 0 var(--inset) 40px; }

.project-group:last-of-type { padding: 24px var(--inset) 72px; }

.group-label {
  font: 700 10.5px/1 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--hairline-card);
}

.group-label-featured { color: var(--sage); }
.group-label-more     { color: var(--ink-45); }

/* Same three-column grid as the homepage. The "More" row simply leaves the
   third cell empty rather than stretching two cards across three columns. */
.project-group .work-grid { margin-top: 28px; }


/* --------------------------------------------------------------------------
  13. Project detail
   One shared column for the whole page, so the title, the body copy and
   every image line up on the same left and right edge.
   -------------------------------------------------------------------------- */

.detail-column {
  box-sizing: content-box;
  max-width: 996px;
  margin: 0 auto;
  padding-left: var(--inset);
  padding-right: var(--inset);
}

.detail-head { padding-top: 64px; padding-bottom: 36px; }

.back-link {
  display: inline-block;
  font: 400 12px/1 var(--sans);
  color: var(--ink-50);
  text-decoration: none;
  margin-bottom: 22px;
  padding: 4px 0;
}

.back-link:hover { color: var(--ink); }

.detail-title {
  font: 400 50px/1.08 var(--serif);
  letter-spacing: -.015em;
}

.detail-meta {
  margin-top: 18px;
  font: 700 10.5px/1.5 var(--sans);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-50);
}

.detail-section { padding-bottom: 56px; }

.detail-section h2 { font: 400 30px/1.2 var(--serif); }

.detail-section p {
  margin-top: 20px;
  font: 400 16.5px/1.75 var(--sans);
  color: rgba(23, 22, 20, .8);
}

/* The labelled sub-blocks under "What I built" */
/* The sub-blocks under "What I built" keep the same 20px rhythm as ordinary
   paragraphs. The margin reset matters: without it these would inherit
   .detail-section p's 20px on top of the flex gap and sit twice as far apart. */
.build-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-top: 20px;
}

.build-list p { margin-top: 0; }

.build-item {
  font: 400 16.5px/1.75 var(--sans);
  color: rgba(23, 22, 20, .8);
}

.build-item b {
  font-weight: 700;
  color: var(--ink);
}

/* --- Screenshot figures --- */
.shot {
  margin: 0;
  padding-bottom: 56px;
}

/* The frame is a button, because clicking it opens the lightbox. */
.shot-frame {
  position: relative;
  display: block;
  width: 100%;
  background: var(--white);
  border: 1px solid var(--hairline-card);
  padding: 16px;
  cursor: zoom-in;
  transition: border-color .15s ease;
}

.shot-frame:hover { border-color: rgba(23, 22, 20, .28); }

/* Every screenshot well scrolls sideways rather than cropping. These are
   dense spreadsheets — cutting off the right-hand columns destroys the
   evidence they are here to provide. A picture narrower than the frame
   simply sits there with no scrollbar. */
.shot-well {
  height: 380px;
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--image-well);
  border: 1px solid rgba(23, 22, 20, .08);
}

.shot-well img {
  height: 100%;
  width: auto;
  max-width: none;
}

.shot-badge {
  position: absolute;
  right: 10px;
  bottom: 10px;
  /* Above the scroll fade, so the fade never washes over the label. */
  z-index: 2;
  font: 400 9.5px/1 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  background: rgba(23, 22, 20, .72);
  color: #fff;
  padding: 6px 9px;
  border-radius: 5px;
}

.shot figcaption {
  margin-top: 10px;
  font: 400 12px/1.5 var(--sans);
  color: var(--ink-50);
}

/* A smaller well, for a figure that illustrates one sub-block */
.shot-sm .shot-frame { padding: 12px; }
.shot-sm .shot-well  { height: 260px; }

/* A figure sitting directly under the sub-block it illustrates. It stays
   close to that text instead of floating half a section away. */
.shot-inline {
  margin-top: 22px;
  padding-bottom: 0;
}

/* The very wide sheets get a hint of lift so the sideways scroll reads as
   a panel rather than part of the page. */
.shot-wide .shot-frame {
  padding: 14px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .05);
}

.shot-wide .shot-well { height: 300px; }

/* --- Scroll affordance -----------------------------------------------------
   A frame whose picture is wider than the frame gets a soft fade down its
   right edge, so there is a visible hint that more exists off to the side
   rather than only the wording in the badge.

   script.js adds .has-overflow when the picture really is wider, and removes
   .at-end once it has been scrolled all the way across, so the fade goes away
   when there is nothing left to reach.
   -------------------------------------------------------------------------- */

.shot-frame { position: relative; }

.shot-frame::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 64px;
  pointer-events: none;
  opacity: 0;
  transition: opacity .25s ease;
  /* Fades into the well colour already behind the screenshots. */
  background: linear-gradient(to right,
              rgba(236, 234, 228, 0),
              rgba(236, 234, 228, .9));
}

.shot.has-overflow:not(.at-end) .shot-frame::after { opacity: 1; }

/* Fit-to-width. For a picture that is not a wide spreadsheet: it sits at the
   full column width and the frame grows to match, so the whole image shows
   with nothing cropped and nothing to scroll. */
.shot-fit .shot-well {
  height: auto;
  overflow: visible;
}

.shot-fit .shot-well img {
  width: 100%;
  height: auto;
}

/* Video banner. The frame shows a still with a play button over it; the clip
   itself is only fetched when someone presses play, and opens full size. */
.shot-play {
  position: absolute;
  left: 50%;
  top: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 66px;
  height: 66px;
  margin-top: -8px;         /* optically centred against the caption below */
  border-radius: 50%;
  background: rgba(23, 22, 20, .72);
  color: #fff;
  font-size: 20px;
  padding-left: 4px;        /* the triangle reads centred this way */
  transform: translate(-50%, -50%);
  transition: background-color .18s ease, transform .18s ease;
}

.shot-video .shot-frame:hover .shot-play {
  background: var(--sage);
  transform: translate(-50%, -50%) scale(1.06);
}

/* Placeholder for a screenshot that has not been supplied yet. Not a button,
   because there is nothing to enlarge. */
.shot-pending .shot-frame {
  cursor: default;
  border-style: dashed;
}

.shot-pending .shot-well {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 20px;
  background: repeating-linear-gradient(45deg, #e9e5dd 0 6px, #f3f0ea 6px 12px);
}

.shot-pending .shot-well span {
  font: 400 11px/1.6 ui-monospace, Menlo, monospace;
  letter-spacing: .06em;
  color: var(--ink-45);
}

/* --- Impact panel --- */
.impact-panel {
  background: #f1ede5;
  border: 1px solid var(--hairline-card);
  padding: 40px 44px;
  max-width: 900px;
  margin: 0 auto;
}

.impact-panel h2 { font: 400 30px/1.2 var(--serif); }

.impact-list { margin-top: 24px; }

.impact-row {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 16px;
  padding: 16px 0;
  border-top: 1px solid rgba(23, 22, 20, .16);
  font: 400 16px/1.65 var(--sans);
}

.impact-row:last-child { border-bottom: 1px solid rgba(23, 22, 20, .16); }

.impact-row::before {
  content: "";
  width: 7px;
  height: 7px;
  background: var(--sage);
  border-radius: 50%;
  margin-top: 9px;
}

/* --- Next project --- */
.next-project {
  border-top: 1px solid var(--hairline);
  padding: 44px var(--inset);
  text-align: center;
}

.next-project a {
  display: inline-block;
  text-decoration: none;
  padding: 8px 12px;
}

/* Label, project name and arrow stack into a centred column. */
.next-project a > span { display: block; }

.next-project-name {
  margin-top: 12px;
  font: 400 28px/1.2 var(--serif);
  transition: color .15s ease;
}

.next-project a:hover .next-project-name { color: var(--sage); }

.next-project-arrow {
  margin-top: 14px;
  font: 500 14px/1 var(--sans);
  color: var(--sage);
}


/* --------------------------------------------------------------------------
  14. Lightbox
   Opens at full resolution. Spreadsheets are the evidence on this site, so
   they are not scaled down to fit — the container scrolls instead.
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 64px 24px 32px;
  background: rgba(23, 22, 20, .88);
}

.lightbox[hidden] { display: none; }

body.has-lightbox { overflow: hidden; }

.lightbox-inner {
  max-width: 100%;
  max-height: 100%;
  overflow: auto;
  background: var(--white);
}

.lightbox-image {
  max-width: none;
  height: auto;
}

/* Video fits the screen rather than scrolling, unlike a wide spreadsheet. */
.lightbox-video {
  display: block;
  max-width: min(1400px, 92vw);
  max-height: 78vh;
}

.lightbox-image[hidden],
.lightbox-video[hidden] { display: none; }

/* A video pane does not need the white mount a screenshot gets. */
.lightbox-inner.is-video { background: transparent; }

.lightbox-close {
  position: absolute;
  top: 16px;
  right: 20px;
  font: 500 13px/1 var(--sans);
  color: #fff;
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 7px;
  padding: 11px 16px;
  cursor: pointer;
}

.lightbox-close:hover { background: rgba(255, 255, 255, .24); }

.lightbox-caption {
  font: 400 12.5px/1.6 var(--sans);
  color: rgba(255, 255, 255, .8);
  max-width: 60em;
  text-align: center;
}


/* --------------------------------------------------------------------------
  15. Responsive
   The wireframes specify 1440 and 375. In between, the card grid steps
   3 → 2 → 1 and the page inset tightens.
   -------------------------------------------------------------------------- */

/* --- Narrower desktop and tablet --- */
@media (max-width: 1080px) {
  :root { --inset: 48px; }

  .hero-headline { font-size: 40px; }
  .section-title { font-size: 32px; }
  .about-title   { font-size: 48px; }
  .page-title    { font-size: 44px; }
  .detail-title  { font-size: 40px; }
}

@media (max-width: 1000px) {
  .work-grid { grid-template-columns: repeat(2, 1fr); }
}

/* --- Mobile --- */
@media (max-width: 760px) {
  :root { --inset: 24px; }

  /* Nav: hamburger left, wordmark centre, small CTA right */
  .nav { padding: 14px var(--inset); }
  .nav-toggle { display: flex; }
  .nav-links { display: none; }

  .nav-wordmark { font-size: 13px; }

  .nav-cta {
    font-size: 11.5px;
    padding: 9px 12px;
    /* Keeps the tap target at 44px without changing how the pill looks */
    outline-offset: 2px;
  }

  /* Timezone bar stacks to two lines and uses the short wording */
  .timezone-bar {
    display: block;
    padding: 8px var(--inset);
  }

  .timezone-clock { font-size: 11px; line-height: 1.35; }
  .timezone-note  { font-size: 10px; line-height: 1.35; }
  .timezone-divider { display: none; }

  .tz-long  { display: none; }
  .tz-short { display: inline; }

  /* Hero */
  .hero { padding: 32px var(--inset) 28px; }

  .hero-eyebrow { font-size: 9px; line-height: 1.6; letter-spacing: .1em; }

  .hero-headline {
    margin-top: 18px;
    font-size: 34px;
    line-height: 1.1;
    letter-spacing: -.01em;
  }

  .hero-subline { margin-top: 18px; font-size: 15px; }

  .hero-buttons {
    flex-direction: column;
    gap: 10px;
    margin-top: 24px;
  }

  /* Full-width stacked buttons, minimum 48px tall */
  .hero-buttons .btn {
    width: 100%;
    min-height: 48px;
    padding: 16px 0;
    text-align: center;
  }

  /* Stat strip: two across, the third spanning the full width */
  .stat-strip {
    grid-template-columns: 1fr 1fr;
    margin-top: 24px;
    border-bottom: 0;
  }

  .stat {
    padding: 18px 0 18px 18px;
    border-bottom: 1px solid var(--hairline-soft);
  }

  .stat:first-child { padding-left: 0; }

  .stat:last-child {
    grid-column: 1 / -1;
    padding: 18px 0;
    border-left: 0;
    border-bottom: 0;
  }

  .stat-number { font-size: 30px; }
  .stat-label  { margin-top: 5px; font-size: 10px; line-height: 1.3; }

  /* What I actually do — one column, hairline between each point.
     The scroll cue is desktop only; on mobile the section is already next. */
  .scroll-cue { display: none; }

  .approach { padding: 36px var(--inset); }

  .approach-intro { margin-top: 12px; font-size: 15px; }

  .approach-grid {
    grid-template-columns: 1fr;
    gap: 0;
    margin-top: 24px;
  }

  .approach-item {
    padding: 20px 0;
    border-top-color: var(--hairline-soft);
  }

  .approach-item:last-child { border-bottom: 1px solid var(--hairline-soft); }

  .approach-heading { font-size: 16px; }

  .approach-text { margin-top: 8px; font-size: 14px; line-height: 1.65; }

  /* Featured work */
  .work { padding: 36px var(--inset); }

  .work-head { display: block; }

  .section-title { font-size: 26px; line-height: 1.2; }

  .work-grid {
    grid-template-columns: 1fr;
    gap: 18px;
    margin-top: 22px;
  }

  .card-body { padding: 18px; }

  .card-title { font-size: 16px; }
  .card-text  { margin-top: 9px; font-size: 13.5px; }
  .card-tags  { gap: 6px; margin-top: 14px; }

  /* Mobile shows the first two tags only */
  .card-tags .tag:nth-child(n + 3) { display: none; }

  .work-all {
    display: inline-block;
    margin-top: 20px;
    border-bottom: 0;
  }

  /* Contact */
  .contact { padding: 40px var(--inset); }

  .contact-title { font-size: 24px; line-height: 1.25; }
  .contact-text  { margin-top: 12px; font-size: 14.5px; }

  .contact-actions {
    flex-direction: column;
    gap: 12px;
    margin-top: 22px;
  }

  .contact-actions .btn {
    width: 100%;
    min-height: 48px;
    padding: 16px 0;
  }

  .contact-actions .text-link { padding: 12px 0; }

  /* Footer stacks and centres */
  .footer {
    flex-direction: column;
    gap: 12px;
    padding: 22px var(--inset);
    text-align: center;
  }

  .footer-links {
    flex-direction: column;
    gap: 0;
  }

  .footer-links a { padding: 11px 0; }

  .footer-copyright { order: 2; margin-top: 6px; font-size: 11px; }

  /* --- About --- */
  .about-intro { padding: 40px var(--inset) 0; }

  .about-title { font-size: 36px; line-height: 1.06; }

  .about-subline { margin-top: 14px; font-size: 14.5px; }

  .about-portrait { padding: 28px var(--inset) 0; }
  .about-portrait img { height: 300px; }

  .keyword-bar { padding: 14px 0; }
  .keyword-group { gap: 26px; padding-right: 26px; }
  .keyword-group span { gap: 26px; font-size: 13px; }

  .about-body { padding: 40px var(--inset); }
  .about-body h2 { font-size: 28px; }
  .about-body p { margin-top: 18px; font-size: 15.5px; line-height: 1.7; }
  .about-body p:first-of-type { margin-top: 22px; }

  .how-i-work { padding: 40px var(--inset) 44px; }
  .how-i-work h2 { font-size: 26px; }

  .principles {
    grid-template-columns: 1fr;
    gap: 28px;
    margin-top: 28px;
  }

  .principle-title { font-size: 19px; }

  .experience { padding: 36px var(--inset) 40px; }

  /* The 210px / 1fr company row stacks */
  .exp-row {
    grid-template-columns: 1fr;
    gap: 14px;
    padding: 20px 0;
  }

  .exp-company { font-size: 20px; }

  .exp-role {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }

  .about-meta {
    grid-template-columns: 1fr;
    gap: 32px;
    margin-top: 36px;
  }

  /* --- Projects index --- */
  .page-header { padding: 36px var(--inset) 28px; }
  .page-title { font-size: 36px; }
  .page-intro { margin-top: 14px; font-size: 15px; }

  .project-group { padding: 0 var(--inset) 32px; }
  .project-group:last-of-type { padding: 16px var(--inset) 40px; }
  .project-group .work-grid { margin-top: 20px; }

  /* --- Project detail --- */
  .detail-head { padding-top: 32px; padding-bottom: 24px; }

  .detail-title { font-size: 32px; line-height: 1.1; }

  .detail-meta { margin-top: 14px; font-size: 9.5px; }

  .detail-section { padding-bottom: 36px; }
  .detail-section h2 { font-size: 24px; }

  .detail-section p,
  .build-item {
    font-size: 15.5px;
    line-height: 1.7;
  }

  .detail-section p { margin-top: 16px; }

  .build-list { gap: 18px; margin-top: 22px; }

  .shot { padding-bottom: 36px; }

  .shot-frame,
  .shot-sm .shot-frame,
  .shot-wide .shot-frame { padding: 8px; }

  .shot-well { height: 220px; }
  .shot-sm .shot-well { height: 180px; }
  .shot-wide .shot-well { height: 180px; }

  .impact-panel { padding: 26px 22px; }
  .impact-panel h2 { font-size: 24px; }

  .impact-row {
    grid-template-columns: 12px 1fr;
    gap: 12px;
    padding: 14px 0;
    font-size: 15px;
  }

  .next-project { padding: 32px var(--inset); }
  .next-project-name { font-size: 22px; }

  .lightbox { padding: 60px 12px 24px; }
}


/* --------------------------------------------------------------------------
  16. Reduced motion
   -------------------------------------------------------------------------- */

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

  html { scroll-behavior: auto; }

  .card:hover { transform: none; box-shadow: none; }
  .card:hover .card-image img,
  .card:hover .card-placeholder { transform: none; }
  .nav-link:hover { transform: none; }

  /* Content appears immediately, with no fade and no drift. */
  .js [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* The header stays put rather than sliding away and back. */
  .site-header { transition: none; }
  .site-header.is-hidden { transform: none; }

  /* The keyword bar stops moving and simply shows the first run of words. */
  .keyword-track { animation: none; }
}
