/* =====================================================
   UXNME Portfolio — base styles & design tokens
   Ported from Figma (desktop 1920px) — fluid + 3 breakpoints
   ===================================================== */

:root {
  --color-bg: #ffffff;
  --color-text: #000000;
  --color-muted: #8b8b8b;
  --color-muted-2: #7e7e7e;
  --color-muted-3: #a1a1a1;
  --color-competition-title: #2c2c2c;
  --color-contact-text: #585858;
  --color-accent: #5b7ebf;
  --color-line: #e3e3e3;

  /* Both tokens now point at the same Pretendard web font (a variable font
     with full latin/number glyph coverage, so a separate English font is
     no longer needed) — kept as two variables only so existing rules that
     reference either one don't need to be touched individually. */
  --font-en: 'PretendardVariable', 'Pretendard', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-kr: 'PretendardVariable', 'Pretendard', -apple-system, sans-serif;

  --nav-width-desktop: 220px;
  --header-height-mobile: 64px;

  --side-gutter: clamp(20px, 5vw, 110px);
  --content-max: 1700px;
}

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

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-kr);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; margin: 0; padding: 0; }
p { margin: 0; }

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--color-text);
  color: #fff;
  padding: 10px 16px;
  z-index: 1000;
}
.skip-link:focus { left: 10px; top: 10px; }

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

/* While the Intro slogan cycle is trapping the wheel, the page must not
   scroll at all — the browser's scrollbar stays put. */
html.intro-locked,
html.intro-locked body {
  overflow: hidden;
  height: 100%;
}

/* =====================================================
   Header / Navigation
   Desktop + Tablet (768px+): fixed vertical sidebar (mirrors Figma left
   column), with the nav docking/sliding animation as it scrolls past Intro.
   Mobile only (<768px): fixed top bar with slide-down menu.
   ===================================================== */

.site-header {
  z-index: 500;
  font-family: var(--font-en);
}

/* .brand is the single clickable logo link (wraps img/logo.png) used at
   every breakpoint — it used to be split into an invisible, unclickable
   .brand and a separate non-link .vertical-mark image that was the only
   thing actually shown on desktop/tablet, which is why clicking the logo
   there did nothing. Now .brand itself is positioned/sized per breakpoint
   below and is always the visible, clickable element. */
.brand {
  display: block;
}
.brand img {
  display: block;
  width: 100%;
  height: auto;
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}
.nav-toggle span {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--color-text);
  transition: transform .25s ease, opacity .25s ease;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.primary-nav ul {
  display: flex;
  flex-direction: column;
  gap: 29px;
}

.nav-link {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
  transition: color .2s ease;
}
.nav-link.is-active,
.nav-link:hover,
.nav-link:focus-visible {
  color: var(--color-accent);
}

/* ---------- Logo image: vertical treatment ----------
   The logo asset (img/logo.png) is exported already rotated into a tall,
   narrow bitmap (20 x 182) with its wordmark running bottom-to-top, so no
   CSS rotation is needed here — it's shown at its natural orientation.
   Fixed at the same literal position the old vertical text occupied
   (left:100px, top:100px), on BOTH desktop and tablet. ---------- */
@media (min-width: 768px) {
  .brand {
    position: fixed;
    left: 100px;
    top: 100px;
    z-index: 500;
    width: 20px;
  }
}

/* ---------- Desktop + Tablet header layout ----------
   Global nav: starts at (735px, 680px) on Intro, then glides its
   `left` to (100px, 680px) — the docked position — once the user
   scrolls past Intro, and stays docked through every later section.
   Scrolling back up to Intro glides it back to (735px, 680px). ---------- */
