/* ============================================================
   Wlad Albakhtsin — portfolio
   Built against Figma "Portfolio" › Desktop - 9 (node 239:1139);
   the home page now follows Desktop - 18 (node 422:20032).

   FLUID SYSTEM
   ------------
   The file is drawn on a 1440 canvas with 40px margins (a 1360
   content column), but its SIZES are meant for a 1920 screen.
   There is no max width: the page fills any window. Two units:

     --u   = Figma px, exact at a 1920-wide window. Grows 1:1 above
             1920; below it shrinks gently (--shrink, 0.4 of the way),
             then proportionally under 1024. Type, spacing, and everything sized
             around type — chips, tags, the photo, the nav chrome,
             text-box widths — are `calc(N * var(--u))`.

     --lu  = content width / 1360
             Horizontal layout only: the work-card blocks and media
             strips and the footer's columns. These keep the file's
             proportions of the 1360 column at every width, which is
             what keeps the strips bleeding off the right edge.

   Column positions written as percentages (66.1% etc.) are layout
   too. Small type carries a readability floor via max().
   ============================================================ */

/* ── Fluid scale ──────────────────────────────────────────── */
:root{
  --gutter: 32px;

  /* One Figma pixel at a 1920-wide window.
     Above 1920 it grows with the window, 1:1. Below 1920 it shrinks
     only by --shrink of the way — at 0.4 a window 25% narrower makes
     things 10% smaller, not 25%. The layout is roomy enough to carry
     type that stays large. 1 = plain proportional scaling, 0 = none. */
  --shrink: 0.4;
  --u:  max(calc(100vw / 1920),
            calc((1 - var(--shrink)) * 1px + var(--shrink) * 100vw / 1920));

  /* one pixel of the file's 1360 content column, at any width */
  --lu: calc((100vw - 64px) / 1360);

  /* The nav is chrome, not canvas — it never drops below 90% of
     its drawn size, so the tab labels stay legible on laptops. */
  --nav-u: max(0.9px, var(--u));
}

/* ── Type scale ───────────────────────────────────────────── */
:root{
  --fs-hero:    max(32px,   calc(60 * var(--u)));   /* intro heading    */
  --fs-lead:    max(26px,   calc(40 * var(--u)));   /* About lead       */
  --fs-contact: max(22px,   calc(30 * var(--u)));   /* email + phone    */
  --fs-title:   max(19px,   calc(24 * var(--u)));   /* work + role name */
  --fs-chip:    max(15px,   calc(20 * var(--u)));
  --fs-body:    max(14.5px, calc(16 * var(--u)));   /* About cols       */
  --fs-meta:    max(13.5px, calc(15 * var(--u)));   /* eyebrow, org     */
  --fs-small:   max(13px,   calc(14 * var(--u)));   /* role text, view  */
  --fs-tag:     max(11px,   calc(12 * var(--u)));   /* work tags, caption */

  --fs-nav:     max(13px,   calc(15    * var(--nav-u)));
  --fs-tab:     max(13px,   calc(15    * var(--nav-u)));
  --fs-tab-n:   max(8px,    calc(9.675 * var(--nav-u)));
}

/* ── Space scale ──────────────────────────────────────────── */
:root{
  --sp-4:   max(3px,  calc(4   * var(--u)));
  --sp-8:   max(6px,  calc(8   * var(--u)));
  --sp-16:  max(12px, calc(16  * var(--u)));
  --sp-24:  max(16px, calc(24  * var(--u)));
  --sp-32:  max(20px, calc(32  * var(--u)));
  --sp-40:  max(24px, calc(40  * var(--u)));
  --sp-80:  max(40px, calc(80  * var(--u)));
  --sp-120: max(56px, calc(120 * var(--u)));
  --sp-160: max(64px, calc(160 * var(--u)));

  /* Sections no longer share one rhythm: each band carries its own
     padding and they are separated by a flat 40 from the file. */
  --gap-section: max(24px, calc(40 * var(--u)));
}

