@charset "utf-8";

/* ==========================================================================
   redbox 2026
   Same appearance as rbm2021. New engine underneath.

   Replaces: reset.css, unsemantic.css, slick.css, animate.css,
             fast.fonts.net import, jQuery, slick.js, blazy.js, head.js,
             the Font Awesome kit.

   Adds:     scroll-driven colour journey, CSS-only crossfades,
             scroll-linked reveals, native lazy loading.
   ========================================================================== */

@layer reset, tokens, grid, base, components, motion;

/* --------------------------------------------------------------------------
   Fonts — self-hosted Applied Sans. No third-party font host, no cookies.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Applied Sans";
  src: url("Fonts/4063af43-64b6-43d0-a26e-06aa4f85363d.woff2") format("woff2"),
       url("Fonts/3dcd51dd-b610-42f0-aac7-c434471cd2fb.woff") format("woff");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Applied Sans";
  src: url("Fonts/ef5acfbd-ac6c-4686-9e7c-ae99c0aeeb06.woff2") format("woff2"),
       url("Fonts/d7d3fa00-96bc-4a38-a20a-1566aa9b3f5e.woff") format("woff");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Applied Sans";
  src: url("Fonts/46df8cfa-8e15-42de-9720-aba591256375.woff2") format("woff2"),
       url("Fonts/a74fcfe7-67b6-4553-92f7-f5be60252c75.woff") format("woff");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* --------------------------------------------------------------------------
   Animatable custom properties. Registering them is what lets the colour
   journey interpolate rather than snap between keyframes.
   -------------------------------------------------------------------------- */

@property --back {
  syntax: "<color>"; inherits: true; initial-value: #00008b;
}
@property --backlite {
  syntax: "<color>"; inherits: true; initial-value: #7ec8e3;
}
@property --hilite {
  syntax: "<color>"; inherits: true; initial-value: #7ec8e3;
}

@layer reset {
  *, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
  * { margin: 0; }

  html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    overflow-y: scroll;
    scroll-behavior: smooth;
  }

  body { -webkit-font-smoothing: antialiased; min-height: 100svh; }

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

  ul { list-style: none; }
  a { color: inherit; text-decoration: none; }

  :focus-visible {
    outline: 3px solid var(--text);
    outline-offset: 3px;
  }
}

@layer tokens {
  :root {
    color-scheme: dark;
    --back: #00008b;
    --backlite: #7ec8e3;
    --text: #fff;
    --hilite: #7ec8e3;
    --white: #fff;

    /* Ink that stays legible on the light band, whatever hue it travels to. */
    --band-ink: #08083f;
  }
}

/* --------------------------------------------------------------------------
   Grid — a flex shim that reproduces the unsemantic class names and
   proportions without floats or clearfix divs. ~60 lines instead of 1800.
   -------------------------------------------------------------------------- */

