/* ------------------------------------------------------------------
   Laura Elizabeth Burnett — artist portfolio
   One stylesheet. Edit the variables below to change the whole site.
   ------------------------------------------------------------------ */

:root {
  --ink:        #000;
  --grey-light: #a9a9a9;   /* "Artist Portfolio" subtitle */
  --grey-mid:   #5c5c5c;   /* body copy */
  --grey-rule:  #e2e2e2;   /* hairlines */
  --tile-rule:  #cfcfcf;   /* the outline around the two landing-page cards */
  --paper:      #fff;

  --display: "Bebas Neue", Impact, "Haettenschweiler", sans-serif;
  --serif: "EB Garamond", Garamond, "Adobe Garamond Pro", Baskerville,
           "Times New Roman", Times, serif;
  --sans:  -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --sans-text: "Helvetica Neue", "HelveticaNeue-Light", Helvetica, Arial, sans-serif;

  --gutter: clamp(1.25rem, 2.6vw, 2.75rem);
  --gap:    clamp(1.75rem, 4.3vw, 4.25rem);  /* text-to-tile, both sides */
  --bar-h:  4.6rem;
  --hero-pad-y: clamp(1.75rem, 4vw, 3.25rem);  /* also feeds the tile height cap */                        /* navbar and news bar share this */
  --measure: 62ch;
}

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

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

body {
  margin: 0;
  overflow-x: clip;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 18px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { display: block; max-width: 100%; height: auto; }
a   { color: inherit; }

.wrap { padding-inline: var(--gutter); }

/* --- navbar ------------------------------------------------------- */

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  min-height: var(--bar-h);
  padding-block: 0.5rem;
  border-bottom: 1px solid var(--grey-rule);
  font-family: var(--sans);
}

.nav__mark {
  /* Matches the nav links rather than the body serif: the whole bar is one
     piece of UI chrome, so it is set in one face. */
  font-family: var(--sans);
  font-size: 0.95rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
}

.nav__links {
  display: flex;
  gap: clamp(1.25rem, 3vw, 2.75rem);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.72rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}

.nav__links a {
  text-decoration: none;
  color: var(--grey-mid);
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
  transition: color .18s, border-color .18s;
}
.nav__links a:hover            { color: var(--ink); border-bottom-color: var(--ink); }

/* The Instagram mark sits in the same row as the links but is an icon, not
   text: no underline on hover (there is nothing to underline), and the flex
   centring stops the 17px square riding above the 0.72rem capitals. */
.nav__social a {
  display: flex;
  align-items: center;
  border-bottom: 0;
  color: var(--grey-mid);
}
.nav__social a:hover,
.nav__social a:focus-visible { color: var(--ink); border-bottom: 0; }
.nav__links a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--ink); }

/* --- hero --------------------------------------------------------- */

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) min-content minmax(0, 1fr);
  gap: var(--gap);                 /* text-to-tile, both sides, equal */
  align-items: center;             /* tiles centred against the text block */
  /* NOT max-width + margin-inline:auto. As a grid item of <main>, auto inline
     margins disable stretch and absorb the free space instead, so .hero
     shrinks to fit its content, the 1fr tracks get nothing, and the tiles
     collapse to their 1px borders. A definite width keeps stretch out of it. */
  width: min(100%, 96rem);         /* ceiling on very wide monitors */
  margin-inline: auto;
  /* A small but ALWAYS present edge. Was 0 (true full bleed), which looked
     right only on a window wide enough to hit the 96rem ceiling, where the
     hero centres and gets margins for free — on a laptop the tiles ran into
     the screen edge. Much smaller than the site --gutter so the tiles give up
     as little width as possible. */
  padding-inline: clamp(0.9rem, 1.4vw, 1.5rem);
  padding-block: var(--hero-pad-y);
}

.hero__text { text-align: center; }

.hero__name {
  margin: 0 0 0.9rem;
  font-family: var(--display);
  font-weight: 400;          /* Bebas ships one weight; 700 would fake it */
  font-size: clamp(2.8rem, 8.6vw, 7.4rem);
  line-height: 0.88;
  letter-spacing: 0.005em;
  color: var(--ink);
}
.hero__name span { display: block; }

.hero__subtitle {
  margin: 0 0 1.5rem;
  font-weight: 300;
  font-size: clamp(0.82rem, 1.25vw, 1.02rem);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--grey-light);
}

