Vista previa en vídeo
GratisHeroAutomoción

Logística y entregas

El prompt
ID: logistic-heavy-industry-hero

Category: Logistics / Freight / Industrial Transport Hero

Type: hero

Create a single-file HTML+CSS+vanilla-JS hero section with the following exact specifications:

## Tech Stack

* Deliverable filename: `logistic-hero.html`.
* Plain HTML + CSS + vanilla JavaScript. No frameworks, no build step.
* Fonts: Google Fonts `DM Sans` (weights 500/700/800) and `Inter` (weights 500/700). Load via:
  ```html
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@500;700;800&family=Inter:wght@500;700&display=swap" rel="stylesheet">
  ```
* Global resets: `*{margin:0;padding:0;box-sizing:border-box;}`. `html,body{height:100%;width:100%;background:#000;font-family:'Inter',sans-serif;overflow:hidden;}`. `img,video{display:block;}`. `a{text-decoration:none;}`.

## Canvas & Scaling

TRUE FULLSCREEN layout — no fixed-size design canvas, no letterboxing, no JS scale/transform of any kind. Every element is `position:fixed` and anchored directly to the real viewport edges with fixed pixel margins (same pattern as `audi-sport-hero.html` / `la-table-eternelle-hero.html`):

* `.stage{position:fixed;inset:0;width:100vw;height:100vh;overflow:hidden;background:#000;}` — fullscreen root.
* `--edge:50px` — single CSS variable used for every top/left/right margin from the viewport edge (header, hero title block via its own centering, bottom-left cluster, bottom-right cluster all reference it, directly or via a shared centered width).
* Do NOT add a `transform:scale(...)` canvas wrapper anywhere. Fullscreen behavior comes entirely from `position:fixed` elements anchored to viewport edges (`top`, `left`, `right`, `bottom`) plus one `left:50%` + `translateX(-50%)` centered block for the hero title, never from scaling a fixed-size canvas.

## Design Tokens

```css
:root{
  --edge:50px;
  --dark:#141411;
  --green:#193505;
  --gray:#4c4b4b;
}
```

## Elements

1. **Background video** — `.bg-video{position:fixed;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;}`. `<video autoplay muted loop playsinline>`. Src: `https://api.promptsites.net/s3/promptsites/previews/e988bd32-89fa-41ef-9221-bc603052f686.mp4`. No gradient/scrim overlay on top of it — the video shows fully unobstructed.

2. **Header** — `.header{position:fixed;top:32px;left:var(--edge);right:var(--edge);z-index:20;height:54px;display:flex;align-items:center;justify-content:space-between;opacity:0;animation:downIn .6s cubic-bezier(.165,.84,.44,1) 0s forwards;}`.
   * Brand (left): `.brand{display:flex;align-items:center;gap:7.5px;}` containing a hand-drawn inline SVG mark (a simple two-petal/flower glyph, NOT a raster export — reproduce as native `<svg viewBox="0 0 30 30">` with two `<path fill="currentColor">` petal shapes) sized `30×30`, color `var(--dark)`, plus `.brand span{font-family:'DM Sans';font-weight:800;font-size:22.5px;color:var(--dark);}` text **"Logistic"**.
   * Nav (center): `.nav{display:flex;align-items:center;gap:48px;}` with four links **Services / About Us / Projects / Press & News**. `.nav a{display:inline-block;color:var(--dark);font-family:'Inter';font-weight:500;font-size:15px;transition:transform .25s ease,font-weight .25s ease;}`. Hover: `.nav a:hover{transform:scale(1.12);font-weight:700;}` — link grows and gets visibly bolder on hover, smoothly.
   * Contact button (right): the reusable `.pill-btn.contact` component (179px wide) — see **Button Component** below.

