/* Doppla — logo reveal (version 1.0.1)

   The converge from section 09 of the brand guidelines. Use it once per
   session at most — a first page load, a splash screen, the opening of a
   presentation. Never on a loop, never on every route change.

   Both blocks set off together and land together. Red starts close; blue
   starts at the end of the wordmark and crosses the whole lockup. Once they
   land, the hole opens and the wordmark fades up.

   Markup. The wordmark is drawn geometry, not text, so it is inline SVG.
   Each block carries the hole mask itself, so the mask travels with it;
   the mask id must be unique on the page.

   Each block is actually two stacked rects, not one: a flat-fill rect (the
   colour the block slides in as) and a gradient-fill rect on top of it,
   invisible until the blocks land, then fading in on the same delay as the
   hole and the wordmark. The flat rect never disappears — it just ends up
   fully covered once the gradient rect reaches opacity 1 — so there is
   nothing to keep in sync during the slide itself, only after it. A block's
   two rects are always drawn as a pair, and ALL of red's layers come before
   ANY of blue's: fill-opacity blending during the crossfade must never let
   red show through where blue belongs, and the only way painter's-order
   guarantees that at every instant, not just at rest, is red-flat,
   red-gradient, then blue-flat, blue-gradient.

     <div class="doppla-reveal">
       <svg class="doppla-reveal__mark" viewBox="0 0 64 64" aria-hidden="true">
         <defs>
           <mask id="doppla-hole" maskUnits="userSpaceOnUse" x="-400" y="-400" width="864" height="864">
             <rect x="-400" y="-400" width="864" height="864" fill="#fff"/>
             <circle class="doppla-reveal__hole"
                     cx="32" cy="32" r="0" fill="#000">
               <!-- grows the actual radius, not a CSS transform: a circle
                    inside a <mask> does not reliably scale from its own
                    centre under transform:scale(), in every browser.
                    <animate attributeName="r" from="0" to="12" begin="650ms" dur="190ms" fill="freeze" calcMode="spline" keyTimes="0;1" keySplines="0 0 0.58 1"/> is the whole <animate> tag,
                    calcMode/keyTimes/keySplines included — the hole's
                    easing must actually be wired in, not just computed
                    and left unused; that was a real bug here once. -->
               <animate attributeName="r" from="0" to="12" begin="650ms" dur="190ms" fill="freeze" calcMode="spline" keyTimes="0;1" keySplines="0 0 0.58 1"/>
             </circle>
           </mask>
           <!-- userSpaceOnUse + the hole's own real cx/cy/reach, shared by
                both blocks — the glow is centred on the hole itself, not on
                wherever each block's own box happens to sit. Valid the whole
                time these rects are visible: the hole's centre never moves
                during the reveal (only its radius grows), and by the time
                these gradient rects start fading in the blocks have already
                finished translating, so nothing here is chasing a moving
                target. See palette.toml's [gradient] comment. -->
           <radialGradient id="doppla-hole-red-grad" gradientUnits="userSpaceOnUse"
                            cx="32" cy="32" r="34">
             <stop offset="0" stop-color="#C97356"/>
             <stop offset="1" stop-color="#A85235"/>
           </radialGradient>
           <radialGradient id="doppla-hole-blue-grad" gradientUnits="userSpaceOnUse"
                            cx="32" cy="32" r="34">
             <stop offset="0" stop-color="#2383AB"/>
             <stop offset="1" stop-color="#175973"/>
           </radialGradient>
         </defs>
         <!-- the mask is on this <g>, once, not on each rect separately —
              two shapes each independently masked can rasterise their
              holes a fraction of a pixel apart from each other; one mask
              applied to both together cannot, as there is only one raster
              pass. The static mark has always done it this way; the
              reveal originally did not, and very occasionally showed a
              sliver of red at the hole's edge as a result. -->
         <g mask="url(#doppla-hole)">
           <rect class="doppla-reveal__red" x="6" y="6" width="39" height="52" rx="10" fill="#C15E3D"/>
           <rect class="doppla-reveal__red-grad" x="6" y="6" width="39" height="52" rx="10" fill="url(#doppla-hole-red-grad)"/>
           <rect class="doppla-reveal__blue" x="25" y="6" width="33" height="52" rx="10" fill="#1D6E8F"/>
           <rect class="doppla-reveal__blue-grad" x="25" y="6" width="33" height="52" rx="10" fill="url(#doppla-hole-blue-grad)"/>
         </g>
       </svg>
       <svg class="doppla-reveal__wordmark" viewBox="0 0 212.65 59.28"
            role="img" aria-label="doppla">
         ...geometry from doppla-reveal-demo.html...
       </svg>
     </div>

   Reversed: add .doppla-reveal--reversed, set both block fills to #FFFFFF
   and give the red block fill-opacity="0.55". The
   gradient rects have no reversed treatment of their own — monochrome mode
   is flat by definition, so .doppla-reveal--reversed should leave the
   *-grad rects out of its markup entirely rather than override them; they
   are only ever present in the full-colour reveal.
*/

