/* Ported from the trulata-hero concept, 2026-09-09. Every selector is scoped ph-, so the
   hero cannot reach the other 171 pages and the site cannot reach into the hero. The concept
   shipped its own :root palette and a bare body{} rule; both are dropped here. tokens.css
   already defines every one of those colours and the site already self-hosts Montserrat and
   Poppins, so re-declaring them would fork the palette, and a body{} rule loaded by one page
   would repaint the whole site. Nothing below invents a colour. */
.ph-runway, .ph-rail, .ph-content{
  --indigo:var(--primary); --cyan:var(--accent); --purple:var(--secondary);
  --cyan-soft:#6fdcf6; --indigo-soft:#5560c9;
  --on-dark:var(--text-on-dark); --on-dark-dim:var(--text-on-dark-dim);
  /* 🔴 The concept set this on body{}, which cannot come across: a body rule shipped by
     one page's stylesheet repaints all 172. Dropping it left the h1 and the finale
     heading inheriting base.css, so the headline over a night street rendered in
     #2a2d30 charcoal. Set the colour on the hero roots instead. */
  color:var(--text-on-dark);
}
.ph-runway h1, .ph-runway h2, .ph-runway h3,
.ph-rail h2, .ph-rail h3, .ph-content h2{ color:var(--text-on-dark); }
.ph-runway *, .ph-rail *, .ph-content *{ box-sizing:border-box; }
.ph-rail, .ph-content{ background:var(--ink-900); color:var(--text-on-dark); }
.ph-content{ font-family:var(--font-body); }

  
  .ph-runway{ height:640vh; position:relative; }
  .ph-stage{ height:100vh; overflow:hidden; position:relative;
          background:radial-gradient(120% 90% at 50% 22%, #2f3133 0%, #232526 45%, #16181a 100%); }
  .ph-stage::after{ content:""; position:absolute; inset:0; pointer-events:none; opacity:.16; z-index:9;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.45'/></svg>"); }

  .ph-plane{ position:absolute; background-repeat:no-repeat; background-size:contain;
          background-position:center bottom; will-change:transform,opacity; }

  /* the chosen storefront: frame + everything visible through its glass, moving as ONE */
  .ph-winwrap{ width:46vw; aspect-ratio:1081/915; left:50%; top:47%;
            transform:translate(-50%,-50%); z-index:3; }
  /* the rest of the building. The storefront is one storey, which left a lake of empty
     sky above it on a phone. Lives inside .ph-winwrap so it scales and travels with it. */
  .ph-upper{ position:absolute; left:-3%; right:-3%; bottom:97%; height:72%;
          background:url("/assets/img/product/hero/upper_floors.webp") center top/cover no-repeat; }

  .ph-through{ position:absolute; left:20.72%; top:20.87%; width:58.19%; height:59.45%;
            overflow:hidden; background:#0e1012; }
  .ph-room-back{ position:absolute; inset:-2% -2% -2% -2%;
              background:url("/assets/img/product/hero/room_back.webp") center/cover; opacity:1; }
  .ph-room-desk{ position:absolute; left:-6%; right:-6%; bottom:-6%; height:34%;
              background:url("/assets/img/product/hero/room_desk.webp") center bottom/contain no-repeat; }
  .ph-frame-img{ position:absolute; inset:0;
              background:url("/assets/img/product/hero/window_frame.webp") center/contain no-repeat; }

  /* the wall of command centers, built in DOM so it can go live later */
  .ph-wall{ position:absolute; left:9%; top:12%; width:82%; height:56%;
         display:grid; grid-template-columns:repeat(4,1fr); grid-template-rows:repeat(3,1fr);
         gap:4.5%; }
  .ph-tile{ border-radius:2px; background:linear-gradient(160deg,#20242a,#171a1e);
         box-shadow:inset 0 0 0 1px rgba(108,220,246,.10); opacity:.28;
         position:relative; overflow:hidden; }
  .ph-tile i{ position:absolute; left:12%; right:12%; height:7%; border-radius:1px;
           background:linear-gradient(90deg,var(--indigo-soft),var(--cyan)); opacity:.55; }
  .ph-tile i:nth-child(1){ top:20%; right:44%; }
  .ph-tile i:nth-child(2){ top:38%; right:26%; }
  .ph-tile i:nth-child(3){ top:56%; right:56%; }
  .ph-tile i:nth-child(4){ top:74%; right:34%; }
  .ph-tile.lit{ opacity:1; box-shadow:inset 0 0 0 1px rgba(108,220,246,.35), 0 0 26px rgba(76,209,244,.18); }

  .ph-block-left{ width:31vw; aspect-ratio:762/712; left:0vw;  bottom:21vh; z-index:4;
               transform-origin:0% 100%; background-image:url("/assets/img/product/hero/block_left.webp"); }
  .ph-block-right{ width:31vw; aspect-ratio:762/712; right:0vw; bottom:21vh; z-index:4;
                transform-origin:100% 100%; background-image:url("/assets/img/product/hero/block_right.webp"); }
  .ph-street{ width:140vw; height:25vh; left:-20vw; bottom:-2vh; z-index:5;
           background-image:url("/assets/img/product/hero/street.webp"); background-size:100% 100%; }

  /* the payoff: the real product on a real screen, standing on the desk.
     Bezel is CSS, not art: it stays sharp at any size and the screen area is an
     exact rectangle the iframe can sit in. */
  /* 🔴 The rig is sized in vw but stood on a vh ground line. On a short wide window
     (a laptop with browser chrome, ~1512x760) a vw-sized monitor grew taller than the
     room and its top ran off the screen. Everything below is driven by ONE variable
     that is clamped by BOTH axes, so the whole rig always fits above the desk. */
  .ph-rig{ --rw: min(52vw, 80vh);
        position:absolute; left:50%; bottom:24vh; width:var(--rw); z-index:7;
        transform:translate(-50%,0) scale(.96); opacity:0; }
  .ph-monitor{ position:relative; width:100%; padding:.9% .9% 5.4%; border-radius:calc(var(--rw)*.019);
            background:linear-gradient(180deg,#3a3d40 0%,#2a2d2f 62%,#232526 100%);
            box-shadow:0 40px 120px rgba(0,0,0,.7), 0 0 0 1px rgba(255,255,255,.06),
                       0 0 110px rgba(76,209,244,.14); }
  /* 🔴 The rail screen is NOT a second monitor. The hero payoff earns its bezel because it
     stands in a lit room on a desk, which is the shot Trace asked for. Repeating that bezel
     on flat ink here produced a hard grey rounded box with a white rectangle inside it, and
     it read as a screenshot in a container rather than as the product (Trace, 2026-09-09).
     So the rail keeps the screen and drops the furniture: no bezel, no chin, no stand, just
     the product with a soft lift off the background. */
  .ph-rig2 .ph-screen{ background:url("/assets/img/product/hero/hero_screen.webp") center/cover, var(--paper); }
  .ph-rig2 .ph-monitor{ padding:0; background:none; border-radius:calc(var(--rw)*.012);
            box-shadow:0 30px 90px rgba(0,0,0,.55), 0 0 90px rgba(76,209,244,.10); }
  .ph-rig2 .ph-screen{ border-radius:calc(var(--rw)*.012);
            box-shadow:inset 0 0 0 1px rgba(255,255,255,.07); }
  .ph-rig2 .ph-screen::before{ box-shadow:inset 0 0 0 1px rgba(255,255,255,.07); }
  .ph-rig2 .ph-chin, .ph-rig2 .ph-neck, .ph-rig2 .ph-foot{ display:none; }
  .ph-screen{ position:relative; width:100%; aspect-ratio:1500/940; overflow:hidden;
           border-radius:calc(var(--rw)*.007);
           background:url("/assets/img/product/hero/hero_screen.webp") center/cover, var(--ink-900);
           box-shadow:inset 0 0 0 1px rgba(0,0,0,.5); }
  /* an opaque 2px inner rim, painted ON TOP of everything in the screen box. Whatever
     the edge artifact is (iframe body background, subpixel clip, resample fringe while
     the rig is being scaled), it cannot reach past this. */
  .ph-screen::before{ content:""; position:absolute; inset:0; z-index:3; pointer-events:none;
                   border-radius:inherit; box-shadow:inset 0 0 0 2px #16181a; }
  .ph-screen::after{ /* glass */
    content:""; position:absolute; inset:0; pointer-events:none;
    background:linear-gradient(128deg, rgba(255,255,255,.10) 0%, rgba(255,255,255,0) 38%); }
  .ph-chin{ position:absolute; left:0; right:0; bottom:1.4%; text-align:center;
         font-size:calc(var(--rw)*.015); letter-spacing:.22em; color:#6d7276; }
  .ph-neck{ position:relative; margin:0 auto; width:9%; height:calc(var(--rw)*.065);
         background:linear-gradient(90deg,#2a2d2f,#3f4346 40%,#2a2d2f); }
  .ph-foot{ position:relative; margin:0 auto; width:26%; height:calc(var(--rw)*.017);
         border-radius:0 0 calc(var(--rw)*.011) calc(var(--rw)*.011);
         background:linear-gradient(180deg,#3f4346,#232526);
         box-shadow:0 18px 40px rgba(0,0,0,.6); }

  /* 🔴 The white hairline Trace saw. Chrome does not reliably clip an IFRAME to a
     rounded, composited ancestor, so the frame's own light document bled one pixel past
     .ph-screen's overflow:hidden and shimmered as the rig was scaled during the scroll.
     Fix is to keep the frame a pixel INSIDE the clip (left/top 1px, and fitFrame scales
     to clientWidth - 2) instead of relying on the clip, and to paint an opaque rim over
     the seam. Do not add transform/translateZ to .ph-screen: that promotion is what makes
     the clip fail in the first place. */
  /* The frame fades in only once it has actually painted. Setting src makes the iframe
     paint an empty white box immediately, which covers the poster screenshot underneath
     and turns the payoff into a grey rectangle in a bezel for as long as demo.trulata.com
     takes to boot. The poster holds the frame until the real thing is ready. */
  .ph-heroframe{ opacity:0; transition:opacity .45s ease; }
  .ph-rig.ph-framed.ph-frame-ready .ph-heroframe{ opacity:1; }
  .ph-heroframe{ display:none; position:absolute; left:1px; top:1px; width:1500px;
              height:940px; border:0; transform-origin:0 0; pointer-events:none;
              background:var(--ink-900); }
  .ph-rig.ph-framed .ph-heroframe{ display:block; }
  .ph-rig.ph-interactive .ph-heroframe{ pointer-events:auto; }
  .ph-finale{ position:absolute; left:0; right:0; bottom:9vh; z-index:8; text-align:center;
           opacity:0; padding:0 6vw; }
  .ph-finale h2{ font-family:var(--font-head); font-weight:800; margin:0 0 .5em;
              font-size:clamp(22px,2.9vw,44px); line-height:1.14; letter-spacing:-.01em; }
  .ph-finale p{ margin:0 auto 1.4em; max-width:62ch; color:#b7bfc6;
             font-size:clamp(14px,1.15vw,18px); line-height:1.6; }
  .ph-finale a{ display:inline-block; text-decoration:none; color:#fff; font-weight:600;
             padding:.85em 1.8em; border-radius:999px; font-size:clamp(14px,1.05vw,17px);
             background:linear-gradient(90deg,var(--indigo),var(--purple)); }
  .ph-herocap{ position:absolute; left:0; right:0; bottom:3.5vh; text-align:center; z-index:8;
            padding:0 6vw;
            opacity:0; font-size:13px; letter-spacing:.16em; text-transform:uppercase;
            color:var(--on-dark-dim); }

  .ph-beats{ position:absolute; inset:0; z-index:6; pointer-events:none; }
  .ph-beat[data-b="1"]{ opacity:1; }
  /* beat 1 is the page's real <h1>, in the DOM at frame zero so a crawler and a screen
     reader get the headline without running the timeline. margin:0 because an h1 carries
     browser margins that a div did not. */
  .ph-beat{ position:absolute; left:0; right:0; top:11vh; text-align:center; opacity:0;
         margin:0; padding:0 6vw;
         font-family:var(--font-head); font-weight:800; letter-spacing:-.01em;
         font-size:clamp(24px,3.5vw,54px); line-height:1.12;
         text-shadow:0 4px 40px rgba(0,0,0,.75); }
  .ph-beat span{ background:linear-gradient(90deg,var(--cyan),var(--indigo-soft),var(--purple));
              -webkit-background-clip:text; background-clip:text; color:transparent; }
  .ph-cue{ position:absolute; left:50%; bottom:3.5vh; transform:translateX(-50%); z-index:8;
        font-size:11px; letter-spacing:.34em; text-transform:uppercase; color:var(--on-dark-dim); }

  /* the room, full frame. We do NOT scale the little in-window grid up to fill the
     screen: past a point that is just a big blurry grid. We cross into a real one. */
  .ph-interior{ position:absolute; inset:0; z-index:6; opacity:0;
             background:url("/assets/img/product/hero/room_back.webp") center/cover, #0e1012; }
  .ph-interior .ph-wall{ position:absolute; left:9%; top:14%; width:82%; height:52%;
                   grid-template-columns:repeat(6,1fr); grid-template-rows:repeat(3,1fr);
                   gap:2.4%; }
  .ph-interior .ph-desk{ position:absolute; left:-4%; right:-4%; bottom:-4%; height:34%;
                   background:url("/assets/img/product/hero/room_desk.webp") center bottom/contain no-repeat; }

  .ph-scrim{ position:absolute; inset:0; background:var(--ink-900); opacity:0; z-index:9; }

  /* 🔴 Full bleed, with the 1400px kept as a reading measure rather than as a box.
     The concept could get away with `max-width:1400px; margin:0 auto` because its body was
     --ink-900, the same colour as this section, so the edges of the box were invisible. On
     the site the body is --ink (#282a2b) and the rail is --ink-900 (#1c1e1f), so the same
     rule drew a hard-edged 1400px rectangle of one dark on another and read as a container
     (Trace, 2026-09-09). The band now runs edge to edge and the padding does the centring,
     which is the site's own full-bleed idiom. */
  .ph-rail{ display:grid; grid-template-columns:1.05fr .95fr; gap:5vw; align-items:start;
         max-width:none; margin:0;
         padding:14vh max(5vw, calc((100% - 1400px) / 2)) 10vh; }
  .ph-rail-media{ position:sticky; top:14vh; }
  .ph-rail-media .ph-rig2{ --rw: min(46vw, 62vh); width:var(--rw); margin:0 auto; }
  .ph-rail-copy{ padding-top:14vh; } /* the first caption meets the monitor after it has faded in */
  .ph-rail-copy section{ min-height:44vh; }
  .ph-rail-copy h2{ font-family:var(--font-head); font-weight:800; margin:0 0 .5em;
                 font-size:clamp(22px,2.3vw,36px); line-height:1.15; }
  .ph-rail-copy p{ margin:0; color:#b7bfc6; font-size:clamp(15px,1.15vw,18px); line-height:1.7;
                max-width:46ch; }
  .ph-rail-cta{ padding:2vh 0 0; }
  .ph-rail-cta a{ display:inline-block; text-decoration:none; color:#fff; font-weight:600;
               padding:.9em 2em; border-radius:999px; font-size:17px;
               background:linear-gradient(90deg,var(--indigo),var(--purple)); }

  @media (max-width:900px){
    .ph-finale a{ display:none; }        /* the CTA lives once, at the end of the rail */
    .ph-herocap{ display:none; }         /* it collided with the finale headline when stacked */
    .ph-rail{ padding-top:16vh; }
    .ph-finale{ bottom:16vh; }           /* clears the docked monitor above it */
    .ph-rail-cta{ text-align:center; }
    .ph-rail{ grid-template-columns:1fr; gap:0; padding:8vh 6vw 6vh; max-width:none; }
    /* 🔴 The rail monitor is DESKTOP ONLY. On a phone the rail stacks directly under the
       hero, so its screen and the hero's docked screen are both on screen through the
       handoff and read as two computers stacked (Trace, 2026-09-09, iPhone). A sticky
       rail-media only made the overlap last longer. The phone already got the product
       screen, full width, one scroll earlier: showing it again smaller is not a second
       beat, it is a duplicate. The copy carries the rail alone. */
    /* 2026-09-11 (Trace: the hero is less wow on mobile): the rail's screen is back on phones as a
       compact sticky monitor that swaps to the view each step talks about; it shows a different view
       from the hero's docked screen, so it reads as the next beat, not a duplicate. */
    /* 2026-09-12 (Trace, iPhone photo): the sticky panel painted --ink (#282a2b) over a section that is
       --ink-900 (#1c1e1f), inside the rail's 6vw side padding, so it read as a lighter box with a hard
       bottom edge slicing the copy. Same token as the section, full bleed, and a short fade under it
       so the copy emerges instead of being cut. */
    .ph-rail-media{ display:block; position:sticky; top:0; z-index:2; padding:8vh 6vw 3vh; margin:0 -6vw; background:var(--ink-900,#1c1e1f); }
    .ph-rail-media::after{ content:""; position:absolute; left:0; right:0; top:100%; height:7vh; pointer-events:none;
                           background:linear-gradient(var(--ink-900,#1c1e1f), rgba(28,30,31,0)); }
    .ph-rail-media .ph-rig2{ --rw: 84vw; width:84vw; margin:0 auto; }
    .ph-rail-media .ph-monitor{ padding:0; border:0; background:none; box-shadow:none; }
    .ph-rail-media .ph-chin, .ph-rail-media .ph-neck, .ph-rail-media .ph-foot{ display:none; }
    .ph-rail-media .ph-screen{ width:100%; aspect-ratio:1500/940; border-radius:10px; background-size:cover; background-position:center;
                               box-shadow:0 24px 48px -22px rgba(0,0,0,.7); }
    .ph-rail-copy{ padding-top:2vh; position:relative; z-index:1; }
    .ph-rail-copy section{ min-height:54vh; display:flex; flex-direction:column; justify-content:flex-end; padding-bottom:8vh; }
    .ph-rail-copy{ padding-top:2vh; }
    .ph-rail-copy section{ min-height:38vh; }
  }

  .ph-content{ max-width:820px; margin:0 auto; padding:6vh 24px 20vh; }
  .ph-content h2{ font-family:var(--font-head); font-weight:800;
               font-size:clamp(28px,4.6vw,52px); margin:0 0 .6em; }
  .ph-content p{ font-size:18px; line-height:1.75; color:#c3cad1; margin:0 0 1.1em; }
  .ph-content .note{ color:var(--on-dark-dim); font-size:14px; }

  /* Mobile is a recomposition, not a shrink. Every plane is re-anchored to the SAME
     ground line, and the second neighbour is cut rather than squeezed. */
  @media (max-width:900px){
    .ph-runway{ height:520vh; }
    .ph-winwrap{ width:94vw; top:auto; bottom:23vh; transform:translate(-50%,0); }
    .ph-upper{ height:132%; }
    /* the neighbour sits BEHIND the storefront on a phone, so it peeks out from the
       edge instead of laying a hard cropped rectangle across the glass */
    .ph-block-left{ width:54vw; left:-30vw; bottom:23vh; z-index:2; }
    .ph-block-right{ display:block; width:54vw; right:-32vw; bottom:23vh; z-index:2; }
    .ph-street{ width:190vw; height:26vh; left:-45vw; bottom:-1vh; }
    .ph-interior .ph-wall{ left:5%; width:90%; top:18%; height:44%;
                     grid-template-columns:repeat(4,1fr); }
    .ph-rig{ --rw: min(88vw, 62vh); bottom:28vh; }
    .ph-beat{ top:7vh; font-size:clamp(21px,6.4vw,34px); }
  }
  /* 🔴 REDUCED MOTION GETS THE END STATE, AND IT HAS TO BE A COMPOSITION, NOT AN UNHIDING.
     This block used to restore only the runway height, the rig and the tiles. Everything
     the timeline fades IN stayed at its opacity:0 start, so a reduced-motion visitor never
     saw the closing headline or the hero's CTA at all, and the rig came back at full scale
     on top of the H1. Measured 2026-09-10 at 1440x900: the monitor sat 255 to 802 while the
     h1 sat 217 to 330, so "Five different trades." was entirely behind the dashboard.
     Both matchMedia conditions in product-hero.js require no-preference, so no JS runs here
     and CSS is the only thing composing this frame. Lay it out properly:
     H1 at the top, the monitor docked in the middle at the same scale the animation ends on,
     the finale headline and button at the bottom. Nothing overlaps, nothing is hidden. */
  /* 🔴 REDUCED MOTION GETS THE END STATE, AND IT IS LAID OUT IN NORMAL FLOW.
     This block used to restore only the runway height, the rig and the tiles, so everything
     the timeline fades IN stayed at its opacity:0 start: a reduced-motion visitor never saw
     the closing headline, and the monitor came back at full scale on top of the H1 (measured
     2026-09-10 at 1440x900: monitor 255 to 802, h1 217 to 330).
     The first fix attempt kept the absolute positioning and nudged offsets, and it still
     collided by 70px and pushed the finale below the fold, because every offset is relative
     to a .ph-stage that starts BELOW the page header (this page has no sticky header, so the
     header takes real vertical space). Any pixel arithmetic here is hostage to that height.
     So the honest fix is to stop positioning absolutely. With the dolly scenery hidden there
     are only three things left, and a flex column cannot overlap itself at any viewport.
     Both matchMedia conditions in product-hero.js require no-preference, so no JS runs here
     and this CSS is the only thing composing the frame. */
  @media (prefers-reduced-motion:reduce){
    .ph-runway{ height:auto; }
    .ph-stage{ height:auto; overflow:visible; display:flex; flex-direction:column;
               align-items:center; justify-content:flex-start;
               gap:clamp(18px,4vh,40px); padding:7vh 6vw 9vh; }
    .ph-stage::after{ display:none; }
    /* the dolly scenery is the journey, not the destination */
    .ph-winwrap, .ph-block-left, .ph-block-right, .ph-street, .ph-cue,
    .ph-interior{ display:none; }
    /* display:contents dissolves the .ph-beats wrapper so beat 1, which IS the page's real
       h1, becomes a flex item of the stage rather than a child of an absolute layer. */
    .ph-beats{ display:contents; }
    .ph-beat[data-b="2"], .ph-beat[data-b="3"], .ph-herocap{ display:none; }
    /* 🔴 .ph-rig sits BEFORE .ph-beats in the markup, so source order alone puts the monitor
       above the headline. Order is explicit rather than inherited from the DOM. */
    .ph-beat[data-b="1"]{ position:static; opacity:1; top:auto; padding:0; max-width:22ch;
                          order:1; }
    .ph-rig{ position:static; opacity:1; transform:none;
             width:min(78vw, 640px); margin:0; order:2; }
    .ph-finale{ position:static; opacity:1; transform:none; bottom:auto; order:3; }
  }

/* 🔴 /product has NO STICKY HEADER. Trace, 2026-09-09: "not the sticky header, just the one
   at the beginning. No sticky header just on this page." Every other page keeps sticky; this
   stylesheet is linked from product.html alone, so a bare header.nav rule here cannot reach
   them. The header sits at the top in normal flow, transparent, and scrolls away into the
   dolly. Nothing pins it back: the CTA at the end of the page and the footer carry the
   navigation from there. */
header.nav{
  position:relative; top:auto;
  background:transparent; backdrop-filter:none; -webkit-backdrop-filter:none;
  border-color:transparent; box-shadow:none;
}
header.nav.is-scrolled,
header.nav.is-scrolled[data-nav-theme="dark"],
header.nav.is-scrolled[data-nav-theme="light"]{
  background:transparent; backdrop-filter:none; -webkit-backdrop-filter:none;
  border-color:transparent; box-shadow:none;
}
/* the rail's monitor shows the view each step talks about; a second layer fades the next one in */
.ph-rig2 .ph-screen{ transition:none; }
.ph-rig2 .ph-screen__next{ position:absolute; inset:0; background-size:cover; background-position:center; opacity:0; transition:opacity .7s cubic-bezier(.4,0,.2,1); will-change:opacity; }
.ph-rig2 .ph-screen__next.is-on{ opacity:1; }
/* the rail's four views are captured at one 1500x940 viewport, so cover fits every one identically */
.ph-rail-media .ph-screen{ background:url("/assets/img/product/rail/outbound.webp") center/cover, #fff; }
.ph-rail-media .ph-screen, .ph-rail-media .ph-screen__next{ background-size:cover; background-position:center; }

