/* ==========================================================================
   Studio Brazier — styles
   Design tokens first, then layout, then components.
   ========================================================================== */

/* Self-hosted webfonts, so the site makes no third-party requests.
   Plus Jakarta Sans — body text, variable 200–800. SIL OFL 1.1.
   Lilita One — display, a single weight (400) only. SIL OFL 1.1.
   Licences in assets/fonts/. */
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/plus-jakarta-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/plus-jakarta-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Lilita One";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/lilita-one-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Lilita One";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/lilita-one-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Colour */
  --cream: #faf6ee;
  --grey: #ececec;
  --amber: #efb94b;
  --ink: #191712;
  --ink-soft: #4a463d;
  --overlay: rgba(35, 33, 30, 0.94);
  --white: #ffffff;

  /* Type */
  --font-display: "Lilita One", "Arial Black", "Helvetica Neue", sans-serif;
  --font-body: "Plus Jakarta Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* Layout */
  --container: 1160px;
  --gutter: 40px;
  --grid-gap: 40px;
  --radius: 20px;
  --radius-sm: 10px;

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

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

body {
  margin: 0;
  /* Keep the footer at the bottom of the viewport on short pages:
     the body fills the screen and <main> absorbs the spare height. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh; /* mobile browsers: exclude the collapsing URL bar */
  font-synthesis: none;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 19px;
  font-weight: 400;
  line-height: 1.85;
  letter-spacing: 0.005em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }

main { flex: 1 0 auto; }

a { color: inherit; }

p { margin: 0 0 1.6em; }
p:last-child { margin-bottom: 0; }

strong, b { font-weight: 700; }

.container {
  width: 100%;
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Skip link ---------------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: 16px;
  top: -100px;
  z-index: 20;
  padding: 12px 20px;
  background: var(--ink);
  color: var(--white);
  border-radius: var(--radius-sm);
  font-size: 16px;
  font-weight: 700;
  text-decoration: none;
  transition: top 0.2s var(--ease);
}
.skip-link:focus { top: 16px; }

/* Header ------------------------------------------------------------------- */
.site-header {
  padding-block: 48px 0;
  background: var(--cream);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

/* The lockup — pill included — is artwork, so the header just sizes it. */
.logo {
  display: inline-block;
  line-height: 0;
  border-radius: var(--radius);
}
.logo img {
  width: 206px;
  height: auto;
  transition: transform 0.2s var(--ease);
}
.logo:hover img { transform: scale(1.03); }
.logo:active img { transform: scale(0.99); }

.site-nav {
  display: flex;
  align-items: center;
  gap: 28px;
}
.site-nav a {
  flex-shrink: 0;
  font-size: 19px;
  text-decoration: none;
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
  transition: border-color 0.2s var(--ease);
}
.site-nav a:hover,
.site-nav a[aria-current="page"] { border-bottom-color: var(--ink); }

/* Social links — header and footer share these rules. */
.social-links {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: 16px;
}
.social-link {
  display: inline-flex;
  color: inherit;
  opacity: 0.85;
  transition: opacity 0.2s var(--ease);
}
.social-link:hover,
.social-link:focus-visible { opacity: 1; }
.social-link svg {
  width: 22px;
  height: 22px;
  fill: currentColor;
}

/* Focus visibility --------------------------------------------------------- */
:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}
/* The panel behind the control is near-black, so the ring must be light. */
.tile__toggle:focus-visible {
  outline: 3px solid var(--white);
  outline-offset: -7px;
  border-radius: calc(var(--radius) - 4px);
}

