/* portrait.css: the About portrait, framed as terminal output.
 *
 * The picture is what the terminal printed. A command line above the glass reads
 * "$ display --portrait", the photograph is the output, and the window rests on a bare
 * prompt underneath. There is no line-number gutter and no selection band stepping down
 * the frame, because both of those read as a code editor rather than a session.
 *
 * The photograph is a normal colour photograph and it is shown that way. No tint is laid
 * over it. One filter chain driven by three custom properties on .pt carries a light grade
 * only, so saturation, brightness and contrast can be retuned from a single block without
 * touching the chain. A filter and not a blend mode: it holds the face legible and it
 * composites more cheaply.
 *
 * Two things move inside the glass. One slow refresh band rolls down it, and the glare
 * layer varies its strength by a few percent on a different period, so the two never
 * fall into step. The caret in the footer prompt blinks on the site's shared keyframes.
 * No script, no timer, no network request.
 *
 * The window shell, titlebar, dots and title reuse .term-window and .term-titlebar from
 * hero.css, so this is visibly the same make of terminal as the other two on the page.
 */

/* ------------------------------------------------------------- placement */

/* The photograph is cropped as wide as the source file goes, so the framing is set by how
   large the window renders, not by the crop. 288px puts the whole torso in frame at a size
   that reads as a portrait of a person. This is the stacked width: below 1000px the portrait
   sits under the prose with the full column to itself. */
.about-portrait {
  width: 100%;
  max-width: 288px;
  margin-inline: auto;
}

/* Beside the prose, the window is sized to finish level with it rather than hang past it.
   1000px is where sections.css turns the About section into two columns, and at 1280px and
   wider the three paragraphs settle at 365px. The window's furniture is fixed height and
   does not scale with the box: 40px of titlebar, 29px of command line, 14px of bezel, 30px
   of prompt and the hairline border come to 115px, which leaves 250px of glass. At the
   source's 1122 / 1402 that glass is 200px wide, so the window is 216px. The crop is the
   same as the stacked one, only scaled. Narrower than 1280px the prose wraps taller and the
   window simply finishes early, which is the side to err on. */
@media (min-width: 1000px) {
  .about-portrait {
    max-width: 216px;
    /* Against the start of its track. The track is the window's own width (sections.css),
       so this is the left edge of the section: the same edge the About heading sits on and
       the how-i-work terminal below runs from. */
    margin-inline: 0;
  }
}

/* ---------------------------------------------------------------- window */

/* The component root. Every number in the grade lives here, on purpose: the picture is meant
   to be retuned from this block alone, by editing three values and nothing else.
   The chain that consumes them is on .pt-photo and should not need to change. */
.pt {
  /* A light grade, not a look. Held close to 1 so the picture reads as a photograph of a
     person rather than a processed frame: a hair of saturation, a small lift to seat it
     against the dark glass, and just enough contrast to keep the edges from going soft. */
  --pt-sat: 1.04;
  --pt-bright: 1.05;
  --pt-contrast: 1.03;
  /* strength of the soft glow sitting behind the head */
  --pt-halo: 0.16;

  margin: 0;
}

/* The titlebar is shared with the hero and how-i-work windows, where there is room for the
   whole path. At 216px the title would wrap to a second line and add 19px to the bar. A real
   terminal truncates a title it cannot fit, so this one does too, and the bar holds its
   single-line height. */
.pt .term-title {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The command that produced the picture. Shell syntax only: it states what the window is
   showing and invents no readout. */
.pt-cmd {
  margin: 0;
  padding: 9px 14px 8px;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  line-height: 1;
  color: var(--color-text-2);
  user-select: none;
}

/* ----------------------------------------------------------------- glass */

/* The bezel seats the glass inside the window and gives it its recess. */
.pt-bezel {
  padding: 7px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 40%),
    var(--color-surface-1);
}

/* The glass carries the photograph's own crop, 1122 by 1402, rather than forcing a shape
   around it. That is the whole source frame: headroom above the cap, the head, the shoulders
   and the torso down to the jacket. Locked on the box, so it holds at every width from a
   320px handset to a 1920px desktop. */