@media (min-width: 768px) {
  .site-header {
    position: static;
  }

  .primary-nav {
    position: fixed;
    left: 735px;
    top: 680px;
    z-index: 500;
    transition: left 0.6s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .primary-nav.is-docked {
    left: 100px;
  }
}

/* ---------- Mobile-only header layout ----------
   Two bugs lived here before:
   1) .site-header had top/left/right set but no `position`, so it was
      still `position: static` — those inset properties do nothing on a
      static element. The header just sat in normal document flow and
      scrolled away with the page instead of staying pinned, which is why
      the logo (and the hamburger button) became unreachable the moment
      you scrolled past the first screen. Fixed with `position: fixed`
      below, plus `main` gets a matching top padding so the first section
      doesn't render underneath it.
   2) .primary-nav (the open dropdown) had `position: fixed` but no
      `z-index`, unlike the desktop/tablet version of this same rule
      (which sets z-index: 500). With z-index left at its default `auto`,
      the dropdown was still painted UNDER whatever page section it
      visually overlapped (Intro), so taps on it were actually hitting
      that section through it — the menu was visible but not clickable.
      Matching the desktop z-index fixes that. */
@media (max-width: 767.98px) {
  .site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: var(--header-height-mobile);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 20px;
    background: rgba(255, 255, 255, 0.92);
    backdrop-filter: saturate(180%) blur(8px);
    border-bottom: 1px solid var(--color-line);
  }
  .nav-toggle { display: flex; margin-left: auto; }

  /* Now that .site-header is taken out of normal flow (position: fixed),
     it no longer pushes the page's content down by its own height the
     way it used to — main needs that offset added back explicitly so the
     Intro section doesn't start out hidden underneath the fixed header. */
  main {
    padding-top: var(--header-height-mobile);
  }

  .primary-nav {
    position: fixed;
    top: var(--header-height-mobile);
    left: 0;
    right: 0;
    z-index: 500;
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-line);
    max-height: 0;
    overflow: hidden;
    transition: max-height .3s ease;
  }
  .primary-nav.is-open { max-height: 420px; }
  .primary-nav ul {
    flex-direction: column;
    gap: 0;
    padding: 8px 20px 20px;
  }
  .primary-nav li { border-top: 1px solid var(--color-line); }
  .primary-nav li:first-child { border-top: none; }
  .nav-link { display: block; padding: 14px 0; font-size: 13px; }
}

/* =====================================================
   Layout helpers
   ===================================================== */

.section {
  position: relative;
  padding: 120px var(--side-gutter);
}

.section-inner {
  position: relative;
  max-width: var(--content-max);
  margin: 0 auto;
  padding-left: calc(var(--nav-width-desktop) + 20px);
}

@media (max-width: 1023.98px) {
  .section { padding: 72px 24px; }
}

/* Tablet (768px+) now keeps the desktop-style fixed left sidebar (logo +
   docking nav), so its content still needs the same left inset to clear
   it; only true mobile — which switches to a top bar instead — drops it. */
@media (max-width: 767.98px) {
  .section-inner { padding-left: 0; }
}

@media (max-width: 599.98px) {
  .section { padding: 56px 18px; }
}

@media (max-width: 767.98px) {
  /* ---------- Logo image: horizontal treatment (mobile only) ----------
     The source bitmap is a tall, narrow strip (20 x 182) with its wordmark
     baked in running vertically, so to lay it out horizontally in the
     mobile top bar it has to actually be rotated 90deg — the wrapper is
     sized to the POST-rotation footprint (182 x 20) and the image itself
     keeps its natural 20 x 182 dimensions and is centered + rotated inside
     it, so the source pixels are reoriented rather than stretched. */
  .brand {
    display: block;
    position: relative;
    width: 182px;
    height: 20px;
    overflow: hidden;
  }
  .brand img {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 20px;
    height: 182px;
    max-width: none;
    transform: translate(-50%, -50%) rotate(-90deg);
  }
}

/* =====================================================
   INTRO
   ===================================================== */

.intro-section {
  min-height: 100vh;
  display: flex;
  align-items: center;
}

/* .slogan-stack only contains absolutely-positioned children, so it
   contributes no intrinsic content width of its own — without an explicit
   width here, this flex item would shrink-to-fit down to ~0 and every
   slogan would wrap one glyph per line. */
.intro-section .section-inner {
  width: 100%;
}

/* ---------- Slogan 3D flip cycle ----------
   Exactly one .slogan carries .is-active at a time (rotateX(0), opacity:1).
   Every other slogan rests at rotateX(-180deg) / opacity:0. Moving the
   .is-active class to a new slogan — in either direction — is enough:
   the outgoing slogan animates 0deg -> -180deg while fading out, and the
   incoming one animates from its resting -180deg -> 0deg while fading in.
   Because the transition always runs from each element's own current
   computed value, playing it "in reverse" (scrolling back up) falls out
   of the same two states for free — no direction-specific code needed. */