/* Intro -------------------------------------------------------------------- */
.intro {
  background: var(--cream);
  padding-block: 118px 60px;
}
.intro p {
  max-width: 688px;
  font-size: 19px;
}
.intro .name {
  font-family: var(--font-display);
  /* Set larger than the body copy, as in the design. line-height is pinned to 1
     so the taller inline box can't stretch the line it sits on — at 1.35em
     Lilita One's content box is 29px against the paragraph's 35px strut. */
  font-size: 1.35em;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

/* Work section ------------------------------------------------------------- */
.work {
  background: var(--grey);
  /* The grid's own bottom margin supplies part of the space beneath it. */
  padding-block: 64px calc(72px - var(--grid-gap));
}

.section-label {
  margin: 0 0 48px;
  font-family: var(--font-display);
  /* Same computed size as .intro .name: 1.35em of the body's 19px. */
  font-size: 1.35em;
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

/* Masonry grid ------------------------------------------------------------- */
/* Masonry, without CSS multi-column.
   column-count fragments its children across "columns" at the engine level,
   and that fragmentation does not reliably coexist with a child (or even
   the column item itself) being promoted to its own GPU compositing
   layer — which every tile's hover transform and hover-panel fade do. In
   testing this showed up two ways depending on what got promoted: tiles
   painted a second time at a pre-fragmentation position after scrolling,
   or a tile's rounded corners failing to clip a composited child. Neither
   is a mistake in a specific rule; the fragmentation model itself is the
   conflict, so no per-element fix holds reliably.
   Instead, assets/js/main.js measures each item's real height and places
   it explicitly on an ordinary grid — a single column of many 1px auto-rows,
   with every item's grid-row and grid-column set directly. No fragments,
   so composited descendants behave like they do everywhere else on the
   page. Without JavaScript this stays a plain single-column block list
   (see .grid below); JS is what turns it into the masonry. */
.grid {
  display: block;
}

.grid > * {
  margin-bottom: var(--grid-gap);
}

.grid.is-masonry {
  display: grid;
  grid-auto-rows: 1px;
  row-gap: 0;
  column-gap: var(--grid-gap);
  /* Grid's default stretch would resize every item to fill its allocated
     track — fatal here, since tracks start as a single 1px auto-row before
     main.js has measured anything and assigned the real span. Items keep
     their own natural height throughout; grid-row only reserves the matching
     vertical space around them. */
  align-items: start;
}

.grid.is-masonry > * {
  margin-bottom: 0;
  min-width: 0;
}

/* Filter chips --------------------------------------------------------------
   Replaced the category label cards that used to sit inside the grid — see
   main.js for the filtering logic. */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0 0 40px;
}

.filter-chip {
  appearance: none;
  padding: 9px 20px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--amber);
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.3;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease), transform 0.2s var(--ease);
}
.filter-chip:not([aria-pressed="true"]):hover { filter: brightness(0.94); }
.filter-chip:active { transform: translateY(1px); }
.filter-chip[aria-pressed="true"] {
  background: var(--ink);
  color: var(--amber);
}
.filter-chip[aria-pressed="true"]:hover { filter: brightness(1.3); }

/* Project tile ------------------------------------------------------------- */
.tile {
  position: relative;
  /* Artwork, hover panel and an invisible ratio spacer share one grid
     cell, so the card is never shorter than its own hover copy: height =
     max(the designed aspect ratio, the panel's content). The ratio lives on
     the spacer, not the tile — aspect-ratio directly on a box with
     overflow: hidden is allowed to clip its content instead of growing. */
  display: grid;
  /* Contain descendants' stacking so composited children can't paint
     outside the card while animating. */
  isolation: isolate;
  /* One column pinned to the card's width — an auto column would size to
     the copy's longest unwrapped line and overflow. */
  grid-template-columns: minmax(0, 1fr);
  width: 100%;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--white);
  -webkit-tap-highlight-color: transparent;
}

/* Set by main.js when a filter chip is active and this tile doesn't match. */
.tile--filtered-out {
  display: none;
}

/* Invisible control covering the tile: gives the hover panel keyboard and
   assistive-tech semantics without wrapping the whole card in a button. */
.tile__toggle {
  position: absolute;
  inset: 0;
  z-index: 2;
  appearance: none;
  padding: 0;
  border: 0;
  background: none;
  border-radius: inherit;
  cursor: pointer;
  font: inherit;
  color: inherit;
}

/* The designed proportions, as an invisible first grid item. */
.tile::before {
  content: "";
  grid-area: 1 / 1;
  aspect-ratio: var(--ratio, 1);
}

