/* ==========================================================================
   Joan Llompart - portfolio design system
   Plain CSS, no framework.
   Themes: graphite (dark, default) and paper (light).
   Copper depicts the board; verdigris is the interface.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Fonts, self-hosted (SIL Open Font License)
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 100% 125%;
  font-display: swap;
  src: url("../fonts/archivo-400-800-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 100% 125%;
  font-display: swap;
  src: url("../fonts/archivo-400-800-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

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

:root {
  /* Type */
  --font-sans: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;

  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-md: clamp(1.0625rem, 1rem + 0.45vw, 1.25rem);
  --text-lg: clamp(1.25rem, 1.1rem + 0.7vw, 1.55rem);
  --text-xl: clamp(1.7rem, 1.35rem + 1.6vw, 2.5rem);
  --text-2xl: clamp(2rem, 1.35rem + 2.8vw, 3.25rem);

  /* Space and shape */
  --container: 1120px;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 12px;

  --ease: cubic-bezier(0.22, 0.75, 0.25, 1);

  /* Graphite theme (default) */
  color-scheme: dark;
  --bg: #0d0d0f;
  --bg-2: #131316;
  --surface: #17171b;
  --surface-2: #1e1e23;
  --border: #282a2f;
  --border-2: #3f434a;
  --text: #ebe9e4;
  --text-2: #a3a7ae;
  --text-3: #838891;
  /* Copper is the board: traces, pads, vias, registration marks, the mark.
     It depicts a material, so it never moves. */
  --copper: #d08650;
  --copper-dim: rgba(208, 134, 80, 0.34);
  /* Verdigris is the interface: action, state, focus, wayfinding.
     It is what copper turns into, and it is the only colour a visitor
     has to learn, so it is spent on nothing decorative. */
  --accent: #4fb8a8;
  --accent-2: #6fcfc0;
  --accent-ink: #0b1614;
  --accent-dim: rgba(79, 184, 168, 0.38);
  --accent-glow: rgba(79, 184, 168, 0.14);
  --grid-dot: rgba(235, 233, 228, 0.075);
  /* Slate: one mid-tone that clears 3:1 on both backgrounds, so non-text
     marks can share a single value instead of a pair */
  --rule: #7d8491;
  --overlay: rgba(8, 8, 10, 0.66);
  --shadow: 0 18px 44px -16px rgba(0, 0, 0, 0.62);
  --header-bg: rgba(13, 13, 15, 0.78);
  --scroll-thumb: #3b3b44;
}

/* Paper theme. Deliberately a neutral gray-white, not a cream:
   warm off-whites in this range are indistinguishable from the
   default palette every AI tool ships. */
[data-theme="light"] {
  color-scheme: light;
  --bg: #f1f1ef;
  --bg-2: #e7e7e4;
  --surface: #fbfbfa;
  --surface-2: #ffffff;
  --border: #d8dade;
  --border-2: #b7bbc2;
  --text: #1c1c1a;
  --text-2: #4f5359;
  --text-3: #62676f;
  --copper: #9a4f24;
  --copper-dim: rgba(154, 79, 36, 0.4);
  --accent: #0c7264;
  --accent-2: #08564b;
  --accent-ink: #ffffff;
  --accent-dim: rgba(12, 114, 100, 0.42);
  --accent-glow: rgba(12, 114, 100, 0.1);
  --grid-dot: rgba(28, 28, 26, 0.09);
  --rule: #7d8491;
  --overlay: rgba(30, 30, 28, 0.5);
  --shadow: 0 18px 40px -18px rgba(30, 30, 28, 0.35);
  --header-bg: rgba(241, 241, 239, 0.84);
  --scroll-thumb: #c2c2be;
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;
  /* Reserve the scrollbar track so locking scroll for a dialog
     does not shift the page sideways */
  scrollbar-gutter: stable;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.65;
  color: var(--text);
  background-color: var(--bg);
  transition: background-color 0.35s ease, color 0.35s ease;
  overflow-x: hidden;
}

