/* ==================================================================
   contact-reveal.css — the centre-opening reveal, and the Contact us
   section built on it.

   One component, two polarities, meant to be dropped on any page.
   Nothing here depends on realestate.css or sme.css, and every colour
   is a literal rather than a token, so the section looks the same on
   security.html, our-story.html and blog.html as it does on the two
   audience pages.

   ------------------------------------------------------------------
   THE DEVICE

   Two opaque plates meet on the section's horizontal centre line.
   Scrolling parts them: the upper edge travels up, the lower edge
   travels down, and the contrasting panel underneath is uncovered
   from the middle outwards. Each plate leads with a hairline, so
   what the eye follows is two edges separating. The plates are the
   page's own ground colour, so what appears to split is the page
   itself - on the foot reveal, that reads as the footer and the
   content just above it parting to let the panel through.

   2026-08-10: the resting state reverses. It used to be open by
   default everywhere, with closing being "the first frame of an
   animation already running" once a section scrolled into view. The
   client wants the opposite: closed by default - a slit, plates
   touching, only the hairline showing - everywhere the mechanism can
   actually reopen it, and open by default everywhere it cannot, so a
   visitor is never left looking at a sealed section with no way in.

   That "everywhere it can" split is real and is gated at source,
   below, rather than picked once for the whole component:

     - a browser with native scroll-driven animation support drives
       the CSS timeline directly. Closed is now the base transform
       for .crack-mid/.crack-foot shutters, inside the very same
       @supports (animation-timeline:view()) block that already
       gated the animation itself, so the override only exists where
       the animation that can undo it also exists.
     - a browser without that support falls back to JavaScript: the
       frame loop in contact-reveal.js writes --p, and it has always
       initialised every [data-crack] node to --p:0 (closed) before
       it starts observing, so this path already opened correctly
       and needed no change.
     - no scroll-driven animation support AND no JavaScript running
       (or prefers-reduced-motion:reduce) means neither driver
       exists, so neither override applies, and the plain declarative
       .crack-shutter rule further down - unconditional, ungated,
       OPEN - is what paints. That is the floor: nothing above it can
       make a section closed without also being able to open it.

   Only transforms move, so the device is composited. The page is
   never pinned and no scroll handler ever writes layout.

   ------------------------------------------------------------------
   THE MARKUP, canonical. Copy this block, set data-world to the
   world the PAGE is in ("dark" or "light"), and the panel takes the
   opposite. contact-reveal.js needs no configuration.

   <section class="crack crack-foot contact-reveal" data-world="dark"
            id="contact" data-crack="foot" aria-label="Talk to us">
     <div class="crack-cue" aria-hidden="true"></div>
     <div class="crack-panel">
       <div class="wrap crack-in cr-cta">
         <p class="cr-cta-line">...</p>
         <a class="btn cr-cta-btn"
            href="https://calendly.com/jeremy-terceroanalytics/tercero-free-consultation"
            target="_blank" rel="noopener">Book a call with a founder</a>
       </div>
     </div>
     <div class="crack-shutter crack-shutter-t" aria-hidden="true"></div>
     <div class="crack-shutter crack-shutter-b" aria-hidden="true"></div>
   </section>

   2026-08-10: this replaced a three-option layout (a calendar link, a
   short-details link, a plain email link) with a single line and a
   single button, on client instruction - "no three options, no
   cards, no eyebrow stack, no lead paragraph." The button is a plain
   <a class="btn"> to the same Calendly link the nav's own "Book a
   call with a founder" button carries, not a bespoke trigger: app.js
   already opens the booking modal for anything that looks like that
   link (event delegation on href containing "calendly.com", or on
   .btn text containing a booking phrase - this text does, twice
   over). That is what "the same pop-up" and "one booking path" mean
   here - nothing in this file or contact-reveal.js decides how the
   modal opens, so there is no second implementation to drift out of
   sync with the first. It is also a real link before it is a
   trigger: with app.js absent the button still goes to Calendly.
   ================================================================== */

/* ==================================================================
   1. the device
   ================================================================== */

.crack{
  position:relative;
  overflow:hidden;
  isolation:isolate;
}

/* A zero-height marker pinned to the section's top edge. It is what
   the scroll-driven timeline measures, which is what makes the
   trigger "the top of this section reaches the middle of the screen"
   rather than something that drifts with the section's own height. */