3. **Button component** (`.pill-btn`, used for both header "Contact Us" and hero "Request a Quote") — reproduces Figma node 317:1361's "Default"/"Variant2" hover states as a live CSS transition, not two static images:
   ```css
   .pill-btn{position:relative;height:54px;flex:none;border-radius:30px;background:var(--green);display:block;cursor:pointer;transition:background-color .35s ease;}
   .pill-btn.contact{width:179px;}
   .pill-btn.quote{width:218px;}
   .pill-btn:hover{background:#000;}
   .pill-btn .label{position:absolute;top:0;height:100%;display:flex;align-items:center;font-family:'DM Sans';font-weight:500;font-size:15px;color:#fff;white-space:nowrap;transition:left .35s cubic-bezier(.4,0,.2,1);}
   .pill-btn.contact .label{left:25px;} .pill-btn.contact:hover .label{left:64px;}
   .pill-btn.quote .label{left:26px;} .pill-btn.quote:hover .label{left:74px;}
   .pill-btn .circle{position:absolute;top:5px;width:44px;height:44px;border-radius:50%;background:#fff;display:flex;align-items:center;justify-content:center;transition:left .35s cubic-bezier(.4,0,.2,1);}
   .pill-btn.contact .circle{left:130px;} .pill-btn.contact:hover .circle{left:5px;}
   .pill-btn.quote .circle{left:169px;} .pill-btn.quote:hover .circle{left:5px;}
   .pill-btn .circle svg{width:19px;height:19px;color:var(--dark);transition:transform .35s ease;}
   .pill-btn:hover .circle svg{transform:rotate(180deg);}
   ```
   Markup: `<a class="pill-btn contact"><span class="label">Contact Us</span><span class="circle"><svg viewBox="0 0 24 24" fill="none"><path d="M5 12H19M19 12L13 6M19 12L13 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span></a>` (swap label text and `.contact`→`.quote` for the hero button).
   * Critical detail: the white circle is positioned with `left` in BOTH default and hover states (never `right` in one state and `left` in the other) — `left` is computed as `buttonWidth - 44 - 5` by default and `5px` on hover — so the browser can animate a single continuous property instead of snapping between incompatible anchor sides. Label position also only ever uses `left`. On hover: background flips green→black, the circle slides to the left edge, the arrow icon rotates 180° (points the other way), and the label slides right to make room — all in the same `.35s` window.

4. **Hero title block** — split into a static positioning wrapper and an animated inner block (see note below on why):
   ```css
   .hero-pos{position:fixed;left:50%;top:136px;transform:translateX(-50%);width:680px;z-index:10;}
   .hero{display:flex;flex-direction:column;align-items:center;gap:24px;opacity:0;animation:upIn .8s cubic-bezier(.165,.84,.44,1) .15s forwards;}
   .hero h1{font-family:'DM Sans';font-weight:800;font-size:65px;line-height:65px;color:#000;text-align:center;}
   .hero p{font-family:'Inter';font-weight:500;font-size:20px;line-height:30px;color:var(--gray);text-align:center;}
   ```
   * IMPORTANT structural rule: `.hero-pos` (the outer div) holds the static `left:50%;transform:translateX(-50%)` centering and nothing else animates on it. `.hero` (the inner div) holds the `opacity`/`upIn` entrance animation, which itself uses `transform:translateY(...)`. Never combine a static centering `transform` and an animated `transform` on the SAME element — a CSS animation replaces the element's entire `transform` value, so the centering would be silently deleted the instant the animation applied, shoving the whole block off-screen. Keeping them on parent/child avoids this.
   * Content: `<h1>Move Heavy Industry<br>Forward Globally</h1>` (explicit `<br>`, not a wrapped line), then `<p>Experience world-class transport of massive industrial loads — safely, globally, and on time.</p>`, then the `.pill-btn.quote` button ("Request a Quote").

