/* Grizzly Beam — /services/ (spec: specs/services-build-spec.md, source trionn.com/services @1440) */

/* .svc-hero, .svc-hero__bg, .svc-hero__ctr, .svc-hero__eyebrow, .svc-hero__title,
   .svc-plus + @keyframes svcDrift → promoted to components.css (Phase 2, shared
   with the service detail pages). Only what is specific to the overview stays here. */


/* ---------- Service list: stacked slide-over pin ---------- */
.svc-list { position: relative; background: var(--paper); color: var(--ink); }
.svc-list__sticky { position: relative; height: 100vh; overflow: clip; }
.svc-item {
  position: absolute; inset: 0; display: flex;
  background: var(--paper);
  border-top: 1px solid rgba(22, 24, 28, 0.2);
  will-change: transform;
}
/* the items' initial offset is set exclusively by GSAP (yPercent) — a CSS transform would
   double up with the GSAP values; the preloader covers the screen until initialization */
.svc-item__line { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: rgba(22, 24, 28, 0.2); }
.svc-item__line::before, .svc-item__line::after {
  content: "+"; position: absolute; left: 50%; transform: translateX(-50%);
  font-size: 1rem; line-height: 1; color: rgba(22, 24, 28, 0.5); background: var(--paper);
  padding-block: 0.15rem;
}
.svc-item__line::before { top: 0.35rem; }
.svc-item__line::after { bottom: 0.35rem; }
.svc-item__left {
  width: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3.6rem;                                            /* source: kicker→image ~46px */
}
.svc-item__num { color: var(--tx-g); }
.svc-item__media { width: 44.84rem; max-width: 80%; }     /* source 574 */
.svc-item__media svg { width: 100%; height: auto; border-radius: var(--r-10); }
.svc-item__content {
  position: absolute; left: 58.6%; top: 50%; transform: translateY(-50%);   /* source x844 */
  width: 32.85%;                                          /* source 473 */
  display: flex; flex-direction: column; align-items: flex-start;
}
.svc-item__title {
  font-size: 2.53rem; line-height: 1; letter-spacing: -0.005em; font-weight: var(--w-h2);   /* brand: H2 (source 32.4) */
}
.svc-item__desc { color: var(--tx-g); margin-top: 1.72rem; max-width: 31.5rem; }    /* source gap 22, max-w 403 */
.svc-item__content .ulink { margin-top: 3.4rem; }

/* ---------- How we price (teaser) ---------- */
.svc-pricing { background: var(--paper); color: var(--ink); padding-block: 6.5rem 7.5rem; }
.svc-pricing__grid { display: grid; grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); gap: 6rem; align-items: center; }
.svc-pricing__eyebrow { color: var(--tx-g); }
.svc-pricing__title { font-size: 3.2rem; line-height: 1.05; letter-spacing: -0.005em; font-weight: var(--w-h2); margin-top: 1.5rem; max-width: 32rem; }   /* brand: H2 (wpa pattern) */
.svc-pricing__right { display: flex; flex-direction: column; gap: 2.4rem; align-items: flex-start; }
.svc-pricing__right .body { color: var(--tx-g); max-width: 36rem; }

/* Closer (.svc-closer*) → promoted to components.css (Phase 2, shared). */

/* ---------- Responsive ---------- */
@media (max-width: 1023px) {
  .svc-list__sticky { height: auto; overflow: visible; }
  .svc-item { position: static; transform: none !important; flex-direction: column; gap: 3rem; padding-block: 4rem; }
  .svc-item__line { display: none; }
  .svc-item__left { width: 100%; }
  .svc-item__media { max-width: min(44.84rem, 88vw); }
  .svc-item__content { position: static; transform: none; width: 100%; max-width: 44rem; margin: 0 auto; padding-inline: 2.8125rem; }
  .svc-pricing__grid { grid-template-columns: 1fr; gap: 3rem; }
}
@media (max-width: 767px) {
  .svc-pricing__title { font-size: 2.6rem; }
}

/* ---------- Reduced motion / no-JS: static list ---------- */
@media (prefers-reduced-motion: reduce) {
  .svc-list__sticky { height: auto !important; overflow: visible !important; }
  .svc-item { position: static !important; transform: none !important; }
  .svc-item__content { position: static; transform: none; margin-top: 2rem; }
}
html:not(.has-js) .svc-list__sticky { height: auto; overflow: visible; }
html:not(.has-js) .svc-item { position: static; }
html:not(.has-js) .svc-item__content { position: static; transform: none; margin-top: 2rem; }