.crack-cue{
  position:absolute;
  left:0; top:0;
  width:100%; height:0;
  pointer-events:none;
}

.crack-panel{
  position:relative;
  z-index:0;
}

.crack-in{
  padding-top:clamp(3.6rem,8vh,6.4rem);
  padding-bottom:clamp(3.6rem,8vh,6.4rem);
}

.crack-shutter{
  position:absolute;
  left:0; right:0;
  height:50.6%;
  z-index:1;
  pointer-events:none;
  background:var(--crack-plate,#0b151d);
  will-change:transform;
}
/* Declarative, ungated floor: OPEN. This is what paints when neither
   driver below is running - the safe state, never a dead end. */
.crack-shutter-t{top:0;    transform:translateY(-100%);}
.crack-shutter-b{bottom:0; transform:translateY(100%);}

/* the hairline each plate leads with */
.crack-shutter::after{
  content:"";
  position:absolute;
  left:0; right:0;
  height:2px;
  background:var(--crack-edge,#00b894);
  opacity:0;
}
.crack-shutter-t::after{bottom:0;}
.crack-shutter-b::after{top:0;}

@keyframes crackUp  {from{transform:translateY(0)} to{transform:translateY(-100%)}}
@keyframes crackDown{from{transform:translateY(0)} to{transform:translateY(100%)}}
@keyframes crackEdge{from{opacity:1} 62%{opacity:1} to{opacity:0}}

/* ---- driven by the scroll itself, where the browser can ---------- */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){

    /* The cue carries the timeline and the plates read it. A plate is
       a following sibling of the cue, which reads as though it ought
       to be enough, and it is not: a name declared on a sibling does
       not resolve, the animation comes out with a null timeline, and
       a null timeline means the plates sit at their base style, which
       is open. The reveal would simply never run and nothing would
       look broken enough to notice. timeline-scope hoists the name to
       the section, and from there every descendant can see it. */
    .crack-mid { timeline-scope:--crack-mid; }
    .crack-foot{ timeline-scope:--crack-foot; }

    .crack-mid  > .crack-cue{view-timeline:--crack-mid block;}
    .crack-foot > .crack-cue{view-timeline:--crack-foot block;}

    /* Closed by default, gated. A view-timeline has no current time
       at all until its subject starts entering the scrollport, and
       an animation on an inactive timeline has no effect - the
       transform falls through to whatever the plain cascade says,
       which is the declarative OPEN rule above. Below, "closed" wins
       that fallback instead, for these two sections only, only in
       this @supports + @media pocket: it is more specific than the
       bare .crack-shutter-* rule, it does not touch the rule itself,
       and once the timeline does go active the animation (next)
       takes over the property completely regardless of specificity,
       so this only ever governs the "not scrolled to yet" moment. */
    .crack-mid .crack-shutter-t,
    .crack-foot .crack-shutter-t{transform:translateY(0);}
    .crack-mid .crack-shutter-b,
    .crack-foot .crack-shutter-b{transform:translateY(0);}
    .crack-mid .crack-shutter::after,
    .crack-foot .crack-shutter::after{opacity:1;}

    .crack-mid .crack-shutter-t{animation:crackUp   linear both; animation-timeline:--crack-mid;}
    .crack-mid .crack-shutter-b{animation:crackDown linear both; animation-timeline:--crack-mid;}
    .crack-mid .crack-shutter::after{animation:crackEdge linear both; animation-timeline:--crack-mid;}
    .crack-mid .crack-shutter,
    .crack-mid .crack-shutter::after{animation-range:cover 50% cover 80%;}

    /* The foot reveal opens earlier and over a shorter run, because
       the page runs out underneath it: that section can only climb as
       far as its own height plus the footer's allows. */
    .crack-foot .crack-shutter-t{animation:crackUp   linear both; animation-timeline:--crack-foot;}
    .crack-foot .crack-shutter-b{animation:crackDown linear both; animation-timeline:--crack-foot;}
    .crack-foot .crack-shutter::after{animation:crackEdge linear both; animation-timeline:--crack-foot;}
    .crack-foot .crack-shutter,
    .crack-foot .crack-shutter::after{animation-range:cover 26% cover 58%;}
  }
}

