:root {
  --text: #37352f;
  --text-light: #6b6b6b;
  --bg: #ffffff;
  --border: rgba(55, 53, 47, 0.09);
  --hover: rgba(55, 53, 47, 0.04);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Pretendard", -apple-system, "Segoe UI", "Apple SD Gothic Neo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  line-height: 1.6;
}

.hero {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  /* Height tracks viewport width (not height) and is capped, so the box's
     aspect ratio stays bounded on any screen: unbounded vw would otherwise
     shrink a vh-based height into a sliver too thin to hold a face. */
  height: clamp(180px, 26vw, 300px);
  background-size: cover;
  /* Y=23% puts the subject's hair top (at 21.3% of the source image) about
     18% of the way down the visible band on every size, instead of flush
     against its top edge. X is a no-op here: the box is always wider than
     the image's 3:2 ratio, so cover scales to width and never overflows
     horizontally. */
  background-position: 50% 23%;
  background-color: #ddd;
}

.page {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 clamp(20px, 6vw, 96px) 12vh;
}

.icon {
  font-size: clamp(52px, 9vw, 78px);
  line-height: 1;
  margin-top: calc(clamp(52px, 9vw, 78px) / -2);
  margin-bottom: 8px;
  background: var(--bg);
  width: fit-content;
  padding-right: 10px;
  border-radius: 12px;
}

.role-line {
  font-size: 16px;
  color: var(--text-light);
  font-weight: 500;
  margin: 0 0 16px;
}

.name-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.name-row h1 {
  margin: 0 0 16px;
}

.lang-switch {
  position: relative;
  display: inline-block;
  width: 68px;
  height: 30px;
  flex-shrink: 0;
}

.lang-switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
  z-index: 1;
}

/* "Regular Glass" per liquid-glass.ybouane.com: near-transparent, minimal
   blur (light bends through it rather than frosting it opaque), a bright
   thin rim doing the edge-highlight work, and a soft cast shadow for depth
   — rather than the milkier frosted-glass look this had before. */
.lang-switch-track {
  position: absolute;
  inset: 0;
  background: #d8d6d0;
  border-radius: 999px;
  transition: background 0.2s ease;
}

.lang-switch input:checked ~ .lang-switch-track {
  background: #0071e3;
}

.lang-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform 0.2s ease;
}

.lang-switch input:checked ~ .lang-switch-track .lang-switch-thumb {
  transform: translateX(38px);
}

.lang-switch-word {
  position: absolute;
  top: 50%;
  right: 9px;
  transform: translateY(-50%);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text-light);
  transition: color 0.2s ease;
}

.lang-switch input:checked ~ .lang-switch-track .lang-switch-word {
  right: auto;
  left: 9px;
  color: #fff;
}

.lang-switch input:focus-visible ~ .lang-switch-track {
  outline: 2px solid #0071e3;
  outline-offset: 2px;
}

/* Needs .icon-link in the selector: the base .icon-link rule sets a fixed
   38px width later in this file, so a bare .resume-link would lose the
   cascade and clip the label out of the box. */
.icon-link.resume-link {
  width: auto;
  gap: 6px;
  padding: 0 12px;
}

.icon-link.resume-link svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.resume-label {
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}

.link-row {
  display: flex;
  gap: 10px;
  margin: 0 0 24px;
  flex-wrap: wrap;
}

.icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  text-decoration: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.icon-link:hover {
  background: var(--hover);
  border-color: rgba(55, 53, 47, 0.2);
}

.icon-link svg {
  width: 21px;
  height: 21px;
  fill: currentColor;
  display: block;
}

.keyword-block {
  margin-bottom: 4px;
}

.kw-heading {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-light);
  margin: 0 0 6px;
}

.kw-heading + .kw-tags {
  margin-bottom: 20px;
}

.kw-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
}

.kw-tag {
  display: inline-block;
  background: #0071e3;
  color: #fff;
  border-radius: 980px;
  padding: 5px 14px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.3;
  transition: background 0.15s ease;
}

.kw-tag:hover {
  background: #0077ed;
}

/* Stat row: the "by the numbers" strip right under the code block */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px;
  margin: 0 0 20px;
}

.stat-card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  background: #fafaf9;
}