/* ============================================================================
   SERVICE CARD DRAWINGS
   Ported from home.codeacting.com/development/ 2026-09-03.
   Source: assets/css/pages/development.css — all drawing rules (.cvs .brm .ogc
   .dbc .hsbg + @keyframes). The lab frame (.dev .dev-hdr .devc .devmap)
   was NOT ported: 31 blocks dropped, 88 kept.

   Changes made while porting:
     .devc.is-armed      -> .svc-item__media.is-armed   carrier of the animation state
     --dev-ease          -> defined on .svc-item__media
     .dbc border-radius  -> var(--r-10) instead of inherit; in the lab it came from
                            .devc__frame, here .svc-item__media has no radius on
                            itself but on its svg
     .devc__replay       -> removed; the replay button is a development tool

   The initial (hidden) state hangs on .is-armed, which JS sets only if
   motion is allowed — with prefers-reduced-motion the drawing stays in its final form.
   ========================================================================= */

.svc-item__media { --dev-ease: cubic-bezier(0.16, 1, 0.3, 1); }

/* .dbc is an HTML drawing, not an svg — the `.svc-item__media svg` rule from the base block
   also hits the watermark inside it, so its dimensions are reset to what the CSS specifies. */
.svc-item__media .dbc svg { width: auto; height: auto; border-radius: 0; }
/* ---------- 1 · Conversion ---------- */
.svc-item__media.is-armed .cvs__eyebrow,
.svc-item__media.is-armed .cvs__copy text { opacity: 0; }

.svc-item__media.is-armed .cvs__path     { stroke-dasharray: 1; stroke-dashoffset: 1; }

.svc-item__media.is-armed .cvs__dot      { opacity: 0; transform: scale(0); transform-box: fill-box; transform-origin: 50% 50%; }

.svc-item__media.is-armed .cvs__cta      { opacity: 0; transform: translateY(10px) scale(0.94); transform-box: fill-box; transform-origin: 50% 50%; }

/* The order is explicit, not estimated: dot 1 -> line to dot 2 -> dot 2 ->
   line to the button -> button. That is why the line has a PAUSE in the middle of its keyframes,
   rather than drawing evenly with dots dropping in "somewhere along the way".

   The pause is at 0.4994 because dot 2 at (218,185) lies at 50.06% of the length
   of the mapped path — measured by numerical integration of the curve, not estimated. */
/* 2026-09-03: slowed down. The line goes 1.7s -> 2.6s, the rest follows. The keyframe positions
   (41% / 65%) do NOT change — they come from dot 2 lying at 50.06%
   of the path length, which is geometry, not tempo. Only the duration changes, so
   the pause stays at the same point on the curve by itself:
     line reaches dot 2         0.5 + 0.41 x 2.6 = 1.57s
     pause lasts until          0.5 + 0.65 x 2.6 = 2.19s
     line reaches the button    0.5 + 2.6        = 3.10s */
.svc-item__media.is-armed.is-in .cvs__eyebrow { animation: devFade .55s var(--dev-ease) .08s both; }

.svc-item__media.is-armed.is-in .cvs__dot--1  { animation: devPop  .5s var(--dev-ease) .08s both; }

.svc-item__media.is-armed.is-in .cvs__path    { animation: cvsGrow 2.6s linear .5s both; }

.svc-item__media.is-armed.is-in .cvs__dot--2  { animation: devPop  .55s var(--dev-ease) 1.57s both; }

.svc-item__media.is-armed.is-in .cvs__cta     { animation: devLand .6s var(--dev-ease) 3.10s both; }

.svc-item__media.is-armed.is-in .cvs__copy text:nth-of-type(1) { animation: devRise .6s var(--dev-ease) 3.40s both; }

.svc-item__media.is-armed.is-in .cvs__copy text:nth-of-type(2) { animation: devRise .6s var(--dev-ease) 3.55s both; }

/* The line grows more deliberately than the rest — it is the only part of the drawing that "travels",
   so the front-loaded --dev-ease does not suit it; it would snap within the first 200ms. */
@keyframes cvsGrow {
   0%  { stroke-dashoffset: 1;      animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); }
  41%  { stroke-dashoffset: 0.4994; animation-timing-function: linear; }
  65%  { stroke-dashoffset: 0.4994; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); }
 100%  { stroke-dashoffset: 0; }
}

