/* ---------------------------------------------------------------------------
   calebacopeland.com — a web rendering of the 2026 résumé.
   Syntax-token palette is the logo palette; type is Fira Code with Montserrat
   for display. No framework, no build step.
   --------------------------------------------------------------------------- */

@font-face {
  font-family: 'Fira Code';
  src: url('/assets/fonts/fira-code.woff2') format('woff2');
  font-weight: 300 600;
  font-style: normal;
  font-display: swap;
}

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

/* --- tokens: light is the base, dark overrides below ---------------------- */

:root {
  --kw:      #e14a22;   /* keywords: var, const, if */
  --ident:   #ff775a;   /* identifier names */
  --key:     #6c5ce7;   /* object keys, company names */
  --val:     #00947a;   /* string values */
  --comment: #8b8b8b;
  --punct:   #111111;

  --bg:      #ffffff;
  --panel:   #f3f3f3;
  --line:    #e2e2e2;
  --text:    #1a1a1a;
  --muted:   #666666;

  --chrome-bar: #e2e2e2;
  --chrome-url: #ffffff;

  --radius: 10px;
  /* minimum space between content and the viewport edge, each side */
  --gutter: 1.25rem;
  /* --gap separates whole sections; --block-gap separates the code blocks and
     cards inside one. Keep --gap at roughly 2.5x --block-gap: below about 2x a
     section break stops reading as one, which is what 40px against 24px did on
     a phone. 64px -> 80px against 24px -> 34px. */
  --gap: clamp(4rem, 2rem + 3.5vw, 5rem);
  --block-gap: clamp(1.5rem, 2.5vw, 2.25rem);
  --measure: 83rem;
  /* the grids (résumé columns, portfolio) get the full measure; the hero and
     the contact block are two items side by side and look stranded in it */
  --measure-narrow: 66rem;

  --mono: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  --display: 'Montserrat', ui-sans-serif, system-ui, sans-serif;

  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --kw:      #ff6b45;
  --ident:   #ff9478;
  --key:     #9b8cff;
  --val:     #2fd4ab;
  --comment: #6f7380;
  --punct:   #c9ccd6;

  --bg:      #14151b;
  --panel:   #1b1d25;
  --line:    #2a2d38;
  --text:    #e4e6ec;
  --muted:   #9aa0ae;

  --chrome-bar: #2a2d38;
  --chrome-url: #14151b;
}

/* --- base ----------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: clamp(0.9375rem, 0.86rem + 0.32vw, 1.125rem);   /* 15px -> 18px */
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

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

a { color: inherit; }

.wrap {
  width: min(100% - var(--gutter) * 2, var(--measure));
  margin-inline: auto;
}

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

.skip {
  position: absolute; left: var(--gutter); top: -4rem;
  padding: 0.6rem 1rem; z-index: 10;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  transition: top 0.15s ease;
}
.skip:focus { top: var(--gutter); }

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

/* --- syntax tokens -------------------------------------------------------- */

.c  { color: var(--comment); }
.kw { color: var(--kw); }
.id { color: var(--ident); }
.k  { color: var(--key); }
.v  { color: var(--val); }
.fn { color: var(--ident); }
.p  { color: var(--punct); }

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

.hero {
  padding-block: clamp(1.5rem, 4vw, 2.5rem) var(--gap);
  border-bottom: 1px solid var(--line);
}

/* The narrow blocks are capped INSIDE a full-measure .wrap rather than by
   narrowing the wrap itself — a block child with a max-width left-aligns, so
   their left edge stays flush with the résumé columns instead of centring. */
.hero-top,
.hero-grid,
.cta-grid { max-width: var(--measure-narrow); }

.hero-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: clamp(2.5rem, 7vw, 4.5rem);
}

.mark { width: 56px; height: auto; }

.theme {
  font: inherit;
  font-size: 0.8em;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.3em 0.9em;
  cursor: pointer;
}
.theme:hover { color: var(--text); border-color: var(--muted); }

/* The label names the theme you would switch TO. Light is the default, so the
   page starts by offering "dark"; the OS preference is deliberately ignored. */
.theme-off { display: none; }
:root[data-theme="dark"] .theme-on { display: none; }
:root[data-theme="dark"] .theme-off { display: inline; }

/* Plain flex-wrap, no breakpoint: the portrait drops below the text the moment
   the two bases stop fitting. A grid with a media query had the column widths
   right but the h1 overflowed its track and ran under the photo. */
.hero-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(2rem, 5vw, 4rem);
}

/* min-width:0 on every flex item that holds code. A flex item defaults to
   min-width:auto, which resolves to its min-content width and refuses to shrink
   past it — .code used to escape that by being a scroll container, and dropping
   overflow-x took the escape away. Without this the contact block's email string
   pushes the page 46px wide at 390px. */
.hero-text { flex: 1 1 28rem; min-width: 0; }

/* The résumé's portrait treatment — brand-purple keyline over a mat — redrawn
   here rather than used as baked into the source PNG, whose mat is opaque white
   and would show as four white corners in dark mode. The image is cropped to the
   photo alone; see README. */
.portrait {
  margin: 0;
  flex: 0 1 18rem;
}

.portrait img {
  width: 100%;
  height: auto;
  /* The source is the full 2:3 photo; the 4:5 window and where the head sits in
     it are set here, so reframing is a CSS edit rather than a re-encode. */
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center 15%;
  padding: 6px;
  background: var(--bg);
  border: 3px solid var(--key);
  border-radius: 12px;
}

.eyebrow {
  margin: 0 0 0.75rem;
  font-size: 0.9em;
}

h1 {
  font-family: var(--display);
  font-weight: 200;
  font-size: clamp(2.5rem, 9vw, 5.25rem);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0;
}

