/* chrome/bar.css — the floating bar. Spec: 092, 097 §2, 099 B5.

   ⚠️ THIS IS THE SITE'S FIRST PAGE-LEVEL ELEMENT (092 §1). Everything else
   lives inside a <section>. The bar is a direct child of <body>, a sibling of
   .loader, outside <main> — it persists across all six sections, so it cannot
   belong to any of them.

   Stacking (099 B5, verified in 098 §7.2): z 70 sits ABOVE .pmodal at 60 —
   Xinlu's call, the bar is the topmost layer of the site — and BELOW .loader
   at 100, which covers it during boot. #perf sits at 200 (loader.css:57), a
   dev-only ?perf harness never present on a normal visit; the bar being under
   it is correct and is noted here so nobody "fixes" it. */

.bar {
  position: fixed;
  top: max(12px, env(safe-area-inset-top));
  left: 50%;
  /* ⚠️ --bar-hide COMPOSES WITH THE CENTRING RATHER THAN REPLACING IT (200 §1).
     A separate `transform` on .is-hidden would overwrite translateX(-50%) and
     the bar would slide out AND jump right by half its width. One transform,
     two terms, and the literal below is the third of reduce's three places. */
  transform: translateX(-50%) translateY(var(--bar-hide, 0px));
  transition: transform var(--bar-hide-dur, 0.32s) cubic-bezier(0.4, 0, 0.2, 1),
              opacity var(--bar-hide-dur, 0.32s) linear;
  z-index: 71;                         /* one above .badge, so the ribbon tucks under */

  --bar-h: 58.67px;                    /* ?barh= — sets the dot size at 4:1 (092 §4). 44 x 4/3 */
  /* ⚠️ DERIVED, NOT A LITERAL, AND THAT IS THE POINT. As `6px` it did not scale
     with the bar, so ?barh= grew the avatar by MORE than the bar: at 58.67 the
     inset would still be 6, giving inner 46.67 against 32 — a 1.458x avatar
     inside a 1.333x bar, with the inset visibly thinned. 6/44 = 0.13636, so
     this reproduces today's proportion and now follows ?barh= exactly. */
  --bar-pad: calc(var(--bar-h) * 0.13636);
  --bar-inner: calc(var(--bar-h) - var(--bar-pad) * 2);
  --bar-dot-rest: #6E7FB8;             /* ?dotrest= — 092 §4's blue-violet; session 8 said gray */
  --bar-dot-go: #4ade80;               /* ?dotgo=   — first colour outside the palette (092 §4) */
  --bar-status: #4ade80;
  /* ?barstatus= — an ABSOLUTE size, deliberately. As `30%` of the avatar it
     scaled with the bar; held at its current 9.6px it stays a status pip
     rather than becoming a second graphic element as the bar grows. */
  --bar-status-size: 9.6px;
  --bar-status-depth: 1;               /* ?statusdepth= — breath amplitude multiplier */
  --bar-speed: 1.2s;                   /* Uiverse's --uib-speed */
  --bar-status-hz: 3.4s;               /* ?statushz= — 2.4s read as panting once the breath deepened */

  display: flex;
  align-items: center;
  gap: calc(var(--bar-h) * 0.20);
  height: var(--bar-h);
  /* ⚠️ RIGHT PADDING IS SET BY THE SWING, NOT BY EYE. The cradle's outer balls
     pivot at transform-origin: center top with the ball's centre inner/2 below
     it, so at bar-h 58.67 the arm is 21.33 px and a 70 deg swing carries the
     ball's centre 20.05 px sideways. The old 0.34 x bar-h is 19.95 px — a
     clearance of MINUS 0.10 px, so the ball genuinely left the capsule.
     0.444 x bar-h is 26.05 px, +6.00 px of clearance, and it holds at every
     ?barh= because it is a ratio. */
  padding: var(--bar-pad) calc(var(--bar-h) * 0.444) var(--bar-pad) var(--bar-pad);
  margin: 0;
  border-radius: calc(var(--bar-h) / 2);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;

  /* 092 §6 — REUSE the recipe that already ships, do not invent a second one.
     intro.css:257-262, verified byte-accurate; the one value that changes is
     the tint, blue-violet -> black. Measured cost over the running starfield:
     +0.25 ms of GPU time per frame, 1.5% of a 16.67 ms budget, and the
     unfiltered arm creates zero render passes so the whole cost is the blur
     (098 §5). Making it bigger or blurrier is close to free; a SECOND
     independently-frosted element would cost another ~0.25 ms. */
  border: 1px solid rgba(175, 185, 216, 0.18);
  /* ⚠️ 0.667, NOT 0.10, SINCE THE AURORA LANDED (115 §1, 116 §1). This is a
     computed value, not a taste one: over the aurora's stop 1 at its brightest
     phase the capsule at 0.10 composited to rgb(160,128,190), and against that
     the four dots measured 1.18:1 — invisible, against a 3:1 floor. 0.667 is
     the minimum that clears all three of the bar's foregrounds at once:
       label 9.81:1 (needs 4.5) · dots 3.00:1 (needs 3) · status 6.71:1 (needs 3)
     The DOTS bind, by about 2x — alone the label needs 0.330 and the status
     dot 0.319.

     ⚠️ DARKENING MAKES THE DOTS WORSE BEFORE BETTER, AND 0.2 IS THE WORST
     VALUE. The capsule starts brighter than the dots and passes THROUGH their
     luminance on the way down: 1.18 at 0.10, 1.04 at 0.20, 3.00 at 0.667.
     That non-monotonicity is why this shipped at the computed value rather
     than as a flag for Xin Lu to find — anyone stepping up from 0.10 tries 0.2
     first and concludes the approach fails (116 §1).

     ?baralpha=0.10 restores the old capsule exactly.

     The tint is nearly free: black would need 0.639 and a hue-keeping dark
     lavender 0.751, all landing at the same luminance because darkness is the
     constraint. #05060d keeps the diff to one number.

     ⚠️ It costs nothing on the other five sections and IMPROVES two: over
     #3d3524 and #243a3d the dots go 3.33 -> 4.64 and 3.29 -> 4.63. Those two
     are marginal against the 3:1 floor on production today, which is a finding
     about the bar rather than about the aurora. */
  background: rgba(5, 6, 13, var(--bar-alpha, 0.667));
  -webkit-backdrop-filter: blur(16px) saturate(130%);
          backdrop-filter: blur(16px) saturate(130%);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
}

