/* ==========================================================================
   francisdarko.com
   One screen. The name runs straight across, the cut-out stands in front of
   it, and the three domains sit along the bottom.
   ========================================================================== */

/* Archivo, not Instrument Sans. The detector flags Instrument Sans, Inter,
   Geist and friends as the faces every AI-built site converges on. Archivo
   sets very slightly wider (7.712em against 7.644em for the wordmark), so the
   fit ceilings below came down a fraction rather than up. */
@font-face {
  font-family: 'Archivo';
  src: url('/assets/fonts/archivo-latin.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bodoni Moda';
  src: url('/assets/fonts/bodoni-moda-latin-italic.woff2') format('woff2');
  font-weight: 400 700; font-style: italic; font-display: swap;
}

:root {
  --paper:    #faf5ef;
  --paper-2:  #f4e8db;
  --clay:     #e9b79c;
  --coral:    #dd8a70;
  --ink:      #241c18;
  /* 4.82:1 against the most saturated ground on the page (#e19a83). The
     previous #4a3f38 measured 4.44:1 and missed WCAG AA by a hair. */
  --ink-soft: #443a33;

  --sans: 'Archivo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --serif: 'Bodoni Moda', 'Didot', 'Bodoni 72', Georgia, serif;

  --maxw: 76rem;
  --pad: clamp(1.1rem, 3.5vw, 3rem);
  --ease: cubic-bezier(.22, 1, .36, 1);

  /* One number drives the portrait height and everything anchored to it.
     The cut-out is 1088x991, so it is 1.098 times as wide as it is tall. Sizing
     on height alone let it run 648px wide inside a 390px viewport, so the
     height is also capped by the width it would demand. Re-derive 1.098 if the
     photograph is ever recropped. */
  --figure: min(84svh, calc(104vw / 1.098));
}

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

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

body {
  margin: 0;
  font-family: var(--sans);
  color: var(--ink);
  line-height: 1.5;
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

/* ---------- the one screen ---------- */

.stage {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  overflow: hidden;
  /* Cream at the top, warmth gathering in the lower right, the way the light
     falls in the reference. */
  background:
    radial-gradient(120% 90% at 88% 108%, rgba(221, 138, 112, .85) 0%, rgba(233, 183, 156, .45) 34%, transparent 66%),
    radial-gradient(90% 70% at 6% 96%, rgba(233, 183, 156, .40) 0%, transparent 60%),
    linear-gradient(168deg, var(--paper) 0%, var(--paper) 38%, var(--paper-2) 72%, var(--clay) 100%);
}

/* Without this the figure has no contact with the ground and reads as a
   sticker laid over the gradient. */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
}

.hero::after {
  content: '';
  position: absolute;
  z-index: 2;
  inset: auto 0 0;
  height: 46vh;
  height: 46svh;
  pointer-events: none;
  /* Sits between him and the boxes, so the bottom of his shirt dissolves into
     the row rather than stopping against it. */
  background: linear-gradient(180deg,
    rgba(244, 226, 212, 0) 0%,
    rgba(242, 214, 196, .12) 42%,
    rgba(238, 199, 175, .40) 66%,
    rgba(233, 183, 156, .78) 86%,
    rgba(233, 183, 156, .95) 100%);
}

/* ---------- top bar ---------- */

.topbar {
  position: absolute;
  z-index: 4;
  inset: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: clamp(1rem, 2.6vw, 1.9rem) var(--pad);
}

.mark {
  font-weight: 700;
  font-size: .95rem;
  letter-spacing: .1em;
  color: var(--ink);
  text-decoration: none;
}
.mark span { color: var(--coral); }

.utility {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: .82rem;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--ink);
  text-decoration: none;
  padding: .6rem 1.05rem;
  border: 1px solid rgba(36, 28, 24, .22);
  border-radius: 999px;
  background: rgba(255, 253, 250, .5);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background-color .3s var(--ease), border-color .3s var(--ease);
}
.utility .arrow { transition: transform .3s var(--ease); }
.utility:hover { background: rgba(255, 253, 250, .95); border-color: rgba(36, 28, 24, .45); }
.utility:hover .arrow { transform: translateX(3px); }

/* ---------- the name, running across, behind the head ---------- */