.hero__tagline {
  margin: 0;
  max-width: none;   /* the column is already sized by the name above it */
  font-family: var(--serif);   /* same face as the body copy on every page */
  font-weight: 400;
  font-size: clamp(1.02rem, 1.3vw, 1.125rem);   /* tops out at body's 18px */
  line-height: 1.7;
  color: var(--grey-mid);
}

/* --- the two collection tiles ------------------------------------- */

/* A tile's two children are both position:absolute, so a tile has no in-flow
   content and its intrinsic width is zero. It therefore needs a DEFINITE
   width — width:100% of its grid area — or content-based sizing (which is
   what justify-self other than stretch triggers) collapses it to nothing.
   max-width then caps it, and justify-self keeps the outer edges flush with
   the page gutters. */
.hero > .tile {
  /* The tile is as wide as its column allows, UNLESS that would make it taller
     than the space between the two bars — in which case the height leads and
     the width follows from the 4:5 ratio. Without this the tile height is
     purely a function of window WIDTH, so a wide-but-short window (any laptop
     once browser chrome is taken off) pushed the news bar past the bottom and
     the landing page scrolled by a few dozen pixels.

     Still a definite width, so stretch never enters into it (see .hero). */
  --tile-h-max: calc(100dvh - 2 * var(--bar-h) - 2 * var(--hero-pad-y));
  width: min(100%, calc(var(--tile-h-max) * 4 / 5));
  aspect-ratio: 4 / 5;  /* portrait: height grows without taking row width */
  justify-self: center; /* when the cap bites, the spare width splits evenly */
}

.tile {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border: 1px solid var(--tile-rule);
  border-radius: 10px;
  text-decoration: none;
  background: var(--paper);
  transition: transform .34s cubic-bezier(.2,.7,.2,1),
              box-shadow .34s cubic-bezier(.2,.7,.2,1);
}

/* the whole tile lifts very slightly and casts a shadow underneath */
.tile:hover,
.tile:focus-visible {
  transform: scale(1.018);
  box-shadow: 0  2px  6px -2px rgba(0, 0, 0, 0.10),
              0 18px 38px -14px rgba(0, 0, 0, 0.32);
}

.tile__grid {
  position: absolute;
  inset: 0;
  border-radius: 9px;
  overflow: hidden;
  display: grid;
  grid-template: 1fr 1fr / 1fr 1fr;
  gap: 2px;
}

.tile__grid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tile__label {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  background: var(--paper);
  border-block: 1px solid var(--tile-rule);  /* matches the card outline; the
                                                tile's own sides close the box */
  padding: clamp(0.6rem, 1.2vw, 0.9rem) clamp(0.7rem, 1.4vw, 1.05rem);
  font-size: clamp(0.95rem, 1.7vw, 1.45rem);
  letter-spacing: 0.01em;
  color: var(--ink);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

.tile__title {
  font-family: var(--display);
  font-weight: 400;          /* Bebas has one weight; 700 would fake it */
  letter-spacing: 0.03em;
  white-space: nowrap;
}


.tile__label small {
  font-family: var(--sans);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--grey-light);
  transition: color .2s;
}

.tile:hover .tile__label small { color: var(--ink); }
.tile:focus-visible           { outline: 2px solid var(--ink); outline-offset: 4px; }

/* --- news bar: mirrors the navbar, sits on the bottom edge ---------- */

.newsbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: var(--bar-h);
  padding-block: 0.5rem;
  border-top: 1px solid var(--grey-rule);
}

.newsbar__mark {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--grey-light);
}

.newsbar__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.newsbar__label {
  font-family: var(--sans);
  font-size: 0.68rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--grey-mid);
  margin-right: 0.35rem;
}

.newsbar__form input[type="email"] {
  width: clamp(9rem, 20vw, 15rem);
  padding: 0.5rem 0.7rem;
  font-family: var(--sans-text);
  font-weight: 300;
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 0;
}
.newsbar__form input[type="email"]::placeholder { color: var(--grey-light); }
.newsbar__form input[type="email"]:focus { outline: 2px solid var(--ink); outline-offset: 1px; }

.newsbar__form button {
  padding: 0.55rem 1.2rem;
  font-family: var(--sans);
  font-size: 0.68rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 0;
  cursor: pointer;
  white-space: nowrap;
  transition: background .2s, color .2s;
}
.newsbar__form button:hover { background: var(--paper); color: var(--ink); }