.tile__media {
  /* A normal, in-flow grid item stacked on the same cell as the ratio
     spacer and the panel — not position: absolute. That combination
     (out-of-flow + clip-path) forces the browser to composite this box on
     its own layer inside a CSS multi-column ancestor, and Chromium's
     layer-raster cache does not reliably invalidate on scroll for that
     case: hovering a tile, scrolling away and back left a stale frame
     painted (an emptied tile here, a stuck hover panel elsewhere), with
     computed styles reporting the correct state throughout — a paint bug,
     not a state bug.
     overflow: hidden alone clips the child during its hover transform, no
     compositing trick required. min-width/height: 0 opt out of this grid
     item's automatic minimum size, which is otherwise the image's
     intrinsic size and would set a floor under the card's height — per
     spec this is already 0 once overflow isn't visible, the explicit
     values just make it unambiguous. */
  grid-area: 1 / 1;
  overflow: hidden;
  border-radius: inherit;
  min-width: 0;
  min-height: 0;
}

/* Each export is a finished tile with its background baked in, so it fills the
   card. The tile ratios come from the design and differ slightly from the
   exports, so `cover` trims the excess — every logo is centred on a flat field,
   which is why the trim never touches artwork. */
.tile__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* The transform below is what gets this element promoted to its own
     compositing layer on hover, and a composited child's own layer is not
     reliably clipped by an ancestor's overflow: hidden alone (this is the
     square-corners-while-hovering bug). Rounding the element itself closes
     that gap regardless of what layer it ends up on — no compositing trick
     on the ancestor required, so it doesn't reintroduce the stale-paint
     scroll bug that trick caused. */
  border-radius: inherit;
  transition: transform 0.45s var(--ease);
}

.tile:hover .tile__media img,
.tile.is-open .tile__media img { transform: scale(1.03); }

/* Hover / tap panel */
.tile__panel {
  grid-area: 1 / 1;
  /* Grid items take z-index without needing position, which is what keeps
     this above the media backdrop despite neither being positioned. */
  z-index: 1;
  /* This element's own opacity transition promotes it to its own
     compositing layer while fading in, and — same as the artwork below —
     a composited layer isn't reliably clipped by the ancestor .tile's
     overflow: hidden alone: without this, the tile's own background color
     shows through the corners while the panel is visible (Chrome/Safari).
     Rounding the panel itself, independent of the ancestor's clip, is
     what actually fixes it. */
  border-radius: inherit;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 20px;
  padding: 28px;
  background: var(--overlay);
  color: var(--white);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.28s var(--ease), visibility 0.28s var(--ease);
}

.tile:hover .tile__panel,
.tile:focus-within .tile__panel,
.tile.is-open .tile__panel {
  opacity: 1;
  visibility: visible;
}

.tile__title {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.tile__desc {
  margin: 0;
  font-size: 18px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.92);
}