/* The same @supports pair intro.css carries, testing each spelling separately. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .bar { background: rgba(5, 6, 13, 0.82); }
}

.bar:focus-visible {
  outline: 2px solid var(--bar-dot-rest);
  outline-offset: 3px;
}

/* ── the avatar ──────────────────────────────────────────────────────────────
   092 §2: flush at the capsule's left edge at close to full bar height, and
   the mid-tone background is LOAD-BEARING — it separates the dark hair from
   the near-black capsule and does not glare like a pale one. Do not "improve"
   it to match the palette. */
.bar__avatar {
  position: relative;
  flex: 0 0 auto;
  width: var(--bar-inner);
  height: var(--bar-inner);
}

.bar__avatar img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
}

/* ── the status dot (092 §3) ─────────────────────────────────────────────────
   Green, always, breathing. A SEPARATE element with a separate job from the
   four dots — availability, not button state. The two were conflated once in
   session 7 and the conflation produced a false objection. */
.bar__status {
  position: absolute;

  /* ⚠️ ANCHORED TO THE CIRCLE, NOT TO THE SQUARE BOX. `right/bottom: -1px`
     pinned the dot to the corner of the avatar's bounding box, so as the circle
     grew the dot drifted off its edge: measured 1.25 px past the edge at
     inner 32, and 3.46 px at inner 42.67 — against a 4.8 px dot radius, which
     reads as detached.

     50% + 50%/sqrt(2) = 85.355% is where the circle's edge crosses the 45 deg
     diagonal, so centring the dot there puts it half-on, half-off the rim at
     ANY size. Negative margins rather than a translate, because the breathing
     keyframes own `transform` and would overwrite it. */
  /* A QUARTER ON THE RIM, three quarters outside. 85.355% is where the circle
     crosses the 45 deg diagonal; subtracting 0.5 x size would put the dot's
     CENTRE there (half in, half out, which is what shipped). 0.3232 is the
     only number that differs: it pushes the centre 0.1768 x size further out
     per axis = 0.25 x size along the diagonal = half a radius, leaving a
     quarter of the diameter inside the rim.

     ⚠️ SCALE-INDEPENDENT, which the equivalent 89.33% is not — that figure is
     only right at the shipped 9.6 px, and ?barstatus= would drift it. The
     -50% margins are folded into the calc rather than kept alongside it. */
  left: calc(85.355% - 0.3232 * var(--bar-status-size));
  top: calc(85.355% - 0.3232 * var(--bar-status-size));
  width: var(--bar-status-size);
  height: var(--bar-status-size);

  border-radius: 50%;
  background: var(--bar-status);
  box-shadow: 0 0 0 2px rgba(5, 6, 13, 0.9);   /* fixed 2px ring, not derived */
  animation: bar-breathe var(--bar-status-hz) ease-in-out infinite;
}