.stat-num {
  font-size: 24px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.1;
}

.stat-label {
  font-size: 12px;
  color: var(--text-light);
  font-weight: 500;
}

.news-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.news-list li {
  display: grid;
  grid-template-columns: 28px 104px 1fr;
  align-items: start;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.news-list li:last-child {
  border-bottom: none;
}

.news-icon {
  font-size: 16px;
  line-height: 1.4;
}

.news-date {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text-light);
  padding-top: 3px;
  white-space: nowrap;
}

.news-body {
  font-size: 15px;
}

.news-body strong {
  display: block;
  font-weight: 600;
  margin-bottom: 2px;
}

.code-block {
  background: #f7f6f3;
  border-radius: 6px;
  padding: 16px 20px;
  font-family: "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.7;
  overflow-x: auto;
  margin: 12px 0 16px;
  color: var(--text);
}

.code-block .kw { color: #9b51e0; }
.code-block .str { color: #0f7b6c; }

.two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}

/* Research and Development sit side by side, but only once each side gets a
   readable measure. These two carry the densest prose on the page, so they
   stay stacked below 1100px rather than squeezing into ~365px columns on a
   tablet in portrait. */
.research-dev {
  grid-template-columns: 1fr;
  gap: 32px;
  align-items: start;
}

@media (min-width: 1100px) {
  .research-dev {
    grid-template-columns: 1fr 1fr;
    gap: 48px;
  }
}

.hint {
  font-size: 13px;
  color: var(--text-light);
  margin-top: -6px;
}

h1 {
  font-size: clamp(28px, 5.5vw, 40px);
  font-weight: 700;
  margin: 0 0 16px;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

h2 {
  font-size: clamp(19px, 2.6vw, 22px);
  font-weight: 600;
  margin: 0 0 12px;
}

.lede {
  font-size: clamp(19px, 2.4vw, 21px);
  line-height: 1.65;
}

h3 {
  font-size: 16px;
  font-weight: 600;
  margin: 0 0 4px;
}

p {
  margin: 0 0 8px;
  font-size: 18px;
  color: var(--text);
}

hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 28px 0;
}

section {
  margin-bottom: 4px;
}

.intro p {
  margin-bottom: 12px;
}

.more-story {
  margin-bottom: 12px;
}

/* The generic hr rule's 28px top margin otherwise wins the collapse against
   this button's 12px bottom margin, leaving more space below the button
   than above it. Pin this specific hr to match. */
.intro + hr {
  margin-top: 12px;
}

.more-story-toggle-row {
  display: flex;
  justify-content: center;
}

.more-story-toggle {
  cursor: pointer;
  font-size: 18.75px;
  font-weight: 700;
  color: #0071e3;
  padding: 7.5px 18px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  border: 1px solid #b9d9f8;
  border-radius: 999px;
  background: var(--bg);
  font-family: inherit;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.more-story-toggle:hover {
  background: #eaf3fd;
  border-color: #8fc0f0;
}

.more-story-toggle::before {
  content: "↓";
  font-size: 19.5px;
  line-height: 1;
  font-weight: 700;
  transition: transform 0.3s ease;
}

.more-story.open .more-story-toggle::before {
  transform: rotate(180deg);
}

/* Animating grid-template-rows from 0fr to 1fr expands to exactly the
   content's own height, so the open panel re-measures itself on every
   reflow. That matters because the height used to be a pixel value snapshot
   taken by JS at click time: rotating a phone or unfolding a Fold changed
   how the prose wrapped, but the frozen number stayed, clipping ~1000px of
   text or leaving an equally large blank gap. Nothing here is in pixels, so
   there is no snapshot to go stale. */
.more-story-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.35s ease;
}

.more-story.open .more-story-panel {
  grid-template-rows: 1fr;
}

.more-story-body {
  /* Required for the 0fr row to clip the content instead of overflowing it.
     Note there is deliberately no padding here: padding sits outside what
     overflow:hidden clips, so it would floor the collapsed 0fr row at the
     padding's height and leave a sliver showing when closed. The gap above
     the text is a margin on the first paragraph instead, which is content
     and therefore clips to nothing. */
  overflow: hidden;
  text-align: center;
  /* Keeps the 80% measure on tablets and up, but lets a phone use the full
     column: at 80% a 390px screen squeezed prose into ~275px (about 18
     characters per line). The max() floor flips to full width below a
     ~600px container on its own, so no media query is involved. */
  max-width: min(100%, max(80%, 30rem));
  margin-left: auto;
  margin-right: auto;
}

.more-story p {
  margin-bottom: 12px;
  /* Fluid type instead of a flat 21px: on a phone the surrounding body copy
     drops to 14.5px, so a fixed 21px here rendered the story 45% larger than
     the paragraph directly above it. Scales 17px to 21px with the viewport. */
  font-size: clamp(1.0625rem, 0.9rem + 0.7vw, 1.3125rem);
}

.more-story-body p:first-child {
  margin-top: 12px;
}

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

.timeline li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: 6px 4px;
  border-bottom: 1px solid var(--border);
}