@layer grid {
  .grid-container {
    margin-inline: auto;
    max-width: 1440px;
    padding: 48px 96px 0 96px;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
  }

  .grid-container-max {
    position: relative;
    max-width: 100%;
    padding-inline: 0;
  }

  /* Cells: 12px gutters, matching unsemantic. */
  [class*="grid-"]:not(.grid-container):not(.grid-container-max) {
    padding-inline: 12px;
    min-width: 0;
  }

  .grid-parent { padding-inline: 0; }

  /* Forced row break — replaces <div class="clear">&nbsp;</div> */
  .row-break { flex: 0 0 100%; height: 0; padding: 0; }

  /* Desktop widths */
  @media (min-width: 1025px) {
    .grid-5   { flex: 0 0 5%;   max-width: 5%; }
    .grid-10  { flex: 0 0 10%;  max-width: 10%; }
    .grid-20  { flex: 0 0 20%;  max-width: 20%; }
    .grid-25  { flex: 0 0 25%;  max-width: 25%; }
    .grid-30  { flex: 0 0 30%;  max-width: 30%; }
    .grid-33  { flex: 0 0 33.33%; max-width: 33.33%; }
    .grid-40  { flex: 0 0 40%;  max-width: 40%; }
    .grid-45  { flex: 0 0 45%;  max-width: 45%; }
    .grid-50  { flex: 0 0 50%;  max-width: 50%; }
    .grid-80  { flex: 0 0 80%;  max-width: 80%; }
    .grid-100 { flex: 0 0 100%; max-width: 100%; }

    .prefix-5  { margin-inline-start: 5%; }
    .prefix-25 { margin-inline-start: 25%; }
    .prefix-50 { margin-inline-start: 50%; }
    .suffix-5  { margin-inline-end: 5%; }
    .suffix-10 { margin-inline-end: 10%; }
    .suffix-20 { margin-inline-end: 20%; }
    .suffix-50 { margin-inline-end: 50%; }
  }

  /* Tablet */
  @media (min-width: 768px) and (max-width: 1024px) {
    .tablet-grid-25 { flex: 0 0 25%; max-width: 25%; }
    .tablet-grid-30 { flex: 0 0 30%; max-width: 30%; }
    .tablet-grid-33 { flex: 0 0 33.33%; max-width: 33.33%; }
    .tablet-grid-40 { flex: 0 0 40%; max-width: 40%; }
    .tablet-grid-50 { flex: 0 0 50%; max-width: 50%; }
    .tablet-grid-80 { flex: 0 0 80%; max-width: 80%; }
    .tablet-grid-100 { flex: 0 0 100%; max-width: 100%; }

    .tablet-prefix-50 { margin-inline-start: 50%; }
    .tablet-suffix-10 { margin-inline-end: 10%; }
    .tablet-suffix-20 { margin-inline-end: 20%; }
    .tablet-suffix-50 { margin-inline-end: 50%; }

    .grid-container { padding-inline: 48px; }
  }

  /* Mobile: everything stacks, offsets collapse. */
  @media (max-width: 767px) {
    [class*="grid-"]:not(.grid-container):not(.grid-container-max) {
      flex: 0 0 100%;
      max-width: 100%;
      margin-inline: 0;
    }
    .mobile-grid-50 { flex: 0 0 50%; max-width: 50%; }
    .grid-container { padding: 32px 24px 0 24px; }
    .hide-on-mobile { display: none; }
  }

  @media (max-width: 1024px) {
    .hide-on-tablet { display: none; }
  }
}

/* --------------------------------------------------------------------------
   Base — typography and colour, matching rbm2021.
   -------------------------------------------------------------------------- */