/* Perfboard: a 0.1 inch dot field, fixed behind everything. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: radial-gradient(var(--grid-dot) 1px, transparent 1.5px);
  background-size: 24px 24px;
  transition: opacity 0.35s ease;
}

/* Slim, theme-aware scrollbars */
html {
  scrollbar-color: var(--scroll-thumb) transparent;
  scrollbar-width: thin;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border-radius: 8px;
  border: 3px solid transparent;
  background-clip: content-box;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--text-3);
  border: 3px solid transparent;
  background-clip: content-box;
}

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

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

a,
button,
summary,
.case-card.is-clickable,
.feature-case.is-clickable {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* A press has to acknowledge itself on touch, where there is no hover */
a:active,
button:active {
  opacity: 0.72;
}

/* WebKit drops the list role when list-style is none.
   Lists whose item count is meaningful carry role="list" in the markup. */
ul,
ol {
  padding: 0;
  list-style: none;
}

::selection {
  background: var(--accent);
  color: var(--accent-ink);
}

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

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

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 200;
  padding: 0.6rem 1rem;
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  border-radius: var(--radius-sm);
  transform: translateY(-300%);
  transition: transform 0.25s var(--ease);
}

.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
}

/* Programmatic focus target for the skip link: no ring on the container */
main[tabindex="-1"]:focus,
main[tabindex="-1"]:focus-visible {
  outline: none;
}

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

.mono {
  font-family: var(--font-mono);
}

/* --------------------------------------------------------------------------
   3. Header
   -------------------------------------------------------------------------- */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: 4rem;
  background: var(--header-bg);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
  transition: background-color 0.35s ease, border-color 0.35s ease;
}

.site-header .container {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  gap: 0.7rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  color: var(--text);
}

.brand svg {
  width: 32px;
  height: 32px;
  flex: none;
}

.brand-name {
  font-weight: 500;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(0.25rem, 1.4vw, 1rem);
}

.site-nav a.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-2);
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius-sm);
  transition: color 0.2s ease;
}

.site-nav a.nav-link {
  position: relative;
}

.site-nav a.nav-link:hover {
  color: var(--text);
}

.site-nav a.nav-link[aria-current="true"] {
  color: var(--accent);
}

/* Current section is marked by a rule as well as by colour */
.site-nav a.nav-link[aria-current="true"]::after {
  content: "";
  position: absolute;
  left: 0.65rem;
  right: 0.65rem;
  bottom: 0.28rem;
  height: 1.5px;
  background: var(--accent);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-2);
  touch-action: manipulation;
  transition: color 0.2s ease, border-color 0.2s ease, transform 0.3s var(--ease);
}

.theme-toggle:hover {
  color: var(--accent);
  border-color: var(--border-2);
}

.theme-toggle svg {
  width: 17px;
  height: 17px;
}

.theme-toggle .icon-sun {
  display: none;
}

[data-theme="light"] .theme-toggle .icon-sun {
  display: block;
}

[data-theme="light"] .theme-toggle .icon-moon {
  display: none;
}

.nav-burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  gap: 5px;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0 0.7rem;
  touch-action: manipulation;
}

.nav-burger span {
  display: block;
  height: 2px;
  background: var(--text);
  transition: transform 0.3s var(--ease), opacity 0.2s ease;
}

.nav-open .nav-burger span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.nav-open .nav-burger span:nth-child(2) {
  opacity: 0;
}

.nav-open .nav-burger span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* --------------------------------------------------------------------------
   4. Buttons and chips
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  touch-action: manipulation;
  gap: 0.55rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: 0.06em;
  padding: 0.75rem 1.25rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.btn svg {
  width: 15px;
  height: 15px;
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
}

.btn-primary:hover {
  background: var(--accent-2);
}

.btn-ghost {
  border-color: var(--border-2);
  color: var(--text);
}

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

.btn-sm {
  min-height: 44px;
  padding: 0.5rem 0.95rem;
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.chip {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.03em;
  color: var(--text-2);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0.28rem 0.6rem;
  white-space: nowrap;
}

/* Inert labels in the About facts: flat, so they cannot be mistaken
   for the chips that do something */
.fact .chip {
  border-color: transparent;
  background: var(--bg-2);
  color: var(--text-2);
}

/* Chips rendered as buttons jump to the matching skill */
button.chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  cursor: pointer;
  color: var(--text);
  border-color: var(--accent-dim);
  touch-action: manipulation;
  transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease;
}

button.chip:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-glow);
}

