/* =============================================================================
   John Durcan, personal site. Ink system.
   Rebuilt 2026-08-04, replacing the retro-terminal template.

   Palette and treatment are ported verbatim from the LinkedIn and X banner
   build (British Dev Diaries/build/_shared.css.txt) so every personal surface
   reads as the same person. Deliberately NOT itch.studio's warm paper system:
   personal surfaces are ink, studio surfaces are paper.

   Contrast, verified against every stop of the background gradient:
     --ink     12.5:1 to 15.6:1
     --muted    5.5:1 to  6.3:1
     --accent   6.1:1 to  7.6:1
   All clear WCAG AA. Do not lighten the ground or drop --muted below 0.60
   without re-checking.

   No CDN, no webfonts, no third-party anything. One stylesheet, one small
   script, system type. The old build pulled in Glide, FontAwesome and four
   Google font families to render a page that had no projects on it.
============================================================================= */

:root {
  --ink: #f4efe8;
  --muted: rgba(244, 239, 232, 0.62);
  --faint: rgba(244, 239, 232, 0.38);
  --accent: #ff8a4c;
  --rim: rgba(255, 255, 255, 0.18);
  --line: rgba(244, 239, 232, 0.11);
  --panel: rgba(255, 255, 255, 0.035);
  --panel-hi: rgba(255, 255, 255, 0.06);
  --shadowcol: rgba(0, 0, 0, 0.78);

  --bg-solid: #14181e;

  --sans: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI",
    "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  --wrap: 1080px;
  --gut: clamp(20px, 5vw, 48px);
  --sec: clamp(64px, 9vw, 128px);
}

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

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

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

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg-solid);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The banners' exact ground, stretched to a page. Fixed so the blooms stay put
   while the content scrolls over them. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  background:
    radial-gradient(circle at 14% 12%, rgba(255, 107, 44, 0.16), transparent 42%),
    radial-gradient(circle at 78% 88%, rgba(124, 164, 255, 0.14), transparent 46%),
    linear-gradient(160deg, #232b34 0%, #1b2129 52%, #14181e 100%);
}

/* faint graph paper, the one motif carried over from the paper system */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px);
  background-size: 80px 80px;
  mask-image: linear-gradient(180deg, #000 0%, #000 40%, transparent 92%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 40%, transparent 92%);
}

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

a { color: inherit; }

::selection { background: rgba(255, 138, 76, 0.28); }

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

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gut);
}

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 12px;
  top: 12px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--ink);
  color: #14181e;
  border-radius: 8px;
  font-weight: 600;
}

/* ---------------------------------------------------------------- type ---- */

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.022em;
  line-height: 1.06;
}

.eyebrow {
  margin: 0;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
}

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

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

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

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  backdrop-filter: saturate(160%) blur(14px);
  -webkit-backdrop-filter: saturate(160%) blur(14px);
  background: rgba(20, 24, 30, 0.62);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s ease, background 0.25s ease;
}
.site-header[data-stuck="true"] {
  border-bottom-color: var(--line);
  background: rgba(20, 24, 30, 0.84);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: 62px;
}

.wordmark {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
}
.wordmark span { color: var(--accent); }

.site-nav ul {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 28px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.site-nav a {
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.18s ease;
}
.site-nav a:hover { color: var(--ink); }

.site-nav .cta {
  padding: 7px 14px;
  border: 1px solid var(--rim);
  border-radius: 999px;
  color: var(--ink);
}
.site-nav .cta:hover {
  border-color: var(--accent);
  color: var(--accent);
}

@media (max-width: 720px) {
  .site-nav .hide-sm { display: none; }
}

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

.hero {
  padding-block: clamp(56px, 10vw, 108px) clamp(48px, 7vw, 84px);
}

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(32px, 6vw, 72px);
  align-items: center;
}

.hero h1 {
  margin-top: 18px;
  font-size: clamp(40px, 7.2vw, 72px);
}

