/* ============================================================
   NASTASI FOODS — shapes.css
   ------------------------------------------------------------
   Nothing on this site is a plain rectangle with a flat border.
   Everything is a piece of paper from an import house: a hand-cut
   note, a postage stamp, a torn-off ticket, a rubber stamp.

   Four primitives, all pure CSS/SVG, all size-independent:

     .cut--deckle   hand-torn edge (SVG turbulence on the fill
                    only, so the text on top stays razor sharp)
     .cut--stamp    postage-stamp perforation on all four sides
     .cut--tab      filing-tab card: one clipped corner + notch
     .btn--ticket   slanted ticket button with notched ends

   The deckle filter lives once in index.html as an inline
   <svg><filter id="nf-deckle">. Without it the fill simply
   renders as a soft rectangle — nothing breaks.
   ============================================================ */

/* ---------- 1. notched ticket ----------
   A rectangle with all four corners bitten out by an inward arc —
   the shape a ticket punch leaves. Two stacked masked layers give
   an outline that follows the notches exactly, which a border
   never could. */
.cut--notch {
  --notch-r: 20px;
  --cut-bg: var(--bone-pure);
  --cut-edge: var(--ink);
  --cut-edge-w: 3px;
  position: relative; background: none; border: 0; border-radius: 0;
}
.cut--notch::before,
.cut--notch::after {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  -webkit-mask:
    radial-gradient(var(--notch-r) at 0 0,        #0000 98%, #000) 0 0 / 100% 100% no-repeat,
    radial-gradient(var(--notch-r) at 100% 0,     #0000 98%, #000) 0 0 / 100% 100% no-repeat,
    radial-gradient(var(--notch-r) at 0 100%,     #0000 98%, #000) 0 0 / 100% 100% no-repeat,
    radial-gradient(var(--notch-r) at 100% 100%,  #0000 98%, #000) 0 0 / 100% 100% no-repeat;
  mask:
    radial-gradient(var(--notch-r) at 0 0,        #0000 98%, #000) 0 0 / 100% 100% no-repeat,
    radial-gradient(var(--notch-r) at 100% 0,     #0000 98%, #000) 0 0 / 100% 100% no-repeat,
    radial-gradient(var(--notch-r) at 0 100%,     #0000 98%, #000) 0 0 / 100% 100% no-repeat,
    radial-gradient(var(--notch-r) at 100% 100%,  #0000 98%, #000) 0 0 / 100% 100% no-repeat;
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}
/* the outline, one step larger so it reads as a drawn edge */
.cut--notch::before {
  inset: calc(var(--cut-edge-w) * -1);
  background: var(--cut-edge);
  --notch-r: calc(20px + var(--cut-edge-w));
}
/* the fill */
.cut--notch::after { inset: 0; background: var(--cut-bg); }
.cut--notch > * { position: relative; z-index: 1; }

/* a second hairline set in from the edge — the printed rule on a real ticket */
.cut--notch.has-rule > .rule-in {
  position: absolute; inset: 9px; z-index: 0; pointer-events: none;
  border: 1.5px solid color-mix(in srgb, var(--cut-edge) 42%, transparent);
  border-radius: 12px;
}

/* ---------- 1b. hand-torn note (used sparingly) ---------- */
.cut--deckle {
  --cut-bg: var(--bone-pure);
  position: relative; background: none; border: 0; border-radius: 0;
  filter: drop-shadow(0 18px 40px rgba(22, 20, 15, .22));
}
.cut--deckle::before {
  content: ""; position: absolute; inset: -5px; z-index: 0;
  background: var(--cut-bg);
  border-radius: 16px;
  filter: url(#nf-deckle);
}
.cut--deckle > * { position: relative; z-index: 1; }

/* ---------- 2. postage stamp ---------- */
.cut--stamp {
  --perf: 7px;                 /* perforation radius */
  --gap: 20px;                 /* distance between perforations */
  background: var(--bone-pure);
  border: 0; border-radius: 0;
  padding: calc(var(--perf) + 5px);
  /* Four rows of bites, intersected so every side is perforated.
     Each tile spans the FULL opposite dimension (var(--gap) 100% for the
     horizontal rows, 100% var(--gap) for the vertical ones). A tile sized
     gap × gap would leave the rest of that layer transparent, and
     intersecting against transparent erases the whole element.
     Only the standard `mask-composite` is set: the legacy
     `-webkit-mask-composite` keyword set means something different, and
     when both are declared Chrome takes the legacy one. */
  mask:
    radial-gradient(var(--perf) at 50% 0,    #0000 98%, #000) 50% 0    / var(--gap) 100% repeat-x,
    radial-gradient(var(--perf) at 50% 100%, #0000 98%, #000) 50% 100% / var(--gap) 100% repeat-x,
    radial-gradient(var(--perf) at 0 50%,    #0000 98%, #000) 0 50%    / 100% var(--gap) repeat-y,
    radial-gradient(var(--perf) at 100% 50%, #0000 98%, #000) 100% 50% / 100% var(--gap) repeat-y;
  mask-composite: intersect;
}
/* a mask clips box-shadow, so lift comes from a filter that follows the cut */
.stamp-shadow { filter: drop-shadow(0 16px 34px rgba(22, 20, 15, .3)); }

/* ---------- 3. filing tab ---------- */
.cut--tab {
  --notch: 22px;
  border: 0; border-radius: 0;
  clip-path: polygon(
    0 0,
    calc(100% - var(--notch)) 0,
    100% var(--notch),
    100% 100%,
    var(--notch) 100%,
    0 calc(100% - var(--notch))
  );
}

/* ---------- 4. ticket button ---------- */
.btn--ticket {
  --tick: 12px;
  position: relative; display: inline-flex; align-items: center; gap: .6rem;
  padding: .82rem 1.5rem; border: 0; border-radius: 0;
  font-family: var(--font-display); font-weight: 600; font-size: .96rem;
  letter-spacing: -.01em; text-decoration: none; cursor: pointer;
  background: var(--saffron); color: var(--ink);
  clip-path: polygon(
    var(--tick) 0, 100% 0, calc(100% - var(--tick)) 100%, 0 100%
  );
  --tilt-deg: -1.6deg;
  transform: rotate(var(--tilt-deg));
  transition: transform 160ms var(--ease-out), background var(--dur-fast) ease;
}
/* hover lift only on real pointers — a tap on touch fires hover and sticks */
@media (hover: hover) and (pointer: fine) {
  .btn--ticket:hover { transform: rotate(var(--tilt-deg)) translateY(-3px); background: var(--saffron-hot); }
}
/* press feedback: the interface has to feel like it heard you */
.btn--ticket:active { transform: rotate(var(--tilt-deg)) scale(.97); }
.btn--ticket:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.btn--ticket svg { width: 18px; height: 18px; }

/* ------------------------------------------------------------
   EL BILLETE A ANCHO COMPLETO — para los botones que ENVIAN
   ------------------------------------------------------------
   Sergio, 23 ago: "cambia el boton de place your order por uno que vaya acorde
   al estilo visual de la pagina web, tambien el boton del check out del
   carrito... tambien mejora el de Schedule my warehouse visit, el de browse the
   catalog first, back to home, tambien el de send a message de contact".

   Los seis eran `.btn--primary`: un rectangulo redondeado que no aparece en
   ningun otro sitio del diseño. El vocabulario de la casa es el BILLETE
   (`.btn--ticket`): papel con los extremos cortados en diagonal.

   Pero el billete normal no sirve tal cual para un boton de ancho completo, y
   por dos razones concretas:

   · LA INCLINACION. `--tilt-deg: -1.6deg` funciona en un boton de 200px. A
     ancho completo, una barra girada 1,6 grados asoma por los lados —
     CLAUDE.md ya tiene anotado que una rotacion ENSANCHA la caja— y ademas
     deja de parecer un billete: parece un boton mal puesto. Aqui se quita.

   · EL CENTRADO. `.btn--ticket` es `inline-flex` sin `justify-content`, asi que
     al estirarlo el texto se queda pegado a la izquierda.

   Se conserva la clase `.btn` en el marcado a proposito: el estado de carga
   (`.btn[data-loading] .btn__label` y `.btn .spinner`) esta enganchado a ella,
   y estos son justamente los botones que giran mientras la peticion viaja.
   Quitarla dejaria el spinner sin CSS y el boton sin decir que esta trabajando.

   El diente crece a 16px porque a 440px de ancho un corte de 12 no se lee.
   ------------------------------------------------------------ */
.btn--ticket.is-block {
  --tick: 16px;
  width: 100%;
  justify-content: center;
  transform: none;
  padding-block: 1rem;
  font-size: 1.02rem;
}
@media (hover: hover) and (pointer: fine) {
  .btn--ticket.is-block:hover { transform: translateY(-2px); }
}
.btn--ticket.is-block:active { transform: scale(.99); }

.btn--ticket.is-tomato { background: var(--tomato); color: var(--bone-pure); }
.btn--ticket.is-tomato:hover { background: var(--tomato-hot); }
.btn--ticket.is-basil { background: var(--basil); color: var(--bone-pure); }
.btn--ticket.is-basil:hover { background: var(--olive-600); }
/* a clip-path crops an inset ring at the slanted ends, so the quiet ticket
   earns its contrast from fill, not from an outline */
.btn--ticket.is-paper { background: var(--adriatic); color: var(--adriatic-ink); }
.btn--ticket.is-paper:hover { background: var(--adriatic-mid); }
.btn--ticket.is-bone { background: var(--bone-pure); color: var(--ink); }
.btn--ticket.is-bone:hover { background: #fff; }
.btn--ticket.is-ink { background: var(--ink); color: var(--bone-pure); }
.btn--ticket.is-ink:hover { background: var(--green-800); }
.btn--ticket.tilt-r { --tilt-deg: 1.6deg; }

@media (prefers-reduced-motion: reduce) {
  .btn--ticket { transition: background var(--dur-fast) ease; }
  .btn--ticket:hover, .btn--ticket:active { transform: rotate(var(--tilt-deg)); }
}

/* ---------- 5. the vintage ticket card ----------
   .cut--notch bites the corners with masks, but its inner rule
   (.rule-in) is a rounded rectangle — so at the corners the printed
   rule runs the wrong way round the bite. On a real ticket the second
   rule follows the cut.

   So this one is drawn: an SVG sits behind the content and traces two
   nested outlines whose corners are quarter-arcs centred exactly ON the
   corner point, which is what makes them scoop inward instead of round
   off. vintage-card.js rewrites the two `d` strings whenever the box
   resizes; the paths are in raw pixels with no viewBox, so the arcs stay
   circular at any aspect ratio (a viewBox + preserveAspectRatio="none"
   would shear them into ellipses).

     <article class="vcard vcard--sky">
       <span class="vcard__shape" aria-hidden="true"></span>
       <div class="vcard__body"> … </div>
     </article>
   ---------------------------------------------------------------- */
.vcard {
  --vc-bg:   var(--bone-pure);
  --vc-ink:  var(--ink);
  --vc-rule: var(--ink);
  --vc-lift: 0 18px 42px rgba(22, 20, 15, .17);
  position: relative; isolation: isolate;
  color: var(--vc-ink);
  border: 0; border-radius: 0; background: none;
}
.vcard__shape { position: absolute; inset: 0; z-index: 1; pointer-events: none; display: block;
  filter: drop-shadow(var(--vc-lift)); }
.vcard__shape svg { display: block; width: 100%; height: 100%; }
.vcard__shape .p-outer { fill: var(--vc-bg); fill-opacity: var(--vc-fill-op, .82); stroke: var(--vc-rule); stroke-width: 2.5; }
.vcard__shape .p-inner { fill: none; stroke: var(--vc-rule); stroke-width: 1.25; opacity: .5; }
.vcard__body { position: relative; z-index: 2;
  padding: clamp(1.5rem, 1rem + 2.4vw, 3.25rem); }

/* Until vintage-card.js has measured the box — and forever if JS is off —
   a plain filled panel stands in, so the type never sits on nothing. */
.vcard:not([data-vcard-ready])::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: var(--vc-bg);
  border: 2.5px solid var(--vc-rule);
  border-radius: 14px;
}

.vcard--paper   { --vc-bg: var(--bone-pure); --vc-ink: var(--ink);         --vc-rule: var(--ink); }
.vcard--cream   { --vc-bg: var(--bone);      --vc-ink: var(--ink);         --vc-rule: var(--ink); }
.vcard--sky     { --vc-bg: var(--adriatic);  --vc-ink: var(--adriatic-ink);--vc-rule: var(--adriatic-ink); }
.vcard--saffron { --vc-bg: var(--saffron);   --vc-ink: var(--ink);         --vc-rule: var(--ink); }
.vcard--tomato  { --vc-bg: var(--tomato);    --vc-ink: var(--bone-pure);   --vc-rule: var(--bone-pure); }
.vcard--basil   { --vc-bg: var(--basil);     --vc-ink: var(--bone-pure);   --vc-rule: var(--bone-pure); }
.vcard--olive   { --vc-bg: var(--green-900); --vc-ink: var(--on-dark);     --vc-rule: var(--saffron); }

/* the tilt that stops a wall of cards reading as a grid */
.vcard--tilt-l { transform: rotate(-1.5deg); }
.vcard--tilt-r { transform: rotate(1.5deg); }

/* ---------- 5b. the rolling import seal ----------
   A disc that rolls without slipping — the JS derives its rotation from the
   distance it travels, so it never skids. Drop artwork at the referenced
   paths and it takes over; until then the CSS wax seal stands in, so the
   motion is visible either way.

   It lives HERE, not in scenes.css, because appointment.html and
   import-inquiry.html use it and never load that sheet. When they did not,
   the disc had no `position` and no `aspect-ratio`, so it sat in normal flow
   at its intrinsic height and pushed the whole hero down behind a band of
   empty paper. Placement stays per-scene; only the primitive is shared. */
.disc { position: absolute; aspect-ratio: 1; pointer-events: none; will-change: transform; }
.disc__img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  filter: drop-shadow(0 22px 46px rgba(0, 0, 0, .42));
}
.disc__fallback { display: none; }
.disc[data-fallback="true"] .disc__img { display: none; }
.disc[data-fallback="true"] .disc__fallback {
  position: relative; overflow: hidden;
  display: grid; place-items: center; width: 100%; height: 100%; border-radius: 50%;
  background: radial-gradient(120% 120% at 32% 26%, var(--brass-soft), var(--brass) 52%, #7d571f 100%);
  box-shadow: var(--shadow-lg), inset 0 0 0 3px rgba(255, 255, 255, .22);
  color: #2a1e08;
}
.disc__fallback::after {
  content: ""; position: absolute; inset: 7%;
  border: 2px dashed rgba(42, 30, 8, .4); border-radius: 50%;
}
.disc__fallback b { font-family: var(--font-display); font-weight: 600; font-size: 3.4em; line-height: 1; }
.disc__fallback > span {
  position: absolute; bottom: 14%; font-family: var(--font-mono);
  font-size: .62em; letter-spacing: .22em; text-transform: uppercase; opacity: .72;
}

/* ---------- 6. the brick edge between two sections ----------
   A two-row masonry course that steps one band into the next. It is a
   plain checkerboard: a tile two blocks wide whose top-right and
   bottom-left quadrants are opaque, which is exactly one conic gradient.
   No composite keyword is involved, so the mask can stay unprefixed.

   Colour it with the section it belongs to:
     <span class="edge" style="--edge-c: var(--bone)"></span>          */
.edge {
  --edge-w: clamp(42px, 4.4vw, 92px);
  /* The course reads as a deliberate graphic band or it reads as an accident.
     The first pass ran 15–28px of bone-pure teeth on a bone ground — the two
     were a shade apart, so it looked like a printing fault rather than a
     border. It is taller now, and --edge-c wants a COLOUR, not a neighbouring
     cream. */
  --edge-h: clamp(20px, 2.1vw, 38px);
  --edge-c: var(--saffron);
  display: block; width: 100%; height: var(--edge-h);
  background: var(--edge-c);
  mask-image: conic-gradient(#000 25%, #0000 0 50%, #000 0 75%, #0000 0);
  mask-size: calc(var(--edge-w) * 2) 100%;
  mask-repeat: repeat-x;
  /* it has to bite INTO the neighbour, so it always overhangs by a hair */
  position: relative; z-index: 3; margin-bottom: -1px;
}
/* teeth pointing up instead of down */
.edge--up { transform: scaleY(-1); margin-bottom: 0; margin-top: -1px; }

/* ============================================================
   7. LIQUID GLASS — the material, never the shape
   ------------------------------------------------------------
   Sergio asked for the iOS Control Centre look on every card:
   frosted, slightly translucent, with a little light sliding
   across the top of the pane. The hard constraint he set is the
   important half of the brief — this may change the SURFACE
   only. No card's silhouette, corner bite, notch, perforation
   or geometry moves by a pixel.

   So nothing here touches width, padding, radius, clip-path or
   mask *geometry*. Each family gets the same three ingredients
   applied to the layer that already draws its fill:

     1. backdrop-filter: blur + a little saturation
     2. the fill goes translucent so that blur is visible
     3. one diagonal specular sheen, clipped to the same shape

   `.vcard` gets a dedicated pane (vintage-card.js writes a
   <clipPath> carrying the outer path's own `d`, so the glass
   stops exactly where the paper does, bitten corners and all).
   `.cut--notch` reuses its ::after, which already carries the
   corner mask. Plain panels just take the properties directly.
   ============================================================ */
.vcard__glass {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; display: block;
  -webkit-backdrop-filter: blur(14px) saturate(1.28);
          backdrop-filter: blur(14px) saturate(1.28);
  /* the light: a bright band across the top-left, fading out, with a faint
     return along the bottom edge — an edge-lit pane, not a gradient wash */
  background:
    linear-gradient(148deg,
      rgba(255, 255, 255, .46) 0%,
      rgba(255, 255, 255, .14) 26%,
      rgba(255, 255, 255, 0) 52%,
      rgba(255, 255, 255, .10) 100%);
}
/* Cards printed in a strong ink read as washed-out if the paper goes as
   translucent as a pale one. Keep those nearly solid — the sheen still
   gives them the material. */
.vcard--tomato, .vcard--basil, .vcard--olive { --vc-fill-op: .93; }
.vcard--saffron { --vc-fill-op: .9; }

/* ---- the notched ticket: its ::after IS the fill ---- */
.cut--notch::after {
  -webkit-backdrop-filter: blur(13px) saturate(1.25);
          backdrop-filter: blur(13px) saturate(1.25);
  background:
    linear-gradient(148deg, rgba(255,255,255,.4) 0%, rgba(255,255,255,.1) 30%,
                            rgba(255,255,255,0) 56%, rgba(255,255,255,.09) 100%),
    color-mix(in srgb, var(--cut-bg) 84%, transparent);
}
/* the ticket buttons are small, solid and clicked — glass there just made
   the label harder to read, so they keep their flat fill */

/* ---- plain panels: same recipe, no shape involved ----
   .form-card is NOT in this list, and that was the last live half of the
   "liquid glass is eating the form" report. Its own card (.desk__form,
   .chero__form) already opts out of the blur under 7b — but .form-card is
   the <div> INSIDE it that holds the heading and every field, so a second
   blur was still being composited over the whole document, which is the
   exact fault 7b exists to stop. Labels, the printed rule and the field
   borders all came back the moment it left this list. Do not re-add it:
   a form is a document, and the size rule in 7b governs. */
.pinstep,
.aside-card,
.brand-logo,
.product-grid .packshot,
.why-card {
  -webkit-backdrop-filter: blur(12px) saturate(1.2);
          backdrop-filter: blur(12px) saturate(1.2);
}
.pinstep { background: color-mix(in srgb, var(--bone-pure) 78%, transparent); }
.pinstep__inner {
  background:
    linear-gradient(150deg, rgba(255,255,255,.38), rgba(255,255,255,0) 58%),
    color-mix(in srgb, var(--pin) 13%, color-mix(in srgb, var(--bone-pure) 76%, transparent));
}
.aside-card { background: color-mix(in srgb, var(--bone-pure) 76%, transparent); }
.brand-logo { background: color-mix(in srgb, var(--bone-pure) 72%, transparent); }

/* A backdrop-filter is a compositing pass per element. On the catalog that
   would be one per card across 396 of them, so the product tiles opt out;
   they sit on flat paper and have nothing behind them worth blurring. */
.product-grid .packshot { -webkit-backdrop-filter: none; backdrop-filter: none; }

/* ------------------------------------------------------------
   7b. WHERE GLASS IS A MATERIAL, AND WHERE IT IS FOG

   Two faults, one cause. Sergio reported "a square behind the card
   that flickers as you scroll", and on Custom Import / Warehouse
   Visit "the label and the text are gone, only the liquid glass is
   left". Measured, both come from the same place:

     .story__letter   411k px of frosted pane — and nf-scenes.js
                    rewrites its `transform` on EVERY scroll frame
     .benefits__card 524k · .visit__ticket 416k
     .desk__form    740k px, the biggest pane on the site

   A `backdrop-filter` has to re-sample its backdrop every frame the
   pane (or any ancestor) moves. On a page-sized pane riding a
   per-frame transform over a video and a photo collage that is a
   full-viewport re-composite per frame, which is what reads as the
   square blinking. And at that size the blur stops being a material:
   it fogs the 2.5px printed rule and the mono label until the card
   looks like nothing but glass.

   So the rule is a size rule, not a taste one. Glass stays on the
   small panes over live imagery — the quote slips, the supplier
   cards, the hero notes, the details card. The big DOCUMENTS keep
   the specular sheen and the silhouette, and drop the blur: no
   compositing pass, no flicker, and the rule and labels come back
   to full contrast. Nothing's geometry moves by a pixel, which was
   the hard constraint on this effect from the start.
   ------------------------------------------------------------ */
/* .chero__form is the contact page's docket. It was missing from this list
   — the list named `.contact__card`, a class that does not exist in
   contact.html — so the biggest pane on that page kept its blur and kept
   re-sampling the snapshots and the postmark behind it on every scroll
   frame. That is the "grey rectangle behind the card" Sergio reported on
   Contact: not a clipping fault, the pane itself. */
.story__letter,
.benefits__card,
.visit__ticket,
.desk__form,
.chero__form,
.hist__card,
.contact__card {
  /* .96 rather than 1: the paper reads as solid and the type gets its
     contrast back, while the diagonal light still shows through it */
  --vc-fill-op: .96;
}
.story__letter  > .vcard__glass,
.benefits__card > .vcard__glass,
.visit__ticket  > .vcard__glass,
.desk__form     > .vcard__glass,
.chero__form    > .vcard__glass,
.hist__card     > .vcard__glass,
.contact__card  > .vcard__glass {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* Same reasoning for the notched tickets that are documents rather than
   chips. .pitch__sheet is the largest element in the hero and sits inside
   .split-hero__reveal, whose opacity is written every frame; the step cards
   sit inside a curtain whose clip-path is written every frame. Both are a
   re-composite per frame for a blur nobody can see behind opaque paper. */
.pitch__sheet::after,
.step-card::after {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  background:
    linear-gradient(148deg, rgba(255,255,255,.4) 0%, rgba(255,255,255,.1) 30%,
                            rgba(255,255,255,0) 56%, rgba(255,255,255,.09) 100%),
    var(--cut-bg);
}

@media (prefers-reduced-transparency: reduce) {
  .vcard__glass { display: none; }
  .vcard { --vc-fill-op: 1; }
  .cut--notch::after { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--cut-bg); }
  .pinstep, .aside-card, .brand-logo, .form-card, .why-card {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}