/* ── Colour ───────────────────────────────────────────────── */
:root{
  --paper:      #F5F3EE;   /* page ground                        */
  --paper-bright:#FFFEFC;  /* kept for reference, unused         */
  --sand:       #E5DFD5;   /* work-card tags                     */
  --nav-bg:     #FFFFFF;   /* nav bar, inactive tab, About band  */
  --ink:        #121212;   /* body copy, inactive tab label      */
  --ink-soft:   #737373;   /* photo caption, "view" in the link  */
  --ink-index:  #423B3B;   /* work row numbers                   */
  --accent:     #D42823;   /* eyebrows, active tab label         */
  --coral:      #FA4D48;   /* hairline rules                     */
  --rule:       #B8B5B2;   /* nav hairline                       */
  --media:      #E6E3E3;   /* project image placeholder          */
  --stack-near: #F5F3F3;   /* work shot, back card (grey-100)    */
  --stack-far:  #FCFBFB;   /* work shot, last card (grey-50)     */
  --tag-bg:     var(--sand);  /* work-card tag chip              */
  --toggle-bg:  #C8BEA6;   /* language switch track              */
  --dark:       #121212;   /* footer ground                      */
  --on-dark:    #F5F3EE;   /* footer copy                        */
  --graphite:   #2A2A2A;   /* About band                         */

  --chip-fields:#DA9E2E;
  --chip-tools: #2EC0DA;
  --chip-ai:    #DA2E61;

  /* written by js/main.js as you scroll — the colour idea */
  --section-bg: var(--paper);
  --section-fg: var(--accent);
}

/* ── Nav metrics ──────────────────────────────────────────── */
:root{
  --nav-h:     max(64px, calc(78 * var(--nav-u)));
  --tab-w:     calc(155.694 * var(--nav-u));
  --tab-pitch: calc(158 * var(--nav-u));
  --tab-h:     calc(32 * var(--nav-u));
  --tab-x:     calc(110 * var(--nav-u));   /* from the gutter edge */

  --font: 'Inclusive Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
}

/* The bar's bottom edge carries the hairline and the foot of every
   tab. On a fractional height they all land between device pixels
   and the whole join goes soft, so snap it to a whole pixel. */
@supports (height: round(1px, 1px)){
  :root{ --nav-h: round(max(64px, calc(78 * var(--nav-u))), 1px) }
}

@media (max-width:1023px){
  :root{
    --gutter: 20px;
    --lu: calc((100vw - 40px) / 1360);
    /* Tablets and phones: continue from the value at 1024 and shrink
       proportionally from there, so there is no jump at the breakpoint;
       the readability floors in the type scale take over on phones. */
    --u: calc(100vw / 1024 * ((1 - var(--shrink)) + var(--shrink) * 1024 / 1920));
  }
}

/* ── Base ─────────────────────────────────────────────────── */
*, *::before, *::after{ box-sizing:border-box }

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

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--font);
  font-size:var(--fs-body);
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  /* The clip for the work-card media strips, which run past the
     content column on purpose — see § Selected works. `clip` rather
     than `hidden` because hidden makes the viewport a scroll
     container: it still allows rubber-band panning on iOS, and it
     would sit between the sticky About photo and its scrollport.
     `hidden` stays first as the fallback for older engines. */
  overflow-x:hidden;
  overflow-x:clip;
}

h1,h2,h3,p,ol,ul,figure,figcaption{ margin:0; padding:0 }
ol,ul{ list-style:none }
img{ display:block; max-width:100% }
a{ color:inherit; text-decoration:none }
button{ font:inherit; color:inherit; background:none; border:0; padding:0; cursor:pointer }

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

.svg-defs{ position:absolute; width:0; height:0; overflow:hidden }

/* ── ASCII character field ────────────────────────────────────
   Painted by js/ascii.js. It fills the hero and scrolls away with
   it — the module reads this element's box for its grid, so the
   size is set here, not in JS. */
.ascii-bg{
  position:absolute; inset:0; z-index:0;
  overflow:hidden; pointer-events:none;
  contain:layout style paint;
}
.ascii-bg[hidden]{ display:none }
/* GPU version (js/field-gl.js) draws into this instead of the <pre>s */
.ascii-bg__gl{ position:absolute; inset:0; width:100%; height:100%; display:block }
.ascii-bg__layer{
  position:absolute; inset:0; margin:0;
  white-space:pre;
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-ligatures:none;
  will-change:contents;
}

/* ── Grain ────────────────────────────────────────────────────
   Removed. The page used to carry a fractal-noise speckle over
   everything (`body::after`, opacity from CONFIG.grain). The field
   panels still expose a `grain` slider and still write
   --grain-opacity; nothing reads it now. */

/* ============================================================
   NAV — the folder tabs
   ============================================================
   The illusion is a z-index sandwich inside the bar:

     inactive tabs   z-index 1   ─┐ the hairline paints across
     hairline        z-index 10  ─┤ their bottom, closing them
     active tab      z-index 20  ─┘ covers the hairline, so the
                                   line breaks and the tab reads
                                   as open onto the page below

   Nothing is drawn per-tab: one hairline, one z-index swap.
   ============================================================ */