/* ---------- 2 · Brand and messaging ---------- */
.svc-item__media.is-armed .brm__eyebrow { opacity: 0; }

.svc-item__media.is-armed .brm__sub     { opacity: 0; }

.svc-item__media.is-armed .brm__line    { transform: translateY(72px); }

.svc-item__media.is-armed .brm__rule    { stroke-dasharray: 1; stroke-dashoffset: 1; }

/* 2026-09-03: slowed down by a factor of 1.4, the same one used to slow down Conversion —
   so all three drawings read as one family. Total ~1.95s -> ~2.75s.
   The gap between the two rows stays the same share of the duration (0.31s of 1.2s). */
.svc-item__media.is-armed.is-in .brm__eyebrow  { animation: devFade .55s var(--dev-ease) .08s both; }

.svc-item__media.is-armed.is-in .brm__line--1  { animation: devLift 1.2s var(--dev-ease) .28s both; }

.svc-item__media.is-armed.is-in .brm__line--2  { animation: devLift 1.2s var(--dev-ease) .59s both; }

.svc-item__media.is-armed.is-in .brm__rule     { animation: devDraw .78s var(--dev-ease) 1.55s both; }

.svc-item__media.is-armed.is-in .brm__sub      { animation: devRise .7s var(--dev-ease) 2.05s both; }

/* ---------- 3 · Ongoing care ---------- */
.svc-item__media.is-armed .ogc__eyebrow { opacity: 0; }

.svc-item__media.is-armed .ogc__head    { opacity: 0; }

.svc-item__media.is-armed .ogc__graph,
.svc-item__media.is-armed .ogc__shield,
.svc-item__media.is-armed .ogc__tick    { stroke-dasharray: 1; stroke-dashoffset: 1; }

/* The segment stays still until the drawing has assembled. */
.ogc__sweep { stroke-dasharray: 0.07 1.93; stroke-dashoffset: 2; opacity: 0; }

/* 2026-09-03: slowed down by the same factor of 1.4. Total ~2.20s -> ~3.08s.
   Overlaps are kept as shares, not as absolute values: the shield still
   starts while the graph is running, and the checkmark while the shield finishes — as in the original.
   The sliding segment is also slowed down (3.4s -> 4.8s), since it too falls under "slower". */
.svc-item__media.is-armed.is-in .ogc__eyebrow { animation: devFade .55s var(--dev-ease) .08s both; }

.svc-item__media.is-armed.is-in .ogc__graph   { animation: devDraw 1.7s var(--dev-ease) .28s both; }

/* The three labels are TYPED, one at a time, as on a keyboard. They do not start together:
   nobody types three lines in parallel, so they go in sequence and there is only ever one cursor.

   The reveal uses `clip-path: inset(...) view-box` on the <text> itself, with steps() per
   character count. `view-box` matters: the reference box is the SVG viewport
   (0 0 574 370), so the values are ABSOLUTE drawing coordinates and still work when the SVG
   is scaled (verified: set to 6 characters -> the ink ends at 5.9).
   With `fill-box` (the default) they would be relative to the text's ink box, which does not
   equal the sum of the advance widths.

   The step is 7.2 SVG units per character — JetBrains Mono is true monospace,
   600/1000 em at font-size 12, measured from the woff2, not estimated.

   WHY A CLIP AND NOT CHANGING THE TEXT: a JS solution that writes out
   `textContent` character by character was also tried. It worked as far as DOM state goes, but left
   STALE PAINTING — at rest, five seconds after the end, rows 2 and 3 were
   drawn twice. Chrome does not invalidate the old SVG text surface when the
   content changes often. The clip does not touch the text, so that problem does not exist.

   (The first attempt with the clip looked like it did not work, but the real cause was
   a leftover rule `.svc-item__media.is-armed .ogc__stats text { opacity: 0 }` from the earliest
   version of the animation, which kept the labels invisible regardless of the mechanism.)

   Tempo 70ms per character (was 46ms — Stevan asked for slower):
     row 1  13 chars  1.40 -> 2.31s
     row 2  13 chars  2.35 -> 3.26s
     row 3  14 chars  3.30 -> 4.28s */
.svc-item__media.is-armed .ogc__ln--1 { clip-path: inset(238px 518.0px 112px 56px) view-box; }

.svc-item__media.is-armed.is-in .ogc__ln--1 { animation: ogcLn1 .91s steps(13, jump-start) 1.40s both; }

