/* Other projects page.
   Page chrome follows the site system (paper/ink/hairline, Archivo display,
   JetBrains Mono labels). The GitHub profile is fetched from the public API
   and drawn inside a framed pane in the same light palette, so the embed
   never brings a theme of its own. Radius system: containers 12px,
   controls 6px. */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/archivo-600-latin.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/jetbrains-mono-400-latin.woff2") format("woff2");
}

:root {
  --bg: #fbfbfa;
  --ink: #141414;
  --body: #4c4c47;
  --mut: #9a9a96;
  --mut-2: #6f6f6a;
  --hair: #e6e6e2;
  --hair-2: #cfcfc9;
  --paper: #ffffff;
  --tint: #f4f4f1;
  --skel: #ecece8;
  --sec: clamp(56px, 7vw, 96px);
  --measure: 56ch;
  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --disp: "Archivo", system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--disp);
  overflow-x: clip;
  -webkit-tap-highlight-color: transparent;
}

a { color: var(--ink); }

:focus-visible {
  outline: 1px dashed var(--ink);
  outline-offset: 4px;
}

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

p { margin: 0; }

.back {
  position: absolute;
  top: 24px;
  left: 28px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--ink);
  text-decoration: none;
  z-index: 3;
}
.back:hover { text-decoration: underline; }
.back svg { vertical-align: -1px; margin-right: 3px; }

/* Shared section shell */
.hero, .embed, .foot {
  max-width: 880px;
  margin: 0 auto;
  padding-left: clamp(20px, 5vw, 48px);
  padding-right: clamp(20px, 5vw, 48px);
}

/* ---------------- Hero ---------------- */

.hero { padding-top: 108px; }

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 26px;
}
.brand-icon { display: block; }
.brand-name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.brand-kind {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  color: var(--mut-2);
  margin-left: 4px;
  padding-top: 1px;
}

.hero h1 {
  font-size: clamp(42px, 7.2vw, 86px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.02;
  margin: 0 0 22px;
  text-wrap: balance;
}

.sub {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.8;
  color: var(--body);
  max-width: var(--measure);
  margin-bottom: 30px;
}

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

.btn {
  display: inline-flex;
  align-items: center;
  min-height: 42px;
  margin: 0;
  padding: 0 20px;
  border: 0;
  border-radius: 6px;
  background: none;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.18s ease, transform 0.12s ease;
}
.btn:active { transform: translateY(1px); }
.btn-ink { background: var(--ink); color: var(--bg); }
.btn-ink:hover { background: #2e2e2c; }
.btn-line { border: 1px solid var(--ink); color: var(--ink); }
.btn-line:hover { background: #f1f1ec; }

/* Staged load-in, matching the home page's quiet fades. The frame joins the
   sequence last so the pane is already in place when the profile arrives. */
.brand, .hero h1, .sub, .ctas, .frame {
  opacity: 0;
  animation: rise 0.7s var(--ease) forwards;
}
.hero h1 { animation-delay: 0.08s; }
.sub { animation-delay: 0.16s; }
.ctas { animation-delay: 0.24s; }
.frame { animation-delay: 0.34s; }
@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---------------- Framed pane ---------------- */

.embed { padding-top: var(--sec); }

.frame {
  background: var(--paper);
  border: 1px solid var(--hair);
  border-radius: 12px;
  box-shadow: 0 24px 60px -32px rgba(40, 38, 20, 0.25);
  overflow: hidden;
}

.chrome {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  background: var(--tint);
  border-bottom: 1px solid var(--hair);
}
.lights { display: flex; gap: 6px; flex-shrink: 0; }
.lights i {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #dcdcd6;
}

.url {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 14px;
  border: 1px solid var(--hair);
  border-radius: 999px;
  background: var(--paper);
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--body);
  text-decoration: none;
  transition: border-color 0.18s ease, color 0.18s ease;
}
.url:hover { border-color: var(--hair-2); color: var(--ink); }
.url-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.url svg {
  display: block;
  flex-shrink: 0;
  margin-left: auto;
  color: var(--mut);
  transition: color 0.18s ease, transform 0.22s var(--ease);
}
.url:hover svg { color: var(--ink); transform: translate(1px, -1px); }

.pane { padding: 28px 32px 30px; }
/* Hold the pane at the skeleton's exact height until the script fills it,
   so nothing below moves when the skeleton arrives. Without scripting the
   noscript copy is the content, so the hold is released. */
.pane[aria-busy="true"] { min-height: 696px; }
.pane:has(.nojs) { min-height: 0; }

.nojs {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.8;
  color: var(--body);
  max-width: var(--measure);
}
.nojs a { text-underline-offset: 3px; }

/* ---------------- Profile ---------------- */

.profile {
  display: flex;
  align-items: flex-start;
  gap: 22px;
  padding-bottom: 26px;
  border-bottom: 1px solid var(--hair);
}
.avatar {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 64px;
  height: 64px;
  border: 1px solid var(--hair);
  border-radius: 50%;
  background: var(--tint);
  overflow: hidden;
}
.avatar img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.avatar-fallback {
  font-size: 24px;
  font-weight: 600;
  color: var(--mut-2);
}
.who {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 8px;
  padding-top: 4px;
}
.name-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
}
.name {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  overflow-wrap: anywhere;
}
.login {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--mut-2);
  overflow-wrap: anywhere;
}
.bio {
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--body);
  max-width: var(--measure);
  overflow-wrap: anywhere;
}
.bio .sep { color: var(--mut); }
.bio .loc { color: var(--mut-2); white-space: nowrap; }
.facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--mut-2);
  padding-top: 2px;
}
.facts .num { color: var(--ink); font-variant-numeric: tabular-nums; }

