Vista previa en vídeo
GratisHeroPortafolio
Automovilismo F1
El prompt
ID: f1-news-hero
Category: Motorsport / News Homepage / Hero Type
Type: hero
Create a single-file HTML+CSS+vanilla-JS hero section with the following exact specifications:
## Tech Stack
* Deliverable filename: `f1-news-hero.html`.
* Plain HTML + CSS + vanilla JS. No frameworks, no build step.
* Fonts — literal copy-pasteable block:
```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=Inter:wght@500;700;800&display=swap" rel="stylesheet">
```
* `Inter` weights 500 (nav, buttons, body copy), 700 (card titles), 800 (hero headline) — used everywhere, single font family.
* Global reset as literal CSS:
```css
*{ margin:0; padding:0; box-sizing:border-box; }
html, body{
width:100%; height:100%; overflow:hidden;
font-family:'Inter', sans-serif;
background:#fdfdfd;
color:#000;
}
```
## Canvas & Scaling
* **Deviation from the default true-fullscreen/edge-anchored approach — deliberate, explained below.**
* This design is a dense pixel-precise composition: three news cards, each with a photo crossfade whose crop window is expressed as exact `width%`/`height%`/`left%`/`top%` values tuned against one specific pixel-for-pixel card size (465×~280–312px), plus a small hand-built "360°" orbit badge with individually rotated/skewed satellite marks. Edge-anchoring the cards (letting their width/height flex independently with the viewport) breaks every one of those crop percentages — a card that isn't exactly its designed aspect ratio makes the crossfade photos visibly stretch or show the wrong part of the image. So the whole design is built as a **fixed 1420×800 canvas, uniformly scaled to fit inside the viewport** (both axes scale together, so the card boxes are always exactly the aspect ratio the crop math assumes).
* Unlike a "cover" (fill-the-screen, crop-the-edges) approach, this canvas uses **"contain"** scaling — it fits entirely inside the viewport with no cropping, and is capped so it is never scaled up past its native 1420×800 size on very large screens. This means on a viewport whose aspect ratio isn't close to 1420:800 you will see empty page background on two sides rather than any part of the design being cropped — that trade-off is intentional here (no content is pinned to a real screen edge, so nothing needs a bottom/top-cropping-proof escape hatch).
* Markup skeleton:
```html
<div class="stage">
<div class="canvas" id="canvas">
... everything ...
</div>
</div>
```
* CSS:
```css
.stage{
position:fixed; inset:0; overflow:hidden; background:#fdfdfd;
display:flex; align-items:center; justify-content:center;
}
.canvas{
position:relative; flex:none;
width:1420px; height:800px;
transform:scale(var(--s,1));
}
```
* JS (runs once on load and on every resize):
```js
var canvas = document.getElementById('canvas');
function fit(){
var s = Math.min(1, window.innerWidth / 1420, window.innerHeight / 800);
canvas.style.setProperty('--s', s);
}
fit();
window.addEventListener('resize', fit);
```
* `Math.min(1, ...)` is intentional: it is the "fit entirely inside, never upscale" formula. Do NOT change this to `Math.max(...)` — that switches to cover/crop behavior, which is the wrong choice for this design (see the two paragraphs above for why).
* Fragile coupling: the `1420` / `800` literals appear in **three** places — `.canvas` width/height, the `fit()` function's divisors, and every element's own `left`/`top`/`width`/`height` values below (including every crop percentage, which is only correct because the card box it's a percentage *of* is a fixed, never-independently-stretched pixel size). Changing the canvas size requires re-deriving every single position and crop percentage; it is not a simple CSS variable swap.
## Design Tokens
```css
:root{
--edge: 40px;
--gap: 13px;
--fade: 1.1s;
}
```
Colors used inline (no palette abstraction in the source): `#fdfdfd` (page bg), `#000`/`#fff` (ink/paper), `#010101` (card 1 bg), `#000000` (card 3 bg), `#002883` (card 2 "Ford" state bg), `#fe2425` (card 2 "Ferrari" state bg), `rgba(0,0,0,0.8)` (hero subhead), `rgba(255,255,255,0.6)` (card body copy on dark bg), `rgba(0,0,0,0.6)` (card body copy on the red card-2 state).
## Elements
Numbered in DOM order (top bar, hero row, then the three-card row left to right).
### 1. Top bar
```css
.topbar{
position:absolute; left:var(--edge); right:var(--edge); top:40px;
display:flex; align-items:center; justify-content:space-between;
}
.logo{ width:128px; height:30px; display:block; }
.nav{
display:flex; align-items:center; gap:50px;
font-size:18px; font-weight:500; letter-spacing:-0.36px;
color:#000; white-space:nowrap;
}
.nav a{
color:inherit; text-decoration:none; display:inline-block;
transition:font-weight 0.2s ease, transform 0.2s ease;
}
.nav a:hover{ font-weight:800; transform:scale(1.08); }
.signin{
width:134px; height:45px; background:#000; color:#fdfdfd;
display:flex; align-items:center; justify-content:center;
font-size:18px; font-weight:500; letter-spacing:-0.36px;
border:none; cursor:pointer;
transition:background 0.25s ease, color 0.25s ease, transform 0.25s ease, box-shadow 0.25s ease;
}
.signin:hover{
background:#fdfdfd; color:#000;
box-shadow:0 0 0 2px #000 inset;
transform:translateY(-3px);
}
.signin:active{ transform:translateY(0); }
```
* Logo asset (128×30): `https://api.promptsites.net/s3/promptsites/previews/e5b42b02-a5bb-4f18-85ec-57925ef0f173.png`
* Nav copy, in order, `gap:50px` between each: **Schedule**, **News**, **Results**, **Drivers**, **Teams**.
* Sign-in button copy: **Sign in**. Hover: colors invert (black↔white) with a 2px inset border and the button lifts 3px; releases on `:active`.
* Nav link hover: font-weight jumps 500→800 and the link scales up 8% in place.
### 2. Hero row
```css
.hero{
position:absolute; left:var(--edge); right:var(--edge); top:125px;
display:flex; align-items:flex-start; justify-content:space-between;
gap:24px;
}
.hero h1{
max-width:534px;
font-size:42px; font-weight:800; line-height:51px; letter-spacing:-1.26px;
color:#000;
}
.hero-side{
width:343px; flex:none;
display:flex; flex-direction:column; justify-content:space-between;
min-height:153px;
}
.hero-side p{
max-width:356px;
font-size:24px; font-weight:500; line-height:25px;
color:rgba(0,0,0,0.8);
}
.hero-nav{
display:flex; align-items:center; gap:8px; margin-top:24px;
}
.hero-nav button{
width:30px; height:30px; border:none; background:none; padding:0; cursor:pointer;
display:flex; align-items:center; justify-content:center;
opacity:0.3; transition:opacity 0.25s ease;
}
.hero-nav button img{ width:100%; height:100%; display:block; }
.hero-nav button:hover{ opacity:1; }
.hero-nav .prev{ transform:rotate(180deg); }
```
* Headline copy (three explicit lines, each its own `<span class="line">`, NOT left to natural wrapping — see the write-in animation below, which needs one element per line): **Breaking news, live** / **updates, championship** / **action — here.**
* Subhead copy: **Join millions experiencing every qualifying lap, overtake, and championship-defining moment.**
* Two circular arrow buttons below the subhead, both using the same black arrow-circle icon (30×30): `https://api.promptsites.net/s3/promptsites/previews/1bd1821f-fb74-4727-8ce9-9496758c197e.png`. Both start at `opacity:0.3` (grey look) and go to full opacity on hover (independently — hovering one does not affect the other). The left one (`.prev`) is rotated 180° so its arrow points left; the right one (`.next`) is unrotated.
### 3. Cards row (edge-to-edge across the full 1420px canvas, flush to the canvas bottom)
```css
.cards{
position:absolute; left:0; right:0; bottom:0;
display:flex; gap:var(--gap);
height:466px;
}
.card{
position:relative; width:0; flex:1 1 0; height:100%;
overflow:hidden; color:#fff;
}
.card-1{ background:#010101; }
.card-3{ background:#000000; }
.card-head{
position:absolute; left:20px; right:20px; top:20px; height:140px;
display:flex; flex-direction:column; justify-content:space-between;
}
.card-head-top{
display:flex; align-items:flex-start; justify-content:space-between; gap:12px;
}
.card-head-top h2{
font-size:30px; font-weight:700; line-height:38px; letter-spacing:-0.9px;
color:#fff;
animation:card-text-in 0.7s cubic-bezier(.22,1,.36,1) both;
}
.card-head-top .icon{
width:30px; height:30px; flex:none; display:block;
transition:transform 0.35s cubic-bezier(.22,1,.36,1);
}
.card:hover .card-head-top .icon{ transform:translateX(6px) rotate(8deg); }
.card-head p{
font-size:16px; font-weight:500; line-height:24px; letter-spacing:-0.32px;
color:rgba(255,255,255,0.6);
animation:card-text-in 0.7s cubic-bezier(.22,1,.36,1) both;
}
.card:nth-child(1) .card-head-top h2{ animation-delay:0.15s; }
.card:nth-child(1) .card-head p{ animation-delay:0.27s; }
.card:nth-child(2) .card-head-top h2{ animation-delay:0.3s; }
.card:nth-child(2) .card-head p{ animation-delay:0.42s; }
.card:nth-child(3) .card-head-top h2{ animation-delay:0.45s; }
.card:nth-child(3) .card-head p{ animation-delay:0.57s; }
```
* Three equal-width cards (`flex:1 1 0`), `13px` gap, `466px` tall, spanning the full canvas width edge-to-edge (no side margin — this row deliberately breaks out of the `--edge` margin used by the top bar and hero row above it).
* White arrow-circle icon (30×30) used in the top-right corner of every card head except the card-2 red state (see below): `https://api.promptsites.net/s3/promptsites/previews/1cf5f073-b0fe-4bcf-ab27-a2a2307d7de9.png`. On hover of the whole card, the icon slides 6px right and rotates 8°.
* Card head text fades up on load, staggered per card (title first, body 0.12s later), see the Animations table.
#### 3a. Card 1 — "Mercedes W16 Breakdown"
```css
.photo-box{ position:absolute; overflow:hidden; }
.photo-box img{
position:absolute; display:block; max-width:none;
opacity:0; transition:opacity var(--fade) ease;
}
.photo-box img.is-active{ opacity:1; }
.card-1 .photo-box{ left:0; top:154px; width:465px; height:275.5735px; }
.card-1 .photo-a{ width:99.92%; height:109.61%; left:0; top:-3.21%; }
.card-1 .photo-b{ width:155.36%; height:174.8%; left:-30.69%; top:-47.82%; }
.orbit-badge{ position:absolute; left:0; top:401px; width:465px; height:54px; pointer-events:none; }
.orbit-badge .deg{
position:absolute; left:195px; top:0; width:80px;
font-size:24px; font-weight:500; line-height:37px; color:#fff; text-align:center;
}
.orbit-badge .curve{ position:absolute; left:84.9px; top:9.97px; width:278.8px; height:24.2px; display:block; }
.orbit-badge .dot{ position:absolute; width:40.6px; height:12.3px; display:flex; align-items:center; justify-content:center; }
.orbit-badge .dot img{ width:21.4px; height:21.05px; display:block; }
.orbit-badge .dot-r{ left:349.45px; top:0; }
.orbit-badge .dot-r span{ display:block; transform:rotate(-14.48deg) scaleY(0.56) skewX(56.24deg); }
.orbit-badge .dot-l{ left:60px; top:0; }
.orbit-badge .dot-l span{ display:block; transform:rotate(-165.52deg) scaleY(-0.56) skewX(-56.24deg); }
```
* Title copy: **Mercedes W16**<br>**Breakdown**. Body copy (max-width 275px): **Spin, zoom, and explore every detail of Mercedes' 2025 contender.**
* Photo A (default hidden, becomes visible every other 3s cycle): `https://api.promptsites.net/s3/promptsites/previews/6f43bace-5bde-4949-b233-19989ddf00c5.png`
* Photo B (default **visible** on load): `https://api.promptsites.net/s3/promptsites/previews/2bc630d3-df22-4bc4-bcc6-77014e34684e.png`
* Fragile coupling: photo A and photo B are two *different* source photos of the car at different native zoom levels. Their `width%`/`height%`/`left%`/`top%` values were hand-tuned so the car appears at the *same apparent size* in both frames of the crossfade — the numbers are not arbitrary crops, they are a deliberate zoom-matching correction. If you swap in a different photo for either slot, recompute its own percentages by eye against the other slot; reusing the other photo's numbers will make the car suddenly jump in size when the crossfade fires.
* "360°" orbit badge sits near the bottom of the photo, overlapping its lower edge by design: a text label ("360°"), an underline curve image (`https://api.promptsites.net/s3/promptsites/previews/3a7e176c-6380-43e2-8cd2-701bffe7e026.png`, 278.8×24.2), and two small satellite marker icons at each end of the curve, each wrapped in its own rotate+skew transform to sit correctly on the curve: right marker `https://api.promptsites.net/s3/promptsites/previews/a71aad99-b134-463f-8067-6353cac97752.png`, left marker `https://api.promptsites.net/s3/promptsites/previews/c994dca1-9c17-4cb1-936c-0be75b5c6499.png`.
#### 3b. Card 2 — "Hamilton" (two full alternating states)
```css
.card-2{ background:transparent; }
.card2-state{
position:absolute; inset:0; opacity:0; transition:opacity var(--fade) ease;
}
.card2-state.is-active{ opacity:1; }
.state-ford{ background:#002883; }
.state-ferrari{ background:#fe2425; }
.state-ferrari .card-head-top h2,
.state-ferrari .card-head p{ color:#000; }
.state-ferrari .card-head p{ color:rgba(0,0,0,0.6); }
.card2-photo{
position:absolute; left:0; top:176px; width:465px; height:290px;
overflow:hidden;
}
.card2-photo img{ position:absolute; display:block; max-width:none; }
.state-ford .card2-photo img{ width:100%; height:100%; left:0; top:0; object-fit:cover; }
.state-ferrari .card2-photo img{ width:119.04%; height:106.75%; left:-2.79%; top:-0.13%; }
```
* This card is not one photo crossfading — it's **two entire alternate card states** (different background color, different headline, different text color, different icon color, different photo) cross-fading as a whole:
* **Ferrari state** (shown first on load, red `#fe2425` bg, black text/icon): title **Hamilton: Ferrari can step forward**, body **Seven-time champion outlines expectations**<br>**for Ferrari's season finale.**, black arrow icon `https://api.promptsites.net/s3/promptsites/previews/1bd1821f-fb74-4727-8ce9-9496758c197e.png`, photo `https://api.promptsites.net/s3/promptsites/previews/3c98b6ca-54db-4713-a5c1-a21ea0aecbdb.png` (custom crop, `object-fit` not used here).
* **Ford state** (blue `#002883` bg, white text/icon): title **Hamilton: Ford can step forward**, same body copy, white arrow icon `https://api.promptsites.net/s3/promptsites/previews/1cf5f073-b0fe-4bcf-ab27-a2a2307d7de9.png`, photo `https://api.promptsites.net/s3/promptsites/previews/4e44d64a-6644-430c-884a-0f55ffff421a.png` (plain `object-fit:cover`).
* Both `<h2>` elements in this card use `style="max-width:324px"` inline (needed so the title wraps to exactly two lines: "Hamilton: Ford can" / "step forward").
* Fragile coupling: same zoom-matching note as card 1 — the Ferrari-state photo's crop percentages were tuned so the subject reads at the same scale as the Ford-state photo (which just uses plain `cover`). Don't copy one photo's numbers onto the other.
#### 3c. Card 3 — "Carlos Sainz's Helmet"
```css
.card-3 .photo-box{ left:0; top:154px; width:465px; height:312px; }
.card-3 .photo-6{ width:100%; height:100%; left:0; top:0; object-fit:cover; }
.card-3 .photo-5{ width:107.29%; height:118.5%; left:-2.49%; top:-11.85%; }
```
* Title copy (inline `style="max-width:280px"`): **Carlos Sainz's Helmet**. Body copy (inline `style="max-width:364px"`): **Explore the Spanish driver's 2025 design and technical innovations.**
* Photo "6" (plain `object-fit:cover`, hidden by default): `https://api.promptsites.net/s3/promptsites/previews/6eccd5f7-40a4-4f81-b245-7778499ebfcf.png`
* Photo "5" (custom crop, **visible on load**): `https://api.promptsites.net/s3/promptsites/previews/aa068a2d-712d-4e6f-a8cd-5dd66d98dc78.png`
* Same zoom-matching fragile coupling as cards 1 and 2 — photo 5's crop percentages correct for its own zoom level relative to photo 6's plain `cover`.
## Interactive (JS) Behavior
**1. Canvas fit (runs on load + resize):**
```js
var canvas = document.getElementById('canvas');
function fit(){
var s = Math.min(1, window.innerWidth / 1420, window.innerHeight / 800);
canvas.style.setProperty('--s', s);
}
fit();
window.addEventListener('resize', fit);
```
**2. Synchronized photo/state crossfade, every 3 seconds, gated by prefers-reduced-motion:**
```js
(function(){
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduceMotion) return;
var groups = [
document.querySelectorAll('.card-1 .photo-a, .card-1 .photo-b'),
document.querySelectorAll('.card-2 .card2-state'),
document.querySelectorAll('.card-3 .photo-6, .card-3 .photo-5')
];
setInterval(function(){
groups.forEach(function(pair){
pair.forEach(function(el){ el.classList.toggle('is-active'); });
});
}, 3000);
})();
```
All three cards flip their `is-active` class **simultaneously** on the same 3-second interval — this is one shared timer driving three independent pairs, not three separate timers, so the cards never drift out of sync with each other.
## Animations
```css
@keyframes write-in{
from{ clip-path:inset(0 100% 0 0); }
to{ clip-path:inset(0 0 0 0); }
}
@keyframes card-text-in{
from{ opacity:0; transform:translateY(14px); }
to{ opacity:1; transform:translateY(0); }
}
```
Hero headline (per-line "written on" reveal, needs the 3 separate `<span class="line">` elements from Element 2 above):
```css
.hero h1 .line{
display:block; overflow:hidden;
clip-path:inset(0 100% 0 0);
animation:write-in 0.9s cubic-bezier(.65,0,.35,1) both;
}
.hero h1 .line:nth-child(1){ animation-delay:0.1s; }
.hero h1 .line:nth-child(2){ animation-delay:0.55s; }
.hero h1 .line:nth-child(3){ animation-delay:1s; }
```
Entrance schedule, in load order:
| Element | Keyframe | Duration | Delay | Easing |
|---|---|---|---|---|
| `.hero h1 .line` (1st) | `write-in` | 0.9s | 0.1s | cubic-bezier(.65,0,.35,1) |
| `.hero h1 .line` (2nd) | `write-in` | 0.9s | 0.55s | cubic-bezier(.65,0,.35,1) |
| `.hero h1 .line` (3rd) | `write-in` | 0.9s | 1s | cubic-bezier(.65,0,.35,1) |
| Card 1 title | `card-text-in` | 0.7s | 0.15s | cubic-bezier(.22,1,.36,1) |
| Card 1 body | `card-text-in` | 0.7s | 0.27s | cubic-bezier(.22,1,.36,1) |
| Card 2 title | `card-text-in` | 0.7s | 0.3s | cubic-bezier(.22,1,.36,1) |
| Card 2 body | `card-text-in` | 0.7s | 0.42s | cubic-bezier(.22,1,.36,1) |
| Card 3 title | `card-text-in` | 0.7s | 0.45s | cubic-bezier(.22,1,.36,1) |
| Card 3 body | `card-text-in` | 0.7s | 0.57s | cubic-bezier(.22,1,.36,1) |
Idle/interactive motion (separate from the entrance list above, none of it shares an element with an entrance animation):
* Nav link hover (`font-weight` + `scale`), sign-in button hover (`background`/`color`/`box-shadow`/`translateY`), hero-nav button hover (`opacity`), card icon hover (`translateX` + `rotate`) — all plain CSS `:hover` transitions, not keyframes.
* Photo/state crossfade — `opacity` transition on `.is-active`, driven by the JS `setInterval` above, not a CSS animation.
`prefers-reduced-motion: reduce`:
```css
@media (prefers-reduced-motion: reduce){
.photo-box img, .card2-state{ transition:opacity 0.01s linear; }
.hero h1 .line{ animation:none; clip-path:none; }
.card-head-top h2, .card-head p{ animation:none; opacity:1; transform:none; }
.card:hover .card-head-top .icon{ transform:none; }
}
```
This covers every CSS-keyframe entrance and shortens the crossfade transition to near-instant. The JS crossfade `setInterval` is gated separately (see Interactive JS Behavior #2) — it never starts at all under reduced motion, rather than running and being visually suppressed.
## Z-index Layering
No explicit `z-index` values are used anywhere in this design — every element's visual stacking is achieved purely through DOM order (later siblings paint over earlier ones) and each card/box's own `overflow:hidden`. The two photo layers in `.photo-box` (and the two `.card2-state` layers) rely on this: whichever one currently has `.is-active` (`opacity:1`) is what's visible, and DOM order only matters during the brief cross-fade overlap.
## Notes
* The single most important rule in this build: **every crop percentage on every photo is only correct because its containing box is a fixed, literal pixel size** (`465px` / `275.5735px` / `290px` / `312px`, etc.), never a `%`, `vw`, or flex-computed dynamic size. This file went through several broken iterations where card boxes were made responsive/flexible and every photo crop visibly distorted or drifted as a result. If any future change makes a card's own box size dynamic again, every percentage-based crop in that card must be re-derived from scratch — they cannot be reused as-is.
* Whenever a card element needs both a one-time entrance animation and a separate hover-driven transform (the card-head icon is the only case here), they already live on different elements (`.card-head-top h2`/`p` get the entrance animation; `.card-head-top .icon` gets the hover transform) — keep it that way; putting both on the same element means the entrance animation's `fill-mode:forwards` permanently overrides the hover transform.
* The three-card crossfade uses one shared `setInterval`, not three. Do not refactor this into per-card timers — the whole point of the design is that all three change at once.
Please ensure the output is a clean, production-ready single HTML file that renders pixel-identically to this spec: a fixed 1420×800 canvas, uniformly scaled to fit inside the browser viewport (never upscaled past 100%, letterboxed rather than cropped on mismatched aspect ratios), with every card's photo crop staying pixel-perfect because its containing box never becomes an independently-stretchable dimension.