.nav{
  position:fixed; inset:0 0 auto; z-index:100;
  height:var(--nav-h);
  background:var(--nav-bg);
  /* Each tab outline ends ON the bar's bottom edge, so half of its
     1px stroke — plus both butt caps — would render below the bar
     and sit as stubs on the page. Clip them off here. */
  overflow:hidden;
}
.nav__inner{
  height:100%;
  padding:0 var(--gutter);
  display:flex; align-items:center;
  position:relative;
}
.nav__rule{
  position:absolute; left:0; right:0; bottom:0;
  height:1px; background:var(--rule);
  z-index:10;
}

/* Logo and the language switch sit on the tab baseline, not on the
   vertical centre of the bar — that is how the file has it. */
.nav__logo{
  flex:none; align-self:flex-end;
  margin-bottom:calc(13 * var(--nav-u));
}
.nav__logo img{
  width:calc(66 * var(--nav-u));
  height:calc(37.767 * var(--nav-u));
}

.tabs{
  position:absolute; bottom:0;
  left:calc(var(--gutter) + var(--tab-x));
  display:flex; align-items:flex-end;
}

.tab{
  position:relative; z-index:1; flex:none;
  width:var(--tab-w); height:var(--tab-h);
  margin-right:calc(var(--tab-pitch) - var(--tab-w));
  --p:0;                                      /* fill 0 → 1 */
}
.tab.is-active{ z-index:20 }
.tab:hover{ z-index:6 }

.tab__svg{ position:absolute; inset:0; width:100%; height:100%; display:block; overflow:visible }
.tab__plate{ fill:var(--nav-bg); transition:fill .35s ease }
.tab__fill { fill:var(--section-bg); transition:fill .35s ease }
.tab__edge {
  fill:none; stroke:var(--rule); stroke-width:1;
  vector-effect:non-scaling-stroke;
}

/* In solid mode the colour eases in when a tab becomes active;
   in progress mode it has to track the scroll frame-for-frame. */
body.fill-solid .tab__fill{ transition:width .3s ease, fill .35s ease }
body.fill-solid .tab__label--fill{ transition:clip-path .3s ease, color .35s ease }

/* Two stacked copies of the label; the top one is clipped to the
   same sweep as the fill, so the text recolours along with it. */
.tab__label{
  position:absolute; inset:0;
  display:flex; align-items:flex-end;
  padding:0 0 calc(6 * var(--nav-u)) calc(14 * var(--nav-u));
  font-size:var(--fs-tab); line-height:1.2; letter-spacing:-.02em;
  text-transform:lowercase; white-space:nowrap;
  pointer-events:none;
}
.tab__label b{
  font-weight:400; font-size:var(--fs-tab-n); margin-right:.35em;
  transform:translateY(calc(-5 * var(--nav-u)));
}
.tab__label--base{ color:var(--ink) }
.tab__label--fill{
  color:var(--section-fg);
  clip-path:inset(0 calc(100% - var(--p) * 100%) 0 0);
  transition:color .35s ease;
}
.tab:hover .tab__label--base{ color:var(--accent) }

.nav__right{
  margin-left:auto; align-self:flex-end;
  margin-bottom:calc(11 * var(--nav-u));
  display:flex; align-items:center;
  font-size:var(--fs-nav); letter-spacing:-.02em; line-height:1.2;
}

/* PL · switch · EN — visual only for now */
.lang{ display:flex; align-items:center; gap:calc(4 * var(--nav-u)) }
.lang__opt{ text-transform:uppercase; opacity:.4 }
.lang__opt.is-on{ opacity:1 }
.lang__switch{
  width:calc(21 * var(--nav-u)); height:calc(10 * var(--nav-u));
  padding:calc(2 * var(--nav-u));
  border-radius:2px; background:var(--toggle-bg);
  display:flex; align-items:center; justify-content:flex-end;
}
.lang__switch[aria-checked="false"]{ justify-content:flex-start }
.lang__knob{
  width:calc(6 * var(--nav-u)); height:calc(6 * var(--nav-u));
  border-radius:2px; background:var(--ink);
}

/* ── Burger (small screens) ───────────────────────────────── */
.burger{
  display:none; margin-left:auto; align-self:center;
  width:40px; height:40px; position:relative;
}
.burger span{
  position:absolute; left:9px; right:9px; height:1px; background:var(--ink);
  transition:transform .3s ease;
}
.burger span:nth-child(1){ top:15px }
.burger span:nth-child(2){ top:24px }
body.menu-open .burger span:nth-child(1){ transform:translateY(4.5px) rotate(45deg) }
body.menu-open .burger span:nth-child(2){ transform:translateY(-4.5px) rotate(-45deg) }

