/* ═══════════════════════════════════════════════════════════════════════
   tulir.systems — shared stylesheet.

   TARGET: assets/reference_v8_latest_overlapping_composition_NOT_PIXEL_FINAL.png
   (1774 x 887). Every proportion below was MEASURED off that file rather than
   eyeballed, and the measurements are recorded inline so the next person can
   check them instead of trusting me:

     content gutter    x=77 and x=1696        -> 4.34% of 1774
     content width     1619                   -> 91.3%
     header            0..72, wash starts 73  -> 73px at that width
     hero wash ends    y=529  (laptops overrun to ~575)
     section heading   y~570
     product cards     y 626..751, x 77..882 and 891..1695  (gap 9px)
     benefit dividers  x=428, 875, 1289
     footer rule       y=826
     card fills        #eef6fe (blue)  #f0f9f5 (sage)   sampled
     page background   #fdfdfe                          sampled

   WHY THIS FILE AND NOT reference_v8_approved_composition.png: the package
   README calls this one "optional composition context ... known overlap
   imperfections, not a pixel-accurate spec", so the first build targeted the
   other reference. That was the wrong call — this is the composition Raj
   wants. The written constraints in CLAUDE_HANDOFF.md still win over this
   image wherever the two disagree, and each such case is commented where it
   occurs.

   CONTRAST. Every colour pairing that carries text is checked by
   test/contrast.mjs against WCAG AA. Change a token and run it.
   ═══════════════════════════════════════════════════════════════════════ */

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}

:root{
  /* §3 style tokens. The mockup's own pixels sample slightly more saturated
     (its card link blue reads near #0034f7); the handoff's written values win. */
  --navy:#092448;
  --navy-700:#0d3160;
  --forest:#125b45;
  --ink:#354d61;
  --ink-soft:#5a7083;

  --xray-blue:#1769c2;--xray-blue-700:#125493;
  --pulse-green:#0f7959;

  --white:#ffffff;
  --page:#fdfdfe;
  --pale-blue:#eef6fe;
  --pale-sage:#f0f9f5;
  --line:#e3e9f0;
  --line-soft:#edf1f6;

  --r:10px;--r-sm:8px;
  --shadow-sm:0 1px 2px rgba(9,36,72,.06);
  --shadow-md:0 8px 24px -10px rgba(9,36,72,.18);
  --shadow-lap:0 26px 46px -18px rgba(9,36,72,.42),0 6px 14px rgba(9,36,72,.10);

  --serif:'Playfair Display',Georgia,'Times New Roman',serif;
  --sans:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;

  /* 1619/1774 of the reference. The handoff says "1480px (or comparable)";
     this is the comparable figure that reproduces the mockup's proportions. */
  --wrap:min(91.3vw,1680px);
  --gutter:clamp(18px,4.34vw,78px);
  --header-h:72px;
}

html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{transition-duration:.01ms!important;animation-duration:.01ms!important;scroll-behavior:auto!important}
}
body{background:var(--page);color:var(--ink);font-family:var(--sans);
  font-size:16px;line-height:1.55;overflow-x:hidden;-webkit-font-smoothing:antialiased}
img{max-width:100%;display:block}

.skip{position:absolute;left:-9999px}
.skip:focus{position:fixed;left:16px;top:16px;z-index:300;background:var(--navy);color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;font-weight:600;text-decoration:none}

a{color:var(--xray-blue);text-underline-offset:3px}
a:focus-visible,button:focus-visible{outline:2px solid var(--xray-blue);outline-offset:3px;border-radius:4px}

/* One container, used by header, hero, every section and the footer, so they
   share a single left/right alignment (§1). */
.wrap{width:var(--wrap);max-width:100%;margin:0 auto;padding:0}
.narrow{max-width:820px;margin:0 auto;padding:0 var(--gutter)}

h1,h2,h3,.serif{font-family:var(--serif)}
strong{font-weight:600;color:var(--navy)}

/* ═══ HEADER ══════════════════════════════════════════════════════════ */
.site-header{position:sticky;top:0;z-index:200;background:var(--white);border-bottom:1px solid var(--line-soft)}
.header-inner{width:var(--wrap);max-width:100%;margin:0 auto;padding:0;
  height:var(--header-h);display:flex;align-items:center;gap:clamp(14px,2vw,34px)}
.logo-link{flex-shrink:0;display:block;line-height:0}
/* Reference lockup: x 88..240, y 14..64 -> 152 x 50, ink aspect 2.10. */
.logo-link img{width:144px;height:auto}

.main-nav{display:flex;align-items:center;gap:clamp(14px,1.9vw,34px);margin:0 auto}
/* Contact Us is a header UTILITY link, not a button: it shares this rule with
   the nav links so its size, weight and colour can never drift from them. It
   sits in .header-actions rather than .main-nav because it belongs with the
   sign-in group, which is also why it is excluded from the current-page
   underline that site-nav.js applies to .main-nav. */
.main-nav>a,.menu-btn,.header-util{font-family:var(--sans);font-size:16px;font-weight:500;color:var(--navy);
  background:none;border:none;cursor:pointer;text-decoration:none;padding:10px 2px;
  display:inline-flex;align-items:center;gap:7px;white-space:nowrap;transition:color .15s}
.main-nav>a:hover,.menu-btn:hover,.header-util:hover{color:var(--xray-blue)}
/* One Contact Us visible at a time: the utility link beside Sign in on wide
   screens, the nav copy once the utility group runs out of room. 959px is the
   breakpoint this header already uses to drop the two product buttons. */
/* Specificity matters here: .main-nav>a is (0,1,1) and sets display:inline-flex,
   so a bare .nav-contact at (0,1,0) loses to it and BOTH Contact Us links showed
   on desktop — a duplicate link and a duplicate tab stop. Matching the selector
   shape is what makes the swap actually happen. */
.main-nav>a.nav-contact{display:none}
@media (max-width:959px){
  .header-util{display:none}
  .main-nav>a.nav-contact{display:inline-flex}
  /* Between 768 and 959 the nav is still horizontal and now carries two more
     links — Pricing, and the Contact Us that moved in from the utility group.
     Measured, that put the header 3px over the viewport at 768. The gap floor
     drops from 14px to 10px for this band only; the handoff asks for modest gap
     adjustment rather than shrinking type or the logo, and this is the whole of
     the adjustment. Above 959px nothing changes. */
  .main-nav{gap:clamp(10px,1.6vw,26px)}
}
.menu-btn .caret{width:10px;height:6px;flex-shrink:0;transition:transform .18s}
.menu-btn[aria-expanded="true"] .caret{transform:rotate(180deg)}