.doppla-reveal {
  display: inline-flex;
  align-items: center;
  gap: 0.225em;
  font-size: 96px;          /* the mark's width; scales the whole lockup */
  line-height: 1;
}

.doppla-reveal__mark {
  width: 1em;
  height: 1em;
  flex: none;
  overflow: visible;        /* blue starts far outside the mark's own box */
}

.doppla-reveal__red,
.doppla-reveal__blue {
  transform-box: fill-box;  /* offsets are percentages of each shape's own size */
  transform-origin: center;
}

.doppla-reveal__wordmark {
  width: 2.1265em;
  height: 0.5928em;
  flex: none;
  overflow: visible;
  color: #15151A;              /* the geometry is drawn in currentColor */
}

.doppla-reveal--reversed .doppla-reveal__wordmark {
  color: #F4F3F0;
}

.doppla-reveal__red {
  animation: doppla-converge-red 650ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

/* Blue's fade runs on its own, shorter clock: it must be fully opaque
   just before it reaches red, or red's right edge shows through it. Same
   easing as the slide, finished sooner. */
.doppla-reveal__blue {
  animation: doppla-converge-blue 650ms cubic-bezier(0.2, 0.7, 0.2, 1) both,
             doppla-block-fade 263ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

/* The hole opens via the <animate> on its own r attribute, in the markup
   above — not here. A circle's radius growing from 0 always expands from
   its own centre; a CSS transform:scale() on a masked element does not
   reliably do the same across browsers. The wordmark still fades in on
   the same delay, once the blocks have landed. */
.doppla-reveal__wordmark {
  animation: doppla-wordmark-in 400ms ease-out 650ms both;
}

/* The gradient rects sit still — only the flat rects beneath them slide —
   so there is no transform keyframe here, just an opacity fade. It shares
   the wordmark's own duration and easing, not the hole's: both are a plain
   opacity fade of a flat shape, where the hole's shorter duration exists
   specifically to correct for a circle's fill growing with the SQUARE of
   its radius, a correction that has no bearing on a rectangle's opacity.
   Matching the wordmark keeps the two things that are actually alike
   actually in step. */
.doppla-reveal__red-grad,
.doppla-reveal__blue-grad {
  animation: doppla-gradient-in 400ms ease-out 650ms both;
}

@keyframes doppla-converge-red {
  from { transform: translateX(-110.3%); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}

@keyframes doppla-converge-blue {
  from { transform: translateX(474.2%); }
  to   { transform: translateX(0); }
}

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

@keyframes doppla-wordmark-in {
  from { transform: translateY(0em); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}

@keyframes doppla-gradient-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Required, not optional: resolve straight to the finished lockup. */
@media (prefers-reduced-motion: reduce) {
  .doppla-reveal__red,
  .doppla-reveal__blue,
  .doppla-reveal__red-grad,
  .doppla-reveal__blue-grad,
  .doppla-reveal__wordmark {
    animation: none;
    opacity: 1;
    transform: none;
  }
  /* overrides the SMIL animation's effect, which CSS media queries alone
     do not pause — !important is required to win against the animation */
  .doppla-reveal__hole {
    r: 12px !important;
  }
}