/* --------------------------------------------------------------------------
   5. Sections
   -------------------------------------------------------------------------- */

.section {
  padding-block: clamp(4rem, 3rem + 5vw, 7rem);
}

/* Work carries the most content and gets the most room; contact closes tighter */
#work {
  padding-block: clamp(5rem, 4rem + 6vw, 9rem);
}

#contact {
  padding-block: clamp(4rem, 3rem + 4vw, 6rem) clamp(3rem, 2rem + 3vw, 4.5rem);
}

.section + .section {
  border-top: 1px solid var(--border);
}

.section-head {
  margin-bottom: clamp(2rem, 1.5rem + 2vw, 3rem);
  max-width: 44rem;
}

.section-head:has(.section-note) {
  margin-bottom: clamp(2.75rem, 2rem + 3vw, 4.25rem);
}

.kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1.1rem;
}

.kicker::before {
  content: "";
  width: 2rem;
  height: 1px;
  background: var(--rule);
}

h1,
h2,
h3 {
  line-height: 1.12;
  font-weight: 640;
  font-stretch: 112%;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

/* Archivo carries a width axis, so use it to separate display from component
   headings instead of setting one constant everywhere */
.hero h1 {
  font-stretch: 118%;
}

.case-card h3,
.skill-group h3,
.feature-case h3 {
  font-stretch: 100%;
}

.section-head h2 {
  font-size: var(--text-xl);
}

.section-head .section-note {
  margin-top: 1rem;
  color: var(--text-2);
  max-width: 38rem;
}

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: min(100svh, 46rem);
  display: flex;
  align-items: center;
  padding-top: 4rem;
  overflow: hidden;
}

.hero-inner {
  position: relative;
  z-index: 2;
  max-width: 42rem;
  padding-block: clamp(3.5rem, 9vh, 6rem);
}

.hero-kicker {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-2);
  margin-bottom: 1.6rem;
}

.hero h1 {
  font-size: var(--text-2xl);
  margin-bottom: 1.5rem;
}

.hero-sub {
  font-size: var(--text-md);
  color: var(--text-2);
  max-width: 34rem;
  margin-bottom: 2.4rem;
}

.hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-bottom: 3.2rem;
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.8rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-2);
}

.hero-place {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--text-2);
  padding-block: 0.8rem;
  margin-block: -0.8rem;
}

.hero-place svg {
  width: 14px;
  height: 14px;
}

.hero-meta a {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--text-2);
  /* Hit area without changing the drawn size */
  padding-block: 0.85rem;
  margin-block: -0.85rem;
  transition: color 0.2s ease;
}

.hero-meta a:hover {
  color: var(--accent);
}

.hero-meta svg {
  width: 14px;
  height: 14px;
}

/* Circuit board artwork */
/* The artwork is masked out of the text column rather than dimmed over it,
   so the traces stay crisp where they show and the copy is never on top of them */