.timeline li:last-child {
  border-bottom: none;
}

.timeline .title {
  font-size: 15px;
}

.timeline .date {
  font-size: 14px;
  color: var(--text-light);
  white-space: nowrap;
}

ul.plain {
  padding-left: 20px;
  margin: 0;
}

ul.plain li {
  font-size: 15px;
  margin-bottom: 4px;
}

ul.plain .sub {
  display: block;
  font-size: 13px;
  color: var(--text-light);
  margin-top: 2px;
}

.github-card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 12px 14px;
  text-decoration: none;
  color: inherit;
  max-width: 320px;
  transition: background 0.15s ease;
}

.github-card:hover {
  background: var(--hover);
}

.gh-name {
  font-weight: 600;
  font-size: 15px;
}

.gh-link {
  font-size: 13px;
  color: var(--text-light);
}

.section-lead {
  font-size: 15px;
  color: var(--text-light);
  margin: -4px 0 18px;
}

.stack-item {
  margin-bottom: 20px;
}

.stack-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.stack-head h3 {
  margin: 0;
}

.stack-icon {
  font-size: 15px;
  line-height: 1;
}

.stack-item p {
  color: var(--text-light);
  font-size: 15px;
  margin-bottom: 6px;
}

.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
  margin-top: 8px;
}

.project-card {
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
  background: var(--bg);
}

.project-card:hover {
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
  transform: translateY(-1px);
}

.project-cover {
  height: 110px;
  /* contain, not cover: covers range from square app icons to tall phone
     screenshots (as narrow as ~0.47 aspect). Cropping a portrait screenshot
     to fill this wide box showed only a thin slice of blank status bar;
     containing it shows the whole image, letterboxed, on every project. */
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
  background-color: #f1f0ee;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cover-fallback {
  font-size: 32px;
  opacity: 0.4;
}

.project-name {
  padding: 10px 12px;
  font-size: 14px;
  font-weight: 500;
}

/* Modal */

.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(15, 15, 15, 0.55);
  z-index: 1000;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.modal-overlay.open {
  display: flex;
}

.modal-box {
  background: var(--bg);
  width: min(720px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  border-radius: 10px;
  position: relative;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}

.modal-close {
  position: sticky;
  top: 12px;
  margin-left: calc(100% - 44px);
  z-index: 5;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.9);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
  cursor: pointer;
  font-size: 14px;
}

.modal-close:hover {
  background: #fff;
}

.modal-cover {
  height: 200px;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
  background-color: #f1f0ee;
  margin-top: -32px;
}

.modal-body {
  padding: 8px 40px 40px;
}

.modal-body h2 {
  font-size: 28px;
  margin: 0 0 12px;
}

.meta-block {
  border-bottom: 1px solid var(--border);
  padding-bottom: 12px;
  margin-bottom: 16px;
}

.meta-row {
  display: flex;
  gap: 12px;
  font-size: 14px;
  padding: 4px 0;
  align-items: flex-start;
}