.menu{position:relative}
.menu-list{position:absolute;top:calc(100% + 10px);left:0;min-width:256px;background:var(--white);
  border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-md);padding:8px;
  list-style:none;margin:0;opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .16s,transform .16s,visibility .16s}
.menu-list.right{left:auto;right:0}
.menu[data-open="true"] .menu-list{opacity:1;visibility:visible;transform:translateY(0)}
.menu-list a{display:block;padding:10px 12px;border-radius:var(--r-sm);font-size:14.5px;
  font-weight:500;color:var(--navy);text-decoration:none}
.menu-list a:hover{background:var(--pale-blue);color:var(--xray-blue)}
.menu-list a .mi{display:block;font-size:12.5px;font-weight:400;color:var(--ink-soft);margin-top:2px}

.header-actions{display:flex;align-items:center;gap:14px;flex-shrink:0}
.header-divider{width:1px;height:30px;background:var(--line);flex-shrink:0}
/* Reference: outlined button x 1356..1545 (189 x 42), solid x 1553..1697. */
.btn-outline,.btn-solid{display:inline-flex;align-items:center;justify-content:center;
  border-radius:var(--r-sm);font-size:15.5px;font-weight:600;padding:11px 22px;
  text-decoration:none;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}
.btn-outline{background:var(--white);color:var(--navy);border:1.5px solid var(--navy)}
.btn-outline:hover{background:var(--navy);color:#fff}
.btn-solid{background:var(--forest);color:#fff;border:1.5px solid var(--forest)}
.btn-solid:hover{background:#0e4a38;border-color:#0e4a38}
.btn-solid:focus-visible{outline-color:var(--forest)}

.nav-toggle{display:none;background:none;border:1px solid var(--line);border-radius:var(--r-sm);
  padding:9px 11px;cursor:pointer;color:var(--navy);margin-left:auto}

/* ═══ HERO ════════════════════════════════════════════════════════════
   The wash ends at y=529 while the laptops run to ~575, so the machines
   deliberately break out of the band and sit on the white below it. That
   overhang is the signature of this composition; it is reproduced with a
   negative margin on the stage rather than by faking a taller band. */
.hero{position:relative;background:
    radial-gradient(760px 430px at 4% 74%, rgba(168,201,183,.34), rgba(168,201,183,0) 72%),
    radial-gradient(560px 320px at 10% 16%, rgba(255,255,255,.40), rgba(255,255,255,0) 70%),
    linear-gradient(104deg,
      #eaf4ef 0%, #eef6f2 16%, #eff7f1 34%, #e6f1e7 58%, #dceed8 78%, #d4e8cf 100%)}

/* The approved production foliage asset is NOT in the package (§8 item 5).
   This is the only unoccluded foliage in the mockup, lifted at native size so
   it is never upscaled at or below a 1774px viewport, and feathered on its
   left and bottom edges so it dissolves into the wash instead of ending on a
   seam. Replace the url() with the real art when it arrives — nothing else
   needs to change. */
/* Sized by WIDTH, not height: the previous rule scaled the band to 46% of the
   hero's height, which blew a 654x172 crop up to ~1281px wide and dragged its
   lower-left corner — which contained PulseAlert's nav bar, baked in from the
   mockup — across the headline. The asset is now cut from a region with no
   product chrome in it at all, and is never drawn above ~1.25x native. */
.hero::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:url("assets/hero-foliage.png") no-repeat right top;
  background-size:46% auto;opacity:.95}
/* Two layers in ONE rule. Adding a second `.hero::after` block would have
   silently replaced this one and taken the white-left fade with it — a
   duplicate pseudo-element does not merge, the later declaration just wins.
   Layer 1 fades the wash out at the bottom, which is §2's "soft transition to
   the next white section" in place of the hard border that made the hero feel
   chopped off. Layer 2 is the original left-side fade that keeps the headline
   on readable white. */
.hero::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(180deg,
      rgba(255,255,255,0) 0 calc(100% - 48px),
      rgba(255,255,255,.85) calc(100% - 46px),
      #ffffff calc(100% - 45px), #ffffff 100%),
    linear-gradient(100deg,rgba(255,255,255,.14) 0%,rgba(255,255,255,.06) 24%,rgba(255,255,255,0) 46%)}
.hero-inner{position:relative;z-index:2;width:var(--wrap);max-width:100%;margin:0 auto;
  padding:clamp(16px,1.5vw,28px) 0 clamp(16px,1.7vw,32px);
  display:grid;grid-template-columns:minmax(0,38fr) minmax(0,62fr);
  gap:clamp(18px,2vw,34px);align-items:center}

.hero h1{font-size:clamp(34px,3.72vw,66px);font-weight:700;line-height:1.04;
  letter-spacing:-.015em;color:var(--navy)}
.hero h1 .nline{display:block}
.hero h1 .gline{display:block;color:var(--forest)}
.hero-sub{font-size:clamp(16px,1.2vw,22px);line-height:1.38;color:var(--ink);
  margin:9px 0 11px;max-width:25em}
.hero-diff{font-size:clamp(16px,1.3vw,23px);font-weight:700;color:var(--navy);
  line-height:1.35;margin-bottom:15px}
.hero-ctas{display:flex;flex-wrap:wrap;gap:16px}
.cta{display:inline-flex;align-items:center;gap:10px;border-radius:var(--r);
  font-size:clamp(15px,1vw,18px);font-weight:600;padding:15px 26px;text-decoration:none;
  transition:background .15s,color .15s,border-color .15s}
.cta-primary{background:var(--navy);color:#fff;border:1.5px solid var(--navy)}
.cta-primary:hover{background:var(--navy-700);border-color:var(--navy-700)}
.cta-primary:focus-visible{outline-color:var(--forest)}
.cta-secondary{background:rgba(255,255,255,.72);color:var(--navy);border:1.5px solid #c3cdd8}
.cta-secondary:hover{border-color:var(--navy);background:#fff}
.cta .arw{transition:transform .15s}
.cta:hover .arw{transform:translateX(4px)}

/* ── Laptops ──────────────────────────────────────────────────────────
   Measured down a column through the mockup's left machine (x=900), below the
   screen content:

     y 494..511   ~18px  dark chin   (21,28,33 -> 57,56,59)
     y 512..523   ~12px  silver base (220,222,225 -> 204,206,210)
     y 523..530   ~7px   contact shadow, fading to white by 536

   About 42px of chrome under a ~500px-wide screen. The previous build had 5px
   of bezel and a 9px bar — a third of that — which is why the machines read as
   dark rectangles rather than objects. Everything below is proportional (em of
   the frame width) so it holds at every size.

   This is CSS, not a photograph. It gets the structure right — chin, base lip,
   thumb notch, contact shadow, screen glass — but the mockup's machines are
   rendered art and a gradient will not fully match them. See README.md. */
.stage{position:relative;min-width:0;margin-bottom:-3%}
.laptop{position:absolute}

/* ── Clicking the hero devices ─────────────────────────────────────────────
   The screenshots are the largest, most interesting thing on the page, and
   people try to click them. Each device therefore carries a full-size hit
   layer over its lid and foot.

   It is a POINTER affordance only, not a seventh route. Each product already
   has six keyboard-reachable links from this page — Products menu, Resources
   menu, Sign in menu, header button, product card and footer — so this link
   takes tabindex="-1" and aria-hidden="true". Adding two more tab stops to the
   same six destinations would make keyboard navigation worse, and the XRay
   image's alt text is a full sentence about a sector chart, which would be a
   useless accessible name for a link.

   Hidden below 768px on purpose. There the devices stack full width and sit
   exactly where a thumb travels, and a full-width tappable image above the
   fold produces accidental navigation while scrolling.

   NOTE FOR ANYONE MOVING THE DEVICES: the hit areas follow the figures, and
   the overlap resolves by stacking order — .laptop--pulse (z-index 2) sits
   over .laptop--xray (z-index 1), so a click where they overlap goes to
   PulseAlert, which is what the visitor sees. Repositioning the figures moves
   the clickable regions with them, silently. */
.laptop-hit{position:absolute;inset:0;z-index:5;display:none;border-radius:inherit}

/* The behaviour ships ONLY where the affordance can. A pointer device gets the
   cursor and the outline on hover; a touch device gets neither, and a region
   that is silently tappable, duplicates six other links and sits where a thumb
   scrolls is worse than one that does nothing. Gating on (hover:hover) rather
   than on a width keeps the two inseparable — an earlier width-based rule left
   a 768px tablet clickable with no feedback at all. */
@media (hover:hover){
  .laptop-hit{display:block;cursor:pointer}
  .laptop:has(.laptop-hit:hover) .laptop-screen{outline:2px solid var(--xray-blue);
    outline-offset:3px;transition:outline-color .15s ease}
  .laptop--pulse:has(.laptop-hit:hover) .laptop-screen{outline-color:var(--pulse-green)}
}
.laptop-screen::before{content:"";position:absolute;inset:-3px -4px 0 -2px;z-index:-1;
  border-radius:inherit;
  background:linear-gradient(250deg,#f2f5f8 0%,#d3d8de 26%,#aab0b8 60%,#8d939b 100%)}
.laptop-screen{position:relative;
  background:linear-gradient(180deg,#2f3032 0%,#232426 62%,#0d0d0e 95.5%,
              #0d0d0e 97.2%,#3e4043 97.5%,#45464a 100%);
  border-radius:2.7% 2.7% .6% .6%/3.4% 3.4% .8% .8%;
  padding:2.4% 2.4% 3.6%;
  box-shadow:inset  1px 0 0 rgba(255,255,255,.07),
             inset -1px 0 0 rgba(255,255,255,.07),
             inset 0  1px 0 rgba(255,255,255,.12),
             inset 0 -1px 0 rgba(0,0,0,.50),
             0 16px 26px -14px rgba(16,24,34,.42)}
.laptop-screen img{width:100%;height:100%;object-fit:cover;object-position:top left;
  border-radius:4px 4px 2px 2px;background:#0b1220;display:block}

/* Row-by-row off the mockup's deck: lit top, mid body, then a 6px chamfer
   bottoming at L=58 before the cast shadow starts. 36/1 puts it at 16.9px on
   the XRay's 607px base; the mockup measures 17px. */
.laptop-base{position:relative;aspect-ratio:36 / 1;width:113%;margin-left:-6.5%;
  background:linear-gradient(180deg,
    #dcdee1 0%, #dcdee1 6%, #ccced2 8%, #cacdd1 62%,
    #858587 65%, #707070 70%, #4f504f 76%, #3b3a3a 84%,
    #595856 90%, #999795 100%);
  border-radius:0 0 8px 8px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.35),
             0 3px 5px rgba(9,36,72,.18),
             0 2px 4px rgba(22,26,30,.22)}
/* Translucent so it tracks the deck gradient at the mockup's measured 9-19
   level delta, instead of a fixed slab that fused with the seam row. */
.laptop-base::after{content:"";position:absolute;left:50%;top:20%;transform:translateX(-50%);
  width:15%;height:38%;border-radius:1px 1px 4px 4px;
  background:linear-gradient(90deg,rgba(18,28,42,.28) 0,rgba(18,28,42,0) 4px),
             linear-gradient(270deg,rgba(18,28,42,.24) 0,rgba(18,28,42,0) 4px),
             linear-gradient(180deg,rgba(24,36,52,.05),rgba(24,36,52,.11))}

/* Reference overlap: XRay spans x 680..1180 and PulseAlert 1105..1710, so the
   front machine covers ~15% of the back one. Both are larger relative to the
   column than the previous build, which is the other half of why the mockup
   reads as a product shot and ours did not. */
.laptop--xray{left:0;top:0;width:53%;z-index:1}
.laptop--xray .laptop-screen{aspect-ratio:964 / 785}
  /* .laptop-view IS the display. The bezel is drawn by .laptop-screen's padding,
     and an absolutely positioned child resolves its percentages against the
     PADDING box — so clipping on .laptop-screen clips at the outer edge and lets
     the inset paint over the rim, which is what it did. This wrapper has the
     content box's own dimensions, so the inset is positioned and clipped against
     the glass rather than the frame around it. height:100% is safe here because
     the aspect-ratio above makes the parent's height definite; the same
     percentage against an auto height is what once made .laptop-base vanish. */
  .laptop--xray .laptop-view{position:relative;display:block;width:100%;height:100%;
    overflow:hidden;border-radius:4px 4px 2px 2px}
  .laptop--xray .laptop-view > img{border-radius:0}
  /* The XRay display CLIPS its inset, unlike PulseAlert's which deliberately
     overhangs. Without this the Thematic panel — 31% + 87.6% = 118.6% wide —
     spilled past the laptop entirely. Harmless on desktop, where the stage
     absorbed it, but on a phone the laptop is full width so the PAGE grew: the
     390px render came back reporting innerWidth 439 with 49px of horizontal
     overflow, and 320 came back as 360. Clipping is also what the real
     application does; a panel does not hang off the side of its own screen. */
.laptop--pulse{right:0;top:24%;width:63%;z-index:2}
.laptop--pulse .laptop-screen{aspect-ratio:990 / 561}
/* CHATGPT V8: foreground is a direct crop of the real PulseAlert AAPL technical view.
   Keep the watchlist/Market Mood visible behind this supplementary authentic view. */
.laptop--pulse .laptop-screen{overflow:visible}
/* Sized to cover BOTH the Danelfin column header and the watchlist rows that
   would otherwise show below it. The previous inset spanned 58% of the screen's
   height and needed 73%, so it was geometrically incapable of hiding both ends
   — moving it only traded one for the other. Widening it to 67% buys the extra
   height (the crop's aspect is fixed), and shifting left to 31% keeps the right
   edge inside the display. 31% still clears the Market Mood rail, which ends at
   24.5% of the capture. */
.pulse-detail-peek{position:absolute;left:31%;top:27%;width:67%;z-index:3;
  aspect-ratio:584 / 360;border:2px solid #39536b;border-radius:10px;
  background:#0f2034;overflow:hidden;
  box-shadow:0 12px 26px rgba(2,13,29,.55),0 0 0 1px rgba(255,255,255,.10)}
.laptop--pulse .pulse-detail-peek img{width:100%;height:100%;object-fit:contain;border-radius:0}

  /* XRay carries a second view the same way PulseAlert carries the AAPL detail:
     a real panel from the product laid over the one behind it. The base frame is
     now the RAW capture, so object-fit:cover fills the display edge to edge — an
     earlier attempt baked padding into the image itself and left 41% of the
     screen empty, which reads as a rendering fault rather than a design choice.
     The Thematic bars are chosen over a second donut deliberately: a different
     shape stops the two devices looking like the same screen twice. Width caps at
     72% because the PulseAlert machine in front covers XRay's rightmost ~15%
     (this file's own reference above: XRay 680..1180, PulseAlert 1105..1710), so
     anything wider is hidden rather than seen. */
  /* Geometry measured over CDP, because this file's own earlier note was wrong.
     The PulseAlert machine in front covers XRay from 69.4% of its width, not the
     85% recorded above — identical at 1898, 1536 and 1440. The donut occupies
     x 3.8%..30.5% and y 31.5%..63% of the display.
     That leaves one band where a second panel can sit without hiding the donut or
     disappearing behind the front laptop: the bottom ~37% of the height, across
     the visible 69%. The inset is shaped to that band — a wide crop carrying the
     Thematic bars and the insight panel beside them — rather than squeezed into
     the narrow 31..69% gap next to the donut, which would have shown far less. */
  /* SAME SCALE AS THE SCREEN BEHIND. The display shows 1097 source pixels across
     its width, so a crop of N pixels must occupy N/1097 of that width for one
     pixel of the inset to equal one pixel of the base. The 961px crop therefore
     takes 87.6%, not a figure picked by eye — at the previous 64% it sat at 49.6%
     of the base's zoom and read as a shrunken thumbnail rather than a window.
     Left 31% clears the donut (which ends at 30.5%). The right end runs under the
     PulseAlert machine, which covers XRay from 69.4%, so 421 of the 961 pixels
     are visible and the rest reads as the panel continuing behind. Showing more
     would mean either breaking the 1:1 scale or covering the donut. */
  .xray-theme-peek{position:absolute;left:31%;top:32%;width:87.6%;z-index:2;
    aspect-ratio:961 / 260;border:1px solid #c3d2dd;border-radius:9px;
    background:#fff;overflow:hidden;
    box-shadow:0 14px 30px rgba(14,40,66,.30),0 0 0 1px rgba(255,255,255,.65)}
  .laptop--xray .xray-theme-peek img{width:100%;height:100%;object-fit:cover;
    object-position:top left;border-radius:0}

.stage-spacer{display:block;width:100%;padding-top:52%}

/* ── V8 hero geometry ─────────────────────────────────────────────────
   A slight art-directed perspective on both COMPLETE devices, so lid,
   bezel, base, contact shadow and (on PulseAlert) the AAPL inset all
   undergo one coherent transform and keep reading as a single physical
   object. Never transform .laptop-screen, .laptop-base, the img or
   .pulse-detail-peek separately — that is what produces a detached base
   or a screenshot overhanging its bezel.

   Desktop only. Below 1200px the hero is single-column and the devices
   stack, so they stay perfectly front-on. */
@media (min-width:1200px){
  .laptop--xray{transform:rotate(-.85deg);transform-origin:48% 88%}
  .laptop--pulse{transform:rotate(-.65deg);transform-origin:55% 90%}
}

/* ═══ PRODUCTS ════════════════════════════════════════════════════════ */
.section{position:relative;z-index:3;padding:clamp(16px,1.85vw,33px) 0 clamp(6px,.45vw,8px)}
.section-head{text-align:center;margin:0 auto clamp(6px,.4vw,8px)}
.section-head h2{font-size:clamp(22px,1.63vw,29px);font-weight:700;letter-spacing:-.012em;
  line-height:1.16;color:var(--navy)}
.section-head p{font-size:clamp(15px,1vw,17.5px);color:var(--ink);margin:0 auto;line-height:1.25;max-width:72ch}

/* Reference: cards 805 wide in a 1619 content box, 9px apart. */
/* Reference: cards 672 wide in a 1362 content box with a 14px gap. */
.product-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;align-items:stretch}
.pcard{border-radius:var(--r);padding:clamp(10px,.62vw,11px) clamp(18px,1.4vw,26px);
  display:flex;align-items:flex-start;gap:clamp(16px,1.5vw,28px)}
.pcard-xray{background:var(--pale-blue)}
.pcard-pulse{background:var(--pale-sage)}
.pcard-icon{width:clamp(44px,3.1vw,56px);aspect-ratio:1;border-radius:50%;flex-shrink:0;
  display:grid;place-items:center}
.pcard-xray .pcard-icon{background:#dceaf8}
.pcard-pulse .pcard-icon{background:var(--pulse-green)}
.pcard-body{min-width:0}
/* Product names are set in the SANS, not the display serif.
   Playfair Display's lowercase 'e' loses its crossbar to anti-aliasing at this
   size: measured at the shipped 25px, "PulseAlert" rasterises as "PulscAlcrt"
   and "Pulse" and "Alert" are BOTH affected, so this was never the e/A kern
   pair it was first reported as. Tested at 25px: font-kerning:none, letter-spacing
   up to .02em, -webkit-font-smoothing and text-rendering:geometricPrecision all
   leave it unreadable, and Playfair at 29px still reads "Pulsc". Inter renders
   both words correctly at the shipped size. The product card on about.html
   already sets these headings in the sans for the same reason. */
.pcard h3{font-family:var(--sans);font-size:clamp(19px,1.35vw,25px);font-weight:700;
  letter-spacing:.005em;line-height:1.2;margin-bottom:5px}
/* PulseAlert carries a beta marker because its own Terms do: "It is in personal
   beta ... opened to a small number of other people." The corporate site
   presenting it as a finished peer of XRay, while the product tells visitors
   otherwise, is the contradiction a prospect meets one click after this card. */
.pcard-beta,.about-beta{display:inline-block;vertical-align:middle;margin-left:9px;
  font-family:var(--sans);font-size:11px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--forest);background:var(--pale-sage);
  border:1px solid #cfe6da;border-radius:999px;padding:2px 8px;
  position:relative;top:-2px}
.pcard-xray h3{color:var(--navy)}
.pcard-pulse h3{color:var(--forest)}
.pcard-intro{font-size:clamp(15px,1.02vw,18px);font-weight:700;color:var(--navy);line-height:1.3;margin-bottom:5px}
.pcard-pulse .pcard-intro{color:var(--forest)}
.pcard-text{font-size:clamp(14px,.92vw,16px);color:var(--ink);margin-bottom:5px;line-height:1.45}
.pcard-link{display:inline-flex;align-items:center;gap:8px;font-size:clamp(14px,.92vw,16px);
  font-weight:600;line-height:1.2;text-decoration:none}
.pcard-xray .pcard-link{color:var(--xray-blue)}

/* The XRay card gains a second action. The footer becomes a row so the existing
   Explore link keeps its place on the left and the demo button sits right,
   rather than the two stacking and making this card taller than PulseAlert's —
   the cards are a matched pair and the hero must not be pushed down the page.
   PulseAlert deliberately has no equivalent: its demo portfolio does not exist. */
.pcard-foot{display:flex;align-items:center;justify-content:space-between;
  gap:12px;flex-wrap:wrap}
/* FILLED, NOT OUTLINED. The CTA handoff asked for a blue outline, which left
   the hands-on action -- open a real portfolio, no account, nothing to upload
   -- carrying less visual weight than the text link beside it pointing at a
   marketing page. The owner asked for it to read as the call to action, so it
   takes the shape every other primary CTA on this site takes: solid fill,
   white label, darker on hover, arrow nudging 4px. No shadow and no lift,
   because nothing else here has them and the brief asks for restraint.

   The fill is the CARD's blue rather than the global --navy. The two product
   cards are colour-coded, and a navy button inside the blue card would read
   as belonging to the page rather than to the product. White measures 5.47:1
   on #1769c2 and 7.74:1 on the hover shade, both clear of AA. */
.pcard-demo{display:inline-flex;align-items:center;gap:7px;flex-shrink:0;
  font-family:var(--sans);font-size:clamp(13px,.84vw,14px);font-weight:600;
  line-height:1;text-decoration:none;color:#fff;
  border:1px solid var(--xray-blue);border-radius:999px;padding:9px 16px;
  background:var(--xray-blue);
  transition:background .15s ease,border-color .15s ease}
.pcard-demo .arw{transition:transform .15s}
@media (hover:hover){
  .pcard-demo:hover{background:var(--xray-blue-700);border-color:var(--xray-blue-700)}
  .pcard-demo:hover .arw{transform:translateX(4px)}
}
@media (max-width:767px){
  .pcard-foot{flex-direction:column;align-items:flex-start;gap:10px}
  .pcard-demo{min-height:40px}
}
.pcard-pulse .pcard-link{color:var(--pulse-green)}
.pcard-link:hover{text-decoration:underline}
.pcard-link .arw{transition:transform .15s}
.pcard-link:hover .arw{transform:translateX(4px)}

/* ═══ BENEFITS ════════════════════════════════════════════════════════
   Reference dividers at x=428, 875, 1289 across a 77..1696 content box: four
   columns, icon to the LEFT of the text rather than above it. */
.benefits{position:relative;z-index:3;padding:clamp(4px,.3vw,5px) 0 clamp(20px,2vw,36px)}
.benefit-grid{display:grid;grid-template-columns:repeat(4,1fr)}
.benefit{display:flex;align-items:flex-start;gap:clamp(12px,1.1vw,20px);
  padding:0 clamp(14px,1.5vw,28px);border-left:1px solid var(--line)}
.benefit:first-child{border-left:0;padding-left:0}
.benefit-icon{width:clamp(28px,2.1vw,38px);flex-shrink:0;color:var(--navy);margin-top:1px}
.benefit h3{font-size:clamp(15px,1.07vw,19px);font-weight:700;line-height:1.2;
  color:var(--navy);margin-bottom:5px}
.benefit p{font-size:clamp(13px,.88vw,15.5px);color:var(--ink);line-height:1.45}

/* ═══ LEGAL / PROSE PAGES ═════════════════════════════════════════════ */
.doc{padding:clamp(40px,4vw,64px) 0 clamp(50px,5.5vw,84px)}
.doc .eyebrow{font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-soft);margin-bottom:14px}
.doc h1{font-family:var(--serif);font-size:clamp(32px,3.4vw,46px);font-weight:700;
  color:var(--navy);line-height:1.1;margin-bottom:10px}
.doc .updated{font-size:13.5px;color:var(--ink-soft);margin-bottom:32px}
.doc h2{font-family:var(--serif);font-size:23px;font-weight:700;color:var(--navy);
  margin:36px 0 12px;scroll-margin-top:100px}
.doc h3{font-family:var(--serif);font-size:18px;font-weight:700;color:var(--navy);margin:26px 0 8px}
.doc p,.doc li{font-size:16px;color:var(--ink);line-height:1.65}
.doc p{margin-bottom:16px}
.doc ul,.doc ol{margin:0 0 16px 0;padding-left:22px}
.doc li{margin-bottom:9px}
.doc a{text-decoration:underline}
.divider{height:1px;background:var(--line-soft);margin:42px 0}
.callout{background:var(--pale-blue);border:1px solid #d7e6f6;border-radius:var(--r);
  padding:20px 24px;margin:26px 0}
.callout .ct{font-family:var(--sans);font-size:12px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--xray-blue);margin-bottom:9px}
.callout p{margin-bottom:0;font-size:15.5px}
.callout.warn{background:#fdf4f1;border-color:#f0cfc4}
.callout.warn .ct{color:#a6401f}
.callout.green{background:var(--pale-sage);border-color:#cfe6da}
.callout.green .ct{color:var(--forest)}

/* ═══ FOOTER ══════════════════════════════════════════════════════════
   Reference: a single hairline at y=826 and a compact 61px band under it. */
.site-footer{border-top:1px solid var(--line);padding:clamp(20px,2vw,34px) 0 clamp(18px,1.8vw,30px)}
.footer-grid{display:grid;
  grid-template-columns:minmax(0,1.55fr) minmax(0,.86fr) minmax(0,.78fr) minmax(0,.86fr) minmax(0,1.5fr);
  gap:clamp(16px,2vw,40px);align-items:start}
.footer-brand{display:flex;align-items:flex-start;gap:clamp(12px,1.2vw,22px)}
.footer-brand img{width:clamp(92px,7vw,118px);height:auto;flex-shrink:0}
.footer-diff{font-size:clamp(12px,.82vw,14px);color:var(--ink);line-height:1.45;max-width:22ch}
.footer-col h4{font-family:var(--sans);font-size:clamp(12px,.82vw,14px);font-weight:700;
  color:var(--navy);margin-bottom:7px}
.footer-col a{display:block;font-size:clamp(12px,.82vw,14px);color:var(--ink);
  text-decoration:none;margin-bottom:5px}
.footer-col a:hover{color:var(--xray-blue);text-decoration:underline}
.footer-entity h4{font-family:var(--sans);font-size:clamp(12px,.82vw,14px);font-weight:700;
  color:var(--navy);margin-bottom:7px}
.footer-entity p{font-size:clamp(11.5px,.78vw,13px);color:var(--ink-soft);line-height:1.5;margin-bottom:5px}
.footer-entity p:last-child{margin-bottom:0}
/* <address> is the right element for an organisation's contact details, and
   every browser renders it italic by default. Reset once, here, rather than
   per use: the footer, both legal contact sections and the contact page all
   use it and none of them is a quotation. */
address{font-style:normal}
.footer-addr{font-size:clamp(11.5px,.78vw,13px);color:var(--ink-soft);
  line-height:1.5;margin-bottom:5px}
.who-addr{font-size:15.5px;color:var(--ink);line-height:1.6;margin:0 0 18px}

/* ═══ RESPONSIVE ══════════════════════════════════════════════════════ */
@media (max-width:1199px){
  :root{--header-h:74px}
  .logo-link img{width:140px}
  .main-nav>a,.menu-btn{font-size:14.5px}
  .hero-inner{grid-template-columns:minmax(0,1fr);gap:28px;padding-bottom:0}
  .hero-sub,.hero-diff{max-width:56ch}
  .stage{margin-bottom:-4%}
  .benefit-grid{grid-template-columns:repeat(2,1fr);gap:26px 0}
  .benefit:nth-child(3){border-left:0;padding-left:0}
  .footer-grid{grid-template-columns:repeat(3,1fr);gap:26px}
  .footer-brand{grid-column:1/-1}
  .footer-entity{grid-column:1/-1}
}
@media (max-width:959px){
  .header-actions .btn-outline,.header-actions .btn-solid,.header-divider{display:none}
  .product-grid{grid-template-columns:1fr;gap:12px}
}
@media (max-width:767px){
  .main-nav{display:none;margin:0}
  .main-nav.open{display:flex;flex-direction:column;align-items:stretch;gap:0;
    position:absolute;top:var(--header-h);left:0;right:0;background:var(--white);
    border-bottom:1px solid var(--line);padding:10px var(--gutter) 18px;box-shadow:var(--shadow-md)}
  .main-nav.open>a,.main-nav.open .menu-btn{padding:13px 0;width:100%;
    justify-content:space-between;min-height:44px}
  .main-nav.open .menu{width:100%}
  .main-nav.open .menu-list{position:static;opacity:1;visibility:visible;transform:none;
    box-shadow:none;border:0;border-left:2px solid var(--line);border-radius:0;
    padding:0 0 0 12px;margin:0 0 8px;display:none}
  .main-nav.open .menu[data-open="true"] .menu-list{display:block}
  .main-nav.open .menu-list a{min-height:44px;display:flex;flex-direction:column;justify-content:center}
  .nav-toggle{display:block;min-height:44px;min-width:44px}
  /* §2 asks for foliage "focused on the far right, fading toward the readable
     white left half". A single-column phone has no left half, so the art ends
     up directly behind the headline. It is pulled back to a top-corner accent
     and the white veil strengthened, which keeps the navy/forest headline on
     near-white rather than on leaves. */
  .hero::before{background-size:auto 26%;opacity:.5}
  .hero::after{background:
    linear-gradient(180deg,
      rgba(255,255,255,0) 0 calc(100% - 34px),
      rgba(255,255,255,.85) calc(100% - 32px),
      #ffffff calc(100% - 31px), #ffffff 100%),
    linear-gradient(180deg,rgba(255,255,255,.86) 0%,rgba(255,255,255,.72) 38%,rgba(255,255,255,0) 72%)}

  /* §5: stacked on a phone, never overlaid, never clipped. The machines stop
     being a composition and become two plain blocks. */
  .stage{margin-bottom:0}
  .stage-spacer{display:none}
  .laptop{position:static;width:100%!important;margin-bottom:22px}
  /* The base is 116% wide with a negative margin so it reads as a laptop foot
     wider than the lid. That overhang lives inside the stage on desktop, but
     once the machine is full-width it breaks the viewport: measured 404px of
     content against a 375px screen. Flush at this size. */
  .laptop-base{width:100%;margin-left:0}
  /* On a phone the 990px capture in a ~335px column renders at 35%. A NARROWER
     frame than the capture makes object-fit:cover crop horizontally instead of
     shrinking: the visible region becomes the left N px — the Market Mood rail
     and the first columns. Full height is kept, so nothing is cut off the
     bottom. Narrower than the capture crops width; WIDER than it crops height,
     which is the bug that cost 37% of this panel earlier.

     WHY 555 AND NOT 620. In the 990px capture the columns fall at roughly
     Symbol 250, Price 335, % Chg 395, Conviction 470–550, PA Signal 563–607,
     then Danelfin, 52-Wk, RSI and MACD. At 620 the crop reached past Conviction
     and included PA SIGNAL — the per-security Buy / Sell column — so every
     phone showed "AAPL Buy", "ADBE Sell", "GRAB Sell" against named tickers, on
     a page headlined "Tools to help you think, not tell you what to do", for a
     company that states it is not a registered investment adviser.

     On desktop that column is covered by .pulse-detail-peek, the AAPL inset.
     The inset is display:none below 767px (see the rule after this block), so
     the desktop fix never reached mobile and the badges were exposed in
     production. 555 ends the crop between Conviction and PA Signal, removing
     the column outright rather than covering it — nothing to uncover if a
     later layout change moves the inset again.

     The aggregate chips above the table ("Buy Signals 25 · 56%") are counts
     across the whole watchlist, not a call on any named security, and they stay
     — which is also what desktop shows, since the inset does not reach them. */
  .laptop--pulse .laptop-screen{aspect-ratio:555 / 561}
  .benefit-grid{grid-template-columns:1fr;gap:22px}
  .benefit{border-left:0;padding:0}
  .footer-grid{grid-template-columns:1fr 1fr}
  .cta{width:100%;justify-content:center}
}

/* On small screens retain the authentic PulseAlert watchlist and Market Mood. */
@media (max-width:767px){.pulse-detail-peek{display:none}}

/* TULIR V8 — compact desktop viewport: show four benefits at normal browser zoom.
   At 90% browser zoom the CSS viewport is taller; this rule activates only
   at 100% on shorter desktop browser windows. Avoid `zoom`, `scale` or
   browser-zoom manipulation: preserve semantics, breakpoints and readability. */
@media (min-width:1200px) and (max-height:920px) {
  :root { --wrap:min(91.3vw,1512px); --header-h:64px; }
  .logo-link img { width:130px; }
  .main-nav>a,.menu-btn { font-size:15px; }
  .header-actions { gap:12px; }
  .btn-outline,.btn-solid { font-size:14px; padding:9px 18px; }

  .hero-inner { padding:18px 0 18px;gap:22px; }
  .hero h1 { font-size:clamp(34px,3.4vw,59px); }
  .hero-sub { font-size:clamp(16px,1.08vw,20px); margin:7px 0 9px; }
  .hero-diff { font-size:clamp(16px,1.15vw,21px); margin-bottom:12px; }
  .hero-ctas { gap:14px; }
  .cta { font-size:16px; padding:13px 22px; }

  .section { padding:17px 0 5px; }
  .section-head { margin-bottom:6px; }
  .section-head h2 { font-size:26px; }
  .section-head p { font-size:15.5px; }
  .product-grid { gap:12px; }
  .pcard { padding:9px 18px;gap:14px; }
  .pcard-icon { width:45px; }
  .pcard h3 { font-size:22px; margin-bottom:3px; }
  .pcard-intro { font-size:15.5px; margin-bottom:4px; }
  .pcard-text { font-size:14.5px; line-height:1.35; margin-bottom:4px; }
  .pcard-link { font-size:14px; }
  .benefits { padding:4px 0 14px; }
  .benefit { gap:12px; padding-right:14px;padding-left:14px; }
  .benefit h3 { font-size:16px;margin-bottom:3px; }
  .benefit p { font-size:13.5px;line-height:1.36; }
}

/* ── Document components for content-rich pages ────────────────────────────
   Added for the methodology and reference pages. The .doc system was built for
   short legal pages — eyebrow, h1, headings, paragraphs, lists, callout — and a
   page that publishes how a figure is computed needs to put a definition beside
   its name without turning into prose. Everything here is scoped under .doc so
   the approved homepage cannot be touched by it. */

/* On-page contents. A reference page that cannot be skimmed does not get read,
   and a reader who arrives from a link about one measure should not have to
   scroll past six others to find it. */
.doc .toc{background:var(--pale-blue);border:1px solid #d7e6f6;border-radius:var(--r);
  padding:18px 22px;margin:0 0 34px}
.doc .toc p{font-family:var(--sans);font-size:12px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--navy);margin:0 0 10px}
.doc .toc ul{margin:0;padding:0;list-style:none;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(215px,1fr));gap:4px 26px}
.doc .toc li{margin:0}
.doc .toc a{font-size:15px;text-decoration:none;color:var(--xray-blue)}
.doc .toc a:hover{text-decoration:underline}

/* Definition tables. Measure in one column, what it actually computes in the
   other. Scrollable rather than squeezed on a phone: a formula that wraps to
   one word per line is unreadable, and shrinking the type to avoid it is worse. */
.doc .tw{overflow-x:auto;margin:0 0 20px;-webkit-overflow-scrolling:touch}
.doc table{border-collapse:collapse;width:100%;min-width:480px;font-size:15px}
.doc th,.doc td{text-align:left;vertical-align:top;padding:11px 14px;
  border-bottom:1px solid var(--line-soft);line-height:1.55}
.doc th{font-family:var(--sans);font-size:12px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--navy);border-bottom:1px solid var(--line);
  white-space:nowrap}
.doc td{color:var(--ink)}
.doc td:first-child{font-weight:600;color:var(--navy);white-space:nowrap}
.doc tbody tr:last-child td{border-bottom:0}
.doc table code,.doc p code,.doc li code{font-family:ui-monospace,SFMono-Regular,
  "SF Mono",Menlo,Consolas,monospace;font-size:.92em;background:#f3f6f9;
  border:1px solid #e3eaf0;border-radius:4px;padding:1px 5px;color:#23485f}

/* A figure stated plainly beside its label, for the handful of numbers worth
   pulling out of a paragraph. */
.doc .facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));
  gap:1px;background:var(--line-soft);border:1px solid var(--line-soft);
  border-radius:var(--r);overflow:hidden;margin:0 0 24px}
.doc .facts div{background:#fff;padding:15px 17px}
.doc .facts b{display:block;font-family:var(--serif);font-size:25px;font-weight:700;
  color:var(--navy);line-height:1.1;margin-bottom:4px}
.doc .facts span{font-size:13.5px;color:var(--ink-soft);line-height:1.4}

/* A short note under a figure or a heading, for the caveat that belongs with
   one specific claim rather than in a callout of its own. */
.doc .qual{font-size:14px;color:var(--ink-soft);line-height:1.55;margin:-8px 0 20px}

.doc h2{scroll-margin-top:calc(var(--header-h) + 18px)}

@media (max-width:767px){
  .doc .toc ul{grid-template-columns:1fr}
  .doc .facts{grid-template-columns:1fr 1fr}
  .doc .facts b{font-size:21px}
  /* On a phone a two-column definition table STACKS: the label on its own line
     above its definition. The first attempt simply let the columns wrap, which
     put the label in a ~55px column and, with overflow-wrap:anywhere, broke it
     one character per line — "Bolli nge r Ban ds". A measure and its definition
     read perfectly well stacked and terribly in a column that narrow, and a
     reader forced to drag a formula sideways mostly does not read it either. */
  .doc table{min-width:0;display:block}
  .doc thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .doc tbody,.doc tr,.doc td{display:block;width:auto}
  .doc tr{padding:12px 0;border-bottom:1px solid var(--line-soft)}
  .doc tbody tr:last-child{border-bottom:0}
  .doc td{border-bottom:0;padding:0}
  .doc td:first-child{white-space:normal;font-size:12px;font-weight:700;
    letter-spacing:.07em;text-transform:uppercase;color:var(--navy);margin-bottom:5px}
  .doc td+td{color:var(--ink)}
  /* Only the code spans may break mid-token; ordinary words must not. */
  .doc td{overflow-wrap:normal}
  .doc td code{overflow-wrap:anywhere}
}

/* ── Tap targets on touch screens ──────────────────────────────────────────
   Measured on the release candidate at 390px and 320px: footer links, the
   Resources contents links and the product-card CTAs all rendered 17-20px
   tall. Width was never the problem — height was, because they are plain
   inline links sitting on their own line, so the hit area is just the text
   box. WCAG 2.5.8 asks for 24px and platform guidance for 44px.

   Applied under (hover:none) so it reaches touch devices without loosening
   the desktop footer, where a mouse makes 19px perfectly clickable and the
   extra leading would open gaps the approved design does not have. */
@media (hover:none){
  .site-footer .footer-col a,
  .doc .toc a,
  .pcard-link,
  .about-page .about-how-col a,
  .about-page .about-product-card a{
    display:inline-flex;align-items:center;min-height:40px;
  }
  .doc .toc li{margin-bottom:0}
  .site-footer .footer-col a{min-height:38px}
}

/* ── Contact form ──────────────────────────────────────────────────────────
   The first form on this site. Styled from the existing tokens rather than a
   component library, so it inherits the same type, colour and focus treatment
   as everything else. Focus is the site's own :focus-visible ring, not a
   browser default, because this is the one place a keyboard user has real work
   to do. */
.cform{margin:0 0 30px}
.cf-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.cf-field{margin-bottom:16px;min-width:0}
.cf-field label{display:block;font-family:var(--sans);font-size:14px;font-weight:600;
  color:var(--navy);margin-bottom:6px}
.cf-req{color:#a6401f}
.cf-opt{font-weight:400;color:var(--ink-soft)}
.cform input,.cform select,.cform textarea{
  width:100%;font-family:var(--sans);font-size:16px;color:var(--ink);
  background:#fff;border:1px solid #c7d4de;border-radius:var(--r);
  padding:11px 13px;line-height:1.45;
  transition:border-color .14s ease,box-shadow .14s ease}
.cform textarea{resize:vertical;min-height:132px}
.cform input:hover,.cform select:hover,.cform textarea:hover{border-color:#9cb6c8}
.cform input:focus-visible,.cform select:focus-visible,.cform textarea:focus-visible{
  outline:none;border-color:var(--xray-blue);
  box-shadow:0 0 0 3px rgba(23,105,194,.18)}
.cform [aria-invalid="true"]{border-color:#c0502c}
.cform [aria-invalid="true"]:focus-visible{box-shadow:0 0 0 3px rgba(192,80,44,.18)}
.cf-err{font-size:13.5px;color:#a6401f;margin:6px 0 0}
.cf-actions{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-top:4px}
.cform .cta-primary{border:none;cursor:pointer;font-family:var(--sans)}
.cform .cta-primary[disabled]{opacity:.62;cursor:default}
.cf-status{font-size:14.5px;color:var(--ink-soft);margin:0;line-height:1.5}
.cf-status.is-error{color:#a6401f}

/* A successful send REPLACES the form instead of sitting above it. An empty
   form under a "Message sent" box reads as though nothing happened, and invites
   someone to send the same message twice.

   These two rules are the NO-JAVASCRIPT path. The endpoint answers a plain form
   post with a redirect to /contact?sent=1#sent, and that fragment makes :target
   match — so the confirmation appears and the form goes away with no script at
   all. Before this, a visitor with JavaScript off submitted successfully and was
   returned to a bare form with no acknowledgement of any kind. Author rules beat
   the user-agent [hidden] rule, so nothing here needs !important. */
#sent:target{display:block}
#sent:target~#cf-block{display:none}

/* The failure twin. Every non-success answer sends an unscripted visitor to
   #not-sent, and the form deliberately STAYS on screen underneath so they can
   try again. Before this existed, the 503 branch redirected to ?sent=0#sent —
   the SUCCESS anchor — so a visitor whose message was never sent was shown the
   green "Message sent" box and told it was in our inbox. */
#not-sent:target{display:block}

.cf-again{margin:14px 0 0}
.cf-again a{font-family:var(--sans);font-size:14.5px;font-weight:600}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   anything a stylesheet has hidden outright. */
.cf-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

@media (max-width:620px){
  .cf-row{grid-template-columns:1fr;gap:0}
  .cform .cta-primary{width:100%;justify-content:center}
  .cf-actions{gap:12px}
}