/* ---------------- Section heads ---------------- */

.repos-head, .contrib-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.repos-head { padding: 24px 0 8px; }
.contrib-head { padding: 0 0 14px; }
.repos-head h2, .contrib-head h2 {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--mut);
  margin: 0;
}
.repos-count, .contrib-total {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--mut);
  font-variant-numeric: tabular-nums;
}
.contrib-total .num { color: var(--ink); }

/* ---------------- Contribution calendar ---------------- */
/* Drawn as SVG in GitHub's light-theme greens, since inside the frame the
   product keeps its own identity (as the language dots do). The empty
   ghost grid shows first and stays underneath while the real cells colour
   in over it, column by column. */

.contrib {
  padding: 24px 0 22px;
  border-bottom: 1px solid var(--hair);
}
.contrib-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--hair) transparent;
}
.contrib-grid { position: relative; }
.contrib-svg {
  display: block;
  position: relative;
  width: 100%;
  height: auto;
  overflow: visible;
}
.contrib-svg.under {
  position: absolute;
  top: 0;
  left: 0;
}
.contrib-label {
  font-family: var(--mono);
  font-size: 9px;
  fill: var(--mut-2);
}
.contrib-svg rect { shape-rendering: geometricPrecision; }
.contrib-svg rect:hover { stroke: rgba(20, 20, 20, 0.4); stroke-width: 1; }
.l0 { fill: #ebedf0; background: #ebedf0; }
.l1 { fill: #9be9a8; background: #9be9a8; }
.l2 { fill: #40c463; background: #40c463; }
.l3 { fill: #30a14e; background: #30a14e; }
.l4 { fill: #216e39; background: #216e39; }

.contrib-legend {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 3px;
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--mut-2);
}
.contrib-legend span { margin: 0 4px; }
.contrib-legend .cell {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}

.repos { list-style: none; margin: 0; padding: 0; }
.repo + .repo { border-top: 1px solid var(--hair); }
.repo a {
  display: block;
  padding: 16px 0 18px;
  color: inherit;
  text-decoration: none;
}
/* The list closes the pane, so the last row hands its slack to the pane
   padding and the frame ends as evenly as it opens. */
.repo:last-child a { padding-bottom: 8px; }
.r-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.r-name {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.3;
  overflow-wrap: anywhere;
  transition: letter-spacing 0.25s ease;
}
.repo a:hover .r-name { letter-spacing: 0.02em; }
.r-arrow {
  flex-shrink: 0;
  color: var(--mut);
  transition: color 0.18s ease, transform 0.22s var(--ease);
}
.r-arrow svg { display: block; }
.repo a:hover .r-arrow { color: var(--ink); transform: translate(1px, -1px); }
.r-desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  margin-top: 6px;
  max-width: 68ch;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--body);
  overflow-wrap: anywhere;
}
.r-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--mut-2);
}
.r-lang { display: inline-flex; align-items: center; gap: 7px; }
.dot {
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--c, #c4c4bf);
  box-shadow: inset 0 0 0 1px rgba(20, 20, 20, 0.12);
}
.r-stars { display: inline-flex; align-items: center; gap: 5px; }
.r-stars svg { display: block; }
.r-time { font-variant-numeric: tabular-nums; }