/* --- inverted news bar -------------------------------------------------

   A TEST. Add `invert` to the section's class list to switch a page over:
       <section class="newsbar wrap invert" id="news">
   Remove the word to go back. Nothing else needs touching, and the ordinary
   bar above is untouched, so the two can sit side by side while deciding.

   To flip every page at once, from the repo root:
       sed -i '' 's/class="newsbar wrap"/class="newsbar wrap invert"/' *.html
   and swap the two halves of that expression to undo it.                 */

.newsbar.invert {
  background: var(--ink);
  border-top-color: var(--ink);
}
.newsbar.invert .newsbar__mark  { color: rgba(255, 255, 255, 0.55); }
.newsbar.invert .newsbar__label { color: rgba(255, 255, 255, 0.85); }

/* The field stays transparent with a white rule rather than becoming a white
   block, so the button is the only bright thing in the bar and still reads as
   the thing to press. */
.newsbar.invert .newsbar__form input[type="email"] {
  background: transparent;
  color: var(--paper);
  border-color: rgba(255, 255, 255, 0.65);
}
.newsbar.invert .newsbar__form input[type="email"]::placeholder {
  color: rgba(255, 255, 255, 0.45);
}
.newsbar.invert .newsbar__form input[type="email"]:focus {
  outline-color: var(--paper);
  border-color: var(--paper);
}

.newsbar.invert .newsbar__form button {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--paper);
}
.newsbar.invert .newsbar__form button:hover {
  background: transparent;
  color: var(--paper);
}

/* On the interior pages the bar closes the content instead of being pinned to
   the viewport, so it needs a little clear space above it. The landing page
   gets none: it is already pushed to the bottom edge by the flex column. */
body:not(.home) .newsbar { margin-top: clamp(1.5rem, 3.5vh, 5rem); }

/* --- landing page fills the viewport, so the news bar lands on the
       bottom edge on load. min-height, not height: if the window is short
       the page simply scrolls instead of clipping. ---------------------- */

body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
body > main { flex: 1 1 auto; }   /* the news bar is pushed to the bottom edge
                                     on any page whose content is shorter than
                                     the window, not just the landing page */
body.home main {
  flex: 1 1 auto;
  display: grid;
  align-content: center;
}

/* --- interior pages ---------------------------------------------- */

.page-head {
  /* vh, not vw. Vertical space should answer to how tall the window is: on a
     short laptop window the vw-based padding spent 152px on air above the
     prose and tipped the page into a needless scroll. */
  padding-block: clamp(2rem, 5.5vh, 6rem) clamp(1.25rem, 3vh, 3.5rem);
}

.page-head h1 {
  margin: 0 0 0.75rem;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.4rem, 7vw, 5rem);
  line-height: 1;
  letter-spacing: 0.01em;
}

.page-head p {
  margin: 0;
  max-width: var(--measure);
  color: var(--grey-mid);
}

/* gallery: explicit justified ROWS.

   Each row is its own flex line, stretched across the full width. Inside a
   row every figure gets `flex: var(--ar) var(--ar) 0` — a basis of zero and a
   grow proportional to its aspect ratio — so the whole available width (after
   gaps) is shared out in proportion to the ratios. Width proportional to ratio
   means every image in the row lands on exactly the same height, with nothing
   cropped or stretched, and the row's right edge comes out flush.

   The variation comes from how many images a row holds, decided in
   build_gallery.py: row height is about (width - gaps) / (sum of the row's
   ratios), so a row of two portraits is tall and a row of five landscapes is
   short. That is why the page does not read as a grid.

   It must be done per ROW, not per image. flex-grow shares out only the
   LEFTOVER space, so width is proportional to ratio only while every item in
   the row carries the same weight. Giving each image its own weight broke the
   justification and scattered the heights within each row. */
.gallery {
  display: flex;
  flex-direction: column;
  gap: clamp(0.5rem, 1.25vw, 1rem);
  padding-bottom: clamp(3rem, 7vw, 5rem);
}

.gallery__row {
  display: flex;
  gap: clamp(0.5rem, 1.25vw, 1rem);
  align-items: flex-start;
}

.piece {
  margin: 0;
  min-width: 0;                 /* flex items must be allowed to shrink */
  flex: var(--ar) var(--ar) 0;
}

/* The last row is usually a remainder and would be stretched grotesquely wide
   if it were justified like the others, so it keeps a fixed height instead and
   simply stops short of the right edge. */
