/* ============================================================================
   toronto-backdrop.css — a sense of place behind the reference surfaces.
   service.tnaado.ca · no dependencies · tokens only
   ----------------------------------------------------------------------------

   WHY THIS EXISTS

   Ethan: "and other pages need toronto images in the background", then, more
   bluntly: "did you place toronto imingaery in the pages that are all just
   black and ugly?"

   He is right about the diagnosis. The Tier 1 doors have a storm behind them.
   The REFERENCE surfaces — /work, /work/software, /work/camera, /stack, /faq,
   /pricing — are flat #0a0a0a with type on it and nothing else. On a site whose
   whole argument is a specific firm in a specific city, that is a missed
   opportunity and it reads as unfinished.

   ----------------------------------------------------------------------------
   WHAT MAKES A PHOTOGRAPHIC BACKDROP WORK RATHER THAN CHEAPEN

   Four rules, and the first two are the ones that get broken:

   1. IT SITS UNDER THE PALETTE, NEVER BESIDE IT. The site is two neutrals and
      one accent: --ink #0a0a0a, --bone #faf8f3, --red #c8102e. A photograph at
      full strength introduces a fourth, fifth and sixth colour and the page
      stops looking designed. So every frame is desaturated, darkened, and held
      under a scrim. The image supplies TEXTURE AND PLACE, not colour.

   2. IT MUST NOT COST CONTRAST. Body copy over a photograph is the classic way
      to fail WCAG 1.4.3 while looking fine to the author on a bright monitor.
      This site has already shipped three composited contrast failures —
      measured at 1.97:1, 2.55:1 and 3.72:1 — so the scrim here is deliberately
      heavy and the image is capped at low opacity. The ink ground still does
      the work; the photograph is barely there.

   3. THE FRAMES ARE CHOSEN, NOT PICKED. PHOTO-RULING.md judges 136 frames and
      REJECTS 77 of them, many for showing a WeWork-style shared office — cane
      chairs, glass partitions, exposed spiral ducts, member badges — which
      contradicts the firm's claim to a private office at 1050 King Street West.
      Every frame used below is one the ruling rates STRONG or better, and its
      per-frame note is honoured: the heritage mosaic is explicitly "a divider
      or BEHIND TYPE. Small file — never full-bleed above 1280px", so it is
      capped at 1000px and used only as a band.

   4. IT MUST NOT COST LOAD TIME. Mobile LCP is ALREADY 3428ms on fast 4G at
      390px BEFORE anything here — past the 2500ms threshold. So the backdrops
      are aggressively small: 1000–1100px wide at quality 18, 31–75 KB each,
      214 KB for all four, and each page loads exactly ONE. A scrimmed image at
      14% opacity carries no detail, so resolution is wasted bytes.
      They are also `background-image` rather than `<img>`, deliberately: a
      background cannot become the LCP element, so it cannot make the headline
      number worse.

   ----------------------------------------------------------------------------
   NOT THE BANNED PATTERN

   Ethan killed "little text then big image as a full-width band" site-wide
   (T-1147, "REMOVE THIS PATTERN SITE WIDE"). This is not that: nothing here is
   a content image with a caption. It is ground. The distinction is that a
   backdrop is never the subject and never carries meaning — remove it and the
   page still says everything it said before.
============================================================================ */

/* ── THE GROUND ───────────────────────────────────────────────────────────
   Fixed attachment so the page moves over the city rather than dragging it
   along — the parallax is free and it stops the texture reading as wallpaper.
   `background-attachment: fixed` is dropped on touch below, where it is both
   janky and unsupported in places. */
.tor-bg {
  position: relative;
  isolation: isolate;               /* keeps the ::before out of sibling stacks */
}

.tor-bg::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;

  /* POSITION-FIXED LAYER, NOT background-attachment: fixed.
     `attachment: fixed` repaints the image every scroll frame — on a page that
     also runs a WebGL storm and matrix3d transforms that repaint shares the
     frame budget and the result tears. A viewport-sized fixed layer is promoted
     by the compositor and the page scrolls OVER it: same parallax, no repaint. */
  position: fixed;
  inset: 0;
  background-image: var(--tor-img);
  background-size: cover;
  background-position: var(--tor-pos, 50% 30%);
  background-repeat: no-repeat;
  transform: translateZ(0);      /* force its own compositor layer */
  backface-visibility: hidden;

  /* THE GRADE, and it is the whole reason this reads as the site rather than
     as a stock photo. Desaturated hard, darkened hard, contrast lifted slightly
     so the remaining structure survives being this dark. */
  /* CALIBRATED, NOT GUESSED. The first version stacked three darkening layers —
     brightness(0.34), then opacity .14, then an 82%-opaque ink scrim over the
     top — which multiplied out to roughly 4.8% luminance over black. The image
     loaded, applied and computed correctly, and was invisible. Each layer looked
     reasonable alone; the product of the three was not.
     Now ONE darkening step does the work and the scrim below is a vignette
     rather than a second grade. */
  filter: grayscale(0.78) brightness(0.72) contrast(1.06);
  opacity: .5;
}