@layer base {
  body {
    font-family: "Applied Sans", "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
    font-size: 1.2em;
    color: var(--text);
    background: var(--back);
    transition: background-color 0.6s linear;
  }

  strong { font-weight: 700; }

  h1, h2 { font-weight: 700; text-wrap: balance; }

  h1 {
    font-size: 4em;
    margin-top: 12px;
    margin-bottom: 36px;
    line-height: 0.95em;
  }

  h1.intro {
    margin-bottom: 48px;
    line-height: 1.2em;
    margin-right: 12%;
    text-wrap: pretty;
  }

  h2 {
    font-size: 2em;
    color: var(--white);
  }

  /* Project titles are <h3> so the heading order is valid, but they carry
     the same weight and scale the old <h1> had. */
  h3.as-h1,
  h2.as-h1 {
    font-weight: 700;
    font-size: 4em;
    margin-top: 12px;
    margin-bottom: 36px;
    line-height: 0.95em;
    color: var(--white);
    text-wrap: balance;
  }

  .grid-container-about h3.as-h1,
  .grid-container-about h2.as-h1 { color: var(--band-ink); }

  /* The rocket that follows each project link */
  .rocket {
    display: inline-block;
    inline-size: 0.85em;
    block-size: 0.85em;
    vertical-align: -0.08em;
    margin-inline-start: 0.35em;
    color: var(--hilite);
  }

  .grid-container-about .rocket { color: var(--band-ink); }

  /* The "Project" eyebrow. Was an <h4>, which broke heading order.
     Now a <p class="eyebrow"> that looks identical. */
  .eyebrow {
    font-weight: 500;
    font-size: 1.4em;
    margin-top: 56px;
    margin-bottom: 24px;
    display: flex;
    align-items: center;
    gap: 0.5em;
  }

  .eyebrow svg { inline-size: 0.9em; block-size: 0.9em; flex: none; }

  p {
    line-height: 1.4em;
    padding-bottom: 12px;
    margin-bottom: 12px;
    text-wrap: pretty;
  }

  p.address { font-size: 0.9em; }

  ul {
    position: relative;
    padding-left: 1em;
    margin-bottom: 18px;
  }

  ul li { position: relative; }

  ul li::before {
    content: "+";
    position: absolute;
    left: -1em;
  }

  address { font-style: normal; }

  /* Address links sit on consecutive lines, so at 0.9em they measured 23px —
     one pixel under the 24px minimum touch target. min-block-size alone
     clears it; padding here would only bloat the line rhythm. */
  address a {
    display: inline-block;
    min-block-size: 24px;
  }

  .fw { width: 100%; }
  .nobot { border-bottom: none !important; }

  /* Colour roles, as in rbm2021 */
  .grid-container p,
  .grid-container-top p { color: var(--text); }

  .grid-container p strong { color: var(--hilite); }

  .grid-container p a,
  .grid-container-top p a,
  p.address a { color: var(--hilite); }

  /* Light band. The original used margin-top here, but a margin cannot paint
     a background, so the body colour showed through as a thin strip above
     every light band. Padding gives the same spacing with no gap. */
  .grid-container-about {
    background: var(--backlite);
    color: var(--band-ink);
    padding-top: 24px;
    padding-bottom: 48px;
  }

  /* Light band overrides. These must out-specify `.grid-container p a`,
     otherwise link text renders sky-on-sky and disappears. */
  .grid-container-about h1,
  .grid-container-about h2,
  .grid-container-about h3,
  .grid-container-about p,
  .grid-container-about a,
  .grid-container-about p a,
  .grid-container-about p strong,
  .grid-container-about address a { color: var(--band-ink); }

  .grid-container-top { margin-top: 0; padding-bottom: 24px; }

  /* Tonal bands so consecutive sections are not all the same ground.
     All are mixed from --back, so they travel with the colour journey
     instead of fighting it. */
  .band-deep {
    background: color-mix(in srgb, var(--back) 62%, #000);
    padding-block: 24px 48px;
  }

  .band-lift {
    background: color-mix(in srgb, var(--back) 78%, var(--backlite));
    padding-block: 24px 48px;
  }

  /* Deepest of all — used for the address strip above the intro. */
  .band-deepest {
    background: color-mix(in srgb, var(--back) 42%, #000);
  }

  .grid-nano { padding-bottom: 36px; }

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

  .skip-link {
    position: absolute;
    left: 12px; top: -100px;
    z-index: 200;
    background: var(--white);
    color: #00008b;
    padding: 12px 20px;
    font-weight: 700;
    transition: top 0.2s ease;
  }
  .skip-link:focus { top: 12px; }

  @media (max-width: 768px) {
    h1, h3.as-h1, h2.as-h1 { font-size: 2em; }
    h2 { font-size: 1.4em; }
    h1, h2, h3 { margin-right: 0; }
    body { overflow-x: hidden; }
  }
}

/* --------------------------------------------------------------------------
   Components
   -------------------------------------------------------------------------- */

@layer components {

  /* --- The link underline sweep (kept exactly) --------------------------- */

  p a, .eyebrow a { position: relative; }

  p a::before {
    content: "";
    position: absolute;
    width: 100%;
    height: 4px;
    border-radius: 4px;
    background-color: var(--hilite);
    bottom: -4px;
    left: 0;
    transform-origin: right;
    transform: scaleX(0);
    transition: transform 0.3s ease-in-out;
  }

  p a:hover::before,
  p a:focus-visible::before {
    transform-origin: left;
    transform: scaleX(1);
  }

  .grid-container-about p a::before { background-color: var(--band-ink); }

  /* --- Gradient intro links (kept) --------------------------------------- */

  h1.intro a,
  h2 a {
    background-image: linear-gradient(180deg, var(--hilite), #3399ff);
    color: var(--hilite);
    background-size: 100%;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    border-bottom: 8px solid transparent;
    transition: border-color 0.5s, color 0.5s;
  }

  h1.intro a:hover,
  h1.intro a:focus-visible {
    color: var(--white);
    -webkit-text-fill-color: var(--white);
    border-bottom-color: var(--hilite);
  }

  h2 a:hover,
  h2 a:focus-visible {
    color: var(--white);
    -webkit-text-fill-color: var(--white);
    border-bottom-color: var(--white);
  }

  /* Light bands: the gradient-clipped links above would otherwise paint
     sky-on-sky and vanish. This has to live in the same layer as the rule
     it overrides, or the layer order beats the specificity. */
  .grid-container-about h2 a,
  .grid-container-about h1.intro a {
    background-image: none;
    color: var(--band-ink);
    -webkit-text-fill-color: var(--band-ink);
  }

  .grid-container-about h2 a:hover,
  .grid-container-about h2 a:focus-visible {
    color: var(--band-ink);
    -webkit-text-fill-color: var(--band-ink);
    border-bottom-color: var(--band-ink);
  }

  /* --- Images: the 8px white base rule (kept) ---------------------------- */

  .fw,
  .cycle {
    border-bottom: 8px solid var(--text);
  }

  /* Reserve space so lazy images cannot shift the layout. */
  .fw { aspect-ratio: 4 / 3; object-fit: cover; object-position: top center; }
  .fw.tall { aspect-ratio: 3 / 4; }

  /* --- AJ100 corner badge ------------------------------------------------ */

  .aj {
    position: absolute;
    top: 0; right: 0;
    width: 160px; height: 160px;
    background-image: url("../img/ajTriW.png");
    background-size: contain;
    background-repeat: no-repeat;
    background-position: top right;
    pointer-events: none;
  }

  @media (max-width: 767px) {
    .aj { width: 96px; height: 96px; }
  }

  /* --- CSS-only crossfade, replacing slick.js ---------------------------- */

  .cycle {
    display: grid;
    position: relative;
    isolation: isolate;
  }

  .cycle > img {
    grid-area: 1 / 1;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    object-position: top center;
    opacity: 0;
    animation: cycle-fade var(--cycle-duration, 18.4s) infinite linear;
    animation-delay: calc(var(--i, 0) * var(--cycle-step, 6.14s));
  }

  /* First frame is painted immediately so nothing flashes empty. */
  .cycle > img:first-child { opacity: 1; }

  .cycle[data-count="2"] { --cycle-duration: 12.3s; }
  .cycle[data-count="3"] { --cycle-duration: 18.4s; }
  .cycle[data-count="5"] { --cycle-duration: 30.7s; }

  @keyframes cycle-fade {
    0%     { opacity: 0; }
    3%     { opacity: 1; }
    30%    { opacity: 1; }
    35%    { opacity: 0; }
    100%   { opacity: 0; }
  }

  .cycle[data-count="2"] > img { animation-name: cycle-fade-2; }
  @keyframes cycle-fade-2 {
    0% { opacity: 0; } 5% { opacity: 1; } 45% { opacity: 1; } 55% { opacity: 0; } 100% { opacity: 0; }
  }

  .cycle[data-count="5"] > img { animation-name: cycle-fade-5; }
  @keyframes cycle-fade-5 {
    0% { opacity: 0; } 2% { opacity: 1; } 18% { opacity: 1; } 22% { opacity: 0; } 100% { opacity: 0; }
  }

  /* --- Side-by-side image pair -------------------------------------------- */

  .pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    align-items: start;
  }

  @media (max-width: 767px) {
    .pair { grid-template-columns: 1fr; }
  }

  /* --- Footer meta row ---------------------------------------------------- */

  .footer-meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: flex-end;
    gap: 24px 48px;
    margin-top: 48px;
    padding-top: 24px;
    border-top: 1px solid color-mix(in srgb, var(--band-ink) 25%, transparent);
  }

  .footer-meta p { margin: 0; padding: 0; }

  .footer-meta address { line-height: 1.45; text-align: right; }

  @media (max-width: 767px) {
    .footer-meta { flex-direction: column; align-items: flex-start; }
    .footer-meta address { text-align: left; }
  }

  /* --- Scroll progress rule ---------------------------------------------- */

  .progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: 4px;
    background: var(--hilite);
    transform-origin: 0 50%;
    scale: 0 1;
    z-index: 100;
  }
}