.tagline {
  margin: 0.9rem 0 0;
  font-size: clamp(1rem, 0.9rem + 0.6vw, 1.3rem);
}

.lede {
  margin: 1.75rem 0 0;
  max-width: 52ch;
  color: var(--muted);
}

/* --- buttons -------------------------------------------------------------- */

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2.25rem;
}

.btn {
  display: inline-block;
  padding: 0.7em 1.3em;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none;
  font-size: 0.9em;
  color: var(--text);
  background: var(--bg);
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.btn:hover { border-color: var(--key); color: var(--key); }

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

/* --- code blocks ---------------------------------------------------------- */

/* Résumé blocks flow down one column then across — no masonry support needed.
   workHistory is last in the DOM because it is about as tall as the other four
   together, so the balancer puts it alone in the second column. */
.code-columns {
  margin-block: var(--gap);
  column-gap: clamp(1.5rem, 3vw, 2.5rem);
}

/* Two columns from 1024px. At that width each column is only ~472px, which 53
   characters will not fit at the single-column type size — so the columns carry
   their own smaller clamp rather than overflowing their boxes. The base clamp
   below still governs the one-column range, where there is room for more. */
@media (min-width: 64rem) {
  .code-columns { columns: 2; }
}

.block { margin-block: var(--block-gap); break-inside: avoid; }
.code-columns > .block:first-child { margin-top: 0; }

.code {
  margin: 0;
  padding: clamp(1.1rem, 3vw, 1.75rem);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* Code reads at the body size. Line structure comes from .ln blocks, not from
     newlines and leading spaces — soft-wrapping hand-aligned text dropped every
     continuation to column 0 and the block zigzagged. white-space is normal so
     the source can be indented for legibility without that indentation
     rendering. */
  font-size: inherit;
  line-height: 1.6;
  white-space: normal;
  text-wrap: pretty;
  overflow-wrap: break-word;
}

/* One logical line. The negative text-indent against the padding is the hanging
   indent: the line starts at its nesting depth and anything that wraps sits two
   characters deeper, so a wrap reads as continuation rather than as a new key. */
.ln {
  display: block;
  padding-left: 2ch;
  text-indent: -2ch;
}

.d1 { padding-left: 4ch; }
.d2 { padding-left: 6ch; }
.d3 { padding-left: 8ch; }

.blank { height: 0.7em; }

.code code { font-family: inherit; }

.code a {
  color: inherit;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 0.2em;
}
.code a:hover { text-decoration-color: currentColor; }

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

.work {
  padding-block: var(--gap);
  border-block: 1px solid var(--line);
  background: color-mix(in srgb, var(--panel) 45%, var(--bg));
}

.work h2 {
  font-family: var(--mono);
  font-weight: 400;
  font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.35rem);
  margin: 0 0 2rem;
}

.close {
  font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.35rem);
  margin: 2rem 0 0;
}

.grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* min() so the track can shrink below 19rem rather than overflow the page —
     at 320px a bare minmax(19rem, 1fr) pushed the body 4px wide */
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  gap: clamp(1.5rem, 4vw, 2.5rem);
}

.card-link {
  display: block;
  text-decoration: none;
  color: inherit;
}

/* browser chrome, redrawn from the studio's browser.svg mock */
.browser {
  display: block;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.chrome {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  height: 28px;
  padding-inline: 0.6rem;
  background: var(--chrome-bar);
}

.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.dot-r { background: #da6164; }
.dot-y { background: #fdd350; }
.dot-g { background: #a8c35a; }

.url {
  flex: 1;
  margin-left: 0.5rem;
  height: 16px;
  border-radius: 4px;
  background: var(--chrome-url);
  color: var(--muted);
  font-size: 9px;
  line-height: 16px;
  padding-inline: 0.5rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.browser img {
  width: 100%;
  /* height:auto is required — the img width/height attributes are presentational
     hints that would otherwise win over aspect-ratio */
  height: auto;
  aspect-ratio: 544 / 319;
  object-fit: cover;
  object-position: top center;
}

a.card-link:hover .browser,
a.card-link:focus-visible .browser {
  transform: translateY(-3px);
  box-shadow: 0 12px 28px -14px rgb(0 0 0 / 0.45);
}

.card-meta {
  display: block;
  margin-top: 0.9rem;
  font-size: 0.9em;
}
.card-meta .k { display: block; }
.card-meta .v { display: block; margin-top: 0.15rem; }

/* --- cta + footer --------------------------------------------------------- */

/* The closing code block is four short lines; left to the full measure it was a
   1150px panel mostly empty on the right. Pair it with the actions instead. */
.cta { margin-block: var(--gap); }

/* Same flex-wrap shape as the hero: copy and actions lead, the code block sits
   to the right and drops below once the two bases stop fitting. */
.cta-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(2rem, 5vw, 4rem);
}

/* 31 + 4 gap + 31 = 66rem, the narrow measure exactly. 31rem is what this
   block's longest line (44 characters) needs at 16px, measured not guessed. */
.cta-side { flex: 1 1 31rem; min-width: 0; }
.cta-grid .code { flex: 0 1 31rem; min-width: 0; }

.cta-lede {
  margin: 0 0 1.75rem;
  max-width: 46ch;
  color: var(--muted);
}

.cta .actions { margin-top: 0; }

footer {
  padding-block: 2.5rem 3.5rem;
  font-size: 0.85em;
}
footer p { margin: 0; }

/* --- motion --------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* --- print ---------------------------------------------------------------- */

@media print {
  .theme, .skip, .actions { display: none; }
  body { background: #fff; color: #000; }
  .code { border-color: #ccc; background: #f5f5f5; }
}
