/*
  Heller's Town motion contract, shared by hellers-town.com, ai., 3d. (and drone. in the
  second pass). It declares no curve and no duration: every one comes from the brand tokens
  (--ht-ease-in, --ht-dur-s, --ht-dur-m, --ht-stagger in /ht/tokens.css), which already
  collapse to 1 ms under reduced motion. The only value it adds is the entrance travel.
  Rules: transform and opacity only, no overshoot, no leave animations. Content is visible
  with JS off, under reduced motion, and if the motion layer never arrives.
*/
:root { --ht-rise: 8px; }

@media (prefers-reduced-motion: reduce) {
  :root { --ht-rise: 0px; }
}

/*
  The hidden state exists only while the head script has set .js-motion (motion allowed,
  JS running). Never put data-reveal on the hero headline or the hero image: Chromium does
  not count an opacity 0 element as an LCP candidate, so a fading hero delays LCP.
*/
.js-motion [data-reveal]:not(.is-in) {
  opacity: 0;
  transform: translate3d(0, var(--ht-rise), 0);
}

/* Micro-interactions stay in CSS; the motion library is never loaded for hover. A hover that
   moves something waits for a real mouse as well as hover (a pen or a TV remote also "hovers"),
   and lists translate so a pressed .ht-hover link still comes back on the curve (chrome.css). */
.ht-hover {
  transition: transform var(--ht-dur-s) var(--ht-ease-in), opacity var(--ht-dur-s) var(--ht-ease-in), translate var(--ht-dur-s) var(--ht-ease-in);
}
@media (hover: hover) and (pointer: fine) {
  .ht-hover:hover { transform: translate3d(0, -2px, 0); }
}

/* A visible focus ring on every control. Zero specificity, so a site may refine it. */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

/*
  Page to page: same-origin cross-document View Transitions, ON by Jack's ruling of 2026-10-10.
  They run only between pages of one site (each subdomain is its own origin) and only under
  prefers-reduced-motion: no-preference; under reduce nothing is named and the browser simply
  navigates. CSS only: a click is never held by a script, and JS off gets the same crossfade.
  - The opt-in lives in this file because the shared head partial links it BEFORE the inline
    head script. Measured 2026-10-11 on the hub and on ai: with the opt-in in a stylesheet linked
    after that script, 4 to 5 transitions in 12 (8 in 18 on ai) were dropped as "opt-in disabled";
    linked before it, none were.
  - The page crossfades on the small time and the entrance curve, old and new on one timing, so
    the two always add up to a whole page and the ground never dips.
  - The header and the call bar are named: each is carried across as one piece and, being the
    same on every page of a site, holds still while the page under it changes. Their old image
    is dropped and the new one shown at once, so they add no fade of their own (at most the
    page's two fades run, well inside the house limit of four moves at once).
  A site may add names or its own timings in its own stylesheet, which loads later and wins.
*/
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  .ht-bar { view-transition-name: ht-header; }
  .ht-callbar { view-transition-name: ht-callbar; }
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-group(ht-header),
  ::view-transition-group(ht-callbar) {
    animation-duration: var(--ht-dur-s);
    animation-timing-function: var(--ht-ease-in);
  }
  ::view-transition-old(ht-header),
  ::view-transition-old(ht-callbar) { display: none; }
  ::view-transition-new(ht-header),
  ::view-transition-new(ht-callbar) { animation-name: none; }
}