.svc-item__media.is-armed .ogc__ln--2 { clip-path: inset(238px 374.0px 112px 200px) view-box; }

.svc-item__media.is-armed.is-in .ogc__ln--2 { animation: ogcLn2 .91s steps(13, jump-start) 2.35s both; }

.svc-item__media.is-armed .ogc__ln--3 { clip-path: inset(238px 204.0px 112px 370px) view-box; }

.svc-item__media.is-armed.is-in .ogc__ln--3 { animation: ogcLn3 .98s steps(14, jump-start) 3.30s both; }

@keyframes ogcLn1 {
  from { clip-path: inset(238px 518.0px 112px 56px) view-box; }
    to { clip-path: inset(238px 424.4px 112px 56px) view-box; }
}

@keyframes ogcLn2 {
  from { clip-path: inset(238px 374.0px 112px 200px) view-box; }
    to { clip-path: inset(238px 280.4px 112px 200px) view-box; }
}

@keyframes ogcLn3 {
  from { clip-path: inset(238px 204.0px 112px 370px) view-box; }
    to { clip-path: inset(238px 103.2px 112px 370px) view-box; }
}

/* The cursor jumps in the same rhythm as the characters, so it sits exactly where the next one goes.
   It turns off at the end of its row — nothing blinks afterward. */
.ogc__caret { opacity: 0; }

.svc-item__media.is-armed.is-in .ogc__caret--1 { animation: ogcCar1 .91s steps(13, jump-start) 1.40s forwards, ogcCarOn .91s linear 1.40s forwards; }

@keyframes ogcCar1 { from { x: 56px; } to { x: 149.6px; } }

.svc-item__media.is-armed.is-in .ogc__caret--2 { animation: ogcCar2 .91s steps(13, jump-start) 2.35s forwards, ogcCarOn .91s linear 2.35s forwards; }

@keyframes ogcCar2 { from { x: 200px; } to { x: 293.6px; } }

.svc-item__media.is-armed.is-in .ogc__caret--3 { animation: ogcCar3 .98s steps(14, jump-start) 3.30s forwards, ogcCarOn .98s linear 3.30s forwards; }

@keyframes ogcCar3 { from { x: 370px; } to { x: 470.8px; } }

@keyframes ogcCarOn { 0%, 92% { opacity: 1; } 100% { opacity: 0; } }

.svc-item__media.is-armed.is-in .ogc__shield  { animation: devDraw 1.0s var(--dev-ease) 1.26s both; }

/* The checkmark snaps in more sharply than the rest — it is the only moment of confirmation in the drawing. */
.svc-item__media.is-armed.is-in .ogc__tick    { animation: devDraw .42s cubic-bezier(0.34, 1.4, 0.64, 1) 2.17s both; }

.svc-item__media.is-armed.is-in .ogc__head    { animation: devRise .7s var(--dev-ease) 2.38s both; }

.svc-item__media.is-armed.is-in .ogc__sweep   { animation: devSweep 4.8s linear 3.1s infinite; }

/* ---------- keyframes ---------- */
@keyframes devFade  { from { opacity: 0; } to { opacity: 1; } }