.hero-circuit {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: min(58vw, 900px);
  z-index: 1;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(100deg, transparent 0%, rgba(0, 0, 0, 0.25) 30%, #000 62%);
  mask-image: linear-gradient(100deg, transparent 0%, rgba(0, 0, 0, 0.25) 30%, #000 62%);
}

.hero-circuit svg {
  width: 100%;
  height: 100%;
}

.hero-circuit .trace {
  fill: none;
  stroke: var(--copper);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.55;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.hero-circuit .trace-dim {
  stroke: var(--text-3);
  opacity: 0.28;
}

.hero-circuit .pad {
  fill: var(--copper);
  opacity: 0;
}

.hero-circuit .via {
  fill: var(--bg);
  stroke: var(--copper);
  stroke-width: 1.4;
  opacity: 0;
}

.hero-circuit .ic {
  fill: var(--surface-2);
  stroke: var(--border-2);
  stroke-width: 1;
  opacity: 0;
}

.hero-circuit .ic-pin {
  fill: var(--copper-dim);
  opacity: 0;
}

.hero-circuit .ic-die {
  fill: none;
  stroke: var(--copper-dim);
  stroke-width: 1;
  opacity: 0;
}

.hero-circuit .ic-dot {
  fill: var(--text-3);
  opacity: 0;
}

.hero-circuit .ic-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  fill: var(--text-3);
  opacity: 0;
}

.hero-circuit .pulse {
  /* Hidden unless offset-path is supported, otherwise the dots
     would sit motionless at the SVG origin */
  display: none;
  fill: var(--copper);
  opacity: 0;
  filter: drop-shadow(0 0 6px var(--copper));
}

@supports (offset-path: path("M0 0 H10")) {
  .hero-circuit .pulse {
    display: block;
  }
}

/* Draw-in animation, staggered per trace via --d custom property */
.hero-loaded .hero-circuit .trace {
  animation: trace-draw 1.6s var(--ease) forwards;
  animation-delay: var(--d, 0s);
}

.hero-loaded .hero-circuit .pad,
.hero-loaded .hero-circuit .via,
.hero-loaded .hero-circuit .ic,
.hero-loaded .hero-circuit .ic-pin,
.hero-loaded .hero-circuit .ic-die,
.hero-loaded .hero-circuit .ic-dot,
.hero-loaded .hero-circuit .ic-label {
  animation: node-in 0.6s ease forwards;
  animation-delay: var(--d, 1.2s);
}

.hero-loaded .hero-circuit .pulse {
  animation: pulse-run var(--pt, 7s) linear infinite;
  animation-delay: var(--d, 2.2s);
}

@keyframes trace-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes node-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes pulse-run {
  0% {
    opacity: 0;
    offset-distance: 0%;
  }
  6% {
    opacity: 0.9;
  }
  46% {
    opacity: 0.9;
  }
  52% {
    opacity: 0;
    offset-distance: 100%;
  }
  100% {
    opacity: 0;
    offset-distance: 100%;
  }
}

/* --------------------------------------------------------------------------
   7. Work
   -------------------------------------------------------------------------- */

.photo-frame {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-2);
}

.photo-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.9s var(--ease);
}

.case-card:hover .photo-frame img,
.feature-case:hover .photo-frame img {
  transform: scale(1.045);
}

.corner-marks::before,
.corner-marks::after {
  content: "";
  position: absolute;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--copper);
  z-index: 3;
  pointer-events: none;
}

.corner-marks::before {
  top: -1.5px;
  left: -1.5px;
  border-right: 0;
  border-bottom: 0;
  border-top-left-radius: var(--radius);
}

.corner-marks::after {
  bottom: -1.5px;
  right: -1.5px;
  border-left: 0;
  border-top: 0;
  border-bottom-right-radius: var(--radius);
}

.feature-case {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(2rem, 4vw, 4rem);
  align-items: center;
  margin-bottom: clamp(3rem, 3vw, 4.5rem);
}

.feature-case .photo-frame {
  aspect-ratio: 4 / 4.75;
}

.case-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 1rem;
}

.case-meta .tag {
  color: var(--accent);
}

.case-meta .rule {
  flex: 0 0 1.6rem;
  height: 1px;
  background: var(--rule);
}

.feature-case h3 {
  font-size: var(--text-lg);
  margin-bottom: 1rem;
}

.feature-case .case-desc {
  color: var(--text-2);
  margin-bottom: 1.6rem;
}

.case-actions {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  margin-top: 1.6rem;
}

.text-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  color: var(--accent);
  /* Hit area without changing the drawn size */
  padding-block: 0.75rem;
  margin-block: -0.75rem;
  transition: color 0.2s ease;
}

.text-link svg {
  width: 15px;
  height: 15px;
  transition: transform 0.25s var(--ease);
}

.text-link:hover svg:not([style]) {
  transform: translateX(4px);
}

.case-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1.4rem, 2.4vw, 2.2rem);
}

.case-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
  transition: border-color 0.25s ease, background-color 0.25s ease;
}

.case-card:hover,
.case-card:focus-within {
  border-color: var(--accent-dim);
  background: var(--surface-2);
}

/* The whole card opens its project. The pointer cursor is only offered over
   the parts that look like a target, so body copy does not claim to be a button. */
.case-card.is-clickable .photo-frame,
.case-card.is-clickable h3,
.feature-case.is-clickable .photo-frame,
.feature-case.is-clickable h3 {
  cursor: pointer;
}

.case-card .photo-frame {
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  aspect-ratio: 16 / 10;
}

.case-card .case-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 1.5rem 1.6rem 1.6rem;
}