.repos-empty {
  padding: 16px 0 4px;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--body);
}

/* ---------------- Loading and error states ---------------- */

.sk {
  display: block;
  height: 12px;
  border-radius: 6px;
  background: var(--skel);
}
.sk-avatar {
  flex-shrink: 0;
  width: 64px;
  height: 64px;
  border-radius: 50%;
}
.skel .who { gap: 12px; padding-top: 8px; }
.sk-chart {
  width: 100%;
  height: 102px;
  margin-top: 26px;
}
.sk-head { padding: 26px 0 12px; }
.sk-row {
  display: grid;
  gap: 11px;
  padding: 18px 0;
}
.sk-row + .sk-row { border-top: 1px solid var(--hair); }
.w20 { width: 20%; }
.w25 { width: 25%; }
.w30 { width: 30%; }
.w35 { width: 35%; }
.w45 { width: 45%; }
.w55 { width: 55%; }
.w60 { width: 60%; }
.w90 { width: 90%; }
.h10 { height: 10px; }
.h16 { height: 16px; }
.h20 { height: 20px; }

.err {
  display: grid;
  justify-items: start;
  gap: 18px;
  padding: 6px 0 4px;
}
.err p {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.8;
  color: var(--body);
  max-width: var(--measure);
}
.err-actions { display: flex; gap: 12px; flex-wrap: wrap; }

/* Content arrives with a short stagger; --i is set per element by script.
   Kept inside the motion query so reduced-motion users never see a hidden
   state. */
@media (prefers-reduced-motion: no-preference) {
  .pane .in {
    opacity: 0;
    animation: rise-sm 0.55s var(--ease) forwards;
    animation-delay: calc(var(--i, 0) * 50ms);
  }
  @keyframes rise-sm {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
  }
  .skel .sk { animation: pulse 1.6s ease-in-out infinite; }
  @keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
  }
  .contrib-svg:not(.ghost) rect {
    opacity: 0;
    animation: cell-in 0.45s ease-out forwards;
    animation-delay: calc(var(--w, 0) * 7ms);
  }
  @keyframes cell-in {
    to { opacity: 1; }
  }
}

/* ---------------- Footer ---------------- */

.foot { padding-top: var(--sec); padding-bottom: 48px; }
.back-foot {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-decoration: none;
}
.back-foot:hover { text-decoration: underline; }
.back-foot svg { vertical-align: -1px; margin-right: 3px; }

/* ---------------- Responsive ---------------- */

@media (max-width: 720px) {
  .back { top: 18px; left: 18px; }
  .hero { padding-top: 92px; }
  .frame { border-radius: 10px; }
  .chrome { gap: 10px; padding: 10px 12px; }
  .pane { padding: 20px 18px 22px; }
  .pane[aria-busy="true"] { min-height: 674px; }
  .profile { gap: 16px; padding-bottom: 20px; }
  .avatar, .sk-avatar { width: 52px; height: 52px; }
  .name { font-size: 20px; }
  .repos-head { padding-top: 20px; }
  /* Narrow columns wrap descriptions onto more lines; give them room. */
  .r-desc { -webkit-line-clamp: 4; }
  /* The year keeps its natural size and scrolls sideways, newest first. */
  .contrib-grid { width: max-content; }
  .contrib-svg { width: auto; }
}

@media (max-width: 380px) {
  .ctas .btn, .err-actions .btn { flex: 1 1 100%; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .brand, .hero h1, .sub, .ctas, .frame { animation: none; opacity: 1; }
  .btn, .url, .url svg, .r-name, .r-arrow, .contrib-svg rect {
    transition: none !important;
    animation: none !important;
  }
}