/* THE SCRIM. A second layer, because filter+opacity alone leaves the mid-tones
   of a bright frame close enough to the type to hurt. This pins the top and
   bottom to solid ink so headings and rules always land on a known ground, and
   only the middle of the band shows any image at all. */
.tor-bg::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    var(--ink, #0a0a0a) 0%,
    rgba(10, 10, 10, .55) 14%,
    rgba(10, 10, 10, .42) 50%,
    rgba(10, 10, 10, .55) 86%,
    var(--ink, #0a0a0a) 100%
  );
}

/* ── THE FRAMES ───────────────────────────────────────────────────────────
   One per page, chosen so a visitor moving through the site does not meet the
   same photograph twice. Each is rated STRONG or better in PHOTO-RULING.md. */

/* Yonge Street canyon at storm hour. The firm's own weather, and it ties the
   reference pages to the storm the Tier 1 doors run. */
.tor-bg--canyon   { --tor-img: url('/assets/bg/storm-hour-city-canyon-bg.jpg'); --tor-pos: 50% 22%; }

/* Union Station colonnade, stone lion, CN Tower. The ruling says "use as-is;
   3:2 crops cleanly to 2:1" — so it is safe at band proportions. */
.tor-bg--union    { --tor-img: url('/assets/bg/union-lion-cn-tower-bg.jpg');    --tor-pos: 50% 42%; }

/* Bay Street crosswalk corridor. Vertical lines, good under dense type. */
.tor-bg--bay      { --tor-img: url('/assets/bg/bay-street-crosswalk-corridor-bg.jpg'); --tor-pos: 50% 34%; }

/* Heritage sun-and-moon mosaic over Art Nouveau brass. Capped at 1000px and
   used ONLY as a narrow band, per the ruling's explicit note. */
.tor-bg--mosaic   { --tor-img: url('/assets/bg/heritage-sun-mosaic-brass-bg.jpg'); --tor-pos: 50% 50%; }

/* ── PHONE ────────────────────────────────────────────────────────────────
   `fixed` attachment is unreliable and expensive on touch, so it becomes
   scroll. Opacity drops again because a small screen puts type closer to the
   texture, and the byte cost matters more here — mobile LCP is already past
   threshold before this file exists. */
@media (max-width: 48rem) {
  .tor-bg::before {
    /* STAYS FIXED. It was `position: absolute` here, and that was my bug --
       MEASURED 2026-08-31. The ::after scrim below is declared `fixed` and this
       block never touched it, so on every viewport <=768px the two layers came
       apart and drifted: on work.html the photo's top ran 92 -> -1815px while
       the scrim sat at 0, and on pricing.html the drift reached 28,730px. The
       vignette was welded to the viewport while the photograph slid underneath,
       so the image was only ever seen through a travelling 574px window.

       Worse, `absolute; inset: 0` made the box the full PAGE height, and
       `background-size: cover` then scaled to that height and threw the width
       away: 390x29,573 on pricing, a 26.9x upscale showing 1.98% of the
       frame's width. On stack.html the mosaic rendered 10,576px wide, directly
       against this file's own rule that it is capped at 1000px and never
       full-bleed above 1280px.

       The iOS Safari worry that motivated `absolute` was misplaced: the base
       rule is a viewport-sized fixed layer the compositor promotes, which is
       the well-supported pattern, not `background-attachment: fixed`, which is
       the one that misbehaves -- and which this file already avoids and says so
       at line 81. The reduced-motion block below sets BOTH to absolute
       together, which is why a reduced-motion phone was the only case rendering
       correctly. */
    opacity: .40;
  }
  .tor-bg::after {
    background: linear-gradient(
      to bottom,
      var(--ink, #0a0a0a) 0%,
      rgba(10, 10, 10, .62) 16%,
      rgba(10, 10, 10, .62) 84%,
      var(--ink, #0a0a0a) 100%
    );
  }
}

/* ── REDUCED MOTION ──────────────────────────────────────────────────────
   `background-attachment: fixed` produces motion the user did not ask for as
   the page scrolls. Somebody who has asked for less motion gets a still
   ground. The image stays — it is texture, not animation — it simply stops
   moving relative to the content. */
@media (prefers-reduced-motion: reduce) {
  .tor-bg::before, .tor-bg::after { position: absolute; }
}

/* ── SAVE-DATA ───────────────────────────────────────────────────────────
   A visitor who has asked the browser to save data does not get a decorative
   photograph. The ink ground is the whole design without it, which is the test
   any backdrop should pass. */
@media (prefers-reduced-data: reduce) {
  .tor-bg::before { background-image: none; }
}
