/* ==========================================================================
   Carson Coots — carsoncoots.com
   Minimal, bold, dark. Small all-caps sans throughout; display face for names.
   ========================================================================== */

:root {
  --bg: #000;
  --fg: #fff;
  --muted: #9a9a9a;
  --muted-2: #bdbdbd;
  --dim: #8f8f8f;
  --line: #1e1e1e;
  --panel: #111;
  --panel-2: #0b0b0b;

  --radius: 12px;
  --radius-lg: 14px;

  /* Layered rather than one soft blob: the tight pass keeps the card in
     contact with the band, the wide ones carry the depth. Tuned to read
     on the near-black panels as well as over a photograph. */
  --shadow-art:
    0 2px 6px rgba(0, 0, 0, 0.34),
    0 10px 22px rgba(0, 0, 0, 0.38),
    0 28px 60px rgba(0, 0, 0, 0.34);

  /* How far a band's photograph is thrown out of focus. */
  --bg-blur: 18px;

  --gutter: 32px;
  --nav-h: 78px;

  --font-ui: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-display: 'Schibsted Grotesk', system-ui, -apple-system, sans-serif;

  /* Video grid columns. Overridable per page: style="--cols:2". */
  --cols: 3;
}

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

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
}

body {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.14em;
  line-height: 1.5;
  text-transform: uppercase;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100vh;
}

a { color: var(--fg); text-decoration: none; }
a:hover { color: var(--muted); }

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

:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* Wins over the display values set on .posts and other flex containers. */
[hidden] { display: none !important; }

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

@keyframes ccfade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.fade { animation: ccfade 0.5s ease both; }

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

/* --------------------------------------------------------------- nav ----- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  column-gap: var(--gutter);
  align-items: center;
  padding: 22px var(--gutter) 34px;
  background: transparent;
  transition: background 0.3s ease, backdrop-filter 0.3s ease, opacity 0.25s ease;
}

.nav.is-scrolled {
  background: linear-gradient(to bottom, rgba(0,0,0,0.75), rgba(0,0,0,0.25));
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  mask-image: linear-gradient(to bottom, #000 0%, #000 62%, rgba(0,0,0,0.5) 84%, rgba(0,0,0,0) 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 62%, rgba(0,0,0,0.5) 84%, rgba(0,0,0,0) 100%);
}

/* Hidden while the video lightbox is open. */
.nav.is-hidden { opacity: 0; pointer-events: none; }

.nav__name { font-weight: 600; }

.nav__links {
  display: flex;
  gap: 28px;
  justify-content: center;
}

.nav__links a { opacity: 0.55; transition: opacity 0.2s ease; }
.nav__links a:hover { opacity: 1; color: var(--fg); }
.nav__links a[aria-current="page"] { opacity: 1; }

.nav__contact {
  display: flex;
  gap: 20px;
  justify-content: flex-end;
}

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

.page {
  padding: 120px var(--gutter) 80px;
  margin: 0 auto;
}

.page--wide { max-width: 1200px; }
.page--narrow { max-width: 760px; }

.lede { margin: 0; max-width: 520px; opacity: 0.6; }

/* Body copy opts out of the all-caps UI treatment. */
.prose {
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted-2);
  font-size: 15px;
  line-height: 1.55;
  text-wrap: pretty;
}

.display {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: none;
  letter-spacing: -0.03em;
  line-height: 0.98;
  margin: 0;
}

.label { opacity: 0.5; }

/* --------------------------------------------------------------- home ---- */

.hero {
  position: relative;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--gutter);
}

.hero__bg,
.hero__still {
  position: absolute;
  inset: 0;
}

.hero__bg { background: radial-gradient(ellipse at 30% 60%, #2a2a2a 0%, #0a0a0a 60%, #000 100%); }

.hero__still {
  background-size: cover;
  background-position: center;
}

.hero__video {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100vw;
  height: 56.25vw;
  min-height: 100vh;
  min-width: 177.78vh;
  transform: translate(-50%, -50%);
  border: 0;
  pointer-events: none;
}

.hero__scrim {
  position: absolute;
  inset: 0;
  /* Two jobs. The radial pool sits under the centred name, which would
     otherwise be swallowed whenever the showcase cuts to a bright shot; the
     linear pass still weights the foot of the frame as before. */
  background:
    radial-gradient(ellipse 58% 42% at 50% 50%, rgba(0,0,0,0.58), rgba(0,0,0,0) 72%),
    linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0) 45%);
}