.case-card h3 {
  font-size: var(--text-md);
  margin-bottom: 0.6rem;
}

.case-card .case-desc {
  color: var(--text-2);
  font-size: var(--text-sm);
  line-height: 1.6;
  margin-bottom: 1.2rem;
}

.case-card .chip-row {
  margin-top: auto;
  padding-top: 0.2rem;
  margin-bottom: 1.2rem;
}

.case-card .case-meta {
  margin-bottom: 0.85rem;
}

.case-open {
  align-self: flex-start;
}

/* Text card for the write-up */
.case-card--text {
  grid-column: 1 / -1;
  position: relative;
  padding: clamp(1.8rem, 3vw, 2.6rem);
  background: var(--surface);
}

.case-card--text .case-body {
  padding: 0;
  max-width: 46rem;
}

.case-card--text h3 {
  font-size: var(--text-lg);
}

.case-card--text .case-desc {
  font-size: var(--text-base);
}

/* --------------------------------------------------------------------------
   8. Project dialogs
   -------------------------------------------------------------------------- */

dialog.project-modal {
  /* The global margin reset removes the UA's auto-centering, so restore it */
  margin: auto;
  width: min(920px, calc(100vw - 2rem));
  max-height: min(88svh, 60rem);
  /* The dialog is the only scroll container. Nesting a second one inside it
     produced two scrollbars and clipped anything drawn at its edge. */
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--bg-2);
  color: var(--text);
  padding: 0;
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(12px);
  transition: transform 0.3s var(--ease), opacity 0.3s ease,
    overlay 0.3s allow-discrete, display 0.3s allow-discrete;
}

dialog.project-modal[open] {
  opacity: 1;
  transform: none;
}

@starting-style {
  dialog.project-modal[open] {
    opacity: 0;
    transform: translateY(12px);
  }
}

dialog.project-modal[data-drag] {
  /* No easing while the finger is down */
  transition: none;
}

/* Do not re-sample a full-viewport blur on every frame of a drag */
dialog.project-modal[data-drag]::backdrop {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

dialog.project-modal::backdrop {
  background: var(--overlay);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.modal-shell {
  display: flex;
  flex-direction: column;
}

/* Stays put while the body scrolls, so the close button is always reachable */
.modal-top {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem 1.4rem;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
}

.modal-top .case-meta {
  margin: 0;
}

.modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-2);
  touch-action: manipulation;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.modal-close:hover {
  color: var(--accent);
  border-color: var(--accent-dim);
}

.modal-close svg {
  width: 15px;
  height: 15px;
}

.modal-scroll {
  padding: 1.6rem 1.4rem 2rem;
}

@media (min-width: 700px) {
  .modal-scroll {
    padding: 2rem 2.4rem 2.6rem;
  }
}

.modal-gallery-main {
  position: relative;
  touch-action: pan-y pinch-zoom;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--bg);
  overflow: hidden;
  /* Most of these photos are portrait, so cap the mat rather than
     letterboxing them across the full width of the dialog */
  max-width: min(100%, 34rem);
  margin: 0 auto 0.8rem;
}

.modal-gallery-main img {
  -webkit-touch-callout: none;
  width: 100%;
  height: min(50vh, 440px);
  height: min(50svh, 440px);
  object-fit: contain;
  padding: 0.9rem;
}

.modal-gallery-main.has-multi img[data-gallery-main] {
  cursor: pointer;
}

/* Gallery arrows, injected only when a project has several photos */
.gal-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--border-2);
  border-radius: 50%;
  background: var(--bg-2);
  color: var(--text-2);
  opacity: 0.9;
  touch-action: manipulation;
  transition: color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
}

.gal-nav:hover,
.gal-nav:focus-visible {
  color: var(--accent);
  border-color: var(--accent-dim);
  opacity: 1;
}

.gal-nav.prev {
  left: 0.7rem;
}

.gal-nav.next {
  right: 0.7rem;
}

.gal-nav svg {
  width: 17px;
  height: 17px;
}

.modal-thumbs {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 1.8rem;
}

.modal-thumbs button {
  width: 64px;
  height: 46px;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  padding: 0;
  opacity: 0.65;
  touch-action: manipulation;
  transition: opacity 0.2s ease, border-color 0.2s ease;
}