.slogan-stack {
  position: relative;
  min-height: clamp(160px, 22vw, 300px);
  perspective: 1600px;
  max-width: 1421px;
}

.slogan {
  position: absolute;
  inset: 0;
  margin: 0;
  font-family: var(--font-en);
  font-weight: 600;
  font-size: clamp(28px, 4.2vw, 64px);
  /* Set on the base rule (not gated behind .is-active) so it's never tied
     to the class toggle: the previous version set this only on
     .slogan.is-active, and since letter-spacing isn't in the `transition`
     list below, it snapped back to normal the instant a slogan LOST
     .is-active — visible mid-flip as the outgoing slogan's tracking
     "releasing" before it finished fading out. Living on the base rule
     means every slogan keeps -5px through the whole rotateX/opacity
     animation, incoming and outgoing alike, regardless of script timing. */
  letter-spacing: -5px;
  line-height: 1.35;
  transform-origin: 50% 50%;
  transform: rotateX(-180deg);
  opacity: 0;
  backface-visibility: hidden;
  transition: transform 0.8s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.6s ease;
  pointer-events: none;
}

.slogan.is-active {
  transform: rotateX(0deg);
  opacity: 1;
  pointer-events: auto;
  z-index: 2;
}

.hero-blank {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  vertical-align: bottom;
  margin: 0 10px 0.3em;
  max-width: 100%;
}

.hero-sub {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.4;
  white-space: nowrap;
  color: var(--color-text);
}

.hero-line {
  display: block;
  width: 100%;
  min-width: clamp(90px, 16vw, 300px);
  height: 1px;
  background: var(--color-text);
}

@media (max-width: 767.98px) {
  .hero-blank { display: flex; width: 100%; margin: 14px 0; }
  .hero-sub { white-space: normal; max-width: 80vw; }
  .hero-line { min-width: 110px; }
}

/* =====================================================
   ABOUT ME
   ===================================================== */

.about-grid {
  display: grid;
  grid-template-columns: 606fr 794fr;
  gap: clamp(24px, 4vw, 64px);
  align-items: start;
}

.about-photo {
  margin: 0;
  aspect-ratio: 606 / 1000;
  background: #f2f2f2;
  overflow: hidden;
  border-radius: 2px;
}
.about-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.about-copy-inner {
  font-family: var(--font-en);
  font-size: 12px;
  line-height: 1.8;
  color: var(--color-muted);
  display: flex;
  flex-direction: column;
  gap: 1.8em;
  padding-top: 4px;
}

/* ---------- About-copy bottom pin ----------
   The copy block sticks to the bottom of the viewport (bottom:50px) as the
   section scrolls past — but only for as long as the photo beside it still
   has room to give. This needs two nested pieces, because a plain
   `position: sticky` element only sticks to an edge its NATURAL (static)
   position is already heading towards:
   - `.about-copy` (the grid item) stretches to the full row height set by
     the taller `.about-photo`, giving the block room to "float" within;
     `justify-content: flex-end` then makes its child's natural, unstuck
     position sit flush with the BOTTOM of that tall box (mirroring the
     photo's own bottom edge) rather than the top.
   - `.about-copy-inner` — the element actually holding the text — is the
     sticky one. Because its natural position is already anchored to the
     bottom of a box taller than the viewport, `position: sticky; bottom:
     50px` correctly holds it 50px above the viewport bottom for as long
     as there's room, then releases it to scroll away in lockstep with the
     photo once the containing box's own bottom (== the photo's bottom)
     comes within that 50px margin — so the two bottom edges stay level
     exactly as they leave the screen, without any JS involved. */
@media (min-width: 1024px) {
  .about-copy {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-self: stretch;
  }
  .about-copy-inner {
    position: sticky;
    bottom: 50px;
  }
}

@media (max-width: 1023.98px) {
  .about-grid { grid-template-columns: 1fr; }
  .about-photo { max-width: 420px; margin: 0 auto; }
}

