/* Kristian Davis portfolio — base tokens, resets, keyframes.
   Layout and colour live as inline styles on the elements themselves; the rules below are
   only the things an inline style cannot express: resets, custom properties, keyframes, and
   the hover/focus/active states. */
/* --accent is the brand purple: decoration (ghost words, strokes, glows, rules, focus ring) and
   LARGE text, where AAA asks 4.5:1 and #a06bff gives 5.86:1. --accent-text is used ONLY where
   accent lands on small body-size text, which AAA holds to 7:1. Decorative elements are exempt
   from contrast rules altogether, so nothing aria-hidden was ever lightened. */
:root{ --accent:#a06bff; --accent-rgb:160,107,255; --accent-text:#b48cff; }
  *{ margin:0; padding:0; box-sizing:border-box; }
  /* smooth on every breakpoint -- an instant/auto mobile override was tried here and reverted
     the same week: an abrupt, unglided snap is what read as "fighting the scroll" on a phone,
     not the smooth motion itself. proximity (not mandatory) is the other half of this -- see the
     "easy to get wrong" note in CLAUDE.md for why mandatory measurably breaks scrolling here. */
  html{ scroll-behavior:smooth; }
  body{ background:#050505; color:#e9e7df; font-family:'Inter',sans-serif; line-height:1.6; -webkit-font-smoothing:antialiased; overflow-x:hidden; }
  a{ color:inherit; text-decoration:none; }
  a:hover{ color:var(--accent-text); }
  summary{ list-style:none; }
  summary::-webkit-details-marker{ display:none; }
  ::selection{ background:rgba(var(--accent-rgb),.35); color:#fff; }
  :focus-visible{ outline:3px solid var(--accent); outline-offset:3px; border-radius:2px; box-shadow:0 0 0 6px rgba(5,5,5,.9); }
  @property --reveal{ syntax:'<percentage>'; inherits:false; initial-value:0%; }
  
  
  
  
  
  @keyframes listenPulse{
    0%{ width:10px; height:10px; opacity:.5; }
    70%{ opacity:0; }
    100%{ width:52px; height:52px; opacity:0; }
  }

/* --- interactive states --- */
.i1:hover, .i1:focus-visible{ top:16px !important; }
.i2:hover .nav-name, .i2:focus-visible .nav-name{ color:var(--accent-text) !important; }
.i3:hover{ background-size:100% 1px !important; color:#e9e7df !important; transform:translateY(-2px) !important; }
.i3:hover, .i3:focus-visible{ background-size:100% 1px !important; color:#e9e7df !important; }
.i4:hover{ color:#e9e7df !important; }
.i5:hover{ color:#e9e7df !important; background:rgba(255,255,255,.06) !important; }
.i6:hover{ color:#e9e7df !important; background:rgba(255,255,255,.06) !important; }
.i7:hover{ color:#e9e7df !important; background:rgba(255,255,255,.06) !important; }
.i8:hover{ color:#e9e7df !important; background:rgba(255,255,255,.06) !important; }
.i9:hover{ color:#e9e7df !important; }
.i10:hover{ background-size:100% 1px !important; color:#e9e7df !important; transform:translateY(-2px) !important; }
.i10:hover, .i10:focus-visible{ background-size:100% 1px !important; color:#e9e7df !important; }
.i11:hover{ background-size:100% 1px !important; color:#e9e7df !important; transform:translateY(-2px) !important; }
.i11:hover, .i11:focus-visible{ background-size:100% 1px !important; color:#e9e7df !important; }
.i14:hover{ border-color:var(--accent) !important; color:var(--accent-text) !important; background-size:100% 100% !important; }
.i14:hover, .i14:focus-visible{ border-color:var(--accent) !important; color:var(--accent-text) !important; background-size:100% 100% !important; }
.i15:hover{ color:#e9e7df !important; border-color:var(--accent) !important; }
.i16:hover{ color:var(--accent-text) !important; border-color:var(--accent) !important; transform:translateY(-3px) !important; }
.i17:hover{ color:var(--accent-text) !important; border-color:var(--accent) !important; transform:translateY(-3px) !important; }

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

/* --- work bands (added with the new work section) --- */
/* Liquid settle: the row lands, compresses, and rebounds, the way a surface absorbs
   something dropped onto it. Vertical, not lateral, so it reads as descent. */
@keyframes bandFall{
  0%{ transform:translateY(-14px) scaleY(1.05) skewX(0deg); }
  38%{ transform:translateY(0) scaleY(.965) skewX(-.35deg); }
  62%{ transform:translateY(0) scaleY(1.018) skewX(.18deg); }
  82%{ transform:translateY(0) scaleY(.995) skewX(-.07deg); }
  100%{ transform:translateY(0) scaleY(1) skewX(0deg); }
}

/* Band rows regroup rather than shrink: the four desktop columns become a stack where the
   screenshot sits beside the project name and the decision runs full width beneath. */
@media (max-width:900px){
  [data-row]{
    grid-template-columns:minmax(0,1fr) auto !important;
    grid-template-areas:'num shot' 'title shot' 'dec shot' !important;
    align-items:start !important;
    gap:12px clamp(16px,4vw,24px) !important;
  }
  [data-row] > *:nth-child(1){ grid-area:num; }
  [data-row] > *:nth-child(2){ grid-area:title; }
  [data-row] > *:nth-child(3){ grid-area:dec; padding-left:0 !important; border-left:0 !important; }
  [data-row] > *:nth-child(4){ grid-area:shot; align-self:center; width:clamp(78px,22vw,104px) !important; }
  [data-row] p{ max-width:none !important; }
}
@media (max-width:520px){
  /* Below this the image and a readable column can't share a row, so it moves above the copy
     and the block still reads top-to-bottom as one project. */
  [data-row]{
    grid-template-columns:minmax(0,1fr) !important;
    grid-template-areas:'num' 'shot' 'title' 'dec' !important;
  }
  [data-row] > *:nth-child(4){ width:96px !important; align-self:start; }
}

/* On desktop, a shared row height and one continuous divider between bands is enough for the
   title/decision/screenshot to read as one project. Stacked on mobile that same divider-only
   approach left title, tags, "the decision" label and the image reading as separate floating
   text blocks with no shared edge tying them together. Each band becomes its own bordered card
   instead -- the divider that used to separate rows becomes the gap BETWEEN cards. #bands' own
   border-top is dropped at the same breakpoint: at this width it sat only ~9px under #workGhost
   (measured), close enough to read as a line drawn through the ghost word itself; the top of the
   first card provides its own edge now, further down, so nothing is lost. */
@media (max-width:900px){
  #bands{ border-top:0 !important; margin-top:20px; }
  [data-band]{
    display:block !important; margin:0 clamp(16px,4vw,20px) 16px !important;
    border:1px solid #1c1c1a !important; border-radius:16px !important;
    background:rgba(255,255,255,.018) !important;
  }
  [data-band]:last-of-type{ margin-bottom:clamp(16px,4vw,20px) !important; }
  [data-band] > div{ padding:0 !important; }

  /* The screenshot runs the full height of the card down the left edge and every word sits to
     its right, so the card reads as one object instead of a stack of loose text blocks. The
     image is flush to the card's rounded corner -- no padding on the row, the padding moves
     onto the text column. */
  [data-band] [data-row]{
    display:grid !important;
    grid-template-columns:clamp(104px,29vw,136px) minmax(0,1fr) !important;
    grid-template-rows:auto auto 1fr !important;
    align-items:start !important;
    gap:0 !important;
  }
  /* 01 */
  [data-band] [data-row] > span{ grid-column:2; grid-row:1; padding:18px 18px 0 clamp(14px,4vw,18px); }
  /* title + tagline + tags */
  [data-band] [data-row] > div:nth-of-type(1){ grid-column:2; grid-row:2; padding:6px 18px 0 clamp(14px,4vw,18px); }
  /* the decision */
  [data-band] [data-row] > div:nth-of-type(2){
    grid-column:2; grid-row:3; align-self:end;
    margin:16px 18px 18px clamp(14px,4vw,18px);
    padding:14px 0 0 0 !important;
    border-left:0 !important; border-top:1px solid #1c1c1a;
  }
  /* the screenshot */
  [data-band] [data-shot]{
    grid-column:1; grid-row:1 / -1; align-self:stretch;
    width:100% !important; aspect-ratio:auto !important; min-height:100%;
    border-radius:15px 0 0 15px !important;
    box-shadow:none !important; border-right:1px solid #1c1c1a;
  }
  [data-band] [data-shot] img{ object-position:top center !important; }

  [data-band] h3{ font-size:clamp(22px,6vw,28px) !important; }
  [data-band] [data-row] > div:nth-of-type(1) p{ font-size:13.5px !important; }
}
@media (max-width:400px){
  [data-band] [data-row]{ grid-template-columns:106px minmax(0,1fr) !important; }
}

/* --- hero water surface ---
   The mirror is foreshortened to half height and pushed back down by translateY(50%) so it
   starts at the waterline and ends inside #heroSpacer. These keyframes set transform outright,
   so the scale has to live here as well as on the element -- a full-height mirror runs through
   the sub-line and CTA. */
@keyframes reflWobble{
  0%{ transform:scaleY(-.5) skewX(0deg) scaleX(1) translateY(50%); filter:blur(4px); }
  26%{ transform:scaleY(-.5) skewX(-1.6deg) scaleX(1.012) translateY(50%); filter:blur(2.6px); }
  54%{ transform:scaleY(-.5) skewX(1deg) scaleX(.995) translateY(50%); filter:blur(2px); }
  78%{ transform:scaleY(-.5) skewX(-.4deg) scaleX(1.002) translateY(50%); filter:blur(1.7px); }
  100%{ transform:scaleY(-.5) skewX(0deg) scaleX(1) translateY(50%); filter:blur(1.6px); }
}
@keyframes reflIdle{
  0%,100%{ transform:scaleY(-.5) skewX(0deg) translateY(50%); }
  50%{ transform:scaleY(-1) skewX(-.5deg); }
}

/* --- hero scroll cue --- */
.i-scroll:hover, .i-scroll:focus-visible{ border-color:var(--accent) !important; gap:18px !important; }

/* --- about ---
   Source order is copy-then-photo now. The skills list moved out of the photo column and sits
   under the body copy, so the left column is one continuous read: heading, rule, paragraphs,
   skills. The photo column stretches (align-items:stretch on .about-row) to exactly the height
   of that copy and takes an equal share of the width, so it reads as a slab matched to the text
   block rather than a thumbnail beside it. --- */
@media (min-width:901px){
  .about-text{ order:1; flex:1 1 0 !important; }
  .about-media{ order:2; flex:1 1 0 !important; }
}
@media (max-width:900px){
  /* Stacked, the photo has no sibling to take its height from, so it gets an explicit ratio.
     It follows the copy and the skills list -- the BIO ghost sits behind the heading at the top
     of the section, which is text over a decorative stroke, not an opaque block covering it. */
  .about-row{ align-items:flex-start !important; }
  .about-text{ order:1; flex:1 1 100% !important; }
  .about-media{ order:2; flex:1 1 100% !important; max-width:none !important; margin-top:clamp(10px,3vw,22px) !important; }
  .about-media > div{ aspect-ratio:4/5; min-height:0 !important; }
}
@media (max-width:900px) and (min-width:641px){
  .about-media > div{ aspect-ratio:3/2; }
}
@media (max-width:520px){
  .about-media > div{ aspect-ratio:4/4.4; }
}

@media (max-width:640px){
  .nav-role{ display:none !important; }
}

/* --- mobile: nav links need a real tap target, not a 13px line of text --- */
@media (max-width:900px){
  nav ul{ gap:clamp(14px,3.6vw,22px) !important; }
  nav ul a, nav ul summary{ padding-top:10px !important; padding-bottom:10px !important; }
  #contact{ min-height:auto !important; padding-bottom:clamp(72px,16vw,120px) !important; }
  /* same split as desktop: the full gap sits above #about, half of it either side of the
     #about/#contact seam */
  #about{ padding-top:clamp(72px,14vw,110px) !important; padding-bottom:clamp(36px,7vw,55px) !important; }
  #contact{ padding-top:clamp(36px,7vw,55px) !important; }
}
/* Ghost words are decorative; on narrow screens they were wider than the viewport and forced
   a horizontal scroll on some Android browsers. */
@media (max-width:600px){
  #heroGhost{ font-size:clamp(64px,17vw,110px) !important; }
  #workGhost{ font-size:clamp(84px,26vw,180px) !important; }
}

/* --- mobile headline: two lines per phrase, on purpose ---
   Measured in Space Grotesk 700: "People don't experience" is 11.0x the font size once the
   negative tracking is counted. Below 623px the phrase can no longer hold a single line, so from
   620px down the break is switched on and the size is driven by viewport width (7.6vw) so the
   longer of the two lines always clears the available column. The indents are dropped there to
   reclaim the width, and the reflection is kept on identical metrics so it stays in register. */
br[data-mob]{ display:none; }
@media (max-width:620px){
  br[data-mob]{ display:inline; }
  #heroHeadline, #reflection{
    /* Upper bound is 41px, not 34px: 6.6vw crosses 41px at 621px, so the size is continuous
       across the breakpoint instead of jumping 20% on a 1px change. */
    font-size:clamp(22px,7.6vw,41px) !important;
    letter-spacing:-.038em !important;
    max-width:none !important;
    margin-left:0 !important;
  }
  .hero-block{ margin-left:0 !important; }
  #heroHeadline [data-line="b"], #reflection [data-refl="b"]{ margin-left:clamp(12px,3.4vw,26px) !important; }
}

/* --- work bands: the project title picks up its own project colour on hover, not the
   site accent. The whole band is an <a>, so the global a:hover rule was tinting the
   inherited h3 purple regardless of which project it belonged to. --- */
[data-band] h3{ transition:color .35s ease; }
[data-band]:hover{ color:#e9e7df; }
[data-band="nitefind"]:hover h3{ color:#cf82ea !important; }
[data-band="smiteforge"]:hover h3{ color:#e0b84a !important; } /* 10.8:1 */
[data-band="zentra"]:hover h3{ color:#4fbf82 !important; } /* 8.8:1 */
[data-band="amun"]:hover h3{ color:#a0a0a0 !important; }

/* --- one continuous scroll ---------------------------------------------------------------
   Scroll snapping (even proximity) is what made the page feel like it was taking the scroll
   away from you, most obviously on a phone where a swipe would be pulled back to a section
   edge instead of carrying. Removed entirely; nothing else here depends on it. */

/* --- hero rhythm ---
   The second phrase now sits the same distance from the line beneath it as it does from the
   phrase above it. The reflection is foreshortened to half height and anchored at the
   waterline (scaleY(-.5) translateY(50%)) so it fits a ~1.3x well instead of needing 2.8x,
   which keeps the sub-line and "SEE THE WORK" inside the first viewport on load. Any change
   to the mirror's scale or mask fade has to move #heroSpacer with it. */
#heroHeadline [data-line="b"], #reflection [data-refl="b"]{ margin-top:clamp(10px,1.2vw,18px); }

/* --- mobile nav: everything collapses into one dropdown, with the project list as a second
   dropdown nested inside it --- */
@media (max-width:900px){
  nav{ position:relative; }
  #navToggle{ display:inline-flex !important; }
  nav[data-open="true"] #navToggle{ border-color:var(--accent) !important; color:var(--accent-text) !important; }
  nav[data-open="true"] #navToggle span{ transform:rotate(180deg); }
  #navMenu{
    display:none !important; position:absolute; top:calc(100% + 12px);
    left:clamp(16px,4vw,24px); right:clamp(16px,4vw,24px); z-index:20;
    flex-direction:column; align-items:stretch; gap:0 !important;
    padding:6px; border:1px solid #1c1c1a; border-radius:14px; background:#0b0b0a;
    box-shadow:0 28px 60px -22px rgba(0,0,0,.95);
  }
  nav[data-open="true"] #navMenu{ display:flex !important; }
  #navMenu > li{ border-bottom:1px solid #141413; }
  #navMenu > li:last-child{ border-bottom:0; }
  #navMenu > li > a, #navMenu > li summary{
    display:flex !important; align-items:center; justify-content:space-between;
    min-height:48px; padding:12px 14px !important; font-size:14px !important;
  }
  /* the nested project list drops into the flow of the panel rather than floating beside it */
  #navMenu details > ul{
    position:static !important; min-width:0 !important; margin:0 0 8px !important;
    padding:0 0 0 14px !important; border:0 !important; background:transparent !important;
    box-shadow:none !important; gap:0 !important;
  }
  #navMenu details > ul a{ min-height:44px; font-size:13px !important; padding:10px 12px !important; }
}

/* --- mobile hero: more air between the eyebrow, the headline and the sub-line, and a
   shallower reflection well so nothing is pushed under the fold --- */
@media (max-width:900px){
  #hero{ min-height:100dvh !important; padding:96px clamp(22px,6vw,40px) clamp(28px,4vh,44px) !important; justify-content:center !important; }
  #heroSpacer{ height:calc(1.15 * clamp(22px,7.6vw,41px)) !important; }
  .hero-block{ margin-top:clamp(26px,6vw,38px) !important; }
  #heroFoot{ margin-top:clamp(28px,7vw,42px) !important; gap:clamp(34px,9vw,48px) !important; padding-left:0 !important; }
  #heroFoot p{ padding-left:18px !important; font-size:15px !important; line-height:1.75 !important; }
}
@media (max-width:620px){
  #heroSpacer{ height:calc(1.05 * clamp(22px,7.6vw,41px)) !important; }
}

/* --- page grid ---
   Nav, hero, work rows, bio and contact all resolve to the same 1280px column with the same
   clamp(24px,5vw,64px) gutter. The work rows used to run in a 1420 column and the hero had no
   column at all (padding only), so past ~1400px three different left edges were visible at once. */
#work [data-band] > div{ max-width:1280px; }

/* --- 2.5.5 target size (AAA): 44x44 for every pointer target ---
   The nav read at 21-27px tall on desktop (mobile was already padded out). Height is added as
   centred padding so the type sits exactly where it did before. */
/* The 44px box belongs to the list item, not the anchor: each nav anchor draws its hover
   underline as a background at "left bottom / 0% 1px", so making the anchor itself 44px tall
   would detach that underline from the words and park it at the bottom of the target. The
   anchors also carry an inline display:inline-block, which would beat a stylesheet display:flex
   and leave their text stuck at the top of the box while <summary> centred. */
#navMenu > li, nav > ul > li{ display:flex; align-items:center; min-height:44px; }
/* the anchors carry padding-bottom:5px (the underline's gap); summary matches it so all four
   labels centre on the same line */
/* Height is added as padding-TOP on the links themselves, so the clickable box really is 44px
   (an li wrapper is not the target) while each link's hover underline -- a background drawn at
   "left bottom / 0% 1px" -- stays 5px under the words instead of dropping to the bottom of a
   taller box. 21.5 + 17.5 line + 5 = 44. */
nav > ul a, nav > ul summary{ padding-top:21.5px; padding-bottom:5px; }
nav > a{ min-height:44px; display:flex; align-items:center; }
nav button{ min-height:44px; }
#navMenu details > ul a{ min-height:44px; }
#navMenu details > ul li{ display:flex; align-items:center; min-height:44px; }
footer a{ min-height:44px; display:inline-flex; align-items:center; }
.i-scroll{ min-height:44px; }
#contact a[href^="mailto"]{ min-height:44px; display:inline-flex; align-items:center; }

.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; }