.tile__tag {
  align-self: flex-start;
  padding: 11px 20px;
  background: var(--amber);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* Tile heights — ratios taken from the design (column width 360px).
   The colour behind each image is sampled from that image's own edge, so
   nothing flashes white while it loads and no seam shows at the corners. */
.tile--pictorial-meadows { --ratio: 360 / 419; background: #4d2680; }
.tile--justice-digital   { --ratio: 360 / 359; background: #ffffff; }
.tile--come-home         { --ratio: 360 / 439; background: #d7b659; }
.tile--comoodle          { --ratio: 360 / 280; background: #ffffff; }

.tile--one-one-five      { --ratio: 360 / 355; background: #0395cc; }
.tile--caja              { --ratio: 360 / 326; background: #80bb27; }
.tile--sixty-five        { --ratio: 360 / 316; background: #ffffff; }
.tile--elefriends        { --ratio: 360 / 356; background: #ffcc00; }
.tile--make-it-click     { --ratio: 360 / 134; background: #00c6e1; }

.tile--thrive            { --ratio: 360 / 488; background: #ffffff; }
.tile--good-things       { --ratio: 360 / 223; background: #001364; }
.tile--jay-brazier       { --ratio: 360 / 380; background: #ffd5bf; }
.tile--mindwell          { --ratio: 360 / 277; background: #513b7e; }

/* Contact ------------------------------------------------------------------ */
.contact {
  background: var(--cream);
  padding-block: 92px 100px;
}
.contact p { max-width: 668px; }

.email-link {
  font-family: var(--font-display);
  /* Set larger than the body copy, as in the design. line-height is pinned to 1
     so the taller inline box can't stretch the line it sits on — at 1.35em
     Lilita One's content box is 29px against the paragraph's 35px strut. */
  font-size: 1.35em;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  text-decoration: none;
  border-bottom: 2px solid var(--ink);
  white-space: nowrap;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.email-link:hover { color: var(--ink-soft); border-color: var(--ink-soft); }

/* Footer ------------------------------------------------------------------- */
.site-footer {
  background: var(--amber);
  padding-block: 52px 60px;
  color: var(--ink);
}
.site-footer__inner {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 20px 32px;
}
.site-footer p {
  margin: 0;
  font-size: 17px;
  line-height: 1.6;
}

/* About page --------------------------------------------------------------- */
.about {
  padding-block: 118px 96px;
}
.about__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 390px;
  gap: 50px;
  align-items: start;
}
.about__body p { max-width: 720px; }
.about__body .lede {
  font-family: var(--font-display);
  /* Set larger than the body copy, as in the design. line-height is pinned to 1
     so the taller inline box can't stretch the line it sits on — at 1.35em
     Lilita One's content box is 29px against the paragraph's 35px strut. */
  font-size: 1.35em;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}
.about__portrait {
  width: 100%;
  height: auto;
  aspect-ratio: 390 / 420;
  object-fit: cover;
}

/* 404 ---------------------------------------------------------------------- */
.error-page { padding-block: 118px 120px; }
.error-page h1 {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-size: 46px;
  font-weight: 400;
  line-height: 1.1;
  text-transform: uppercase;
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* Two columns — the column count itself is set by main.js reading this
   breakpoint (kept in sync with --breakpoint-2col below); this block only
   carries the spacing token change. */
@media (max-width: 1060px) {
  :root { --gutter: 36px; --grid-gap: 28px; --breakpoint-2col: 1; }
}

@media (max-width: 900px) {
  body { font-size: 18px; }
  .logo img { width: 182px; }
  .site-header { padding-block: 32px 0; }
  .intro { padding-block: 72px 48px; }
  .work { padding-block: 52px calc(56px - var(--grid-gap)); }
  .contact { padding-block: 68px 72px; }
  .about { padding-block: 72px 72px; }
  .about__inner {
    grid-template-columns: 1fr;
    gap: 40px;
  }
  .about__portrait {
    max-width: 420px;
    aspect-ratio: 390 / 420;
  }
  .site-footer { padding-block: 40px 44px; }
  .site-footer p { font-size: 16px; }
}

/* Single column */
@media (max-width: 640px) {
  :root { --gutter: 22px; --grid-gap: 20px; --radius: 16px; --breakpoint-1col: 1; }
  body { font-size: 17px; line-height: 1.75; }

  .logo img { width: 164px; }
  .site-nav { gap: 18px; }
  .site-nav a { font-size: 17px; }
  .social-links { gap: 12px; }
  .social-link svg { width: 20px; height: 20px; }

  .intro { padding-block: 52px 40px; }
  .section-label { margin-bottom: 28px; }
  .filters { gap: 10px; margin-bottom: 28px; }
  .filter-chip { padding: 8px 16px; font-size: 14px; }

  .error-page h1 { font-size: 34px; }
}

/* The very narrowest supported phones (iPhone SE and similar, ~320px) don't
   have room for the logo plus "About me" plus both icons at the sizes above
   without a few more pixels back. */
@media (max-width: 340px) {
  :root { --gutter: 16px; }
  .logo img { width: 148px; }
  .site-nav { gap: 12px; }
  .social-links { gap: 8px; }
}

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