/* Amplitude doubled: opacity swung 0.45 and scale 0.14, now 0.90 and 0.28.
   ⚠️ PEAK STAYS scale(1) so --bar-status-size remains the maximum the dot ever
   reaches — deepening the breath must not make it bigger. And the period went
   2.4s -> 3.4s in the same change: doubled amplitude at the old period reads
   as panting rather than as breathing. */
@keyframes bar-breathe {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% {
    opacity: calc(1 - 0.90 * var(--bar-status-depth));
    transform: scale(calc(1 - 0.28 * var(--bar-status-depth)));
  }
}

/* ── the name (092 §5) ───────────────────────────────────────────────────────
   Costs zero bytes: both shipped subsets already carry full printable ASCII.
   Which face is Xinlu's call — Blinker does headings, Poppins does body, and
   page chrome is neither. Poppins here provisionally. */
.bar__name {
  font-family: 'Poppins Subset', system-ui, sans-serif;   /* the name projects.css declares */
  font-weight: 500;
  font-size: calc(var(--bar-h) * 0.36);
  line-height: 1;
  letter-spacing: 0.01em;
  color: #E8EBF4;
  user-select: none;
}

/* ── the four dots ───────────────────────────────────────────────────────────
   From Uiverse.io by dovatgabriel
   Renamed to project BEM per 092 §4; the attribution comment stays.

   ⚠️ FOUR IS NOT A PREFERENCE, IT IS WHAT THE CSS REQUIRES (092 §4). The ball
   is 100% of its column's width but 25% of the container's height. At four
   dots the column is 25% wide, both resolve to S/4, and the ball is a circle.
   At three the column becomes 33.3% while the height stays 25% — a 1.33:1
   ellipse. Changing the count means decoupling the ball's height from the
   column width. */
.bar__dots {
  /* The same 20.05 px excursion on the other side: the LEFT-most ball swings
     left, toward the name. The flex gap of 0.20 x bar-h is 11.73 px — 8.31 px
     short, which is the overlap with the "u". Topped up to 0.444 x bar-h in
     total, matching the right padding. ⚠️ Dots-specific: the avatar-to-name gap
     is left alone. */
  margin-left: calc(var(--bar-h) * 0.244);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--bar-inner);
  height: var(--bar-inner);
}

.bar__dot {
  position: relative;
  display: flex;
  align-items: center;
  width: 25%;
  height: 100%;
  transform-origin: center top;
}

.bar__dot::after {
  content: '';
  display: block;
  width: 100%;
  height: 25%;
  border-radius: 50%;
  background-color: var(--bar-dot-rest);
  transition: background-color 260ms ease;
}

/* Open state — the dots go green and stay green until the badge retracts.
   097 §2 resolves 092 §7 Q3 as a TOGGLE, not a one-way change. */
.bar[aria-expanded='true'] .bar__dot::after { background-color: var(--bar-dot-go); }

.bar__dot:first-child { animation: bar-swing  var(--bar-speed) linear infinite; }
.bar__dot:last-child  { animation: bar-swing2 var(--bar-speed) linear infinite; }