5. **Bottom-left: icon badges + copy** — `.bl{position:fixed;left:var(--edge);bottom:38px;width:402px;z-index:10;opacity:0;animation:upIn .8s cubic-bezier(.165,.84,.44,1) .3s forwards;}`.
   * `.bl-icons{position:relative;height:62px;width:119px;}` containing two overlapping circular badges, `.badge{position:absolute;top:0;width:62px;height:62px;border-radius:50%;display:flex;align-items:center;justify-content:center;}`, each with an inline hand-drawn SVG icon (`width:26px;height:26px`) — NOT raster exports, since these were plain single-color line icons in Figma:
     * `.truck{left:0;background:#fff;color:var(--dark);z-index:1;}` — simple truck outline (cab rectangle + trailer box + two wheel circles), `viewBox="0 0 26 26"`, `stroke="currentColor" stroke-width="1.4"`.
     * `.globe{left:57px;background:#000;color:#fff;}` — globe icon (circle + tall ellipse + 2 horizontal meridian lines), same viewBox/stroke treatment, white-on-black. It overlaps the truck badge by 5px (`left:57px` against a 62px-wide truck badge).
   * Copy: `.bl p{margin-top:28px;font-family:'Inter';font-weight:500;font-size:30px;line-height:40px;color:#fff;width:346px;}`. Text **"Logistics is now a global logistics partner to many companies"** — revealed via the **typewriter reveal system** (see below), not a flat fade-in.

