/* ============================================================
   3 SIDED CUBE — COMPONENTS + ANIMATIONS  (cube-kit.css)
   Ported from 3sidedcube/3sc-website. Tokens come from cube-tokens.css;
   behaviour that needs a trigger is in cube-kit.js.

   Animation names, durations and keyframes are THEIRS, verbatim, so a port
   back is a rename:
     --animate-left-appear:   leftAppear 1.5s ease-in-out forwards
     --animate-right-appear:  rightAppear 1.5s ease-in-out forwards
     --animate-top-appear:    topAppear 1.5s ease-in-out forwards
     --animate-bottom-appear: bottomAppear 1.5s ease-in-out forwards
     --animate-fade-in:       fadeIn 0.5s ease-in-out forwards
     --animate-zoom-in-out:   zoomInOut 3.5s ease-in-out
   ============================================================ */

/* ---------- their keyframes, copied exactly ---------- */
@keyframes leftAppear   { 0% { transform: translateX(-100%); } 100% { transform: translateX(0); } }
@keyframes rightAppear  { 0% { transform: translateX(100%); }  100% { transform: translateX(0); } }
@keyframes topAppear    { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } }
@keyframes bottomAppear { 0% { transform: translateY(100%); }  100% { transform: translateY(0); } }
@keyframes fadeIn       { 0% { transform: scale(0); opacity: 0; } 50% { opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
@keyframes zoomInOut    { 0%, 100% { transform: scale(1,1); } 50% { transform: scale(1.2,1.2); } }
@keyframes highlightOvershoot { 0% { transform: scaleX(0); } 70% { transform: scaleX(1.1); } 100% { transform: scaleX(1); } }

/* ---------- AnimatedContainer: the effect set, triggered on entry ----------
   Theirs is a React wrapper using useInView({ once: true }) and swapping a
   Tailwind animate-* class in. Here: put the effect on the element and
   cube-kit.js adds .is-inview when it enters the viewport.
     <div class="cube-anim" data-anim="left_appear"> … </div>          */
.cube-anim[data-anim="left_appear"]   { transform: translateX(-100%); }
.cube-anim[data-anim="right_appear"]  { transform: translateX(100%); }
.cube-anim[data-anim="top_appear"]    { transform: translateY(-100%); }
.cube-anim[data-anim="bottom_appear"] { transform: translateY(100%); }
.cube-anim[data-anim="fade_in"]       { opacity: 0; }

.cube-anim[data-anim="left_appear"].is-inview   { animation: leftAppear   1.5s ease-in-out forwards; }
.cube-anim[data-anim="right_appear"].is-inview  { animation: rightAppear  1.5s ease-in-out forwards; }
.cube-anim[data-anim="top_appear"].is-inview    { animation: topAppear    1.5s ease-in-out forwards; }
.cube-anim[data-anim="bottom_appear"].is-inview { animation: bottomAppear 1.5s ease-in-out forwards; }
.cube-anim[data-anim="fade_in"].is-inview       { animation: fadeIn       0.5s ease-in-out forwards; }
/* pulse is the one effect they DON'T run once — it loops */
.cube-anim[data-anim="pulse"].is-inview         { animation: zoomInOut    3.5s ease-in-out infinite; }
/* scroll_zoom: theirs is a react-spring scale of 1 + scrollYProgress */
.cube-anim[data-anim="scroll_zoom"] { transform: scale(var(--cube-zoom, 1)); will-change: transform; }



/* ============================================================
   .cube-chip — their Chips element
   "shadow-strong w-fit bg-black p-2 text-white" + text-body-700
   (text-body-700 = Anonymous Pro, .875rem, weight normal. `shadow-strong`
   isn't defined in their stylesheets — Tailwind emits nothing for it — so
   this uses SHADOW.m, which is what it looks like on the page.)
   ============================================================ */
.cube-chip {
  display: inline-block; width: fit-content; padding: .5rem;
  background: var(--cube-black, #000); color: var(--cube-white, #fff);
  box-shadow: var(--cube-shadow-m, 4px 4px 0 0 rgba(0,0,0,1));
  font-family: var(--cube-font-secondary, monospace); font-size: .875rem; font-weight: 400;
}

/* ============================================================
   .cube-icon-card — their IconTextCard, minus Storyblok
   flex column, basis 1/2 -> 1/3, dashed grid borders, and on hover a 6px
   outline that turns primary-700 (#adec39) with a secondary-1100 card shadow
   ============================================================ */
.cube-icon-grid { display: flex; flex-wrap: wrap; }
.cube-icon-card {
  display: flex; flex-direction: column; justify-content: space-between; align-self: stretch;
  flex-basis: 50%; padding: .5rem;
  border-bottom: 1px dashed var(--cube-black, #000);
  color: inherit; text-decoration: none;
}
.cube-icon-card:nth-child(odd) { border-right: 1px dashed var(--cube-black, #000); }
.cube-icon-card:nth-child(-n+2) { border-top: 1px dashed var(--cube-black, #000); }
.cube-icon-card img, .cube-icon-card .ic { width: 60px; height: 60px; display: block; }
.cube-icon-card h4 {
  margin-top: .75rem; font-family: var(--cube-font-primary, sans-serif);
  font-weight: 700; text-transform: uppercase; letter-spacing: .025em;
}
.cube-icon-card p {
  margin-top: 1rem; font-family: var(--cube-font-secondary, monospace); font-size: .75rem;
}
@media (min-width: 640px) {
  .cube-icon-card { flex-basis: 33.333%; padding: 1rem; border: none !important; }
  a.cube-icon-card {
    outline: 6px solid var(--cube-white, #fff);
    transition: outline .3s ease-in-out, box-shadow .3s ease-in-out;
  }
  a.cube-icon-card:hover {
    outline-color: #adec39;                         /* primary-700 */
    box-shadow: 6px 6px 0 0 var(--cube-secondary-1100, #f4f4f4);
  }
}
a.cube-icon-card:hover h4 { text-decoration: underline; }
@media (min-width: 640px) { a.cube-icon-card:hover h4 { text-decoration: none; } }

/* ============================================================
   .cube-pinned-card — their PinnedCard, minus Storyblok
   8px secondary-950 border that turns primary-900 (#a0ff1f) on hover,
   a dotted 4px divider under the image, and the pin hanging off the corner
   ============================================================ */
.cube-pinned-card {
  position: relative; display: block; margin: 1rem auto 0; width: 70%; height: 100%;
  background: var(--cube-white, #fff); color: var(--cube-black, #000);
  border: 8px solid var(--cube-secondary-950, #e9e9e9);
  text-decoration: none; transition: border-color .3s ease;
}
.cube-pinned-card:hover { border-color: #a0ff1f; }       /* primary-900 */
.cube-pinned-card .pin {
  position: absolute; top: 0; left: 0; width: 60px; height: 40px;
  margin-top: -1.5rem; margin-left: -2.5rem;
}
.cube-pinned-card .shot {
  display: flex; align-items: center; justify-content: center; padding: 1rem;
  border-bottom: 4px dotted var(--cube-secondary-950, #e9e9e9);
}
.cube-pinned-card .shot img { width: 100%; height: 11.25rem; object-fit: cover; display: block; }
.cube-pinned-card .body { padding: 1rem; }
@media (min-width: 640px) { .cube-pinned-card { width: 100%; } }
@media (min-width: 1024px) { .cube-pinned-card .shot img { height: 15.625rem; } }

/* ============================================================
   .cube-process — their ProcessCard
   3sidedcube/3sc-website · src/components/extensive/(beta)/ProcessCard
   (ProcessCard.tsx + DesktopProcessCard.tsx + MobileCardsContatiner.tsx)

   Tabbed process steps: nav on the left, and a panel on the right whose image
   overlaps the top of the card. Their spec:

     grid        mx-auto max-w-5xl md:grid md:grid-cols-5, gap-8 / 2sm:gap-14
     nav         col-span-2 space-y-4
     tab         border-3 border-neutral-black shadow-m p-4 text-lg font-bold
                 flex justify-between; active takes tab.color / tab.textColor,
                 inactive is white on black
     tab motion  whileHover y:-2 shadow 5px · whileTap y:2 x:2 shadow 1px .1s
                 easeIn · spring stiffness 500 damping 30   (as PressButton)
     panel       col-span-3 relative
     image       absolute -top-16 left-1/2 -translate-x-1/2 w-100 z-50
                 rounded-xl object-cover  (lg: -top-18, w-85%)
     card        relative mt-30 rounded-lg border-3 border-neutral-gray-dark shadow-m
     head        px-12 pt-30 pb-6.5 rounded-t-md, tab colours, gap-2
                 h2 text-4.25xl font-bold · h3 text-2xl font-medium italic
     body        bg-white rounded-b-md border-t-4 border-black px-12 py-6
                 text-lg/8 font-medium

   Per-step colours come in as --tab-color / --tab-text, matching their
   TabContent { color, textColor }.
   ============================================================ */
.cube-process { display: grid; grid-template-columns: 1fr; gap: 2rem; max-width: 64rem; margin: 0 auto; }
@media (min-width: 768px) {
  .cube-process { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 3.5rem; }
  .cube-process-nav { grid-column: span 2 / span 2; }
  .cube-process-panel { grid-column: span 3 / span 3; }
}

/* ---- left: the title and the tabs ---- */
.cube-process-nav { display: flex; flex-direction: column; gap: 1rem; }
/* their structure: a relative flex row holding a 70%-wide title, with the
   sticker absolutely placed BESIDE it — not inside the paragraph, which is
   what made it land on the words */
.cube-process-titlerow { position: relative; display: flex; align-items: center; margin-bottom: 3rem; }
.cube-process-title {
  font-family: var(--cube-font-primary, sans-serif); font-weight: 700;
  font-size: 1.75rem; line-height: 2.125rem;          /* text-2.5xl/8.5 */
}
@media (min-width: 768px) { .cube-process-title { max-width: 70%; } }
.cube-process-titleicon {
  /* theirs: absolute -top-20 left-46 w-40. Anchored right here so it stays
     the row. Theirs is literally -top-20 left-46 w-40, which only clears the
     text because their title is two short lines; anchored right it stays clear
     at any copy length. */
  position: absolute; top: -2.5rem; left: auto; right: -2rem; width: 7rem; height: auto;
  pointer-events: none;
}
@media (max-width: 480px) { .cube-process-titleicon { display: none; } }

.cube-process-tab {
  display: flex; width: 100%; align-items: center; justify-content: space-between;
  gap: .75rem; padding: 1rem; text-align: left; cursor: pointer;
  font-family: var(--cube-font-primary, sans-serif); font-size: 1.125rem; font-weight: 700;
  border: var(--cube-border-m, 3px) solid var(--cube-black, #000);
  border-radius: 0; background: var(--cube-white, #fff); color: var(--cube-black, #000);
  box-shadow: var(--cube-shadow-m, 4px 4px 0 0 rgba(0,0,0,1));
  transition: all .1s cubic-bezier(.175, .885, .32, 1.275);
}
.cube-process-tab:hover { transform: translateY(-2px); box-shadow: var(--cube-lift-shadow, 5px 5px 0 0 rgba(0,0,0,1)); }
.cube-process-tab:active { transform: translate(2px, 2px); box-shadow: var(--cube-press-shadow, 1px 1px 0 0 rgba(0,0,0,1)); }
/* the selected tab's colours are set inline by the driver, per their
   DesktopProcessCard — no rule needed here beyond the resting white */
.cube-process-tab .lead { display: flex; align-items: center; pointer-events: none; }
.cube-process-tab .ic { margin-right: .8125rem; font-size: 1.625rem; line-height: 1; }
.cube-process-tab .arw {
  /* lucide ArrowRight, as theirs uses: <ArrowRight className="size-10" />.
     Thin stroked, NOT the solid brand arrow — that one is for buttons. */
  flex: none; width: 2.5rem; height: 2.5rem; color: currentColor;
}

/* ---- right: the overlapping image and the card ---- */
.cube-process-panel { position: relative; }
.cube-process-shot {
  position: absolute; top: -4rem; left: 50%; transform: translateX(-50%);
  width: 25rem; max-width: 100%; z-index: 50;
}
.cube-process-shot img { width: 100%; height: auto; object-fit: cover; border-radius: .75rem; display: block; }
/* .framed reproduces the white-mount + keyline + shadow that their step images
   carry as part of the asset itself */
.cube-process-shot.framed {
  background: var(--cube-white, #fff); padding: .75rem;
  border: var(--cube-border-m, 3px) solid var(--cube-black, #000);
  border-radius: .75rem; box-shadow: var(--cube-shadow-l, 6px 6px 0 0 rgba(0,0,0,1));
}
.cube-process-shot.framed img { border-radius: .375rem; }
@media (min-width: 1024px) { .cube-process-shot { top: -4.5rem; width: 85%; } }

.cube-process-card {
  position: relative; margin-top: 7.5rem; border-radius: .5rem;
  border: var(--cube-border-m, 3px) solid var(--cube-ink, #0a0a0a);
  box-shadow: var(--cube-shadow-m, 4px 4px 0 0 rgba(0,0,0,1));
  overflow: hidden;
}
.cube-process-head {
  display: flex; flex-direction: column; gap: .5rem; border-radius: 6px 6px 0 0;
  padding: 7.5rem 3rem 1.625rem;
  background: var(--tab-color, var(--cube-green, #00ed71));
  color: var(--tab-text, var(--cube-black, #000));
}
@media (min-width: 1024px) { .cube-process-head { padding-top: 11.75rem; } }
.cube-process-head h3 { font-size: 2.5rem; font-weight: 700; line-height: 1.1; letter-spacing: -.02em; }
.cube-process-head h4 { font-size: 1.5rem; font-weight: 500; font-style: italic; }
.cube-process-body {
  background: var(--cube-white, #fff); border-top: 4px solid var(--cube-black, #000);
  padding: 1.5rem 3rem; font-size: 1.125rem; line-height: 2rem; font-weight: 500;
  color: var(--cube-ink, #0a0a0a);
}

/* ---- their entrance: nav from the left, panel from the right ---- */
.cube-process-nav, .cube-process-panel { opacity: 0; }
.cube-process-nav { transform: translateX(-20px); }
.cube-process-panel { transform: translateX(20px); }
.cube-process.is-inview .cube-process-nav {
  opacity: 1; transform: none; transition: opacity .4s ease .2s, transform .4s ease .2s;
}
.cube-process.is-inview .cube-process-panel {
  opacity: 1; transform: none;
  /* their spring: stiffness 300, damping 15 — underdamped, so it overshoots */
  transition: opacity .25s ease .2s, transform .45s cubic-bezier(.34,1.4,.64,1) .2s;
}
/* On tab change theirs re-mounts the WHOLE right panel (key={activeTab}) — image
   AND card — replaying its enter (panel slides in from the RIGHT carrying the
   illustration; inner card fades up). That replay is driven explicitly by the Web
   Animations API in cube-kit.js (the CSS class-swap was unreliable for the delayed
   panel transition, so only the card moved). */

/* ---- mobile: theirs is a horizontal card rail; this stacks ---- */
@media (max-width: 767px) {
  .cube-process-shot { position: static; transform: none; width: 100%; }
  .cube-process-card { margin-top: 1rem; }
  .cube-process-head { padding: 1.5rem; }
  .cube-process-body { padding: 1.25rem 1.5rem; }
  .cube-process-nav, .cube-process-panel { opacity: 1; transform: none; }
}


/* ============================================================
   .cube-process mobile — MobileCardsContatiner + MobileCard + handleMobileCardsScroll
   Read from the source rather than approximated. What it actually does:

     card      sticky at index*10+120, w-full, rounded, border-3
               border-neutral-950, shadow-[3px_3px_0px_#000], and it ONLY
               scales — transform: scale(cardScale), origin top center,
               transition-transform duration-300 ease-out. THE CARD IS NOT
               ROTATED.
     order     the coloured HEAD comes first (px-3.75 pt-4.75 pb-23.5, big
               bottom padding), then the image is pulled UP over it (-mt-24),
               then the description.
     image     mx-auto w-81.25 px-5 py-4.25, and THIS is what rotates:
               transform: rotate(imageRotation), duration-300 ease-out.
     overlay   absolute inset-0 z-50, tinted with the card's OWN background
               colour via hexToRgba(backgroundColor, overlayOpacity) — not black.
     heights   every card is padded to the tallest card's scrollHeight.
   ============================================================ */
.cube-process-mobile { display: none; }

@media (max-width: 767px) {
  /* ProcessCard.tsx renders DesktopProcessCard as `hidden md:flex` and
     MobileCardsContatiner as `md:hidden` — the whole desktop side goes, title
     row included. Hiding only the tabs left its title on screen above the
     deck's own title, i.e. the headline twice. */
  .cube-process-nav, .cube-process-panel { display: none; }
  /* Tailwind's preflight gives them border-box everywhere; without it the
     deck's 1rem padding sits outside its 100% width. min-width:0 matters too:
     the image is a fixed w-81.25 (325px), which sets a 363px min-content floor
     once borders and padding are counted. That fits a 375px phone — theirs is
     cut to exactly that — but it forced the grid track wider than the column
     when the component sits in a narrower container, like the catalog's wrap.
     Letting the boxes shrink keeps the image's max-width:100% in charge. */
  .cube-process-mobile, .cube-process-mobile * { box-sizing: border-box; }
  .cube-process-mobile, .cube-process-mstack,
  .cube-process-msticky, .cube-process-mcard { min-width: 0; }
  .cube-process-mobile {
    display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
    width: 100%; max-width: 56rem; padding: 1rem;
    min-height: var(--deck-min-height, auto);
  }
  .cube-process-mstack { display: flex; flex-direction: column; gap: 2.5rem; width: 100%; }  /* space-y-10 */

  /* THE PILE. MobileCardsContatiner wraps every card in its own sticky div —
     top: index*10 + 160, z-index: 10 + index — so each card parks 10px lower
     than the one before and the later cards ride over it. The CARD inside is
     position:relative (that's what you measure if you inspect the card), which
     is why reading the card alone tells you nothing about the stacking. The
     driver writes top/z-index inline from the card's index. */
  .cube-process-msticky { position: sticky; width: 100%; }

  .cube-process-mcard {
    position: relative; top: auto;
    width: 100%; overflow: hidden; background: var(--cube-white, #fff);
    border: var(--cube-border-m, 3px) solid #0a0a0a;      /* border-neutral-950 */
    border-radius: 10px;                                   /* rounded-2lg — 10px, measured */
    box-shadow: 3px 3px 0 0 #000;                          /* shadow-[3px_3px_0px_#000000] */
    min-height: var(--card-min, auto);
    /* the card only ever SCALES — no rotation on the card itself */
    transform: scale(var(--scale, 1));
    transform-origin: top center;
    transition: transform .3s cubic-bezier(0, 0, .2, 1);   /* their ease-out */
  }
  /* head first, with room beneath for the image to sit over */
  .cube-process-mcard .head {
    display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: .25rem;
    padding: 1.1875rem .9375rem 5.875rem;                  /* pt-4.75 px-3.75 pb-23.5 */
    border-bottom: 3px solid #000;
    border-radius: 6px 6px 0 0;                            /* rounded-t-md */
    background: var(--tab-color, var(--cube-green, #00ed71));
    color: var(--tab-text, var(--cube-black, #000));
  }
  .cube-process-mcard .head h3 {
    width: 100%; text-align: center; font-weight: 700; font-size: 1.125rem; line-height: 1.4375rem;
    font-family: var(--cube-font-primary, sans-serif); color: inherit;
  }
  .cube-process-mcard .head h4 {
    width: 100%; text-align: center; font-weight: 500; font-size: .875rem; font-style: italic;
    letter-spacing: -.24px; font-family: var(--cube-font-primary, sans-serif); color: inherit;
  }
  /* the image, pulled up over the head */
  .cube-process-mcard .shotwrap {
    margin-top: -6rem;                                     /* -mt-24 */
    display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  }
  /* NO ROTATION HERE, on purpose. MobileCard animates the image wrapper with
     transform: rotate(imageRotation) — but our step visuals draw their own
     tilt into the artwork (03-just-ask.svg carries -10deg and -4deg inside the
     SVG), the same reason their desktop image is transform:none. Rotating on
     top double-tilts it. The driver still computes --imgrot from their maths,
     so untilted artwork can have it back with one line:
       transform: rotate(var(--imgrot, 0deg));
       transition: transform .3s cubic-bezier(0, 0, .2, 1); */
  .cube-process-mcard .shot {
    margin: 0 auto; width: 20.3125rem; max-width: 100%;    /* w-81.25 */
    padding: 1.0625rem 1.25rem;                            /* py-4.25 px-5 */
  }
  .cube-process-mcard .shot img { width: 100%; height: auto; display: block; }
  .cube-process-mcard .body {
    padding: 0 1rem 1.75rem; text-align: center;
    font-family: var(--cube-font-primary, sans-serif);
    font-weight: 400; font-size: .875rem; line-height: 1.5rem; color: #0a0a0a;
  }
  /* the veil is the card's own colour, not black */
  .cube-process-mcard .veil {
    position: absolute; inset: 0; z-index: 50; pointer-events: none;
    background: var(--tab-color, var(--cube-green, #00ed71));
    opacity: var(--veil, 0); transition: opacity .3s cubic-bezier(0, 0, .2, 1);
  }
  /* their title sits INSIDE the space-y-10 div alongside the card wrappers
     (sticky top-0 z-5), so it releases when the stack ends rather than
     outlasting it. The stack's 2.5rem gap does the spacing — no margin here. */
  /* MobileCardsContatiner's title row: sticky top-0 z-5, flex items-center
     gap-4, pt-10 pb-0 — the h2 with its yellow highlight plus the sticker */
  .cube-process-mtitlerow {
    position: sticky; top: 0; z-index: 5; width: 100%;
    display: flex; align-items: center; gap: 1rem;
    padding: 2.5rem 0 0;
  }
  /* Their icon is h-30 w-30 (120px) pulled in over the title:
     -translate-x-10 -translate-y-6 under 420px, -x-18 -y-1.75 above it. Those
     exact values are kept from 420px up, where the row has the width their
     design assumes. Below that the pull is dropped and the sticker shrinks —
     at a 303px column (the catalog's wrap is tighter than a 375px phone) a
     40px pull puts the artwork straight over the words. Their own layout
     already treats 420px as the seam where this row gets tight. */
  .cube-process-mtitleicon {
    flex-shrink: 0; width: 6rem; height: auto;
    transform: translateY(-1rem);
    pointer-events: none;
  }
  @media (min-width: 420px) {
    .cube-process-mtitleicon {
      width: 7.5rem;                                       /* h-30 w-30 */
      transform: translate(-4.5rem, -.4375rem);            /* -x-18 -y-1.75 */
    }
  }
  .cube-process-mtitle {
    max-width: 15rem;                                      /* max-w-60 — this is
      what keeps the sticker off the words: the h2 stops short and the icon
      takes the space beside it, rather than the row squeezing it over the text */
    font-family: var(--cube-font-primary, sans-serif); font-weight: 700;
    font-size: 1.625rem; line-height: 1.2; text-align: left; max-width: 15rem;
  }
}


/* ---- their Section variant="gray-dot": the 40px dotted ground ---- */
.cube-section-graydot {
  background-color: var(--cube-gray-200, #f8f8f8);
  background-image:
    radial-gradient(rgba(0,0,0,.10) 1.5px, transparent 0),
    radial-gradient(rgba(0,0,0,.10) 1.5px, transparent 0);
  background-position: 0 0, 20px 20px;
  background-size: 40px 40px, 40px 40px;
}

/* ============================================================
   .cube-annotation — their Annotation
   ProcessCard/Annotation.tsx: a hand-drawn curved arrow and a scribbled note,
   sitting off the bottom-left of the panel.
     wrapper  absolute md:-bottom-4 md:-left-14   (max-md: -bottom-45 left-1/2)
     inner    font-secondary z-50 flex rotate-6 flex-col gap-6 uppercase italic
     arrow    size-20 (5rem), max-md:-rotate-60
     note     text-sm/5.5 font-bold uppercase italic, md:-mt-18 md:-ml-70,
              max-w-65, rotated -12deg
     entrance spring damping 64, mass 4.9, stiffness 703, delay .8
   The arrow is their asset, assets/arrows/arrow-left-down.svg.
   ============================================================ */
.cube-annotation { position: absolute; z-index: 50; left: 50%; bottom: -11.25rem; }
@media (min-width: 768px) { .cube-annotation { left: -3.5rem; bottom: -1rem; } }
.cube-annotation-in {
  position: relative; display: flex; flex-direction: column; gap: 1.5rem;
  transform: rotate(6deg);
  font-family: var(--cube-font-secondary, monospace); font-weight: 700;
  text-transform: uppercase; font-style: italic; text-align: center;
}
.cube-annotation-arrow {
  width: 5rem; height: auto; display: block;
  opacity: 0; transform: scale(0) rotate(0deg); transform-origin: 80% 0%;
}
@media (max-width: 767px) { .cube-annotation-arrow { transform: scale(0) rotate(-60deg); } }
.cube-annotation-note {
  max-width: 16.25rem; margin-left: -10rem;
  font-size: .875rem; line-height: 1.375rem; font-weight: 700;
  text-transform: uppercase; font-style: italic; text-align: center;
  opacity: 0; transform: scale(0) rotate(0deg); transform-origin: 80% 0%;
}
@media (min-width: 768px) { .cube-annotation-note { margin-top: -4.5rem; margin-left: -17.5rem; } }
/* their spring is heavily damped (damping 64, mass 4.9, stiffness 703) so it
   settles hard rather than wobbling — and it waits .8s, like the highlight */
.cube-process.is-inview .cube-annotation-arrow {
  opacity: 1; transform: scale(1) rotate(6deg);
  transition: opacity .3s ease .8s, transform .5s cubic-bezier(.2,.9,.25,1) .8s;
}
.cube-process.is-inview .cube-annotation-note {
  opacity: 1; transform: scale(1) rotate(-12deg);
  transition: opacity .3s ease .95s, transform .5s cubic-bezier(.2,.9,.25,1) .95s;
}