/* =====================================================
   WORKS — "Categories" card grid
   Ported from a React/Tailwind spec the user supplied; the structural
   ideas (full-bleed section, 1-col -> 3-col grid, photo card with a
   darkening hover overlay, large vertical category label that lifts on
   hover, pill "view" button with a sweep highlight, staggered
   IntersectionObserver reveal) are reproduced here in plain CSS/JS to
   match this site's existing stack. Breakpoints mirror the spec's own
   Tailwind defaults (sm: 640px, md: 768px, lg: 1024px) rather than this
   site's usual 768/1024 pair, since the card proportions were tuned
   against those.
   ===================================================== */

.works-section {
  padding: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: #fff;
}

.works-grid {
  display: grid;
  grid-template-columns: 1fr;
}

/* The spec's grid is edge-to-edge on both sides, but this site keeps a
   persistent fixed sidebar (logo + nav) on the left from 768px up — every
   other section reserves `--nav-width-desktop` + 20px of left padding so
   its content never sits underneath it. A true full-bleed grid here would
   let the first card scroll to a vertical position where the fixed nav's
   z-index:500 sits on top of it, blocking clicks/hover on that corner.
   Keeping that same left inset (and staying edge-to-edge only on the
   right, like the rest of the site's breakout sections) avoids that. */
@media (min-width: 768px) {
  .works-grid {
    grid-template-columns: repeat(3, 1fr);
    padding-left: calc(var(--nav-width-desktop) + 20px);
  }
}

.work-card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  overflow: hidden;
  min-height: 400px;
  padding: 24px;
  background: #eee;
  /* Reveal-on-scroll starting state; js/main.js adds .is-visible to
     #worksGrid once it's ~10% in view, which is what triggers the
     transition below. Each card's own transition-delay (inline style,
     index * 150ms) staggers the three of them. */
  opacity: 0;
  transform: translateY(48px);
  transition: opacity 1s ease-out, transform 1s ease-out;
}

#worksGrid.is-visible .work-card {
  opacity: 1;
  transform: translateY(0);
}

@media (min-width: 640px) {
  .work-card { min-height: 500px; padding: 32px; }
}
@media (min-width: 768px) {
  .work-card { min-height: 750px; padding: 48px; }
}

.work-card-media {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  transition: transform 0.7s ease;
}
.work-card:hover .work-card-media {
  transform: scale(1.05);
}

/* TEMP placeholder — solid black stand-in for the three work photos while
   they're not final yet. Remove this class (and uncomment the
   background-image inline style on each .work-card-media in the HTML) to
   bring the real images back. */
.work-card-media--placeholder {
  background-color: #000;
}

.work-card-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.1);
  transition: background-color 0.5s ease;
  pointer-events: none;
}
.work-card:hover .work-card-overlay {
  background: rgba(0, 0, 0, 0.2);
}

.work-card-name {
  position: relative;
  z-index: 1;
  margin: 0;
  color: #fff;
  font-family: var(--font-en);
  font-weight: 500;
  font-size: 48px;
  line-height: 1;
  writing-mode: vertical-lr;
  transform: rotate(180deg);
  transition: transform 0.5s ease;
}
.work-card:hover .work-card-name {
  transform: rotate(180deg) translateX(8px);
}

@media (min-width: 640px) { .work-card-name { font-size: 60px; } }
@media (min-width: 768px) { .work-card-name { font-size: 72px; } }
@media (min-width: 1024px) { .work-card-name { font-size: 96px; } }

/* "view ◯◯" pill button — sits at the bottom of the card (margin-top:auto
   pushes it there regardless of how tall the category label runs) and
   carries the sweep-highlight treatment from the supplied spec. */
.work-card-btn {
  position: relative;
  z-index: 1;
  margin-top: auto;
  overflow: hidden;
  padding: 12px 32px;
  background: #fff;
  color: #000;
  border-radius: 999px;
  font-family: var(--font-kr);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.work-card-btn::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 0%, rgba(0, 0, 0, 0.05) 50%, transparent 100%);
  transform: translateX(-100%);
  transition: transform 0.5s ease;
}
.work-card-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.work-card-btn:hover::before {
  transform: translateX(100%);
}
.work-card-btn:active {
  transform: translateY(0);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* =====================================================
   SKILLS
   ===================================================== */

.skills-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 40px 32px;
}

.skill-item { min-width: 0; }

