/* ============================================================================
   vahary-auth-shell.css — the VaharyAuthShell v2 stylesheet (Monument/Aperture
   template system; FD-LOGIN-1..4, 2026-08-07).

   Self-contained and consumable from ANY stack — static HTML, React, Blazor —
   no JS required for the static rendering: motion is pure CSS, and the
   interactive states are class toggles. Pairs with brand.css for the @font-face
   declarations and --vahary-* tokens, but degrades to its own defaults when
   brand.css is absent (every token here is `var(--vahary-*, <literal>)`).

   ROOT:            <div class="vh-shell vh-t-monument vh-m-ripple"> … </div>
   Template class:  vh-t-monument (default) | vh-t-field | vh-t-sitework
   Motion class:    vh-m-still | vh-m-ripple
                    still  = dot ignition + breathing halo only (FD-LOGIN-3)
                    ripple = + ambient blob drift, dot bloom, and a page-wide
                             accent wave born at the dot travelling behind the V.
                             All light is FILLED radial gradient — no stroked
                             rings anywhere in the motion system (founder rule).
   State classes on the root (toggle from your form logic):
     vh-s-error  — shows .vh-error, rise-in
     vh-s-busy   — primary button scanline + dimmed label (disable it yourself)
   Element-level toggles: .vh-caps.is-on (caps-lock warning visible).

   TENANT TOKEN SURFACE (set inline on the root, or on any ancestor):
     --vh-accent        one accent everything saturated derives from (#5B21B6)
     --vh-accent-ink    button text ink, COMPUTED for AA ≥4.5:1 — white or
                        #181E24 — never assume white (the ReadableText rule)
     --vh-accent-bright accent lifted until it reads ≥4.5:1 on Night, used for
                        the dot, focus, caret, kicker arrow (#8B5CF6)
     --vh-accent-rgb    the bright variant as "r,g,b" for rgba() glows
   Vahary violet/white are the DEFAULT TENANT, not the base.

   DOCUMENTED MARKUP (Monument/Aperture; the Razor component emits exactly this):

   <div class="vh-shell vh-t-monument vh-m-ripple">
     <div class="vh-ripple" aria-hidden="true">            <!-- ripple motion layer -->
       <div class="vh-blob vh-blob-a"></div>
       <div class="vh-blob vh-blob-b"></div>
       <div class="vh-wave"></div><div class="vh-wave vh-wave-2"></div>
       <div class="vh-grain"></div>
     </div>
     <div class="vh-monument" aria-hidden="true">          <!-- off-edge V, dot in frame -->
       <svg viewBox="0 0 1000 1000">
         <defs>
           <mask id="vh-apmask"><rect x="-200" y="-200" width="1400" height="1400" fill="white"/>
             <circle cx="500" cy="916.3" r="135" fill="black"/></mask>
           <radialGradient id="vh-halogr">
             <stop offset="0%"   style="stop-color:rgb(var(--vh-accent-rgb,139,92,246))" stop-opacity=".30"/>
             <stop offset="40%"  style="stop-color:rgb(var(--vh-accent-rgb,139,92,246))" stop-opacity=".12"/>
             <stop offset="72%"  style="stop-color:rgb(var(--vh-accent-rgb,139,92,246))" stop-opacity=".04"/>
             <stop offset="100%" style="stop-color:rgb(var(--vh-accent-rgb,139,92,246))" stop-opacity="0"/>
           </radialGradient>
         </defs>
         <g fill="currentColor" mask="url(#vh-apmask)">
           <path d="M0,0 L260,0 L635.2,1000 L364.8,1000 Z"/>
           <path d="M740,0 L1000,0 L635.2,1000 L364.8,1000 Z"/>
         </g>
         <circle class="vh-monument-halo"  cx="500" cy="916.3" r="230" fill="url(#vh-halogr)"/>
         <circle class="vh-monument-pulse" cx="500" cy="916.3" r="230" fill="url(#vh-halogr)"/>
         <circle class="vh-monument-dot"   cx="500" cy="916.3" r="56.7"/>
       </svg>
     </div>
     <div class="vh-groundline" aria-hidden="true"></div>
     <div class="vh-page">
       <div class="vh-top">
         <div class="vh-brand">[wordmark svg — MUST carry the violet aperture dot, FD-LOGIN-4]</div>
         <span class="vh-meta">Secure sign-in</span>
       </div>
       <main class="vh-core">
         <h1 class="vh-product"><small class="vh-product-kicker">Vahary<span class="vh-ap"> ▸</span> physical security</small>Access</h1>
         <div class="vh-error" role="alert"><span aria-hidden="true">✕</span><span><b>Sign-in failed.</b> …</span></div>
         <form class="vh-form">
           <div class="vh-fgroup"><label class="vh-label" for="email">Email</label>
             <input class="vh-input" id="email" type="email"></div>
           <div class="vh-fgroup"><label class="vh-label" for="pw">Password</label>
             <input class="vh-input" id="pw" type="password">
             <button type="button" class="vh-reveal">SHOW</button></div>
           <div class="vh-rowmeta"><span class="vh-caps">⇪ Caps Lock on</span>
             <a class="vh-quiet" href="#">Forgot password?</a></div>
           <div class="vh-actions">
             <button class="vh-btn-primary" type="submit">
               <span class="vh-btn-scan" aria-hidden="true"></span><span class="vh-btn-label">Sign in</span></button>
             <button class="vh-btn-sso" type="button">SSO</button>
           </div>
         </form>
       </main>
       <div class="vh-foot"><span>Vahary Access 4.2.0</span><span><span class="vh-ok">●</span> All systems normal</span></div>
     </div>
   </div>

   vh-t-field replaces .vh-ripple/.vh-monument with:
     <div class="vh-field-bg" aria-hidden="true">
       <div class="vh-blob vh-blob-a"></div><div class="vh-blob vh-blob-b"></div>
       <div class="vh-wave"></div><div class="vh-wave vh-wave-2"></div>
       <div class="vh-wave vh-wave-3"></div>
       <div class="vh-field-src"></div><div class="vh-grain"></div></div>
   vh-t-sitework replaces them with (plan svg is the per-product variable):
     <div class="vh-sitework-bg" aria-hidden="true">
       <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
         [class="vh-sw-wall|vh-sw-arc|vh-sw-fov|vh-sw-led|vh-sw-ann" elements]
       </svg><div class="vh-sw-vignette"></div></div>
   Sitework plan vocabulary (author your own drawing with these):
     vh-sw-wall  hairline wall run — inks on at load by a left-to-right clip wipe
                 (length-independent: no pathLength/dasharray to get wrong)
     vh-sw-2 / vh-sw-3   delay modifiers on a wall group, so the draw staggers
     vh-sw-arc   door swing — draws after the walls
     vh-sw-fov   camera cone — fades up after the arcs
     vh-sw-sweep on a cone: pans ±6°; set style="--vh-sw-pivot:<x>px <y>px" to its apex
     vh-sw-led   reader point — ignites, then breathes (the hardware LED language)
     vh-sw-ann   mono annotation; vh-sw-ann-hot renders it in the accent (live state)
     vh-sw-tick  an edge that flashes every 14s — the plan is a live site, not a drawing

   The full-page state surface (VaharyError) is NOT styled here. It renders on the Monument
   pattern too, but its CSS lives in vahary-branding.css — see the note there. This sheet is
   route-gated in some products; that one is not.

   Source of truth: AUDIT/login/06-PROTOTYPES/{monument-aperture,monument-ripple,aperture-field,
   sitework,template-system}.html; decisions in AUDIT/login/06-DIRECTION.md (FD-LOGIN-1..4).

   0.4.0: introduced (VaharyAuthShell v2 — Monument/Aperture template system).
   0.7.0: Wave 3 art direction — Aperture Field gains its ripple (re-authored as filled gradient,
          the prototype's stroked rings being what FD-LOGIN-3 bans) and its grain; Sitework gains
          the ink-on draw, the panning cone, the live tick and a scrim that keeps the reading
          column legible without a card. (VaharyError also moves onto the Monument pattern, but
          its CSS ships in vahary-branding.css — this sheet is route-gated in some products.)
   ============================================================================ */