.hero__scrim--top {
  inset: 0 0 auto 0;
  height: 140px;
  background: linear-gradient(to bottom, rgba(0,0,0,0.6), rgba(0,0,0,0));
}

.hero__name {
  position: relative;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(56px, 15vw, 260px);
  line-height: 0.88;
  text-align: center;
  letter-spacing: -0.035em;
  text-transform: none;
  animation: ccfade 1.2s ease both;
}

.hero__tag {
  position: absolute;
  right: var(--gutter);
  bottom: 44px;
  opacity: 0.6;
}

/* -------------------------------------------------------------- video ---- */

.reel {
  padding: 64px 20px 20px;
}

.reel__grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: 20px;
}

.tile {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--panel);
  cursor: pointer;
  border: 0;
  padding: 0;
  width: 100%;
  color: inherit;
  font: inherit;
  text-align: left;
  display: block;
}

.tile__thumb {
  position: absolute;
  inset: 0;
  background: linear-gradient(140deg, #1c1c1c, #090909);
  background-size: cover;
  background-position: center;
  opacity: 0.85;
  transform: scale(1);
  transition: transform 0.6s cubic-bezier(.2,.7,.2,1), opacity 0.4s ease;
}

.tile:hover .tile__thumb,
.tile:focus-visible .tile__thumb {
  transform: scale(1.04);
  opacity: 1;
}

/* Local mp4 loop. Sits over the poster and fades in once it actually plays,
   so a slow network shows a still rather than a black hole.

   The resting dim is brightness, not opacity. At less than full opacity the
   poster underneath shows through the moving video as a ghost image. */
.tile__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1);
  filter: brightness(0.85);
  transition: opacity 0.45s ease, filter 0.4s ease,
              transform 0.6s cubic-bezier(.2,.7,.2,1);
}

.tile__video.is-ready { opacity: 1; }

.tile:hover .tile__video.is-ready,
.tile:focus-visible .tile__video.is-ready {
  filter: brightness(1);
  transform: scale(1.04);
}

.tile__preview {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  transform: translate(-50%, -50%) scale(1.02);
  border: 0;
  pointer-events: none;
}

.tile__scrim {
  position: absolute;
  inset: auto 0 0 0;
  height: 68%;
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(0,0,0,0.9) 0%,
    rgba(0,0,0,0.6) 35%,
    rgba(0,0,0,0.2) 70%,
    rgba(0,0,0,0) 100%);
}

/* Name and client share one line: name bold, client regular after it. The
   client is the part allowed to truncate, since the name identifies the work. */
.tile__meta {
  position: absolute;
  left: 18px;
  right: 18px;
  bottom: 16px;
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
  pointer-events: none;
}

.tile__title {
  font-weight: 600;
  flex: 0 0 auto;
  white-space: nowrap;
}

.tile__client {
  opacity: 0.7;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Nothing to separate when a clip has no client yet. */
.tile__client:empty { display: none; }

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

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(0,0,0,0.9);
  /* A closed <dialog> must stay hidden, so only [open] gets a display value. */
  display: none;
  align-items: center;
  justify-content: center;
  padding: clamp(24px, 6vw, 96px);
  animation: ccfade 0.25s ease both;
  border: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
}

.lightbox[open] { display: flex; }

.lightbox::backdrop { background: rgba(0,0,0,0.9); }