.hero .lede {
  margin-top: 22px;
  max-width: 30ch;
  font-size: clamp(17px, 1.7vw, 20px);
  line-height: 1.5;
  color: var(--muted);
}

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

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 22px;
  border-radius: 999px;
  border: 1px solid var(--rim);
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  transition: transform 0.16s ease, border-color 0.18s ease,
    background 0.18s ease, color 0.18s ease;
}
.btn:hover { transform: translateY(-1px); }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #17110c;
}
.btn-primary:hover { background: #ff9a63; border-color: #ff9a63; }

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

@media (prefers-reduced-motion: reduce) {
  .btn:hover { transform: none; }
}

/* the six-icon specimen grid, same rules as the profile banners */
.rack {
  --icon: clamp(72px, 9.5vw, 108px);
  --gutter: clamp(16px, 2.2vw, 26px);
}

.grid6 {
  display: grid;
  grid-template-columns: repeat(3, var(--icon));
  gap: var(--gutter);
}

.rack .caption {
  margin-top: 18px;
  text-align: center;
  font-size: 14px;
  color: var(--muted);
}
.rack .caption b { font-weight: 600; color: var(--accent); }

.slot {
  position: relative;
  width: var(--icon);
  height: var(--icon);
}
/* clip-path takes the box shadow with it, so the shadow is a blurred clipped
   copy of the same shape sitting behind the artwork */
.slot::before {
  content: "";
  position: absolute;
  inset: 0;
  clip-path: url(#squircle);
  -webkit-clip-path: url(#squircle);
  background: var(--shadowcol);
  filter: blur(12px);
  transform: translateY(7px) scale(0.965);
  opacity: 0.55;
}
/* Hairline rim, 1.5px proud so it reads as an edge. Applied to all six so the
   grid stays uniform, and because PoeticAI's cream artwork would otherwise
   float. An inset border cannot do this: clip-path cuts it back to the
   bounding rectangle. */
.slot::after {
  content: "";
  position: absolute;
  inset: -1.5px;
  clip-path: url(#squircle);
  -webkit-clip-path: url(#squircle);
  background: var(--rim);
}

.slot img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  clip-path: url(#squircle);
  -webkit-clip-path: url(#squircle);
}

@media (max-width: 860px) {
  .hero-grid { grid-template-columns: minmax(0, 1fr); }
  .hero .lede { max-width: none; }
  .rack { justify-self: start; }
}

/* ------------------------------------------------------------- section ---- */

.section { padding-block: var(--sec); }
.section + .section { padding-top: 0; }

.section-head {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding-bottom: 22px;
  margin-bottom: clamp(28px, 4vw, 44px);
  border-bottom: 1px solid var(--line);
}
.section-head h2 {
  font-size: clamp(26px, 3.4vw, 36px);
}
.section-head .note {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.06em;
  color: var(--faint);
  white-space: nowrap;
}
@media (max-width: 620px) {
  .section-head { flex-wrap: wrap; }
  .section-head .note { margin-left: 0; width: 100%; }
}

/* ---------------------------------------------------------------- work ---- */

/* Two up, not three. Each card carries a cropped phone screenshot in a reserved
   right-hand gutter, and at three columns the copy column was too narrow to
   hold an app description without turning into a ladder of two-word lines. */
.work-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(430px, 100%), 1fr));
  gap: clamp(16px, 2vw, 24px);
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 300px;
  /* the right gutter is where the screenshot lives */
  padding: 28px 168px 24px 28px;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--panel);
  overflow: hidden;
  transition: border-color 0.22s ease, background 0.22s ease,
    transform 0.22s ease;
}
.card:hover {
  border-color: var(--rim);
  background: var(--panel-hi);
  transform: translateY(-3px);
}
@media (prefers-reduced-motion: reduce) {
  .card:hover { transform: none; }
}

.card-top {
  display: flex;
  align-items: center;
  gap: 16px;
}

.card .slot { --icon: 60px; flex: 0 0 auto; }

/* The title block absorbs the leftover width so the status pill never squeezes
   the kicker onto two lines. Long titles wrap; the kicker never does. */
.card-top > :not(.slot):not(.status) { flex: 1 1 auto; min-width: 0; }

.card h3 {
  font-size: 20px;
  letter-spacing: -0.015em;
}

.card .role {
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}

.status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  align-self: flex-start;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.status[data-state="live"] { color: #6fd08c; border-color: rgba(111, 208, 140, 0.32); }
.status[data-state="beta"] { color: var(--accent); border-color: rgba(255, 138, 76, 0.34); }
.status[data-state="soon"] { color: var(--faint); }

.card p {
  margin-top: 16px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--muted);
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 16px;
  padding: 0;
  list-style: none;
}
.tags li {
  padding: 3px 9px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.05);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.card-foot {
  margin-top: auto;
  padding-top: 20px;
}

.card-links {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
.card-links a {
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  color: var(--accent);
  border-bottom: 1px solid rgba(255, 138, 76, 0.34);
  padding-bottom: 1px;
  transition: border-color 0.18s ease;
}
.card-links a:hover { border-bottom-color: var(--accent); }
.card-links .muted {
  color: var(--faint);
  border-bottom-color: transparent;
  cursor: default;
}

/* The screenshot sits in the card's right gutter and runs off the bottom edge,
   cropped by the card. Height is fixed and the image is cover-cropped from the
   top, so six screenshots of slightly different proportions still line up. */
.shot {
  position: absolute;
  right: 26px;
  bottom: -34px;
  width: 122px;
  height: 250px;
  object-fit: cover;
  object-position: top center;
  border: 1px solid var(--rim);
  border-radius: 16px;
  box-shadow: 0 22px 48px rgba(0, 0, 0, 0.6);
  transition: transform 0.32s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.card:hover .shot { transform: translateY(-10px); }
@media (prefers-reduced-motion: reduce) {
  .card:hover .shot { transform: none; }
}

/* Below the two-column breakpoint the gutter costs more than the screenshot is
   worth, so the copy takes the full card width and the shot steps aside. */
@media (max-width: 520px) {
  .card { padding-right: 28px; min-height: 0; }
  .shot { display: none; }
}

/* --------------------------------------------------------------- about ---- */

.two-col {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: start;
}
@media (max-width: 860px) {
  .two-col { grid-template-columns: minmax(0, 1fr); }
}

.prose p {
  font-size: 17px;
  line-height: 1.65;
  color: var(--muted);
}
.prose strong { color: var(--ink); font-weight: 600; }
.prose a { color: var(--accent); text-decoration-color: rgba(255, 138, 76, 0.4); }

.facts {
  display: grid;
  gap: 1px;
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
  background: var(--line);
}
.facts div {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  background: #191e25;
  font-size: 14.5px;
}
.facts dt {
  margin: 0;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  align-self: center;
}
.facts dd { margin: 0; text-align: right; color: var(--ink); }

/* ---------------------------------------------------------- experience ---- */

.timeline {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.timeline li {
  display: grid;
  grid-template-columns: 168px minmax(0, 1fr);
  gap: clamp(16px, 3vw, 36px);
  padding: 26px 0;
  border-top: 1px solid var(--line);
}
.timeline li:last-child { border-bottom: 1px solid var(--line); }

.timeline .when {
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  padding-top: 3px;
}
.timeline h3 { font-size: 18px; }
.timeline .where {
  margin-top: 3px;
  font-size: 14.5px;
  color: var(--accent);
}
.timeline p {
  margin-top: 10px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--muted);
}

@media (max-width: 640px) {
  .timeline li { grid-template-columns: minmax(0, 1fr); gap: 8px; }
}

/* ------------------------------------------------------------- writing ---- */

.links-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 14px;
}

.link-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 20px 22px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--panel);
  text-decoration: none;
  transition: border-color 0.2s ease, background 0.2s ease,
    transform 0.2s ease;
}
.link-card:hover {
  border-color: var(--rim);
  background: var(--panel-hi);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .link-card:hover { transform: none; }
}
.link-card strong {
  display: block;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.link-card .meta {
  display: block;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--faint);
}
.link-card .arrow {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: 18px;
  transition: transform 0.2s ease;
}
.link-card:hover .arrow { transform: translateX(3px); }

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

.contact-panel {
  padding: clamp(32px, 5vw, 56px);
  border: 1px solid var(--line);
  border-radius: 24px;
  background:
    radial-gradient(circle at 88% 8%, rgba(255, 107, 44, 0.12), transparent 55%),
    var(--panel);
}
.contact-panel h2 {
  margin-top: 14px;
  font-size: clamp(28px, 4.4vw, 44px);
  max-width: 18ch;
}
.contact-panel p {
  margin-top: 18px;
  max-width: 52ch;
  color: var(--muted);
}

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

.site-footer {
  margin-top: var(--sec);
  padding-block: 34px 44px;
  border-top: 1px solid var(--line);
}
.site-footer .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  color: var(--faint);
}
.site-footer a { color: var(--muted); text-decoration: none; }
.site-footer a:hover { color: var(--accent); }
.site-footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
}

/* --------------------------------------------------------------- reveal --- */

.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}

/* Nothing should stay hidden if the script never runs. */
.no-js .reveal { opacity: 1; transform: none; }

@media print {
  body::before, body::after, .site-header, .reveal { display: none; }
  body { background: #fff; color: #000; }
}