/* ---- driven from a frame loop, where it cannot ------------------
   contact-reveal.js sets .crack-js on the root only once it has
   decided to take over, and only in a browser with no scroll-driven
   animation. It initialises every [data-crack] node's --p to 0
   (closed) before it starts observing, so this path already rests
   closed wherever it is the one driving, with no separate override
   needed here - the calc below reads that same --p. */
html.crack-js .crack-shutter-t     {transform:translateY(calc(var(--p,1) * -100%));}
html.crack-js .crack-shutter-b     {transform:translateY(calc(var(--p,1) * 100%));}
html.crack-js .crack-shutter::after{opacity:calc(1 - var(--p,1));}

/* Reduced motion: open, still, and never touched by either driver. */
@media (prefers-reduced-motion:reduce){
  .crack-shutter{display:none;}
}

/* A short viewport has very little travel between the trigger and the
   top of the screen, so full plates would snap rather than part. Give
   them a shallower reveal: the panel arrives already mostly uncovered
   and only the last of the movement is animated. */
@media (max-height:620px){
  .crack-shutter{height:24%;}
}

/* ==================================================================
   2. the two polarities

   data-world names the world the PAGE is in. --crack-plate is the
   colour of the closed slit and the parting plates, and it stays
   the page's own ground so the section reads as the page itself
   before and while it opens. --crack-edge is fixed brand cyan on
   both worlds now, because that is the colour of what the slit is
   about to open onto.
   ================================================================== */

.contact-reveal[data-world="dark"]{
  --crack-plate:#0b151d;      /* the dark field's ground */
  --crack-edge:#00b894;
}

.contact-reveal[data-world="light"]{
  --crack-plate:#f7fafb;      /* the light field's ground */
  --crack-edge:#00b894;
}

/* ==================================================================
   3. the panel

   Solid brand cyan, both worlds - not paper, not deep, no gradient.
   Text sits directly on it, so the colour choice below is measured,
   not assumed: ink (#0d1b24) on #00b894 is 6.9:1, snow (#eef3f6) on
   the same cyan is 2.3:1. Dark ink is not a style preference, it is
   the only one of the two that clears 4.5:1.
   ================================================================== */

.contact-reveal .crack-panel{
  background:#00b894;
  color:#0d1b24;
  min-height:38vh;
  display:flex;
  align-items:center;
}
.contact-reveal .crack-in{width:100%;}

@media (max-height:620px){
  .contact-reveal .crack-panel{min-height:0;}
}

/* ==================================================================
   4. the line and the button

   One sentence, one call to action. Centred, because there is no
   longer a grid of options either side of it to align against.
   ================================================================== */

.cr-cta{
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
  gap:clamp(1.6rem,3.4vw,2.2rem);
}

.cr-cta-line{
  font-family:var(--exp,'Archivo Expanded','Archivo',sans-serif);
  font-weight:800;
  font-size:clamp(1.5rem,1.15rem + 1.6vw,2.3rem);
  letter-spacing:-.02em;
  line-height:1.15;
  margin:0;
  color:#0d1b24;
}

/* The button has to read as a distinct control on a cyan ground, not
   a shape in the same colour as the ground it sits on. It takes the
   page's own dark ink as a solid fill, the inverse of the panel, so
   the two colours trade places rather than one disappearing into the
   other. White text on that fill is 15.7:1. */
.cr-cta-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  font-weight:700;
  font-size:1rem;
  padding:.95em 1.9em;
  border-radius:8px;
  background:#0d1b24;
  color:#eef3f6;
  text-decoration:none;
  box-shadow:0 6px 20px rgba(0,0,0,.22);
  transition:transform .3s var(--e-out,cubic-bezier(.16,1,.3,1)),
             box-shadow .3s var(--e-out,cubic-bezier(.16,1,.3,1));
}
.cr-cta-btn:hover,
.cr-cta-btn:focus-visible{
  transform:translateY(-2px);
  box-shadow:0 10px 26px rgba(0,0,0,.28);
}
.cr-cta-btn:focus-visible{
  outline:2px solid #0d1b24;
  outline-offset:3px;
}

@media (prefers-reduced-motion:reduce){
  .cr-cta-btn{transition:none;}
  .cr-cta-btn:hover,
  .cr-cta-btn:focus-visible{transform:none;}
}