.meta-label {
  color: var(--text-light);
  width: 90px;
  flex-shrink: 0;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tag {
  background: #e8f2fd;
  color: #0071e3;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 12px;
}

.status-pill {
  background: #e8f5e9;
  color: #2e6b35;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 12px;
}

.impact-badge {
  display: inline-block;
  background: #eaf3ea;
  color: #2e6b35;
  border: 1px solid #cfe4cf;
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 16px;
}

.app-blurb {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 12px 14px;
  margin-bottom: 20px;
  background: #fafafa;
}

.app-blurb-title {
  font-weight: 600;
  font-size: 14px;
  margin-bottom: 4px;
}

.app-blurb-body {
  font-size: 13px;
  color: var(--text-light);
  line-height: 1.6;
}

.modal-section {
  margin-bottom: 20px;
}

.modal-section h3 {
  font-size: 17px;
  margin-bottom: 6px;
}

.modal-section p {
  font-size: 14px;
  color: var(--text);
}

.modal-section ul {
  margin: 0;
  padding-left: 20px;
}

.modal-section li {
  font-size: 14px;
  margin-bottom: 3px;
}

.pub-note {
  font-size: 13px;
  color: var(--text-light);
  background: #f7f6f3;
  border-radius: 6px;
  padding: 10px 12px;
  line-height: 1.5;
  margin-top: 4px;
}

.pj-video {
  display: block;
  width: 100%;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: #000;
}

.shot-row {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 6px;
}

.shot {
  height: 420px;
  width: auto;
  border-radius: 10px;
  border: 1px solid var(--border);
  flex-shrink: 0;
}

.pub-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.pub-link {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 12px;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.pub-link:hover {
  background: var(--hover);
  border-color: rgba(55, 53, 47, 0.2);
}

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

/* Tablet and below */
@media (max-width: 860px) {
  .two-col {
    grid-template-columns: 1fr;
    gap: 20px;
  }

  .modal-body {
    padding: 8px 24px 32px;
  }

  .modal-cover {
    height: 160px;
  }

  .shot {
    height: 340px;
  }
}

/* Phones */
@media (max-width: 560px) {
  hr {
    margin: 22px 0;
  }

  p,
  .kw-line,
  .news-body,
  .timeline .title,
  ul.plain li {
    font-size: 14.5px;
  }

  .code-block {
    font-size: 11.5px;
    padding: 12px 14px;
  }

  /* stack date under title instead of pushing it off the edge */
  .timeline li {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }

  .news-list li {
    grid-template-columns: 1fr;
    gap: 4px;
  }

  .news-date {
    padding-top: 0;
  }

  .project-grid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
  }

  .project-cover {
    height: 86px;
  }

  .project-name {
    font-size: 13px;
    padding: 8px 10px;
  }

  .modal-overlay {
    padding: 0;
  }

  .modal-box {
    max-height: 100vh;
    height: 100%;
    border-radius: 0;
    width: 100%;
  }

  .modal-cover {
    height: 130px;
    margin-top: -32px;
  }

  .modal-body {
    padding: 4px 18px 40px;
  }

  .modal-body h2 {
    font-size: 21px;
  }

  .meta-row {
    flex-direction: column;
    gap: 2px;
  }

  .meta-label {
    width: auto;
  }

  .shot {
    height: 260px;
  }

  .github-card {
    max-width: none;
  }
}

/* Any regular desktop/laptop screen (not just ultrawide): reflow the
   text-heavy sections into two columns so a long page doesn't force a
   single tall column of scrolling when there's clearly room beside it.
   Keyed on width alone, not aspect ratio, so a normal 16:9 monitor
   qualifies as soon as it's wide enough; ultrawide just inherits the
   same rule. Project grids stay full-width outside this wrapper since
   their own auto-fill grid already uses extra width by fitting more
   cards per row. */
.wide-columns > section,
.wide-columns > .two-col {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
}

@media (min-width: 1200px) {
  .page {
    max-width: 1600px;
  }

  .wide-columns {
    column-count: 2;
    column-gap: 72px;
  }

  /* Drop the rules inside the wrapper. A column break can land right after
     one, leaving a stray rule floating at the top of the second column with
     no content above it. The h2 headings plus the gap below carry the
     separation on their own. */
  .wide-columns > hr {
    display: none;
  }

  .wide-columns > section,
  .wide-columns > .two-col {
    margin-bottom: 34px;
  }

  /* Avoid a 2x2 nested grid: let Education/Awards and Patents/Languages
     each stack within their own text column instead of splitting again. */
  .wide-columns .two-col {
    grid-template-columns: 1fr;
    gap: 0;
  }
}