@keyframes devRise  { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

@keyframes devDraw  { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

@keyframes devPop   { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }

@keyframes devLand  { from { opacity: 0; transform: translateY(10px) scale(0.94); } to { opacity: 1; transform: none; } }

@keyframes devLift  { from { transform: translateY(72px); } to { transform: none; } }

@keyframes devSweep { from { stroke-dashoffset: 2; opacity: 1; } to { stroke-dashoffset: 0; opacity: 1; } }

/* transform on SVG elements is computed in user space; fill-box keeps
   the origin on the element itself instead of at the viewBox corner. */
.brm__line, .cvs__cta, .cvs__dot, .ogc__head, .cvs__copy text, .ogc__stats text {
  transform-box: fill-box;
}

.brm__line { transform-origin: 0 50%; }

@media (prefers-reduced-motion: reduce) {
  .ogc__sweep { display: none; }
}

/* ---------- Conversion: the button is a real link ----------
   For now href is the mailto that all the other "Book a call" CTAs on the site already use,
   so the button works as of today. data-calendly is a hook: once Calendly is ready, only
   the href changes (and target/rel get added). */
.cvs__cta { cursor: pointer; }

.cvs__cta rect { transition: fill 0.3s var(--dev-ease); }

.cvs__cta:hover rect { fill: #C38B2B; }

/* --amber-hover, brand book §07: ~7% darker */
.cvs__cta:focus-visible { outline: 2px solid #16181C; outline-offset: 3px; border-radius: var(--r-10); }

/* ============================================================================
   DESIGN AND BUILD — SVG translated to HTML

   All dimensions are derived from the SVG coordinates (viewBox 0 0 574 370) and expressed in
   cqw, so the drawing scales uniformly just like the original: 1cqw = 5.74 SVG px.

   Baseline positions from the SVG are converted to box-top using the ACTUAL ascenders
   read from the woff2 files (fontTools), not estimates:
       Montserrat      asc 0.968 em   desc 0.251 em
       JetBrains Mono  asc 1.020 em   desc 0.300 em
       box-top = baseline - ((lh - (asc + desc)) / 2 + asc) x font-size

   Everything is absolutely positioned by SVG coordinates. Normal flow would introduce drift from
   font metrics that cannot be controlled, and the original itself was not in flow either.
   ========================================================================= */

.dbc {
  position: relative;
  aspect-ratio: 574 / 370;
  /* Exact color of the outer rect from the SVG. The beam field has no background of its own — on
     the home page it came from .hs { background: var(--ink) }. */
  background: #16181C;
  container-type: inline-size;
  overflow: hidden;
  border-radius: var(--r-10);
}

/* Window: rect x=40 y=34 w=494 h=302 rx=8, fill #1D2026, stroke #2A2E36.
   The border is in cqw because an SVG stroke of 1 scales together with the drawing. */
.dbc__win {
  position: absolute; z-index: 1;
  top: 5.9233cqw; left: 6.9686cqw;
  width: 86.0627cqw; height: 52.6132cqw;
  background: #1D2026;
  border-radius: 1.3937cqw;
  /* the beam field lives INSIDE the panel, so it is clipped to its rounded corners */
  overflow: hidden;
}

/* The border goes on ::before, NOT on .dbc__win.
   With `border` on the panel itself, absolutely positioned children are measured from
   the padding box, which is inset by the border width — which shifted ALL the content
   1px down and to the right. In SVG a stroke does not shift anything. Measured: before this, all
   elements were at (+1, +1); after, 0. */
.dbc__win::before {
  content: ""; position: absolute; inset: 0; z-index: 2;   /* above the beam field */
  border: 0.1742cqw solid #2A2E36;
  border-radius: var(--r-10);
  pointer-events: none;
}

/* Three dots: cx 62/78/94, cy 52, r 4, fill #5C564E */
.dbc__dots { position: absolute; z-index: 1; top: 2.4390cqw; left: 3.1359cqw; display: flex; gap: 1.3937cqw; }

.dbc__dots i { width: 1.3937cqw; height: 1.3937cqw; border-radius: 50%; background: #5C564E; }

/* x=64 y=118, JetBrains Mono 13, letter-spacing 3, #D2952E */
.dbc__eyebrow {
  position: absolute; z-index: 1; top: 12.6865cqw; left: 4.1812cqw;
  font-family: var(--f-mono); font-size: 2.2648cqw; line-height: 1;
  letter-spacing: 0.5226cqw; color: #D2952E; white-space: nowrap;
}

/* x=62, baseline 168 and 212 -> line-height 44/40 = 1.1, Montserrat 40/600, #FBFAF8 */
.dbc__title {
  position: absolute; z-index: 1; top: 17.0140cqw; left: 3.8328cqw;
  font-family: var(--f-disp); font-size: 6.9686cqw; font-weight: 600;
  line-height: 1.1; color: #FBFAF8; white-space: nowrap;
}

/* rect x=62 y=244 w=150 h=42 rx=7 #D2952E; label x=84 baseline 271, Mont 16/500 */
.dbc__cta {
  position: absolute; z-index: 1; top: 36.5854cqw; left: 3.8328cqw;
  width: 26.1324cqw; height: 7.3171cqw;
  border-radius: 1.2195cqw; background: #D2952E;
  font-family: var(--f-disp); font-size: 2.7875cqw; font-weight: 500;
  line-height: 1; color: #16181C;
  padding-left: 3.8328cqw; padding-top: 2.3101cqw;
  box-sizing: border-box; white-space: nowrap;
  transition: background-color 0.3s var(--dev-ease);
}

.dbc__cta:hover { background: #C38B2B; }

/* --amber-hover, brand book §07 */
.dbc__cta:focus-visible { outline: 0.3484cqw solid #FBFAF8; outline-offset: 0.3484cqw; }

/* baseline 318, x=64 and x=150, JetBrains Mono 13, #ECE8E1 at 0.75 */
.dbc__stats {
  position: absolute; z-index: 1; top: 47.5297cqw; left: 0;
  font-family: var(--f-mono); font-size: 2.2648cqw; line-height: 1;
  color: #ECE8E1; opacity: 0.75; white-space: nowrap;
}

.dbc__stats span { position: absolute; }

.dbc__stats span:nth-child(1) { left: 4.1812cqw; }

.dbc__stats span:nth-child(2) { left: 19.1638cqw; }

/* ---------- Background: beam field + watermark, verbatim from the home page ----------
   Copied from assets/css/home.css unchanged; only the container name changes
   (.hs__bg -> .dbc__bg). */
/* ---------- Backdrop: beams at the angle of the mark ----------
   Was: a showreel video (7.4 MB, every frame decoded) under a 0.93 overlay.
   The overlay had to be that strong because the showreel captured sites in Serbian,
   so their text read through beneath the English copy. When a backdrop has to be dimmed
   by 93% to stay out of the way, it is no longer a backdrop but a cost.

   Now: inline SVG, with zero requests and no decoding. Seventeen beams at
   24 degrees — the slope is measured from the mark itself (it matches the rotate(24) that
   already sits in the work-card SVGs). Colors are from the brand book: bone at 0.04-0.085
   and Beam Amber at 0.16, and on only two beams out of seventeen.

   Only three things move: two beam layers and the glow. Only
   transform and opacity are animated, with no JS and no rAF loop. The layers slide along their length
   (translateY is inside the rotated parent), so you do not see the beams travel
   but the light moving through them. Two different speeds produce parallax.
   `alternate` is the same pattern .svc-plus already uses on the service pages. */
/* Beam field container. It sits inside .dbc__win: inset:0 covers the whole panel, because
   the border is on ::before, so padding box == border box. */
.dbc__bg { position: absolute; inset: 0; z-index: 0; border-radius: var(--r-10); }

.hsbg { width: 100%; height: 100%; display: block; }

/* Sliding ACROSS the beams. The first attempt moved translateY inside the rotated
   group, which pushes a beam along its length — and since the gradient travels with it, nothing
   visibly changes. translateX in the same rotated space is perpendicular to the
   beam, so the field actually moves across the backdrop. */
.hsbg__layer--a { animation: hsbgSlideA 20s ease-in-out infinite alternate; }

.hsbg__layer--b { animation: hsbgSlideB 27s ease-in-out infinite alternate; }

@keyframes hsbgSlideA { from { transform: translateX(0); }   to { transform: translateX(178px); } }

@keyframes hsbgSlideB { from { transform: translateX(0); }   to { transform: translateX(-134px); } }

/* Each beam breathes on its own, with its own duration and negative delay, so the field
   is never in the same state twice. Opacity only — the cheapest thing there is. */
.hsbg__beam { animation: hsbgPulse var(--t, 18s) ease-in-out infinite alternate; animation-delay: var(--d, 0s); }

@keyframes hsbgPulse { from { opacity: 0.16; } to { opacity: 1; } }

/* The watermark stays still. The mark is the only thing in the field with a
   recognizable shape, so it has to be the reference point — everything else moves around it.

   It is sized as a BOX, not as a width. The section changes both width and height
   (1031px at 2560, 1178px at 390), so specifying the width alone broke at both
   ends: on narrow screens the mark was a 179px smudge, on wide ones its head stuck out of the
   section. With a set box and the default preserveAspectRatio="xMidYMid
   meet" the mark fits into it along the shorter side and always fits whole. */
.hsbg__mark {
  position: absolute;
  right: -2%;
  top: 50%;
  transform: translateY(-50%);
  width: 52%;
  height: 76%;
  opacity: 0.045;
  pointer-events: none;
}

.hsbg__glow { animation: hsbgGlow 11s ease-in-out infinite alternate; }

@keyframes hsbgGlow { from { opacity: 0.3; transform: translate(-130px, 26px); }
                      to   { opacity: 1;   transform: translate(120px, -22px); } }

/* Mark box for this smaller area (the section on the home page is considerably larger). */
/* Mark box for the panel (494x302); opacity is the same as on the home page: 0.045. */
.dbc .hsbg__mark { width: 58%; height: 72%; right: -4%; opacity: 0.045; }