.modal-thumbs button img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.modal-thumbs button:hover,
.modal-thumbs button:focus-visible {
  opacity: 1;
}

.modal-thumbs button[aria-current="true"] {
  border-color: var(--accent);
  opacity: 1;
  box-shadow: 0 0 0 2px var(--accent);
}

.modal-title {
  font-size: var(--text-xl);
  margin-bottom: 1rem;
}

.modal-body p {
  color: var(--text-2);
  max-width: 60ch;
}

.modal-body p + p {
  margin-top: 0.9rem;
}

.spec-table {
  width: 100%;
  margin-top: 1.8rem;
  border-top: 1px solid var(--border);
  font-size: var(--text-sm);
}

.spec-table th,
.spec-table td {
  text-align: left;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.spec-table th {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
  width: 34%;
  padding-right: 1.2rem;
}

.spec-table td {
  color: var(--text-2);
}

/* --------------------------------------------------------------------------
   9. About
   -------------------------------------------------------------------------- */

.about-grid {
  display: grid;
  grid-template-columns: 0.8fr 1.2fr;
  gap: clamp(2.5rem, 5vw, 5rem);
  align-items: start;
}

.about-portrait {
  position: sticky;
  top: 6rem;
}

.about-portrait .photo-frame {
  aspect-ratio: 4 / 4.75;
}

.portrait-caption {
  margin-top: 0.9rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-3);
}

.about-copy p {
  color: var(--text-2);
  font-size: var(--text-md);
  max-width: 58ch;
}

.about-copy p + p {
  margin-top: 1.2rem;
}

.about-copy strong {
  color: var(--text);
  font-weight: 560;
}

.about-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: 1.8rem 2.4rem;
  margin-top: 2.6rem;
  padding-top: 2rem;
  border-top: 1px solid var(--border);
}

.fact h3 {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  font-stretch: 100%;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 0.75rem;
}

.fact ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

/* --------------------------------------------------------------------------
   10. Skills
   -------------------------------------------------------------------------- */

.skills-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.4rem, 2.4vw, 2.2rem);
}

.skill-group {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: 1.5rem 1.5rem 1.7rem;
  transition: border-color 0.25s ease;
}

.skill-group:hover {
  border-color: var(--border-2);
}

.skill-group header {
  margin-bottom: 1.1rem;
}

.skill-group h3 {
  font-size: var(--text-base);
  font-weight: 600;
}

.skill-group li {
  position: relative;
  padding: 0.42rem 0 0.42rem 1.1rem;
  color: var(--text-2);
  font-size: var(--text-sm);
}

.skill-group li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.98em;
  width: 5px;
  height: 5px;
  background: var(--copper-dim);
  border-radius: 1px;
  transform: rotate(45deg);
}

/* Brief highlight when a project chip points here. Outline only: it follows
   the element's own radius and cannot clobber the card's background. */
.skill-flash {
  animation: skill-flash 1.4s ease-out;
}

@keyframes skill-flash {
  0% {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }
  100% {
    outline: 2px solid transparent;
    outline-offset: 3px;
  }
}

/* --------------------------------------------------------------------------
   11. Contact and footer
   -------------------------------------------------------------------------- */

.contact-wrap {
  max-width: 44rem;
}

.contact-lede {
  font-size: var(--text-md);
  color: var(--text-2);
  max-width: 42rem;
  margin-bottom: 2.6rem;
}

.contact-email {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-block: 0.3rem 0.5rem;
  font-family: var(--font-mono);
  font-size: clamp(1.15rem, 0.8rem + 2vw, 1.9rem);
  color: var(--text);
  border-bottom: 1.5px solid var(--accent-dim);
  padding-bottom: 0.35rem;
  transition: color 0.2s ease, border-color 0.2s ease, text-shadow 0.25s ease;
  overflow-wrap: anywhere;
}

.contact-email:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.contact-rows {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2.4rem;
  margin-top: 2.6rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-2);
}

.contact-rows a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* Hit area without changing the drawn size */
  padding-block: 0.85rem;
  margin-block: -0.85rem;
  transition: color 0.2s ease;
}

.contact-rows a:hover {
  color: var(--accent);
}

.contact-rows svg {
  width: 15px;
  height: 15px;
}