.menu{
  position:fixed; inset:var(--nav-h) 0 0; z-index:90;
  background:var(--section-bg);
  padding:28px var(--gutter) var(--gutter);
  display:flex; flex-direction:column;
  transition:background .35s ease, opacity .22s ease, visibility .22s;
  visibility:hidden; opacity:0;
}
body.menu-open{ overflow:hidden }
body.menu-open .menu{ visibility:visible; opacity:1 }
.menu a{
  display:flex; align-items:baseline; gap:16px;
  padding:18px 0; border-bottom:1px solid var(--rule);
  font-size:clamp(22px, 6.4vw, 30px);
  letter-spacing:-.02em; text-transform:lowercase;
}
.menu a b{ font-weight:400; font-size:12px; opacity:.5; font-variant-numeric:tabular-nums }
.menu a.is-active{ color:var(--section-fg) }
.menu a.is-active b{ opacity:1 }

/* ============================================================
   LAYOUT
   ============================================================ */
.shell{
  padding:0 var(--gutter);
}

/* x=501 → 1400 on the 1440 canvas = 66.1% of the content width */
.col-right{ width:66.1%; margin-left:auto }

.section{ scroll-margin-top:var(--nav-h) }
.section + .section{ margin-top:var(--gap-section) }

/* Every heading in the file is bracketed and letter-spaced wide. */
.eyebrow{
  font-size:var(--fs-meta); font-weight:400; line-height:normal;
  letter-spacing:.2em; text-transform:uppercase;
  color:var(--accent);
}
.eyebrow--ink{ color:var(--ink) }

/* ── 01 Intro ─────────────────────────────────────────────── */
/* One screen: the wordmark field fills the band between the bar and the
   copy, and the copy sits on the hero's bottom edge — photo on the left,
   heading and the three facts in the right-hand column. The bar floats
   over the top, so the band starts under it. */
.section--intro{
  position:relative;
  overflow:hidden;
  min-height:100vh;
  min-height:100svh;
  display:flex; flex-direction:column;
  padding:var(--nav-h) 0 var(--sp-40);
}
/* The field's box — js/field-gl.js and js/ascii.js read it. The padding
   is the content column, which the GPU field places the mark against. */
.intro__mark{ position:relative; flex:1; min-height:calc(240 * var(--u)) }
.intro__mark .ascii-bg{ padding:0 var(--gutter) }

.intro{
  position:relative; z-index:1;
  display:flex; justify-content:space-between; align-items:flex-start;
}

.intro__aside{ flex:none; display:flex; flex-direction:column; gap:var(--sp-8) }
/* The frame clips, the photo inside it can be scaled — js/loader.js
   opens the frame upward while the photo settles from a slight zoom. */
.intro__frame{
  display:block; overflow:hidden; border-radius:2px;
  width:calc(207 * var(--u)); height:calc(259 * var(--u));
}
.intro__photo{ width:100%; height:100%; object-fit:cover }
.intro__caption{
  font-size:var(--fs-tag); font-weight:300; font-style:italic;
  line-height:1.5; letter-spacing:-.02em;
  color:var(--ink-soft);
}

.intro__text{ width:66.1%; color:var(--ink) }
.intro__text h1{
  font-size:var(--fs-hero); font-weight:400;
  line-height:1.08; letter-spacing:-.07em;
}
/* Each line is a mask its text rises out of (js/loader.js). The padding
   keeps the descenders inside the mask; the negative margin gives the
   space back so the lines sit exactly as set. */
.intro__line{ display:block; overflow:hidden; padding-bottom:.12em; margin-bottom:-.12em }
.intro__line > span{ display:block }
/* 663 → 708 in the file */
.intro__facts{
  margin-top:calc(45 * var(--u));
  display:flex; justify-content:space-between; align-items:flex-start;
  gap:var(--sp-24);
  font-size:var(--fs-body); line-height:1.5; letter-spacing:-.02em;
}
.intro__focus{ width:calc(321em / 16) }      /* the file's 321px box for 16px type, in ems */
.intro__skills, .intro__place{ white-space:nowrap }

/* ── 02 Selected works ────────────────────────────────────── */
/* Figma › Work row (node 422:20371). The rows run edge to edge with the
   gutter inside them, so the hairlines and the hover ground reach both
   window edges. Four tracks: the number; the name column, starting on
   the right-hand column's line (x=501, 33.9% of the content width); the
   view link; the shot, 248 × 187, on the right edge. */