.pt-glass {
  position: relative;
  aspect-ratio: 1122 / 1402;
  border-radius: 24px / 30px;
  overflow: hidden;
  isolation: isolate;
  background: radial-gradient(140% 110% at 50% 40%, #0E1622 0%, #05080F 100%);
  box-shadow:
    inset 0 0 0 1px rgba(97, 168, 255, 0.09),
    inset 0 0 46px rgba(3, 6, 12, 0.9),
    0 0 0 1px rgba(0, 0, 0, 0.6);
}

/* Everything inside the glass is absolutely positioned, so without a ratio the box would
   collapse. The padding trick holds the same 1122 by 1402 shape. */
@supports not (aspect-ratio: 1 / 1) {
  .pt-glass {
    height: 0;
    padding-bottom: 124.96%;
  }
}

/* Soft glow behind the head, which is what stops the portrait reading as a cutout pasted
   onto a dark rectangle. Near neutral and faint, so it lifts the background off black
   without laying a colour cast back onto the face. Its strength is a knob like the grade is. */
.pt-halo {
  position: absolute;
  left: 50%;
  /* Sat on the head, which in the wider crop is smaller and higher up the frame than it was. */
  top: 10%;
  width: 56%;
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(circle, rgba(236, 232, 226, var(--pt-halo)), transparent 62%);
  pointer-events: none;
}

/* --------------------------------------------------------------- picture */

/* The glass carries the same ratio as the file, so the picture fills it exactly. cover is
   here to absorb sub pixel rounding at odd viewport widths rather than to crop, and the top
   anchor keeps the cap in frame if it ever does trim.
   max-width is released because base.css caps every image at the width of its box. */
.pt-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  object-position: 50% 0%;
  /* No tint. The picture keeps its own colour and the chain carries a light grade only:
     how saturated, how bright and how contrasty. Read the numbers off .pt above. */
  filter:
    saturate(var(--pt-sat))
    brightness(var(--pt-bright))
    contrast(var(--pt-contrast));
}

/* ------------------------------------------------------------- overlays */

.pt-fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Hairline scanlines. Static: one line in three darkened by a third, held faint enough
   that it reads as the surface of the glass and never as a filter over the face. */
.pt-scan {
  background-image: repeating-linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.34) 0 1px,
    transparent 1px 3px
  );
  opacity: 0.28;
  mix-blend-mode: multiply;
}

/* The refresh band: the soft roll bar a tube shows when it disagrees with the camera about
   frame rate. Seven and a half percent light at its centre, one pass every nine seconds.
   Transform only, so it stays on the compositor. */
.pt-band {
  inset: -34% 0 auto 0;
  height: 34%;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(232, 238, 246, 0.03) 28%,
    rgba(232, 238, 246, 0.055) 50%,
    rgba(232, 238, 246, 0.03) 72%,
    transparent 100%
  );
  mix-blend-mode: screen;
  /* Resting pose for the still version: parked low across the chest, well clear of the
     face, so the composed frame still holds one incident of light. The chest sits lower in
     the taller frame, so the band parks lower with it. */
  transform: translate3d(0, 210%, 0);
  animation: pt-refresh 9s linear infinite;
  will-change: transform;
}

@keyframes pt-refresh {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, 394%, 0); }
}

/* Curved glass: the head and the shoulders seated top and bottom, a corner vignette, and
   one broad specular sweep off the top left. All static gradients, one layer.
   That layer is also the flicker. Varying its opacity by a few percent lifts and settles
   the whole frame slightly, which is the brightness wobble of a tube finding its level.
   7.3 seconds against the band's 9 means the two never repeat together. Opacity only, so
   it costs a composite and no repaint. */
.pt-glare {
  background:
    linear-gradient(to bottom, rgba(10, 15, 22, 0.3), transparent 12%),
    linear-gradient(to top, rgba(10, 15, 22, 0.45), transparent 18%),
    radial-gradient(118% 96% at 50% 44%, transparent 58%, rgba(0, 0, 0, 0.38) 100%),
    linear-gradient(148deg, rgba(255, 255, 255, 0.05) 0%, transparent 40%);
  opacity: 1;
  animation: pt-flicker 7.3s ease-in-out infinite;
}

@keyframes pt-flicker {
  0%, 100% { opacity: 1; }
  37%      { opacity: 0.94; }
  63%      { opacity: 0.98; }
}

/* --------------------------------------------------------------- prompt */

/* The window rests on a bare prompt. Nothing is claimed and nothing is measured: the
   session simply printed the picture and is waiting for the next command. */
.pt-prompt {
  display: flex;
  align-items: center;
  gap: 3px;
  margin: 0;
  padding: 8px 14px 9px;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  line-height: 1;
  color: var(--color-accent);
  user-select: none;
}

.pt-caret {
  display: inline-block;
  width: 7px;
  height: 13px;
  flex: none;
  background: var(--color-accent-bright);
  /* the blink keyframes are the site's own, shared with the nav cursor and both terminals */
  animation: blink 1.06s step-end infinite;
}

/* ---------------------------------------------------------- narrow view */

/* Coarser scanlines on a phone panel, so they never turn into moire. */
@media (max-width: 480px) {
  .pt-scan {
    background-image: repeating-linear-gradient(
      180deg,
      rgba(0, 0, 0, 0.34) 0 1px,
      transparent 1px 4px
    );
    opacity: 0.24;
  }

  .pt-glass {
    border-radius: 18px / 24px;
  }
}

/* ------------------------------------------------------- reduced motion */

/* Nothing moves. The band rests low across the chest, the glare rests at full strength,
   the caret rests solid. The result is a composed still, not a stopped frame of the
   moving one. */
@media (prefers-reduced-motion: reduce) {
  .pt-band,
  .pt-glare,
  .pt-caret {
    animation: none;
  }

  .pt-band {
    transform: translate3d(0, 210%, 0);
    will-change: auto;
  }

  .pt-glare,
  .pt-caret {
    opacity: 1;
  }
}