.site-footer {
  border-top: 1px solid var(--border);
  padding: 2.2rem 0 2.6rem;
}

.site-footer .container {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 2rem;
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

.footer-brand svg {
  width: 26px;
  height: 26px;
}

.footer-note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-3);
}

.footer-note a {
  display: inline-block;
  color: var(--text-2);
  border-bottom: 1px solid var(--border-2);
  /* Hit area without changing the drawn size */
  padding-block: 0.85rem;
  margin-block: -0.85rem;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.footer-note a:hover {
  color: var(--accent);
  border-color: var(--accent-dim);
}

/* --------------------------------------------------------------------------
   11b. Long-form article pages
   -------------------------------------------------------------------------- */

.article-main {
  padding-top: 4rem;
}

.article-hero {
  padding: clamp(3.5rem, 8vh, 6rem) 0 clamp(2rem, 4vh, 3rem);
  border-bottom: 1px solid var(--border);
}

.article-hero .container {
  max-width: 52rem;
}

.article-hero h1 {
  font-size: var(--text-xl);
  margin-bottom: 1.4rem;
}

.article-byline {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-3);
}

.article-byline strong {
  color: var(--text-2);
  font-weight: 500;
}

.article-summary {
  margin-top: 1.2rem;
  color: var(--text-2);
  max-width: 44rem;
}

.article-body {
  padding: clamp(2.5rem, 6vh, 4rem) 0 clamp(4rem, 3rem + 5vw, 7rem);
}

.article-body .container {
  max-width: 46rem;
}

.article-body h2 {
  font-size: var(--text-lg);
  margin: 2.6rem 0 1rem;
}

.article-body p {
  color: var(--text-2);
  margin-bottom: 1rem;
}

.article-body strong {
  color: var(--text);
  font-weight: 560;
}

.article-body ul {
  margin: 1rem 0 1.4rem;
}

.article-body li {
  position: relative;
  padding: 0.35rem 0 0.35rem 1.4rem;
  color: var(--text-2);
}

.article-body li::before {
  content: "";
  position: absolute;
  left: 0.15rem;
  top: 0.98em;
  width: 6px;
  height: 6px;
  background: var(--copper-dim);
  border-radius: 1px;
  transform: rotate(45deg);
}

.article-body code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 0.1em 0.4em;
}

.article-body blockquote {
  margin: 1.6rem 0;
  padding: 1.1rem 1.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.article-body blockquote p {
  margin: 0;
  font-size: var(--text-sm);
}

.article-back {
  margin-top: 3rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--border);
}

/* --------------------------------------------------------------------------
   12. Reveal on scroll
   -------------------------------------------------------------------------- */

/* Content is visible by default. The observer adds .js-reveal to <html>
   before it arms anything, so a JS failure can never leave the page blank. */
.reveal {
  transition: opacity 0.7s ease, transform 0.7s var(--ease);
  transition-delay: calc(var(--stagger, 0) * 90ms);
}

.js-reveal .reveal {
  opacity: 0;
  transform: translateY(18px);
}

.js-reveal .reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------
   13. Responsive
   -------------------------------------------------------------------------- */

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

  .hero-circuit {
    width: 72vw;
  }
}