@keyframes bar-swing {
  0%   { transform: rotate(0deg);  animation-timing-function: ease-out; }
  25%  { transform: rotate(70deg); animation-timing-function: ease-in; }
  50%  { transform: rotate(0deg);  animation-timing-function: linear; }
}

@keyframes bar-swing2 {
  0%, 50% { transform: rotate(0deg);   animation-timing-function: linear; }
  75%     { transform: rotate(-70deg); animation-timing-function: ease-in; }
  100%    { transform: rotate(0deg);   animation-timing-function: ease-out; }
}

/* ⚠️ REDUCED MOTION IS NOT OPTIONAL HERE (092 §4). These are the only
   animations on the site that never stop: the sky and the cue are both
   IntersectionObserver-gated and pause when the hero leaves the screen, but a
   bar fixed to the viewport is on screen by definition. The Uiverse snippet
   has no handling for it. */
@media (prefers-reduced-motion: reduce) {
  .bar__dot:first-child,
  .bar__dot:last-child { animation: none; }
  .bar__status { animation: none; }
}


/* ── THE BAR GETS OUT OF THE WAY (200 §1) ─────────────────────────────────────
   Xinlu, twice: it "really obstructs visitors from viewing the page content on
   mobile." ⚠️ AND IT WAS ALSO EATING TAPS — C41's pre-fix run measured
   document.elementFromPoint returning #bar for cards 3, 4 and 5, so three of
   the five stacked cards could not be opened at all.

   ⚠️ pointer-events GOES WITH IT AND IS THE POINT, NOT A TIDY-UP. A bar that is
   merely translated off-screen still answers the hit test where it used to be
   on some engines, and an invisible control that takes taps is 156 §12's defect.
   C43 asserts the hit test, not the position. */
.bar.is-hidden {
  --bar-hide: calc(-100% - max(12px, env(safe-area-inset-top)) - 8px);
  opacity: 0;

  /* ⚠️⚠️ DELAYED BY THE WHOLE TRANSITION, AND THE UNDELAYED VERSION IS THE
     DEFECT XINLU PHOTOGRAPHED (204 §2.1, C45). `pointer-events` is discrete: it
     flipped the instant .is-hidden landed while `opacity` took 0.32s to follow.
     MEASURED at 0ms after the hide fires: the bar sat at its normal top of 12.0
     at opacity 1.00 — fully visible, unmoved — and a tap at its own centre hit
     the section behind it and opened nothing. That is exactly "the bar is
     plainly visible and does not respond".

     Delaying it to the end of the transition means the rule is simply: if you
     can see it, you can tap it. Restoring is immediate because removing
     .is-hidden removes this declaration outright — a bar fading back IN must be
     live from the first frame, not 0.32s later.

     ⚠️ AND IT IS A SECOND CLASS, NOT A DELAYED TRANSITION. Two earlier attempts
     failed and both were measured rather than reasoned about: declaring
     `transition` here alone RESET transform and opacity to `all 0s`, so the bar
     jumped instead of sliding; and repeating the full list with
     `pointer-events 0s linear <delay>` did nothing either, because
     pointer-events is a DISCRETE property and Chrome does not transition those
     without `transition-behavior: allow-discrete`. bar-hide.js adds .is-inert
     on a timer instead, so the flip is explicit and testable. */
}

/* ⚠️ THE HIT TEST DIES HERE, ONE TRANSITION LATER THAN THE FADE STARTS. Added
   by bar-hide.js after --bar-hide-dur has elapsed, removed the instant the bar
   is asked to come back. C45 asserts a tap lands while the bar is still
   visible. */
.bar.is-inert {
  pointer-events: none;
}

/* ⚠️ SECOND OF reduce's THREE PLACES, and here it means NO HIDING AT ALL rather
   than an instant one (200 §1 item 3). js/chrome/bar-hide.js's active() is the
   first and never adds the class; this is the belt to that brace, so a stuck
   class from any path still renders the bar. The third is the `0px` literal in
   the transform above. */
@media (prefers-reduced-motion: reduce) {
  .bar { transition: none; }
  .bar.is-hidden { --bar-hide: 0px; opacity: 1; pointer-events: auto; }
}