.lightbox__inner {
  position: relative;
  width: 100%;
  max-width: 1400px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.lightbox__frame {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: #000;
}

.lightbox__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.lightbox__close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  background: rgba(0,0,0,0.55);
  border: 0;
  border-radius: 999px;
  padding: 9px 14px;
  color: #fff;
  font: inherit;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.lightbox__close:hover { background: #fff; color: #000; }

/* Sits below the player, outside the frame, flush left. Same one-line
   treatment as the tile it was opened from. */
.lightbox__meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}

.lightbox__credit {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.lightbox__title { font-weight: 600; }
.lightbox__client { opacity: 0.6; }
.lightbox__client:empty { display: none; }

.lightbox__desc { margin: 0; color: var(--muted); font-size: 14px; }
.lightbox__desc:empty { display: none; }

/* ------------------------------------------------------------ digital ---- */

/* Each project is a full-width colour band. The 20px gutter and gap echo the
   video grid, so black frames every band the way it frames every tile. */
.projects {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* Same 64px as .reel, so both sections start at the same height. */
  padding: 64px 20px 20px;
}

.project {
  position: relative;
  /* Lit charcoal, a shade above the page behind it so the band reads as a
     panel catching light rather than a hole. The pool sits high on the left
     where the art is, with a fainter one opposite to keep the far corner from
     going dead flat. A project that supplies its own photograph paints over
     all of this. */
  background:
    radial-gradient(ellipse 75% 130% at 16% -10%, rgba(255, 255, 255, 0.075), rgba(255, 255, 255, 0) 60%),
    radial-gradient(ellipse 70% 110% at 104% 110%, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0) 58%),
    #0e0f12;
  border-radius: var(--radius-lg);
  /* Taller than it is wide-padded, so the band breathes above and below its
     contents without pushing the grid in from the sides. */
  padding: clamp(48px, 6.5vw, 104px) clamp(28px, 4.5vw, 72px);
  overflow: hidden;
}

/* A photograph behind the band, thrown out of focus so it reads as texture
   instead of competing with the words. Picture and scrim are layers of their
   own because filtering the band itself would blur its text too. Tune per
   project with `scrim` in content.js. */
.project--photo::before,
.project--photo::after {
  content: '';
  position: absolute;
  pointer-events: none;
}

.project--photo::before {
  /* A blur samples past its own edges, which would leave the band's sides
     washed out, so the layer is pushed further out than the blur radius. */
  inset: calc(var(--bg-blur) * -2);
  background-image: var(--bg);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  filter: blur(var(--bg-blur));
}

/* The scrim rides above the picture, so the heading keeps its contrast
   whatever the photograph does. */
.project--photo::after {
  inset: 0;
  background: rgba(0, 0, 0, var(--scrim, 0.55));
}

/* Over a photograph the muted body grey drops below 4.5:1, so lift it rather
   than darken the picture. */
.project--photo .prose { color: #d4d4d4; }
.project--photo .label { opacity: 0.68; }

/* Holds the band's contents on the same grid as the rest of the site, and
   above the picture and scrim layers behind it. */
.project__inner {
  position: relative;
  z-index: 1;
  display: grid;
  /* min() keeps the 320px floor from exceeding the band's inner width on a
     phone, which would push the column past the padding and clip the text. */
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: clamp(28px, 4vw, 56px);
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
}

/* Art sits on the left on every row. Opt into alternating sides with
   data-projects="alternate" on the container. */
/* Once the row is genuinely two columns, the cover takes the larger share of
   it. Below that the auto-fit rule above has already stacked them, so this
   would only force a squeezed two-up. */
@media (min-width: 901px) {
  .project__inner { grid-template-columns: 1.35fr 1fr; }
}

.project__inner--flip { direction: rtl; }
.project__inner > * { direction: ltr; }

/* Entrance: a short fade with a slight grow, once, as the row arrives.
   The hidden state is scoped to .reveal, which script adds, so the rows stay
   visible if JavaScript never runs. */
.projects.reveal .project {
  opacity: 0;
  transition: opacity 0.7s cubic-bezier(.22,.61,.36,1);
}

.projects.reveal .project__art,
.projects.reveal .project__body {
  transform: translateY(10px) scale(0.985);
  transform-origin: center;
  transition: transform 0.8s cubic-bezier(.22,.61,.36,1);
}

/* The art leads, the words follow a beat later. */
.projects.reveal .project__body { transition-delay: 0.08s; }

.projects.reveal .project.is-in { opacity: 1; }

.projects.reveal .project.is-in .project__art,
.projects.reveal .project.is-in .project__body {
  transform: none;
}

.project__art {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  /* Darkens the band rather than painting a slab of its own, so a missing
     screenshot still belongs to the section. */
  background: rgba(0, 0, 0, 0.28);
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.4);
  box-shadow: var(--shadow-art);
}

/* The cover is a link. Nothing announces itself until the pointer is on it,
   then the card lifts. */
a.project__art {
  text-decoration: none;
  transition: box-shadow 0.35s ease;
}

a.project__art:hover {
  box-shadow:
    0 4px 10px rgba(0, 0, 0, 0.38),
    0 16px 32px rgba(0, 0, 0, 0.42),
    0 36px 72px rgba(0, 0, 0, 0.38);
}

.project__img {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.project__body {
  display: flex;
  flex-direction: column;
  gap: 18px;
  max-width: 440px;
}

.project__name { font-size: clamp(26px, 3.4vw, 44px); }

.project__link {
  align-self: flex-start;
  border-bottom: 1px solid rgba(255,255,255,0.4);
  padding-bottom: 3px;
  white-space: nowrap;
}

/* ------------------------------------------------------------ journal ---- */

.posts { display: flex; flex-direction: column; }

.post-link {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 24px;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}

.post-link__date { opacity: 0.5; }

.post-link__title {
  font-family: var(--font-display);
  font-size: 19px;
  letter-spacing: -0.01em;
  text-transform: none;
}

.post__back { opacity: 0.5; }
.post__date { margin-top: 48px; opacity: 0.5; }
.post__title { margin: 8px 0 32px; font-size: clamp(28px, 4vw, 44px); line-height: 1.02; }

.post__body {
  margin: 0;
  color: #d0d0d0;
  font-size: 17px;
  line-height: 1.65;
}

.post__body p + p { margin-top: 1.1em; }

/* -------------------------------------------------------------- about ---- */

.about {
  display: grid;
  grid-template-columns: minmax(260px, 360px) minmax(0, 1fr);
  gap: 64px;
}

.about__aside {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-self: start;
  position: sticky;
  top: 96px;
}

.about__portrait {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background: linear-gradient(160deg, #242424, #0c0c0c);
}

.about__portrait img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A panel of its own directly beneath the portrait, sharing the portrait's
   corner radius so the two read as a pair. */
.about__contact {
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: var(--panel);
  border-radius: var(--radius);
  padding: 18px;
}

/* Name, what I do, where I am. Tight enough to read as one stamp. */
.about__card { display: flex; flex-direction: column; gap: 3px; }
.about__card-name { color: var(--fg); }

.about__links { display: flex; flex-direction: column; gap: 6px; }
.about__links a { color: var(--dim); word-break: break-word; }
.about__links a:hover { color: var(--fg); }

.about__social { display: flex; gap: 8px; }

.about__social a {
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.55);
  transition: color 0.2s ease, background 0.2s ease;
}

.about__social a:hover {
  color: var(--fg);
  background: rgba(255, 255, 255, 0.12);
}

.about__social svg { width: 17px; height: 17px; display: block; }

.about__main {
  display: flex;
  flex-direction: column;
  gap: 56px;
  max-width: 600px;
}

/* The column above spaces its big blocks 56px apart, which is far too much
   between a name, its line of subtitle and the prose. The intro is one of
   those blocks and keeps its own, much tighter rhythm inside. */
.about__intro {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.about__name {
  font-size: clamp(30px, 3.6vw, 46px);
  margin: 0;
}


.about__bio {
  margin: 0;
  text-transform: none;
  letter-spacing: 0;
  font-size: 20px;
  line-height: 1.5;
  color: #e6e6e6;
  text-wrap: pretty;
}


.block { display: flex; flex-direction: column; gap: 12px; }

.skills { display: flex; flex-wrap: wrap; gap: 6px 24px; }

.timeline { display: flex; flex-direction: column; }
.timeline__head { opacity: 0.5; padding-bottom: 12px; }

.timeline__row {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 24px;
  padding: 14px 0;
  border-top: 1px solid var(--line);
}

.timeline__years { opacity: 0.5; }
.timeline__org { opacity: 0.5; }

/* An index rather than a wall of tiles: as many columns as the width allows,
   each row only as tall as the name inside it. A list that grows to forty or
   more then lengthens gently instead of adding a 64px band per client. */
.clients {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr));
  gap: 4px;
}

.clients__item {
  border-radius: 8px;
  background: var(--panel);
  padding: 12px 11px;
  color: rgba(255, 255, 255, 0.45);
}

/* ---------------------------------------------------------- responsive --- */

@media (max-width: 900px) {
  :root { --cols: 2; --gutter: 20px; }

  .about {
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
  }

  .about__aside { position: static; max-width: 320px; }
}

@media (max-width: 640px) {
  :root { --cols: 1; }

  /* Name and contact share the top row; the links wrap onto their own. */
  .nav {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "name  contact"
      "links links";
    row-gap: 12px;
    padding: 16px var(--gutter) 20px;
  }

  .nav__name { grid-area: name; }
  .nav__contact { grid-area: contact; }

  .nav__links {
    grid-area: links;
    justify-content: flex-start;
    /* Tighter than the desktop nav: "Video Production" and "Web Development"
       need 351px of the 335px a 375px phone has, and the tracking is where
       the room is. Still wraps below about 365px, which flex-wrap handles. */
    gap: 14px;
    letter-spacing: 0.1em;
    flex-wrap: wrap;
  }

  .page { padding-top: 150px; }
  .reel { padding-top: 110px; }

  .post-link { grid-template-columns: 1fr; gap: 6px; }
  .timeline__row { grid-template-columns: 1fr; gap: 4px; }
  .hero__tag { display: none; }
}