/* ---------------- root + token surface ---------------- */
.vh-shell{
  /* neutral shell tier — brand-free geometry. The auth shell is ALWAYS the
     dark register ("precision instrument, powered on") regardless of theme. */
  --vh-paper:var(--vahary-paper, #F2F3F1);
  --vh-steel:#8B949C;               /* brand steel lifted for dark ground (4.6:1) */
  --vh-void:#0D1013;
  --vh-night:var(--vahary-night, #12161A);
  --vh-hairline:#252C33;
  --vh-carve:#181E25;
  --vh-placeholder:#3B434B;
  --vh-attention:#D97706;
  --vh-fault:#F87171;
  --vh-font-display:var(--vahary-font-display, 'Tektur', system-ui, sans-serif);
  --vh-font-body:var(--vahary-font-body, 'Work Sans', system-ui, sans-serif);
  --vh-font-mono:var(--vahary-font-mono, 'IBM Plex Mono', ui-monospace, monospace);
  --vh-ignite-dur:450ms;  --vh-ignite-ease:cubic-bezier(.2,.9,.3,1.4);
  --vh-rise-dur:360ms;    --vh-rise-ease:cubic-bezier(.2,.7,.2,1);
  --vh-breath-dur:2600ms; --vh-breath-ease:cubic-bezier(.45,0,.55,1);
  --vh-touch-dur:150ms;
  /* brand tier — the tenant layer. Vahary violet is the DEFAULT TENANT. */
  --vh-accent:#5B21B6;
  --vh-accent-ink:#FFFFFF;
  --vh-accent-bright:#8B5CF6;
  --vh-accent-rgb:139,92,246;

  position:relative; min-height:100vh; min-height:100dvh;
  overflow:clip; color-scheme:dark;
  background:var(--vh-void); color:var(--vh-paper);
  font-family:var(--vh-font-body); font-size:1rem; line-height:1.6;
  -webkit-font-smoothing:antialiased;
  box-sizing:border-box;
}
.vh-shell *,.vh-shell *::before,.vh-shell *::after{box-sizing:border-box}
.vh-t-field{background:var(--vh-night)}
.vh-t-sitework{background:var(--vh-night)}

/* ---------------- MONUMENT template: the off-edge V, dot in frame ------------ */
/* the V bleeds off the top and right edge; the bottom vertex (and its aperture
   dot) stays pinned in frame */
.vh-monument{
  position:absolute; z-index:0; pointer-events:none; color:var(--vh-carve);
  height:132vh; width:132vh;
  right:-34vh; bottom:-4vh;
  display:none;
}
.vh-t-monument .vh-monument{display:block}
.vh-monument svg{width:100%; height:100%; overflow:visible; display:block}
/* the aperture dot: the page's single point of light */
.vh-monument-dot{
  fill:var(--vh-accent-bright);
  transform-origin:center; transform-box:fill-box;
  animation:vh-ignite var(--vh-ignite-dur) var(--vh-ignite-ease) 700ms both,
            vh-breath var(--vh-breath-dur) var(--vh-breath-ease) 2.4s infinite;
  filter:drop-shadow(0 0 26px rgba(var(--vh-accent-rgb),.4));
}
@keyframes vh-ignite{
  0%{transform:scale(0)}
  70%{filter:drop-shadow(0 0 46px rgba(var(--vh-accent-rgb),.75))}
  100%{transform:scale(1); filter:drop-shadow(0 0 26px rgba(var(--vh-accent-rgb),.4))}
}
@keyframes vh-breath{0%,100%{opacity:1}50%{opacity:.55}}
/* light born at the dot — FILLED radial gradient only, never a stroked edge:
   a base halo that breathes (both motion modes), and a slow bloom that swells
   and fades (ripple mode only). Reads as light, never as a drawn ring. */
.vh-monument-halo{
  transform-origin:500px 916.3px;
  animation:vh-halobreath 6s var(--vh-breath-ease) infinite alternate;
}
@keyframes vh-halobreath{from{transform:scale(.92); opacity:.8}to{transform:scale(1.14); opacity:1}}
.vh-monument-pulse{transform-origin:500px 916.3px; opacity:0}
.vh-m-ripple .vh-monument-pulse{
  animation:vh-bloom 9s cubic-bezier(.2,.6,.4,1) 3.2s infinite;
}
@keyframes vh-bloom{
  0%{transform:scale(.45); opacity:0}
  14%{opacity:.5}
  100%{transform:scale(2.6); opacity:0}
}
/* faint ground line the monument stands on */
.vh-groundline{
  position:absolute; left:0; right:0; bottom:9vh; height:1px; z-index:0; display:none;
  background:linear-gradient(90deg, transparent 34%, var(--vh-hairline) 58%, transparent 96%);
}
.vh-t-monument .vh-groundline{display:block}

/* ---------------- ripple motion layer (vh-m-ripple, monument template) ------- */
.vh-ripple{position:absolute; inset:0; z-index:0; overflow:hidden; pointer-events:none; display:none}
.vh-t-monument.vh-m-ripple .vh-ripple{display:block}
.vh-ripple .vh-blob{position:absolute; border-radius:50%; will-change:transform; filter:blur(90px)}
.vh-ripple .vh-blob-a{
  width:110vmax; height:110vmax; right:-35vmax; bottom:-55vmax;
  background:radial-gradient(circle at 50% 50%,
    rgba(var(--vh-accent-rgb),.15) 0%, rgba(var(--vh-accent-rgb),.06) 35%,
    rgba(var(--vh-accent-rgb),.03) 55%, transparent 72%);
  animation:vh-drift-a 76s var(--vh-breath-ease) infinite alternate;
}
.vh-ripple .vh-blob-b{
  width:70vmax; height:70vmax; left:-30vmax; top:-30vmax;
  background:radial-gradient(circle at 50% 50%,
    rgba(var(--vh-accent-rgb),.05) 0%, rgba(var(--vh-accent-rgb),.04) 45%, transparent 70%);
  animation:vh-drift-b 58s var(--vh-breath-ease) infinite alternate;
}
@keyframes vh-drift-a{from{transform:translate(0,0) scale(1)}to{transform:translate(-7vmax,-6vmax) scale(1.10)}}
@keyframes vh-drift-b{from{transform:translate(0,0) scale(1.05)}to{transform:translate(6vmax,5vmax) scale(.95)}}
/* the page-wide ripple: soft annular waves of accent light born at the dot,
   traveling across the whole background BEHIND the V. Filled gradient band,
   heavily blurred, peak alpha ≤.09 — flashy in mechanism, reserved in
   amplitude. Transform/opacity only (60fps on kiosk hardware). */
.vh-ripple .vh-wave{
  position:absolute; width:220vmax; height:220vmax; border-radius:50%;
  right:calc(32vh - 110vmax); bottom:calc(7vh - 110vmax);
  background:radial-gradient(circle,
    transparent 52%,
    rgba(var(--vh-accent-rgb),.05) 62%,
    rgba(var(--vh-accent-rgb),.09) 68%,
    rgba(var(--vh-accent-rgb),.05) 74%,
    transparent 82%);
  filter:blur(30px); will-change:transform,opacity;
  transform:scale(.03); opacity:0;
  animation:vh-wavego 16s cubic-bezier(.15,.5,.35,1) 1.2s infinite;
}
.vh-ripple .vh-wave-2{animation-delay:9.2s}
@keyframes vh-wavego{
  0%{transform:scale(.03); opacity:0}
  8%{opacity:.9}
  70%{opacity:.35}
  100%{transform:scale(1.1); opacity:0}
}
/* grain stops the gradient banding */
.vh-ripple .vh-grain{
  position:absolute; inset:0; opacity:.04;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

/* ---------------- APERTURE FIELD template ------------------------------------ */
.vh-field-bg{position:absolute; inset:0; z-index:0; overflow:hidden; pointer-events:none; display:none}
.vh-t-field .vh-field-bg{display:block}
.vh-field-bg .vh-blob{position:absolute; border-radius:50%; will-change:transform; filter:blur(60px)}
.vh-field-bg .vh-blob-a{
  width:85vmax; height:85vmax; left:-15vmax; bottom:-45vmax;
  background:radial-gradient(circle,
    rgba(var(--vh-accent-rgb),.26) 0%, rgba(var(--vh-accent-rgb),.10) 42%, transparent 68%);
  animation:vh-fdrift-a 76s var(--vh-breath-ease) infinite alternate;
}
.vh-field-bg .vh-blob-b{
  width:60vmax; height:60vmax; right:-25vmax; top:-25vmax;
  background:radial-gradient(circle, rgba(var(--vh-accent-rgb),.10) 0%, transparent 70%);
  animation:vh-fdrift-b 58s var(--vh-breath-ease) infinite alternate;
}
@keyframes vh-fdrift-a{from{transform:translate(0,0) scale(1)}to{transform:translate(9vmax,-7vmax) scale(1.12)}}
@keyframes vh-fdrift-b{from{transform:translate(0,0) scale(1.05)}to{transform:translate(-7vmax,6vmax) scale(.94)}}
/* the luminance source: a filled glowing point, not a stroked ring */
.vh-field-src{
  position:absolute; right:18%; bottom:22%; width:10px; height:10px; border-radius:50%;
  background:var(--vh-accent-bright); box-shadow:0 0 14px 2px rgba(var(--vh-accent-rgb),.55);
  animation:vh-breath var(--vh-breath-dur) var(--vh-breath-ease) infinite;
  z-index:1;
}
/* THE RIPPLE — the composition's whole subject. The prototype drew three 1px-STROKED rings
   (border:1px solid rgba(139,92,246,.5)); FD-LOGIN-3 bans that outright, so the same gesture is
   re-authored as a filled radial-gradient band — light leaving a source, never a drawn edge.
   Anchored on .vh-field-src (right:18% / bottom:22%) so it is visibly born AT the dot; a 12s
   period with three waves 4s apart, peak alpha .085 — the ring cadence, the wave's physics. */
.vh-field-bg .vh-wave{
  position:absolute; width:200vmax; height:200vmax; border-radius:50%;
  right:calc(18% - 100vmax); bottom:calc(22% - 100vmax);
  background:radial-gradient(circle,
    transparent 50%,
    rgba(var(--vh-accent-rgb),.045) 60%,
    rgba(var(--vh-accent-rgb),.085) 67%,
    rgba(var(--vh-accent-rgb),.045) 74%,
    transparent 84%);
  filter:blur(26px); will-change:transform,opacity;
  transform:scale(.02); opacity:0;
  animation:vh-fwavego 12s cubic-bezier(.15,.5,.35,1) .9s infinite;
}
.vh-field-bg .vh-wave-2{animation-delay:4.9s}
.vh-field-bg .vh-wave-3{animation-delay:8.9s}
@keyframes vh-fwavego{
  0%{transform:scale(.02); opacity:0}
  6%{opacity:.95}
  62%{opacity:.4}
  100%{transform:scale(1); opacity:0}
}
/* grain stops the gradient banding — the field is nothing but gradients, so it needs this more
   than the Monument does */
.vh-field-bg .vh-grain{
  position:absolute; inset:0; opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}
/* still mode parks the field blobs and the waves — drift and ripple are ripple-tier motion; the
   breathing source survives, which is exactly FD-LOGIN-3's "still = ignition + breathing halo" */
.vh-m-still .vh-field-bg .vh-blob{animation:none; transform:translate(-4vmax,-3vmax) scale(1.05)}
.vh-m-still .vh-field-bg .vh-wave{animation:none; opacity:0}

/* ---------------- SITEWORK template ------------------------------------------ */
.vh-sitework-bg{position:absolute; inset:0; z-index:0; overflow:hidden; pointer-events:none; display:none}
.vh-t-sitework .vh-sitework-bg{display:block}
.vh-sitework-bg svg{width:100%; height:100%; display:block}
/* THE INK-ON. Sitework's identity is "an abstracted site plan INKED ON AT LOAD" (00-EXECUTIVE) —
   without the draw it is wallpaper. Walls draw by dashoffset (staggered via vh-sw-2 / vh-sw-3),
   then door arcs, then the FOV cones fade up, then the reader LEDs ignite and settle into the
   breathing LED language, then the annotations. Same order and cadence as the prototype. */
/* The draw is a CLIP WIPE, not stroke-dashoffset. dashoffset needs a dasharray >= the path's own
   length, and a plan is authored per product — the first version hardcoded 1400, every real wall
   run was longer than that, and the "drawn" plan rendered permanently DASHED: a page of
   disconnected fragments that passed every assertion because the animation had genuinely
   completed. A wipe is length-independent, so any drawing a product authors inks on correctly
   with no per-path attribute to remember. */
.vh-sw-wall{
  stroke:#333E49; stroke-width:1.5; fill:none;
  clip-path:inset(0 100% 0 0);
  animation:vh-sw-ink 1100ms var(--vh-rise-ease) forwards;
}
.vh-sw-2{animation-delay:180ms}
.vh-sw-3{animation-delay:340ms}
@keyframes vh-sw-ink{to{clip-path:inset(0 -2% 0 0)}}
.vh-sw-arc{
  stroke:#47525E; stroke-width:1; fill:none;
  clip-path:inset(0 100% 0 0);
  animation:vh-sw-ink 700ms var(--vh-rise-ease) 700ms forwards;
}
.vh-sw-fov{
  /* coverage, not a shard: at .10 fill these read as solid slabs against Night once the scrim
     stops covering them on the right of the frame */
  fill:rgba(var(--vh-accent-rgb),.04); stroke:rgba(var(--vh-accent-rgb),.30); stroke-width:.75;
  opacity:0; animation:vh-sw-in 600ms var(--vh-rise-ease) 1050ms forwards;
}
@keyframes vh-sw-in{to{opacity:1}}
/* a camera that PANS — set --vh-sw-pivot to the cone's apex ("<x>px <y>px", user units) */
.vh-sw-sweep{
  transform-origin:var(--vh-sw-pivot, center); transform-box:view-box;
  animation:vh-sw-in 600ms var(--vh-rise-ease) 1050ms forwards,
            vh-sw-pan 22s var(--vh-breath-ease) 2s infinite alternate;
}
@keyframes vh-sw-pan{from{transform:rotate(-6deg)}to{transform:rotate(6deg)}}
.vh-sw-led{
  fill:var(--vh-accent-bright); opacity:0;
  animation:vh-sw-in 300ms var(--vh-ignite-ease) 1200ms forwards,
            vh-breath var(--vh-breath-dur) var(--vh-breath-ease) 2.6s infinite;
}
.vh-sw-led:nth-of-type(even){animation-delay:1350ms, 3.4s}
.vh-sw-ann{
  font-family:var(--vh-font-mono); font-size:9.5px; letter-spacing:.16em; fill:#5A636C;
  opacity:0; animation:vh-sw-in 500ms var(--vh-rise-ease) 1400ms forwards;
}
/* the one annotation that carries state — accent, so the plan reads as live */
.vh-sw-ann-hot{fill:var(--vh-accent-bright)}
/* a door edge flashing every 14s: the plan is a live site, not a drawing. Filled stroke on an
   existing wall edge — a highlight, not a ring. */
.vh-sw-tick{stroke:var(--vh-accent-bright); stroke-width:1.5; opacity:0;
  animation:vh-sw-tick 14s linear 4s infinite}
@keyframes vh-sw-tick{0%,3%{opacity:.9} 6%,100%{opacity:0}}
/* THE SCRIM. The prototype floated a solid card over the plan; this shell deliberately has no
   card (the template must be DATA, one shared page layout for all three), so the scrim has to do
   the card's job: keep the reading column quiet while the plan stays legible everywhere else.
   The old single radial was centred on the PAGE, not on the column, so wall linework ran straight
   through the noun and the fields. Two layers now: a left-edge wash that tracks the reading column
   at every width, over the original radial settle. */
.vh-sw-vignette{position:absolute; inset:0;
  background:
    linear-gradient(90deg,
      rgba(18,22,26,.96) 0%, rgba(18,22,26,.93) 24%, rgba(18,22,26,.66) 40%,
      rgba(18,22,26,.24) 58%, rgba(18,22,26,0) 74%),
    radial-gradient(ellipse 70% 82% at 28% 50%,
      rgba(18,22,26,.55) 0%, rgba(18,22,26,.18) 62%, rgba(18,22,26,0) 100%);
}
/* single-column below 760px: the form spans the width, so the wash has to as well */
@media (max-width:760px){
  .vh-sw-vignette{background:linear-gradient(180deg,
    rgba(18,22,26,.86) 0%, rgba(18,22,26,.93) 30%, rgba(18,22,26,.93) 100%)}
}

/* ---------------- the page (shared shell) ------------------------------------ */
.vh-page{
  position:relative; z-index:1; min-height:100vh; min-height:100dvh;
  display:flex; flex-direction:column;
  padding:clamp(1.6rem,4vh,3rem) clamp(1.6rem,6vw,6rem);
  max-width:100rem; margin-inline:auto;
}
.vh-top{display:flex; justify-content:space-between; align-items:center; min-height:24px}
.vh-brand{display:flex; align-items:center; gap:.6rem; color:var(--vh-paper)}
.vh-brand svg{height:19px; width:auto; display:block}
.vh-brand img{height:24px; width:auto; display:block; max-width:200px; object-fit:contain}
/* generated text lockup (no uploaded mark): name + accent dot, swapped wholesale */
.vh-brand-textmark{font-family:var(--vh-font-display); font-weight:500; font-size:1.05rem; letter-spacing:.04em}
.vh-brand-textmark::after{content:""; display:inline-block; width:7px; height:7px; border-radius:50%;
  background:var(--vh-accent-bright); margin-left:.45rem; vertical-align:baseline}
/* the wordmark's aperture dot ignites with the monument's */
.vh-brand-dot{animation:vh-ignite var(--vh-ignite-dur) var(--vh-ignite-ease) 700ms both;
  transform-origin:center; transform-box:fill-box}
.vh-meta{font-family:var(--vh-font-mono); font-size:.66rem; letter-spacing:.22em;
  text-transform:uppercase; color:var(--vh-steel)}

.vh-core{flex:1; display:flex; flex-direction:column; justify-content:center;
  max-width:24rem; padding-bottom:6vh}
.vh-t-sitework .vh-core{max-width:23rem}

.vh-product{
  font-family:var(--vh-font-display); font-weight:500; letter-spacing:.01em;
  font-size:clamp(2.6rem,6vw,4.6rem); line-height:1.02; margin:0 0 .35em;
  animation:vh-rise var(--vh-rise-dur) var(--vh-rise-ease) 150ms both;
}
.vh-product-kicker{
  display:block; font-family:var(--vh-font-mono); font-size:.72rem; font-weight:400;
  letter-spacing:.30em; text-transform:uppercase; color:var(--vh-steel); margin-bottom:1.1rem;
}
.vh-ap{color:var(--vh-accent-bright)}
@keyframes vh-rise{from{opacity:0; transform:translateY(14px)}to{opacity:1; transform:none}}

/* ---------------- the form spec (hairline underline fields) ------------------ */
.vh-form{margin-top:2.2rem; animation:vh-rise var(--vh-rise-dur) var(--vh-rise-ease) 260ms both}
.vh-fgroup{margin-bottom:1.9rem; position:relative}
.vh-label{
  display:block; font-family:var(--vh-font-mono); font-size:.64rem; letter-spacing:.22em;
  text-transform:uppercase; color:var(--vh-steel); margin-bottom:.15rem;
}
.vh-input{
  width:100%; height:52px; padding:0 3rem 0 0; font:inherit; font-size:1.08rem; color:var(--vh-paper);
  background:transparent; border:0; border-bottom:1px solid var(--vh-hairline); border-radius:0;
  transition:border-color var(--vh-touch-dur); caret-color:var(--vh-accent-bright);
}
.vh-input::placeholder{color:var(--vh-placeholder)}
.vh-input:focus{outline:none; border-bottom-color:var(--vh-accent-bright)}
.vh-fgroup::after{
  content:""; position:absolute; left:0; bottom:0; height:2px; width:100%;
  background:var(--vh-accent-bright); transform:scaleX(0); transform-origin:left;
  transition:transform 220ms var(--vh-rise-ease);
}
.vh-fgroup:focus-within::after{transform:scaleX(1)}
.vh-input:-webkit-autofill{-webkit-box-shadow:0 0 0 60px var(--vh-void) inset; -webkit-text-fill-color:var(--vh-paper)}

.vh-reveal{
  position:absolute; right:0; bottom:4px; width:44px; height:44px; border:0; background:transparent;
  color:var(--vh-steel); cursor:pointer; font-family:var(--vh-font-mono); font-size:.6rem; letter-spacing:.1em;
}
.vh-reveal:hover,.vh-reveal:focus-visible{color:var(--vh-paper)}
/* Focus ring on things a person can actually operate — NOT a blanket :focus-visible.
   The blanket form drew a violet box around the product noun on first paint, because Blazor's
   FocusOnNavigate puts tabindex="-1" on the <h1> and focuses it on every navigation. That is a
   programmatic focus on a heading nobody chose, so a focus ring there is noise, and it read as a
   rendering fault. Three products each worked around it separately — one suppressed it in its own
   app.css, one refused to and escalated, one countermanded it in its supplement — which is the
   divergence this shared sheet exists to prevent. Scoped here so none of them has to.
   :focus-visible still gates it, so pointer users see nothing; keyboard users keep every ring. */
.vh-shell a[href]:focus-visible,
.vh-shell button:focus-visible,
.vh-shell input:focus-visible,
.vh-shell select:focus-visible,
.vh-shell textarea:focus-visible,
.vh-shell summary:focus-visible,
.vh-shell [tabindex]:not([tabindex="-1"]):focus-visible{
  outline:2px solid var(--vh-accent-bright); outline-offset:3px
}

/* …and SUPPRESS the ring the browser draws for us on the element nobody chose. Scoping the rule
   above away from [tabindex="-1"] stops THIS sheet drawing a violet box round the product noun; it
   does not remove the UA default underneath, and Chrome does match :focus-visible on the <h1
   tabindex="-1"> that Blazor's FocusOnNavigate focuses on every navigation. Measured on a running
   app: Access's login draws the browser's own outline:auto box round the noun on first paint, while
   Visitor does not — because Visitor's app.css privately carries `h1:focus{outline:none}`. That is
   the same per-product divergence, one level down, that the 0.6.0 note above records; so it is
   fixed here once instead of in each product. Costs no keyboard affordance: a [tabindex="-1"]
   element cannot be reached by tabbing, so no one can arrive here by choice. */
.vh-shell [tabindex="-1"]:focus{outline:none}

.vh-rowmeta{display:flex; justify-content:space-between; align-items:center; margin:-0.9rem 0 1.6rem}
.vh-caps{font-family:var(--vh-font-mono); font-size:.64rem; letter-spacing:.12em;
  color:var(--vh-attention); visibility:hidden; text-transform:uppercase}
.vh-caps.is-on{visibility:visible}
.vh-quiet{color:var(--vh-steel); font-size:.82rem; text-decoration:none; border-bottom:1px solid transparent;
  transition:color var(--vh-touch-dur),border-color var(--vh-touch-dur)}
.vh-quiet:hover,.vh-quiet:focus-visible{color:var(--vahary-violet-tint, #D8C9F7); border-bottom-color:var(--vahary-violet-tint, #D8C9F7)}

/* ---------------- states ----------------------------------------------------- */
.vh-error{
  display:none; gap:.55rem; margin:0 0 1.4rem; padding:.7rem 0;
  border-top:1px solid rgba(248,113,113,.4); border-bottom:1px solid rgba(248,113,113,.4);
  color:var(--vh-paper); font-size:.86rem; line-height:1.45;
}
.vh-s-error .vh-error{display:flex; animation:vh-rise 200ms var(--vh-rise-ease) both}
.vh-error b{color:var(--vh-fault)}

.vh-actions{display:flex; gap:1rem; align-items:stretch}
.vh-btn-primary{
  flex:1; height:52px; border:0; border-radius:2px; cursor:pointer;
  font-family:var(--vh-font-body); font-weight:700; font-size:.98rem;
  background:var(--vh-accent); color:var(--vh-accent-ink); position:relative; overflow:hidden;
  transition:filter var(--vh-touch-dur), transform var(--vh-touch-dur);
}
.vh-btn-primary:hover{filter:brightness(1.15)}
.vh-btn-primary:active{transform:translateY(1px)}
.vh-btn-scan{position:absolute; left:0; top:0; height:100%; width:38%; display:none;
  background:linear-gradient(90deg,transparent,rgba(216,201,247,.5),transparent);
  animation:vh-scanmove 900ms cubic-bezier(.4,0,.2,1) infinite}
@keyframes vh-scanmove{from{transform:translateX(-120%)}to{transform:translateX(380%)}}
.vh-s-busy .vh-btn-primary .vh-btn-scan{display:block}
.vh-s-busy .vh-btn-primary .vh-btn-label{opacity:.75}
.vh-btn-sso{
  height:52px; padding:0 1.2rem; border:1px solid var(--vh-hairline); border-radius:2px; cursor:pointer;
  background:transparent; color:var(--vh-paper); font:inherit; font-size:.9rem;
  display:flex; align-items:center; gap:.55rem;
  transition:border-color var(--vh-touch-dur), background var(--vh-touch-dur);
}
.vh-btn-sso:hover{border-color:var(--vh-steel); background:rgba(242,243,241,.03)}
.vh-btn-sso svg{width:14px; height:14px}

.vh-foot{
  display:flex; justify-content:space-between;
  font-family:var(--vh-font-mono); font-size:.64rem; letter-spacing:.16em;
  color:var(--vh-placeholder); text-transform:uppercase;
}
.vh-ok{color:var(--vh-accent-bright)}

/* ---------------- responsive ------------------------------------------------- */
@media (max-width:760px){
  .vh-shell{overflow:auto}
  .vh-monument{height:104vh; width:104vh; right:-46vh; bottom:-2vh; opacity:.85}
  .vh-core{max-width:100%}
  .vh-actions{flex-direction:column}
  .vh-actions .vh-btn-primary{flex:none}
  .vh-actions .vh-btn-sso{justify-content:center}
  .vh-meta{display:none}
}
@media (min-width:2200px){ .vh-core{max-width:26rem} }

/* ---------------- reduced-motion: the composed still ------------------------- */
@media (prefers-reduced-motion: reduce){
  .vh-ripple .vh-blob{animation:none; transform:translate(-4vmax,-3vmax) scale(1.05)}
  .vh-ripple .vh-wave{animation:none; opacity:0}
  .vh-monument-dot,.vh-brand-dot{animation:none; opacity:1;
    filter:drop-shadow(0 0 26px rgba(var(--vh-accent-rgb),.4))}
  .vh-monument-halo{animation:none; transform:scale(1); opacity:1}
  .vh-monument-pulse{animation:none!important; opacity:0}
  .vh-field-bg .vh-blob{animation:none; transform:translate(-4vmax,-3vmax) scale(1.05)}
  .vh-field-bg .vh-wave{animation:none; opacity:0}
  .vh-field-src,.vh-sw-led{animation:none; opacity:1}
  /* the composed still of the ink-on: the plan is simply already drawn */
  .vh-sw-wall,.vh-sw-arc{animation:none; clip-path:none}
  .vh-sw-fov,.vh-sw-ann{animation:none; opacity:1}
  .vh-sw-sweep{animation:none; opacity:1; transform:none}
  .vh-sw-tick{animation:none; opacity:0}
  .vh-product,.vh-form,.vh-s-error .vh-error{animation:none; opacity:1; transform:none}
  .vh-fgroup::after{transition:none}
  .vh-btn-scan{display:none!important}
}