.section--works{ padding:var(--sp-120) 0 2px }
.works{ margin-top:var(--sp-40) }

.work{ border-top:.5px solid var(--rule) }
.work__row{
  display:grid;
  grid-template-columns:33.9% minmax(0, 1fr) auto calc(248 * var(--lu));
  align-items:start;
  padding:var(--sp-32) var(--gutter);
  transition:background-color .25s ease;
  isolation:isolate;   /* the shot's back cards sit on the row's ground */
}
.work__row:hover{ background:var(--nav-bg) }

.work__index{
  font-size:var(--fs-body); line-height:normal;
  color:var(--ink-index);
}

/* As tall as the shot: name and tags on top, the description on its foot. */
.work__body{
  align-self:stretch;
  width:calc(420 * var(--u)); max-width:100%;   /* room for the headline on two lines */
  display:flex; flex-direction:column; justify-content:space-between; gap:var(--sp-24);
}
.work__head{ display:flex; flex-direction:column; gap:var(--sp-16) }
/* The project's name as an eyebrow, and what the work achieved as the
   headline under it. */
.work__title{ display:flex; flex-direction:column; gap:var(--sp-8) }
.work__project{
  font-size:var(--fs-meta); line-height:normal;
  letter-spacing:.2em; text-transform:uppercase;
  color:var(--ink-soft);
}
.work__name{
  font-size:var(--fs-title); font-weight:500;
  line-height:1.15; letter-spacing:-.05em;
  text-wrap:balance;
}
.work__note{
  max-width:calc(255 * var(--u));   /* the file's description box */
  font-size:var(--fs-body); line-height:normal; letter-spacing:-.05em;
  opacity:.6;
}

.work__tags{
  display:flex; flex-wrap:wrap; align-content:flex-start; gap:var(--sp-4);
}
.work__tags li{
  background:var(--tag-bg); border-radius:2px;
  padding:calc(4 * var(--u)) calc(8 * var(--u));
  font-size:var(--fs-tag); line-height:1; letter-spacing:-.04em;
  text-transform:uppercase;
}

/* "( view )" — the word in the soft ink; the arrow opens between it and
   the closing bracket on hover. 40 to the shot. */
.work__view{
  margin-right:var(--sp-40);
  display:flex; align-items:center;
  white-space:nowrap;
  font-size:var(--fs-small); line-height:1.2; letter-spacing:-.02em;
  text-transform:uppercase;
  transition:transform .15s ease-out;
}
/* Figma › Work row, state=hover: the stack widens the media strip by
   29 to the left, so the link steps back by the same 29. */
.work__row:hover .work__view,
.work__row:focus-visible .work__view{ transform:translateX(calc(-29 * var(--lu))) }
.work__view-word{ color:var(--ink-soft); padding:0 calc(2 * var(--u)) }
/* collapsed to nothing at rest, so the brackets sit 2 off the word on
   both sides; on hover it opens to the arrow plus its own 2 */
.work__arrow{
  display:inline-flex; align-items:center; justify-content:flex-end; overflow:hidden;
  width:0; opacity:0;
  transition:width .3s cubic-bezier(.22,.61,.36,1), opacity .25s ease;
}
.work__arrow svg{ width:calc(12 * var(--u)); height:auto; flex:none }
.work__row:hover .work__arrow{ width:calc(14 * var(--u)); opacity:1 }

.work__shot{
  position:relative;
  display:block; aspect-ratio:248 / 187;
  background:var(--media);
}
.work__shot img{ width:100%; height:100%; display:block; object-fit:cover }

/* Figma › Work row, state=hover: two smaller cards slide out from
   behind the shot to the left, centred on it — a stack. In the
   248 × 187 frame the near one is 229 × 173 at x −17, y 7, the far
   one 224 × 161 at x −29, y 13. At rest both sit hidden behind the
   shot. Smart animate, ease out, 150 ms. */
.work__shot::before,
.work__shot::after{
  content:""; position:absolute; z-index:-1;
  opacity:0;
  transition:transform .15s ease-out, opacity .15s ease-out;
}
.work__shot::before{          /* far */
  left:-11.69%; top:6.95%; width:90.32%; height:86.1%;
  background:var(--stack-far);
  transform:translateX(12.95%);
}
.work__shot::after{           /* near */
  left:-6.85%; top:3.74%; width:92.34%; height:92.51%;
  background:var(--stack-near);
  transform:translateX(7.42%);
}
/* A case with its own colour stacks it behind the shot: the near card
   at 70% of the colour, the far one at 40%, each mixed into the row's
   white so they read as tints yet hide one another like solid cards.
   A work without a colour keeps the greys. */