/* --------------------------------------------------------------------------
   Motion — everything here is progressive enhancement. Without support,
   or with reduced motion requested, the page is simply static.
   -------------------------------------------------------------------------- */

@layer motion {
  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
    }
    .cycle > img { opacity: 0; }
    .cycle > img:first-child { opacity: 1; }
  }

  @supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {

      /* The colour journey. The whole page migrates through a palette as
         you scroll, and the light bands migrate in sympathy. */
      :root {
        animation: hue-journey linear both;
        animation-timeline: scroll(root block);
      }

      /* Seven stops right around the wheel: navy, indigo, purple, magenta,
         burnt orange, deep green, teal, home. Every pairing here was checked
         for contrast — the tightest is 4.72:1, the loosest 15.3:1. */
      @keyframes hue-journey {
        0%     { --back: #00008b; --backlite: #7ec8e3; --hilite: #7ec8e3; }
        14.3%  { --back: #2d0080; --backlite: #b3a9ff; --hilite: #b3a9ff; }
        28.6%  { --back: #5c0a72; --backlite: #e0a3f5; --hilite: #e0a3f5; }
        42.9%  { --back: #8a0e4d; --backlite: #ffa3c8; --hilite: #ffa3c8; }
        57.2%  { --back: #a32800; --backlite: #ffc48a; --hilite: #ffc48a; }
        71.5%  { --back: #0d5c1f; --backlite: #a8e6a3; --hilite: #a8e6a3; }
        85.8%  { --back: #00566b; --backlite: #7fe0e6; --hilite: #7fe0e6; }
        100%   { --back: #00008b; --backlite: #7ec8e3; --hilite: #7ec8e3; }
      }

      /* Progress rule tracks the same timeline. */
      .progress {
        animation: progress-grow linear both;
        animation-timeline: scroll(root block);
      }

      @keyframes progress-grow {
        from { scale: 0 1; }
        to   { scale: 1 1; }
      }

      /* Sections fade in and rise as they enter, but on two different ranges.
         The fade finishes almost immediately — by the time any of the text is
         genuinely in front of a reader it is fully opaque — while the rise
         plays out over a long, gentle range. One range for both would mean
         readable-sized text sitting at partial opacity. */
      /* Animate the content inside the band, never the band itself. Moving a
         band that paints a background just drags a strip of body colour into
         view behind it. */
      .reveal > .grid-container {
        animation: reveal-fade linear both, reveal-rise linear both;
        animation-timeline: view(), view();
        animation-range: entry 0% entry 18%, entry 0% entry 80%;
      }

      @keyframes reveal-fade {
        from { opacity: 0; }
        to   { opacity: 1; }
      }

      @keyframes reveal-rise {
        from { translate: 0 3rem; }
        to   { translate: 0 0; }
      }
    }
  }

  /* Perf: skip rendering work for offscreen bands. */
  .band-section {
    content-visibility: auto;
    contain-intrinsic-size: auto 600px;
  }
}