.skill-score {
  font-family: var(--font-kr);
  font-weight: 400;
  font-size: clamp(48px, 6.2vw, 96px);
  line-height: 1.5;
  color: var(--color-text);
}

.skill-label {
  font-family: var(--font-kr);
  font-weight: 600;
  font-size: 13px;
  color: var(--color-muted-2);
  margin-bottom: 12px;
}

.skill-desc {
  font-family: var(--font-en);
  font-size: 12px;
  line-height: 1.5;
  color: var(--color-muted);
}

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

@media (max-width: 1023.98px) {
  .skills-grid { grid-template-columns: repeat(3, 1fr); gap: 32px 24px; }
}

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

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

/* =====================================================
   COMPETITIONS
   ===================================================== */

.competitions-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(4, auto);
  grid-auto-flow: column;
  column-gap: 80px;
}

.competition-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding: 18px 0;
  border-top: 1px solid var(--color-line);
}
.competition-item:nth-child(4) { border-bottom: 1px solid var(--color-line); }
.competitions-grid .competition-item:last-child { border-bottom: 1px solid var(--color-line); }

.competition-title {
  font-family: var(--font-kr);
  font-weight: 400;
  font-size: 13px;
  color: var(--color-competition-title);
}

.competition-status {
  font-family: var(--font-kr);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--color-muted-3);
  white-space: nowrap;
}
.competition-status.status-award { color: var(--color-accent); }

@media (max-width: 767.98px) {
  .competitions-grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    grid-auto-flow: row;
    column-gap: 0;
  }
  .competition-item:nth-child(4) { border-bottom: none; }
  .competitions-grid .competition-item:last-child { border-bottom: 1px solid var(--color-line); }
}

/* ---------- Custom cursor (COMPETITIONS list only) ----------
   While the pointer is inside .competitions-grid, main.js adds
   .cursor-active to it (hiding the native cursor there and only there)
   and toggles .is-visible / .is-expanded on #customCursor. The outer
   .custom-cursor element is repositioned every animation frame in JS
   (no CSS transition on it) so the eased/"lerp" follow motion is smooth;
   the inner dot scales up on hover via its own CSS transition, kept on a
   separate element so the constant position updates on the outer element
   don't fight with the scale transition on the inner one. Fine-pointer
   devices only — main.js skips all of this on touch. */
.competitions-grid.cursor-active,
.competitions-grid.cursor-active * {
  cursor: none;
}

.custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 9999;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.custom-cursor.is-visible { opacity: 1; }

.custom-cursor-dot {
  position: absolute;
  top: 0;
  left: 0;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  background: var(--color-accent);
  transform: scale(1);
  transition: transform 0.2s ease, background-color 0.2s ease;
}
.custom-cursor.is-expanded .custom-cursor-dot {
  transform: scale(2.4);
  background: rgba(91, 126, 191, 0.35);
}

/* =====================================================
   CONTACT / CLOSING
   ===================================================== */

.contact-section { padding-bottom: 160px; }

.closing-quote {
  margin: 0 0 80px;
  font-family: var(--font-en);
  font-weight: 600;
  font-size: clamp(24px, 3.4vw, 48px);
  letter-spacing: -5px;
  line-height: 1.4;
  text-align: center;
  max-width: 1200px;
  margin-left: auto;
  margin-right: auto;
}

.contact-block {
  max-width: 420px;
  margin-left: auto;
  text-align: right;
}

.contact-quote {
  font-family: var(--font-kr);
  font-size: 14px;
  line-height: 1.6;
  color: var(--color-contact-text);
  margin-bottom: 28px;
}

.contact-info {
  font-family: var(--font-kr);
  font-size: 14px;
  line-height: 1.6;
  color: var(--color-contact-text);
  margin-bottom: 14px;
}

.contact-info2{
  margin-top:100px;
  width:45px;
  float:right;
}

.contact-label { color: var(--color-accent); }
.contact-info a:hover { text-decoration: underline; }

@media (max-width: 767.98px) {
  .contact-block { margin: 0 auto; text-align: center; }
}

/* =====================================================
   FOOTER
   ===================================================== */

.site-footer {
  padding: 32px var(--side-gutter) 48px;
  text-align: center;
  font-size: 11px;
  color: var(--color-muted-3);
  font-family: var(--font-en);
}