.work--bondata{ --work:#72A5FF }     /* Bondata blue-400, its case page ground */
.work--evoke{ --work:#72A5FF }       /* Evoke's case ground in the file is the same blue-400 */
.work--dishcover{ --work:#FF8177 }   /* Dishcover red-400 */
.work--bondata, .work--dishcover, .work--evoke{
  --stack-near:color-mix(in srgb, var(--work) 70%, var(--nav-bg));
  --stack-far: color-mix(in srgb, var(--work) 40%, var(--nav-bg));
}
/* A work not ready to open: no link and no view, faded, no hover.
   Its shot keeps the last column the view would have pushed it to. */
.work__row--off{ cursor:default }
.work__row--off:hover{ background:none }
.work__row--off .work__index, .work__row--off .work__head{ opacity:.45 }
/* Instead of the description: what state the work is in. Full ink, so it
   reads first on a faded row; the dot breathes slowly. */
.work__status{
  display:inline-flex; align-items:center; gap:calc(8 * var(--u));
  font-size:var(--fs-small); line-height:1.2; letter-spacing:-.02em;
  text-transform:uppercase;
}
.work__status-dot{
  width:calc(7 * var(--u)); height:calc(7 * var(--u)); flex:none;
  border-radius:50%; background:var(--coral);
  animation:work-pulse 2.4s ease-in-out infinite;
}
@keyframes work-pulse{ 50%{ opacity:.35 } }
@media (prefers-reduced-motion:reduce){ .work__status-dot{ animation:none } }
.work__row--off .work__shot{ grid-column:4 }
.work__row--off .work__shot::before, .work__row--off .work__shot::after{ display:none }
.work__row:hover .work__shot::before,
.work__row:hover .work__shot::after,
.work__row:focus-visible .work__shot::before,
.work__row:focus-visible .work__shot::after{ opacity:1; transform:none }

/* ── 03 About ─────────────────────────────────────────────── */
/* Figma › Frame 59 (422:20057): a graphite band, the photo hidden, the
   eyebrow and copy in paper, the lead in red. */
.section--about{
  background:var(--graphite); color:var(--on-dark);
  padding:var(--sp-120) 0;
}
.about{ display:flex; align-items:flex-start; justify-content:space-between }
.about__aside{ display:none }
.section--about .eyebrow{ color:var(--on-dark) }

.about__aside{
  flex:none;
  position:sticky; top:calc(var(--nav-h) + var(--sp-24));
}
.about__photo{
  display:block;
  width:calc(177 * var(--u)); height:calc(236 * var(--u));
  object-fit:cover; border-radius:2px;
}

.about__body{ width:66.1%; margin-left:auto; display:flex; flex-direction:column; gap:var(--sp-160) }

.about__head{ display:flex; flex-direction:column; gap:var(--sp-24) }
.about__lead{
  width:calc(591em / 40);            /* the file's 591px box for 40px type, in ems */
  font-size:var(--fs-lead); font-weight:600;
  line-height:1.1; letter-spacing:-.04em;
  color:var(--accent);
}

.about__cols{
  width:calc(787 * var(--u)); max-width:100%;   /* Frame 57: 787 wide, the second column at 465 */
  display:flex; justify-content:space-between;
  font-size:var(--fs-body); font-weight:400;
  line-height:1.5; letter-spacing:-.02em;
  color:var(--on-dark);
}
.about__col{ width:calc(322em / 16) }       /* the file's 322px box for 16px type, in ems */
.about__col p + p{ margin-top:var(--sp-16) }

/* ── 04 Career ────────────────────────────────────────────── */
.section--career{ padding:var(--sp-80) 0 }
.roles{ margin-top:var(--sp-24); display:flex; flex-direction:column; gap:var(--sp-8) }

.role{
  border-bottom:.5px solid var(--rule);
  padding:var(--sp-24) 0;
  display:flex; flex-direction:column; gap:var(--sp-32);
}
.role__head{ display:flex; flex-direction:column; gap:var(--sp-16) }
.role__top{
  display:flex; justify-content:space-between; gap:var(--sp-24);
  font-size:var(--fs-title); font-weight:500;
  line-height:1; letter-spacing:-.04em;
  white-space:nowrap;
}
.role__title{ font-weight:inherit }
.role__years{ text-align:right; font-variant-numeric:tabular-nums }
.role__org { font-size:var(--fs-meta); line-height:normal; opacity:.6 }
.role__text{ max-width:64ch; font-size:var(--fs-small); line-height:1.5; letter-spacing:-.02em }   /* 60–70 characters a line */

/* ── 05 Skills ────────────────────────────────────────────── */
.section--skills{ padding:var(--sp-80) 0 }
.skills{ display:flex; flex-direction:column; gap:var(--sp-80) }
.skills__group{ display:flex; flex-direction:column; gap:var(--sp-24) }

.chips{ display:flex; flex-wrap:wrap; gap:var(--sp-4) }
.chip{
  display:flex; align-items:center; justify-content:center;
  padding:calc(8 * var(--u)) calc(16 * var(--u));
  border:1px solid currentColor; border-radius:2px;
  font-size:var(--fs-chip); line-height:1; letter-spacing:-.04em;
  white-space:nowrap;
  color:var(--ink);
}
.chips--fields .chip{ border-color:var(--chip-fields) }
.chips--tools  .chip{ border-color:var(--chip-tools) }
.chips--ai     .chip{ border-color:var(--chip-ai) }

/* ── 06 Contact ───────────────────────────────────────────── */
/* A full screen (Figma › Footer, node 319:2849): the logo field fills
   the top, the copyright and the contacts sit along the foot. Scrolled
   to the end, the fixed bar covers the band's top, so the bar's height
   is taken as top padding and the mark starts under it. */
.site-footer{
  margin-top:var(--gap-section);
  background:var(--dark); color:var(--on-dark);
  min-height:100vh;
  min-height:100svh;
  display:flex;
  padding:var(--nav-h) 0 var(--sp-40);
}
.site-footer__inner{
  position:relative;
  flex:1; width:100%;
  padding:0 var(--gutter);
  display:flex; flex-direction:column;
  gap:calc(59 * var(--u));                    /* mark foot 643 → contacts 702 */
  font-size:var(--fs-body); font-weight:600; letter-spacing:-.04em;
  line-height:1.1;
}

/* The logo field. Its box is what js/field-gl.js reads for the grid, so
   the size lives here. It runs out to the viewport edges (negative
   margin, gutter as padding) so the mark can drift and tilt past the
   content column without being cut; the script places the mark's left
   edge on the padding and its top on the band's top. */
.site-footer__mark{
  position:relative; flex:1;
  min-height:calc(300 * var(--u));
  margin:0 calc(var(--gutter) * -1);
}
/* the padding goes on the field itself — that is the element the script reads */
.site-footer__mark .ascii-bg{ padding:0 var(--gutter) }

/* Everything on one bottom line. The contacts start on the right-hand
   column (x=501); the links sit midway between them and Download CV,
   which is how the file's equal gaps fall. */
.site-footer__bottom{
  display:grid;
  grid-template-columns:33.9% auto 1fr auto;
  align-items:end;
}
.site-footer__col--reach{
  display:flex; flex-direction:column; gap:calc(20 * var(--u));
  font-size:var(--fs-contact); font-weight:500;
  letter-spacing:-.04em; text-transform:uppercase;
  white-space:nowrap;
}
.site-footer__col--social{ justify-self:center; display:flex; flex-direction:column; gap:var(--sp-16) }
/* Back to top over Download CV, stacked like the links beside them;
   the arrow points the way it goes. */
.site-footer__col--cv{ display:flex; flex-direction:column; align-items:flex-start; gap:var(--sp-16) }
/* on its own, in the footer's top right corner, under the bar */
.site-footer__top{
  position:absolute; z-index:2; top:0; right:var(--gutter);
  display:inline-flex; align-items:center; gap:.4em;
}
.site-footer__top svg{ width:.7em; height:auto; flex:none; transition:transform .25s ease }
.site-footer__top:hover svg{ transform:translateY(-3px) }
.site-footer a:hover{ color:var(--coral) }

/* ============================================================
   RESPONSIVE
   The fluid scale carries most of it; what follows is the
   structural regrouping the units cannot do on their own.
   ============================================================ */
@media (max-width:1360px){
  .role__top{ white-space:normal }
}

@media (max-width:1023px){
  .tabs, .nav__right{ display:none }
  .burger{ display:block }
  .col-right, .about__body{ width:100%; margin-left:0 }

  main{ padding-top:var(--nav-h) }

  /* <main> is pushed down by the bar here, so take that back out
     or the hero would run one nav-height past the fold. The field is
     off under 1024, so its band goes and the copy is centred. */
  .section--intro{
    min-height:calc(100vh - var(--nav-h));
    min-height:calc(100svh - var(--nav-h));
    padding:40px 0;
    justify-content:center;
  }
  .intro__mark{ display:none }
  .intro{ flex-direction:column; gap:32px }
  .intro__frame{ width:160px; height:200px }
  .intro__text{ width:100% }
  .intro__facts{ margin-top:28px; flex-wrap:wrap; row-gap:20px }
  .intro__focus{ width:100% }

  /* The row stacks: number, name and tags, the description, the link,
     then the shot at full width. */
  .work__row{ display:flex; flex-direction:column; gap:16px; padding:24px var(--gutter) }
  .work__body{ width:100%; gap:16px }
  .work__note{ max-width:none }
  .work__view{ margin-right:0 }
  .work__row:hover .work__view, .work__row:focus-visible .work__view{ transform:none }
  .work__shot{ width:100% }
  .work__shot::before, .work__shot::after{ display:none }
  .work__arrow{ display:none }

  .section--about{ padding:56px 0 }
  .about{ flex-direction:column; gap:40px }
  .about__aside{ position:static }
  .about__photo{ width:177px; height:236px }
  .about__body{ gap:40px }
  .about__lead{ width:100% }
  .about__cols{ width:100%; flex-direction:column; gap:26px }
  .about__col{ width:100% }

  .role__top{ flex-direction:column; gap:4px }
  .role__years{ text-align:left }

  .chips{ gap:6px }
  .chip{ padding:7px 13px }

  .site-footer__inner{ justify-content:flex-end }
  .site-footer__bottom{ grid-template-columns:1fr; gap:32px }
  .site-footer__col--social{ justify-self:start }
  .site-footer__col--reach{ white-space:normal; overflow-wrap:anywhere }
  .site-footer__mark{ display:none }   /* the field is off under 1024, as in the hero */
  .site-footer__copy{ order:1 }
}

@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto }
  *,*::before,*::after{ transition-duration:.01ms !important; animation-duration:.01ms !important }
}

/* ============================================================
   Loader — Figma "Portfolio" › Loader (node 269:936)

   Everything is laid out in Figma pixels of the 1440 frame, times
   --lk. The composition is 722.62 × 199.17 there (half the frame),
   and keeps that proportion of the window, never wider than the
   window minus the gutters.

     W half   x 0        348.73 wide
     frame    x 297.88   308.50 wide   (parallelogram, the W's slant)
     A half   x 548.94   173.68 wide

   The first frame is exactly the Figma frame — nothing enters.
   Only drawn while <html> carries .is-loading (set in <head>), so
   the page is never trapped behind it. Timeline: js/loader.js.
   ============================================================ */
.loader{ display:none }
/* Scroll is locked only until the exit starts — released before the
   flight is measured, so a scrollbar coming back cannot move the
   nav logo out from under the landing logo. */
.is-locked{ overflow:hidden }
.is-loading .loader{
  display:block;
  position:fixed; inset:0; z-index:300;   /* over the nav, under the grain */
  pointer-events:none;
  --lk: min(calc(100vw / 1440), calc((100vw - 2 * var(--gutter)) / 722.62));
  --lo-ink: #F5F3EE;
  --lo-shared: #F5F3EE;
}
.loader__bg{
  position:absolute; inset:0;
  background:#121212;
}
.loader__stage{
  position:absolute; left:50%; top:50%;
  width:calc(722.62 * var(--lk)); height:calc(199.17 * var(--lk));
  margin:calc(-99.585 * var(--lk)) 0 0 calc(-361.31 * var(--lk));
}
.loader__half{ position:absolute; top:0 }
.loader__half--w{ left:0; width:calc(348.73 * var(--lk)); height:calc(198.514 * var(--lk)) }
.loader__half--a{ left:calc(548.94 * var(--lk)); width:calc(173.684 * var(--lk)); height:calc(199.166 * var(--lk)) }
.loader__glyph, .loader__glyph svg{ display:block; width:100%; height:100% }
.loader__glyph svg *{ fill:var(--lo-ink) }
.loader__glyph svg .is-shared{ fill:var(--lo-shared) }

.loader__frame{
  position:absolute; top:0; left:calc(297.88 * var(--lk));
  width:calc(308.5 * var(--lk)); height:calc(198 * var(--lk));
  /* 94.3 / 308.5 — the same slant as the W's last stroke */
  clip-path:polygon(30.57% 0, 100% 0, 69.43% 100%, 0 100%);
}
.loader__reveal{
  position:absolute; inset:0;
  background:#1d1d1d;
}
.loader__reveal img{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  visibility:hidden;
}
.loader__reveal img:first-child{ visibility:visible }