.gallery__row--last {
  --row-h: clamp(12rem, 26vw, 30rem);
}
.gallery__row--last .piece {
  flex: 0 0 calc(var(--ar) * var(--row-h));
}

.piece img { width: 100%; cursor: zoom-in; }

.piece figcaption {
  margin-top: 0.65rem;
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--grey-mid);
}
.piece figcaption strong { color: var(--ink); font-weight: 400; font-style: italic; }

/* about */
.about {
  display: grid;
  /* Column one is the prose measure exactly, not a round number larger than
     it: at 38rem the column was 608px against 492px of text, so 116px of dead
     space sat between the words and the photo and no gap value could close it.
     justify-content: start then packs both columns to the left. */
  /* Column two is a definite width, not auto: its only child is absolutely
     positioned and so contributes no intrinsic width. (Same trap as the
     landing-page tiles.) */
  grid-template-columns: minmax(0, var(--measure)) clamp(13rem, 23vw, 22rem);
  justify-content: start;
  gap: clamp(1.5rem, 3vw, 2.75rem);
  align-items: start;
  padding-bottom: clamp(1.5rem, 3.5vh, 5rem);
}

/* The portrait is matched to the prose, top and bottom.

   The source photo is much taller than the text block, so align-self alone
   cannot do it: the taller item sets the row height, and the photo WAS the
   taller item (625px of portrait against 450px of text). Instead the column is
   given a definite width and the image is taken out of flow to fill the row,
   which is then set by the prose alone. The photo therefore starts and ends
   exactly where the text does, at any window width.

   The trade is a crop. object-position is the single value to turn: the second
   number is which part of the photo survives vertically, 0% = keep the top,
   100% = keep the bottom. */
.about__photo {
  position: relative;
  align-self: stretch;
  overflow: hidden;
}
.about__photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  object-position: 50% 30%;
}
.about__prose { max-width: var(--measure); }
.about__prose p { margin: 0 0 1.4rem; }
/* The trailing margin would otherwise put 1.4rem of air between the last line
   of text and the bottom of the row, so the photo would hang below it. */
.about__prose > :last-child { margin-bottom: 0; }
.about__prose h2 {
  margin: 2.5rem 0 0.75rem;
  font-weight: 400;
  font-size: 1.35rem;
  letter-spacing: 0.01em;
}
.about__prose h2:first-child { margin-top: 0; }

/* --- lightbox ----------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(255, 255, 255, 0.97);
}
.lightbox[open-state="on"] { display: grid; }
.lightbox img {
  max-width: 100%;
  max-height: 82vh;
  object-fit: contain;
}
.lightbox figcaption {
  margin-top: 1rem;
  text-align: center;
  font-size: 0.92rem;
  color: var(--grey-mid);
}
.lightbox__close {
  position: absolute;
  top: clamp(0.75rem, 2vw, 1.5rem);
  right: clamp(0.75rem, 2vw, 1.5rem);
  width: 2.75rem;
  height: 2.75rem;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--ink);
  background: none;
  border: 0;
  cursor: pointer;
}

/* --- small screens ----------------------------------------------- */

@media (max-width: 1200px) {
  /* text first, then the two tiles side by side underneath */
  .hero { grid-template-columns: 1fr 1fr; gap: clamp(1.5rem, 4vw, 2.5rem); }
  .hero__text { grid-column: 1 / -1; order: -1; }
  .hero { padding-inline: var(--gutter); }
  .hero > .tile { max-width: none; width: 100%; aspect-ratio: 1 / 1; }
}

/* On a phone a row of four would make each print a thumbnail, so rows unwrap
   into a single column. */
@media (max-width: 560px) {
  .gallery__row { flex-direction: column; }
  .gallery__row--last .piece { flex: 0 0 auto; }
}

@media (max-width: 680px) {
  .nav         { flex-direction: column; align-items: flex-start; gap: 1rem; }
  .newsbar     { flex-direction: column; align-items: flex-start; gap: 1rem;
                 padding-block: 1.5rem; }
  .newsbar__form { flex-wrap: wrap; }
  .hero { grid-template-columns: 1fr; }
  .about       { grid-template-columns: 1fr; }
  /* Stacked, there is no text beside it to match, so the photo returns to
     normal flow at its own aspect ratio. */
  .about__photo { order: -1; position: static; overflow: visible; }
  .about__photo img { position: static; width: auto; height: auto;
                      max-width: 18rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
  .tile:hover, .tile:focus-visible { transform: none; }   /* shadow only */
}