@media (max-width: 820px) {
  /* Off-screen when closed, and taken out of the tab order with it.
     The visibility flip is delayed on close so the slide-out is still seen. */
  .site-nav {
    position: fixed;
    top: 4rem;
    right: 0;
    left: 0;
    z-index: 99;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.8rem var(--gutter) 1.4rem;
    background: var(--header-bg);
    -webkit-backdrop-filter: blur(18px);
    backdrop-filter: blur(18px);
    border-bottom: 1px solid var(--border);
    transform: translateY(-130%);
    visibility: hidden;
    transition: transform 0.35s var(--ease), visibility 0s linear 0.35s;
  }

  .nav-open .site-nav {
    transform: translateY(0);
    visibility: visible;
    transition: transform 0.35s var(--ease), visibility 0s;
  }

  .site-nav a.nav-link {
    display: flex;
    align-items: center;
    min-height: 48px;
    padding: 0.7rem 0.4rem;
    font-size: var(--text-sm);
    border-bottom: 1px solid var(--border);
  }

  .site-nav a.nav-link[aria-current="true"]::after {
    left: 0.4rem;
    right: auto;
    width: 1.4rem;
    bottom: 0.55rem;
  }

  .nav-burger {
    display: inline-flex;
  }

  .feature-case {
    grid-template-columns: 1fr;
  }

  .feature-case .photo-frame {
    aspect-ratio: 16 / 11;
  }

  .case-grid {
    grid-template-columns: 1fr;
  }

  .about-grid {
    grid-template-columns: 1fr;
  }

  .about-portrait {
    position: static;
    max-width: 24rem;
  }

  /* On one column there is no side to hide in, so the artwork becomes a
     top-right corner motif that fades out well above the headline */
  .hero-circuit {
    width: min(125vw, 700px);
    height: auto;
    aspect-ratio: 9 / 8;
    right: -14vw;
    -webkit-mask-image: radial-gradient(105% 92% at 100% 0%, #000 18%, rgba(0, 0, 0, 0.28) 44%, transparent 64%);
    mask-image: radial-gradient(105% 92% at 100% 0%, #000 18%, rgba(0, 0, 0, 0.28) 44%, transparent 64%);
  }

  .hero-inner {
    max-width: 100%;
    padding-block: clamp(5.5rem, 17vh, 8rem) clamp(2.5rem, 6vh, 4rem);
  }

  .hero-meta {
    gap: 0.9rem 1.8rem;
  }

  .contact-rows {
    gap: 1rem 2.4rem;
  }
}

@media (max-width: 560px) {
  .skills-grid {
    grid-template-columns: 1fr;
  }

  .spec-table th,
  .spec-table td {
    display: block;
    width: 100%;
  }

  .spec-table th {
    border-bottom: 0;
    padding-bottom: 0.1rem;
  }

  .spec-table td {
    padding-top: 0.1rem;
  }

  .about-facts {
    grid-template-columns: 1fr;
  }

  .brand-name {
    display: none;
  }

  .hero {
    min-height: auto;
  }

  .modal-thumbs button {
    width: 56px;
    height: 46px;
  }
}

/* Hover states latch on touch and stay stuck until the next tap elsewhere,
   so the movement-based ones are switched off where there is no pointer. */
@media (hover: none) {
  .case-card:hover .photo-frame img,
  .feature-case:hover .photo-frame img {
    transform: none;
  }

  .text-link:hover svg:not([style]) {
    transform: none;
  }

  .modal-thumbs button:hover {
    opacity: 0.65;
  }

  .gal-nav:hover {
    color: var(--text-2);
    border-color: var(--border-2);
  }
}

/* Landscape phones: the fixed chrome cannot eat half of a 390px viewport */
@media (max-height: 560px) and (orientation: landscape) {
  .site-header {
    height: 3.25rem;
  }

  .site-nav {
    top: 3.25rem;
  }

  .hero {
    min-height: 0;
    padding-top: 3.25rem;
  }

  .hero-inner {
    padding-block: clamp(2rem, 8vh, 3rem);
  }

  .hero-circuit {
    aspect-ratio: auto;
    height: 100%;
  }

  dialog.project-modal {
    max-height: calc(100svh - 1.5rem);
  }

  .modal-gallery-main img {
    height: min(60svh, 300px);
  }
}

/* --------------------------------------------------------------------------
   14. Reduced motion
   -------------------------------------------------------------------------- */

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

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .hero-circuit .trace {
    stroke-dashoffset: 0;
  }

  .hero-circuit .pad,
  .hero-circuit .via,
  .hero-circuit .ic,
  .hero-circuit .ic-pin,
  .hero-circuit .ic-die,
  .hero-circuit .ic-dot,
  .hero-circuit .ic-label {
    opacity: 1;
  }

  .hero-circuit .pulse {
    display: none;
  }

  /* Zeroing the duration turns an ease into a jump-cut, so suppress the
     movement itself rather than just its timing */
  .case-card:hover,
  .case-card:focus-within,
  .case-card:hover .photo-frame img,
  .feature-case:hover .photo-frame img,
  .text-link:hover svg:not([style]) {
    transform: none;
  }

  dialog.project-modal {
    opacity: 1;
    transform: none;
  }

  .js-reveal .reveal {
    opacity: 1;
    transform: none;
  }
}