6. **Bottom-right: stat panel + trust row** — `.br{position:fixed;right:var(--edge);bottom:35px;width:278px;z-index:10;display:flex;flex-direction:column;align-items:center;gap:12px;opacity:0;animation:upIn .8s cubic-bezier(.165,.84,.44,1) .4s forwards;}`.
   * `.stat-wrap{display:flex;flex-direction:column;align-items:center;gap:12px;width:202.815px;}` containing:
     * `.stat-panel{position:relative;width:202.815px;height:110.774px;overflow:hidden;background:rgba(255,255,255,.2);backdrop-filter:blur(2.851px);-webkit-backdrop-filter:blur(2.851px);}` — a translucent glass plaque (matches Figma's own `bg rgba(255,255,255,.2)` + blur spec) giving contrast against the video wherever it extends past the photo underneath.
     * `.stat-photo{position:absolute;left:17px;top:17px;width:173px;height:70px;object-fit:cover;opacity:.9;pointer-events:none;}` — a real photo (green shipping-container shot) pulled from Figma, uploaded to promptsites.net at `https://api.promptsites.net/s3/promptsites/previews/484b516a-b38a-4633-a6d9-46d54f905443.png`.
     * Four corner-bracket marks, hand-drawn inline SVG (NOT raster — the source Figma layer was a pure vector with no raster fill, which exports with an opaque matte instead of real transparency): `.stat-panel .corner{position:absolute;width:12.218px;height:12.218px;color:rgba(255,255,255,.85);}`, each containing `<svg viewBox="0 0 13 13" fill="none"><path d="M1 13V4C1 2.3 2.3 1 4 1H13" stroke="currentColor" stroke-width="1.4" stroke-dasharray="2 2"/></svg>`, positioned/rotated per corner: `.tl{left:0;top:0;}` `.tr{right:0;top:0;transform:rotate(90deg);}` `.br2{right:0;bottom:0;transform:rotate(180deg);}` `.bl2{left:0;bottom:0;transform:rotate(-90deg);}`.
     * `.stat-num{position:absolute;left:0;right:0;top:31px;text-align:center;font-family:'Inter';font-weight:500;font-size:28px;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.6);}` — markup `<span class="stat-num"><span id="statCount">0</span>M+</span>`. The inner `#statCount` span gets a recurring emphasis pulse: `.stat-num span{display:inline-block;animation:pulse 5s ease-in-out 1.6s infinite;} @keyframes pulse{0%,80%,100%{transform:scale(1)}86%{transform:scale(1.18)}92%{transform:scale(1)}}`.
     * `.stat-label{position:absolute;left:0;right:0;top:65px;text-align:center;font-family:'Inter';font-weight:500;font-size:12.218px;color:#fff;}` text **"Tons delivered"**.
     * `.stat-divider{width:1px;height:57px;background:rgba(255,255,255,.5);}` below the panel.
   * `.trust{display:flex;flex-direction:column;align-items:center;gap:14px;width:100%;}` containing:
     * `.trust-avatars{position:relative;width:183px;height:77px;}` with three real headshot photos (uploaded to promptsites.net: `https://api.promptsites.net/s3/promptsites/previews/50037311-0f4d-433b-9861-f6e2e3eb9ca7.png` (left), `https://api.promptsites.net/s3/promptsites/previews/5dbea9d5-aeca-4135-a110-09b1bbeb9af0.png` (center), `https://api.promptsites.net/s3/promptsites/previews/8fefa935-c533-4346-b0b1-be30d7590391.png` (right)), `.trust-avatars img{position:absolute;border-radius:50%;object-fit:cover;transition:left .8s cubic-bezier(.4,0,.2,1),top .8s cubic-bezier(.4,0,.2,1),width .8s cubic-bezier(.4,0,.2,1),height .8s cubic-bezier(.4,0,.2,1);}`. Three position classes: `.av-left{left:3px;top:10px;width:62px;height:62px;z-index:0;}`, `.av-right{left:120px;top:10px;width:62px;height:62px;z-index:0;}`, `.av-center{left:57px;top:0;width:72px;height:72px;z-index:1;}`. A script (see **Interactive Behavior**) rotates which photo holds which class every few seconds, so they visibly swap places and whichever lands in the center grows, driven by the CSS transition rather than a snap.
     * `.trust p{font-family:'Inter';font-weight:500;font-size:30px;line-height:40px;color:#fff;text-align:center;}` text **"Trusted global partner in logistics"** — also driven by the typewriter reveal system.

## Typewriter Reveal System (plain-text build-up, no cursor)

Two copy lines ("Logistics is now…" and "Trusted global partner…") are revealed character-by-character by directly mutating `textContent`, NOT via per-character `<span>` opacity like the Audi build's system, and with NO `::after` caret/cursor element of any kind:

Markup: `<p class="type-target" data-text="Logistics is now a global logistics partner to many companies"></p>` (element starts empty; the real text lives only in `data-text`).

```js
(function(){
  var targets = document.querySelectorAll('.type-target');
  targets.forEach(function(el, i){
    var text = el.getAttribute('data-text');
    var idx = 0;
    var delay = 500 + i * 300;
    setTimeout(function(){
      var iv = setInterval(function(){
        idx++;
        el.textContent = text.slice(0, idx);
        if(idx >= text.length) clearInterval(iv);
      }, 32);
    }, delay);
  });
})();
```
Each `.type-target` on the page starts `300ms` after the previous one (`delay = 500 + i*300`), typing at `32ms` per character, and simply stops appending once the full string is reached — no blinking bar, no trailing pipe character, nothing added after the last letter.

## Interactive (JS) Behavior

**Avatar carousel** — the three trust photos rotate through the left/center/right slots forever:
```js
(function(){
  var wrap = document.querySelector('.trust-avatars');
  if(!wrap) return;
  var order = Array.prototype.slice.call(wrap.querySelectorAll('img'));
  var slots = ['av-left','av-center','av-right'];
  function apply(){
    order.forEach(function(img, i){
      img.classList.remove('av-left','av-center','av-right');
      img.classList.add(slots[i]);
    });
  }
  apply();
  setInterval(function(){
    order.push(order.shift());
    apply();
  }, 3200);
})();
```
Every `3200ms` the array rotates by one (`push(shift())`), reassigning position classes; because `.trust-avatars img` transitions `left/top/width/height`, this reads as the three photos smoothly swapping places, with whichever one lands on `.av-center` visibly growing to 72×72 as part of the same motion.

**"2M+" count-up with overshoot** — on load the number animates 0→2 with a back-out (slight overshoot/bounce) ease, then settles and relies on the recurring CSS `pulse` keyframe (defined above) for ongoing life, rather than counting forever:
```js
(function(){
  var el = document.getElementById('statCount');
  var target = 2;
  var duration = 1200;
  var start = null;
  function easeOutBack(t){
    var c1 = 1.70158, c3 = c1 + 1;
    return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
  }
  function step(ts){
    if(!start) start = ts;
    var p = Math.min((ts - start) / duration, 1);
    var eased = easeOutBack(p);
    var val = Math.max(0, Math.round(eased * target));
    el.textContent = val;
    if(p < 1) requestAnimationFrame(step);
    else el.textContent = target;
  }
  setTimeout(function(){ requestAnimationFrame(step); }, 900);
})();
```
Starts `900ms` after load, runs for `1200ms` using the cubic "back" ease (`easeOutBack`) so the digit briefly overshoots past its target before settling exactly on `2`.

## Animations

Keyframes (exact):
```css
@keyframes downIn{from{opacity:0;transform:translateY(-16px)}to{opacity:1;transform:translateY(0)}}
@keyframes upIn{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}}
@keyframes pulse{0%,80%,100%{transform:scale(1)}86%{transform:scale(1.18)}92%{transform:scale(1)}}
```

Entrance schedule, in load order:
* `.header` → `downIn` → `.6s` → delay `0s`.
* `.hero` → `upIn` → `.8s` → delay `.15s`.
* `.bl` → `upIn` → `.8s` → delay `.3s`.
* `.br` → `upIn` → `.8s` → delay `.4s`.
* `.type-target` (2 elements) → typewriter, base delay `.5s`/`.8s`, `32ms`/char.
* Avatar carousel → starts immediately on load, rotates every `3.2s`, forever.
* `#statCount` → count-up starts at `.9s`, `1.2s` duration, `easeOutBack`; `.stat-num span` → recurring `pulse`, `5s` cycle, starting `1.6s` after load, forever.

`prefers-reduced-motion: reduce` behavior:
```css
@media (prefers-reduced-motion: reduce){
  .header,.hero,.bl,.br{animation:none !important;opacity:1 !important;transform:none !important;}
}
```
Note this rule only covers the four entrance-animated blocks; it intentionally does NOT null the button hover transition, the nav hover, the avatar-carousel transition, or the count-up/pulse — those are short, purposeful, and interaction- or content-driven rather than an ambient decorative loop.

## Z-index Layering

* `.bg-video` → 0
* `.hero-pos` → 10
* `.bl` → 10
* `.br` → 10
* `.header` → 20

## Notes on Asset Sourcing (why some elements are inline SVG/CSS instead of exported images)

Several small vector elements in the source Figma file (brand mark, truck/globe icons, corner brackets) are pure vector shapes with no raster fill. Figma's screenshot/PNG/SVG export endpoints matte isolated vector nodes like this against a fully opaque canvas with no real alpha channel — exporting them as `<img>` produces a visible box/matte instead of a transparent icon. The fix used throughout this build is to hand-reproduce these as native inline `<svg>` (or, for the button pill shape, plain CSS `border-radius` divs) using `currentColor`/`stroke` so they inherit color from their parent and are transparent by construction, with no export step at all. Only genuine raster photo content (container photo, three headshot avatars) is pulled from Figma and hosted on Cloudinary, since raster fills DO preserve real alpha correctly on export.

Please ensure the output is a clean, production-ready single HTML file that renders pixel-identically to this spec, filling the entire browser viewport edge-to-edge at any window size with no letterboxing and no canvas scaling — every block must remain correctly pinned to its respective viewport edge (top, left, right, bottom, or horizontal-center) via `position:fixed` and the shared `--edge:50px` margin.