@@ -2283,9 +2283,16 @@ fluent-select {
22832283 loader's pre-transform width MUST stay at 25rem (= 400px) for the
22842284 plane to land on the runway. On narrow viewports we apply a uniform
22852285 transform: scale() instead of shrinking width - that keeps the
2286- coordinate space intact while still fitting the screen. */
2286+ coordinate space intact while still fitting the screen.
2287+
2288+ Height bumped from 6.25rem → 8rem so the plane's 14px drop-shadow
2289+ halo has breathing room above the wave peak and below the trough -
2290+ the previous height clipped the glow on tall waves. overflow:visible
2291+ is explicit (defensive) so any descendant filter spill renders even
2292+ if a future layout adds a clipping ancestor. */
22872293 width : 25rem ;
2288- height : 6.25rem ;
2294+ height : 8rem ;
2295+ overflow : visible;
22892296 z-index : 2 ;
22902297 /* Uniform downscale so the 400px SVG fits any viewport. The 2rem
22912298 safety margin on each side keeps the loader off the screen edges
@@ -2298,7 +2305,10 @@ fluent-select {
22982305
22992306.runway-wave {
23002307 position : absolute;
2301- top : 1.25rem ;
2308+ /* Centered vertically inside the 8rem loader (8 - 3.75) / 2 ≈ 2.125rem,
2309+ which leaves ~1rem above the wave peaks for the plane's drop-shadow
2310+ halo and ~1rem below the troughs before the status text. */
2311+ top : 2.125rem ;
23022312 left : 0 ;
23032313 width : 25rem ;
23042314 height : 3.75rem ;
@@ -2326,43 +2336,52 @@ fluent-select {
23262336 A horizontal mask gradient fades the trail's tail so the oldest part of
23272337 the flight path dissolves behind the plane - visually separates the
23282338 glowing plane from the line, which were blending into one violet smear. */
2339+ /* Register --pf-trail-progress as a typed <number> so it interpolates
2340+ smoothly under transitions. Without @property, custom properties are
2341+ opaque strings to CSS - the consumer property still transitions, but
2342+ var-driven calc() results sometimes pin to discrete computed values
2343+ in WebView2 / older Chromium, which is exactly the asymmetry that
2344+ made the trail "snap" while the plane glided behind it. */
2345+ @property --pf-trail-progress {
2346+ syntax : "<number>" ;
2347+ inherits : true;
2348+ initial-value : 0 ;
2349+ }
2350+
23292351/* Honest deterministic fill: the runway draws itself in lockstep with
23302352 ACTUAL Blazor boot progress, not a hardcoded 2.4s timeline.
23312353
2332- Blazor WebAssembly auto- publishes its load progress to two CSS custom
2354+ Blazor WebAssembly publishes its load progress to two CSS custom
23332355 properties on <html>:
23342356 --blazor-load-percentage - e.g. "47%" (length-percentage)
23352357 --blazor-load-percentage-text - e.g. "47%" (string for content:)
2336- The status text below already consumes the second one. Here we wire
2337- the trail fill (and the plane's offset-distance) to the first so the
2338- visual matches reality.
2339-
2340- Math: pathLength="100" means dash values are interpreted as percent
2341- of declared path length. A single dash of 100% covers the whole path;
2342- stroke-dashoffset 100% pushes it fully off, 0% draws it fully. As
2343- Blazor reports progress 0 → 100%, dashoffset moves 100% → 0%. The
2344- transition smooths Blazor's chunky discrete bumps into a fluid bar. */
2358+ The status text below consumes the second one. The trail fill below
2359+ uses --pf-trail-progress (unitless 0..100), which a small JS bridge
2360+ in index.html mirrors from --blazor-load-percentage in the SAME
2361+ frame Blazor writes it (MutationObserver, not polled).
2362+
2363+ The bridge ALSO pins --pf-trail-length-px to the path's real total
2364+ length (~416px). We then drive stroke-dasharray as an ABSOLUTE pixel
2365+ length (`progress/100 * length`) instead of relying on the path's
2366+ pathLength="100" attribute to rescale unitless dash values - because
2367+ Chromium / WebView2 does not apply pathLength scaling to length-
2368+ typed dash values produced by var()/calc(). With "30px 100px" on a
2369+ 416px curve the pattern repeats four times (visible drawn segments
2370+ at 0-30, 130-160, 260-290, 390-416), which is exactly the "line ahead
2371+ of the plane" symptom. Hard pixel math sidesteps the bug. */
23452372.runway-trail {
23462373 stroke : url (# runwayGradient);
23472374 stroke-width : 2 ;
23482375 stroke-linecap : round;
2349- /* Progressive reveal MUST be in path-length space so the trail tip
2350- lands exactly under the plane. The plane uses
2351- offset-distance: var(--blazor-load-percentage)
2352- which is path-length-relative. Mixing in a horizontal-pixel mask
2353- for the reveal desyncs them on a wavy path (path length > box
2354- width, so x-progress runs ahead of path-progress) - this caused
2355- the previous bug where the line drew first and the plane chased
2356- it. The path declares pathLength="100", so unitless dash values
2357- are percent-of-path-length. The /1% trick collapses the percent
2358- unit (e.g. 60% / 1% = 60) so we can pass a path-relative offset
2359- where SVG would normally treat % as viewport-diagonal. */
2360- stroke-dasharray : 100 ;
2361- stroke-dashoffset : calc (100 - var (--blazor-load-percentage , 0% ) / 1% );
2376+ /* ON segment = progress% of the curve's actual length. The OFF
2377+ segment is set far longer than the path so the dash pattern can
2378+ never wrap onto a second visible repetition. */
2379+ stroke-dasharray : calc (var (--pf-trail-progress , 0 ) / 100 * var (--pf-trail-length-px , 416px )) 9999px ;
2380+ stroke-dashoffset : 0 ;
23622381 /* Smooth fade-in at the very start of the runway. This mask is
23632382 STATIC (not progress-dependent) - it always fades the leftmost
23642383 portion of the SVG box so the trail never starts abruptly at
2365- x=0. Combined with the dashoffset reveal above, the result is:
2384+ x=0. Combined with the dasharray reveal above, the result is:
23662385 trail grows from start to plane, leftmost ~12% softened. */
23672386 -webkit-mask-image : linear-gradient (to right,
23682387 rgba (0 , 0 , 0 , 0 ) 0% ,
@@ -2374,7 +2393,11 @@ fluent-select {
23742393 rgba (0 , 0 , 0 , 1 ) 100% );
23752394 filter : drop-shadow (0 0 4px var (--pf-accent ));
23762395 opacity : 0.9 ;
2377- transition : stroke-dashoffset 0.35s cubic-bezier (0.4 , 0 , 0.2 , 1 );
2396+ /* Match the plane's offset-distance transition so the trail tip and
2397+ the plane glide together (both 0.35s, same easing). Transitioning
2398+ --pf-trail-progress (typed as <number> via @property above) makes
2399+ any consumer of var(--pf-trail-progress) interpolate smoothly. */
2400+ transition : --pf-trail-progress 0.35s cubic-bezier (0.4 , 0 , 0.2 , 1 );
23782401}
23792402
23802403/* Plane follows the EXACT same path the SVG drew. Both use the 400x60
@@ -2383,11 +2406,15 @@ fluent-select {
23832406 Inline SVG with gradient fill - bulletproof visibility across themes. */
23842407.plane {
23852408 position : absolute;
2386- top : 1.25rem ;
2409+ /* Match .runway-wave's top so offset-path coordinates stay aligned -
2410+ the plane's offset-path uses the same 0..400 / 0..60 space as the
2411+ wave SVG, so both must anchor at the same loader-relative y. */
2412+ top : 2.125rem ;
23872413 left : 0 ;
23882414 width : 1.75rem ;
23892415 height : 1.75rem ;
23902416 display : block;
2417+ overflow : visible;
23912418 pointer-events : none;
23922419
23932420 offset-path : path ("M 0 30 Q 50 5, 100 30 T 200 30 T 300 30 T 400 30" );
0 commit comments