.nameplate {
  position: absolute;
  z-index: 1;
  left: 0;
  right: 0;
  /* Anchored off the figure rather than off the top of the screen, so the head
     keeps crossing the letters at mid cap-height as the portrait scales. */
  top: auto;
  bottom: calc(var(--figure) * 0.52);
  margin: 0;
  text-align: center;
  font-weight: 700;
  /* The wordmark measures 7.6em wide, so 13vw is the point at which it would
     touch the viewport edges. 12vw keeps a deliberate margin instead of the
     four-pixel near-miss that reads as an accident. */
  font-size: min(12vw, 13rem);
  line-height: 1;
  letter-spacing: -0.048em;
  text-transform: uppercase;
  white-space: nowrap;
  background-image: linear-gradient(112deg,
    #dcb99b 0%, #dda682 30%, #d18a6d 58%, #c4715c 82%, #b9604c 100%);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  user-select: none;
}

/* Without background-clip the text would vanish, so it falls back to a flat
   fill of roughly the same weight. */
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .nameplate { color: #cf8a6f; background-image: none; }
}

.nameplate span { display: inline-block; }

/* A full word space at this size opens a hole between the two words. This
   pulls them back into one plate. */
.nameplate i { font-style: normal; letter-spacing: -0.09em; }

/* ---------- the cut-out ---------- */

.portrait {
  position: absolute;
  z-index: 2;
  bottom: 0;
  /* Centred with auto margins rather than translateX(-50%). The entrance
     animation ends on `transform: none`, which would wipe out a centering
     transform and, with fill-mode both, hold him off to one side forever. */
  left: 0;
  right: 0;
  width: max-content;
  margin-inline: auto;
  pointer-events: none;
}

/* The image carries the height, not the wrapper. Sizing the wrapper instead
   makes it shrink-to-fit whatever horizontal room is left over, and the global
   `img { max-width: 100% }` then crushes the width while the height holds,
   which squeezes him into a sliver. `max-width: none` is load-bearing. */
.portrait-img {
  height: var(--figure);
  width: auto;
  max-width: none;
  /* Nearly black and white, but carrying the page's own warmth so he belongs
     to the ground rather than sitting on top of it. */
  filter: grayscale(0.92) sepia(0.30) hue-rotate(-8deg) saturate(1.15) contrast(0.97) brightness(1.04);
  -webkit-filter: grayscale(0.92) sepia(0.30) hue-rotate(-8deg) saturate(1.15) contrast(0.97) brightness(1.04);
}

/* ---------- the three domains ---------- */

.domains {
  position: relative;
  z-index: 3;
  margin: -17svh auto 0;
  padding: 0 var(--pad) clamp(1.5rem, 4vh, 2.75rem);
  max-width: var(--maxw);
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: stretch;
  gap: clamp(.6rem, 1.4vw, 1.1rem);
}

.domain {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(1rem, 1.7vw, 1.5rem);
  border: 1px solid rgba(36, 28, 24, .32);
  border-radius: 14px;
  background: rgba(255, 253, 250, .94);
  /* Rises in once, then breathes. The three are staggered on both, so they
     arrive in sequence and pulse out of sync. */
  animation: rise-in .8s var(--ease) .34s both,
             throb 3.4s ease-in-out 1.5s infinite;
}
.domain:nth-child(2) { animation-delay: .44s, 2.05s; }
.domain:nth-child(3) { animation-delay: .54s, 2.6s; }

@keyframes throb {
  0%, 100% {
    border-color: rgba(36, 28, 24, .20);
    box-shadow: 0 0 0 0 rgba(201, 118, 96, 0);
  }
  50% {
    border-color: rgba(190, 104, 82, .58);
    box-shadow: 0 0 0 3px rgba(201, 118, 96, .13);
  }
}

.idx {
  margin: 0 0 .35rem;
  font-size: clamp(.58rem, .78vw, .68rem);
  font-weight: 600;
  letter-spacing: .16em;
  color: var(--ink-soft);
}

.domain h2 {
  margin: 0 0 .5rem;
  font-size: clamp(1rem, 1.5vw, 1.3rem);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.15;
}

.domain-title {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color .3s var(--ease);
}
.domain-title:hover { border-bottom-color: currentColor; }

.blurb-short { display: none; }

.blurb {
  margin: 0;
  font-size: clamp(.78rem, .95vw, .875rem);
  line-height: 1.5;
  color: #3d332c;
}

/* Pinned to the foot of every box so the action groups read level however the
   paragraphs wrap. */
.actions {
  margin-top: auto;
  padding-top: 1rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .3rem;
}

.btn {
  display: inline-block;
  margin-top: .7rem;
  background: linear-gradient(180deg, #e0a184, var(--coral) 55%, #c9705a);
  color: #2a140c;
  font-weight: 600;
  font-size: clamp(.76rem, .92vw, .85rem);
  line-height: 1.3;
  text-decoration: none;
  padding: .62rem 1.05rem;
  border-radius: 999px;
  box-shadow: 0 1px 0 rgba(255, 236, 225, .5) inset,
              0 8px 20px -10px rgba(201, 112, 90, .8);
  transition: transform .3s var(--ease), filter .3s var(--ease);
}
.btn:hover { transform: translateY(-2px); filter: brightness(1.05); }
.btn:active { transform: translateY(0); }

.aside-head {
  margin: 0 0 .1rem;
  font-size: clamp(.74rem, .9vw, .82rem);
  font-weight: 600;
  color: var(--ink);
}

.link {
  color: #a8503a;
  font-size: clamp(.72rem, .88vw, .8rem);
  font-weight: 500;
  line-height: 1.4;
  text-decoration: none;
  border-bottom: 1px solid rgba(168, 80, 58, .35);
  transition: border-color .3s var(--ease);
}
.link .arrow { display: inline-block; transition: transform .3s var(--ease); }
.link:hover { border-bottom-color: #a8503a; }
.link:hover .arrow { transform: translateX(3px); }

:where(a):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---------- the way it arrives ---------- */

@keyframes rise-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

@keyframes settle-in {
  from { opacity: 0; transform: translateY(10px) scale(1.012); }
  to   { opacity: 1; transform: none; }
}

.topbar   { animation: rise-in .7s var(--ease) .05s both; }
.nameplate { animation: rise-in .9s var(--ease) .12s both; }

/* Longer and later than the name, so a photograph that finishes decoding a
   beat behind the text still arrives inside its own fade rather than after it. */
.portrait { animation: settle-in 1.1s var(--ease) .22s both; }

/* ---------- grain ---------- */

.grain {
  position: absolute;
  z-index: 5;
  inset: 0;
  pointer-events: none;
  opacity: .20;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}

/* ---------- narrow screens ---------- */

@media (max-width: 44rem) {
  /* Most of this traffic arrives from an Instagram link, and the in-app
     browser keeps roughly 660px of a 390px-wide phone. All three boxes have to
     be on that first screen, so the hero gives up height and the paragraphs
     drop to a single line each. */
  :root { --figure: min(38svh, calc(86vw / 1.098)); }

  .hero { min-height: 38svh; }

  .nameplate {
    top: auto;
    bottom: calc(var(--figure) * 0.52);
    white-space: normal;
    font-size: min(14vw, 3.4rem);
    line-height: .92;
  }
  .nameplate i { letter-spacing: normal; }

  .hero::after { height: 30svh; }

  .domains {
    margin-top: 0;
    grid-template-columns: 1fr;
    gap: .45rem;
    padding-block: 0 .6rem;
  }

  .domain { padding: .8rem .9rem; }

  .idx { margin-bottom: .2rem; }
  .domain h2 { margin-bottom: .25rem; font-size: 1.05rem; }

  .blurb { display: none; }
  .blurb-short {
    display: block;
    margin: 0;
    font-size: .82rem;
    line-height: 1.4;
    color: #3d332c;
  }

  /* The whole card is the tap target; the button is redundant at this size. */
  .domain-title::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
  }

  .actions { padding-top: .5rem; }
  .btn { display: none; }

  /* Has to clear the card-wide overlay above, or it cannot be tapped. */
  .link {
    position: relative;
    z-index: 2;
    font-size: .76rem;
  }
  .aside-head { font-size: .76rem; margin-bottom: .15rem; }
}


/* Small Android phones (360x640 and similar) are ~20px shorter than the
   iPhone-class viewport the block above is tuned for, and the third box was
   landing 13px past the fold. The hero gives up more height rather than the
   boxes giving up content. */
@media (max-width: 44rem) and (max-height: 41rem) {
  :root { --figure: min(32svh, calc(112vw / 1.098)); }

  .hero { min-height: 32svh; }
  .nameplate {
    white-space: normal;
    font-size: min(17vw, 5.5rem);
  }
  .hero::after { height: 24svh; }

  .domain { padding: .68rem .85rem; }
  .domains { gap: .4rem; }
}

/* Short landscape windows. This has to reset `bottom`, or an element that
   matches both this and the narrow query ends up with top AND bottom set and
   gets stretched to fit between them. */
@media (max-height: 34rem) {
  :root { --figure: min(54svh, calc(60vw / 1.098)); }
  .nameplate {
    top: auto;
    /* Anchored off the figure, like the base rule, so the head still crosses
       the letters in a window only a few hundred pixels tall. */
    bottom: calc(var(--figure) * 0.45);
    white-space: nowrap;
    font-size: min(12vw, 13rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition-duration: .001ms !important;
  }
}

/* ---------- 404 ---------- */

.stage-plain {
  display: grid;
  place-items: center;
  text-align: center;
  padding: var(--pad);
}

.notfound { max-width: 32rem; }
.notfound .idx { margin: 0 0 .6rem; letter-spacing: .18em; font-size: .72rem; font-weight: 600; color: var(--ink-soft); }
.notfound h1 { margin: 0 0 1.1rem; font-size: clamp(1.6rem, 4.4vw, 2.6rem); font-weight: 600; letter-spacing: -0.02em; }
.link-home { color: var(--ink); font-weight: 500; text-underline-offset: 4px; }
