Window seat
const CLOUDS = [
{ top: 22, w: 46, d: 0 },
{ top: 48, w: 34, d: -3 },
{ top: 66, w: 56, d: -6.5 },
];
const css = `
@keyframes card-window-seat-drift { from { transform: translateX(80px); } to { transform: translateX(-90px); } }
.card-window-seat {
display: flex;
align-items: center;
gap: 16px;
width: 260px;
padding: 14px;
border-radius: 18px;
background: light-dark(#f3f4f7, #1a1c1f);
box-shadow: inset 0 0 0 1px light-dark(#dbdde1, #2e3035), 0 12px 24px -14px light-dark(rgba(0, 0, 0, 0.21), rgba(0, 0, 0, 0.7));
outline: none;
}
.card-window-seat:focus-visible { outline: 2px solid light-dark(#50535b, #c6c8cd); outline-offset: 3px; }
/* The cabin window: a rounded frame with the sky behind and a shade that slides up. */
.card-window-seat__frame {
position: relative;
flex: none;
width: 78px;
height: 104px;
padding: 7px;
border-radius: 40px;
background: light-dark(#dbdde1, #2e3035);
box-shadow: inset 0 2px 4px light-dark(rgba(0, 0, 0, 0.11), rgba(0, 0, 0, 0.35)), 0 0 0 1px light-dark(#cbced3, #393c41);
}
.card-window-seat__pane { position: relative; height: 100%; border-radius: 34px; overflow: hidden; background: linear-gradient(light-dark(#6d7179, #7d8087), light-dark(#50535b, #c6c8cd)); }
.card-window-seat__cloud { position: absolute; left: 0; height: 12px; border-radius: 8px; background: light-dark(#373a40, #f1f2f3); opacity: 0.85; animation: card-window-seat-drift 9s linear infinite; }
.card-window-seat__cloud::before { content: ''; position: absolute; left: 25%; bottom: 5px; width: 50%; height: 12px; border-radius: 50%; background: inherit; }
.card-window-seat__wing { position: absolute; right: -6px; bottom: 10px; width: 64px; height: 30px; background: light-dark(#ebedf0, #232529); clip-path: polygon(0 100%, 100% 30%, 100% 50%, 18% 100%); }
.card-window-seat__shade {
position: absolute;
inset: 0;
border-radius: 34px;
background: linear-gradient(light-dark(#ebedf0, #232529), light-dark(#dbdde1, #2e3035));
box-shadow: inset 0 -2px 0 light-dark(#6d7179, #7d8087);
transition: transform 0.7s cubic-bezier(0.5, 0, 0.2, 1);
}
.card-window-seat__shade::after { content: ''; position: absolute; left: 50%; bottom: 6px; width: 16px; height: 3px; margin-left: -8px; border-radius: 2px; background: light-dark(#6d7179, #7d8087); }
.card-window-seat:is(:hover, :focus-visible) .card-window-seat__shade { transform: translateY(-84%); }
.card-window-seat__info { display: grid; gap: 2px; min-width: 0; color: light-dark(#5f626a, #a7abb2); font-size: 11.5px; }
.card-window-seat__route { color: light-dark(#373a40, #f1f2f3); font-size: 19px; font-weight: 600; letter-spacing: 0.02em; }
.card-window-seat__route span { color: light-dark(#5f626a, #a7abb2); font-weight: 400; }
.card-window-seat__seat { margin-top: 8px; display: inline-flex; gap: 6px; align-items: baseline; color: light-dark(#50535b, #c6c8cd); }
.card-window-seat__seat b { color: light-dark(#373a40, #f1f2f3); font-size: 15px; font-weight: 600; }
@media (prefers-reduced-motion: reduce) {
.card-window-seat__cloud { animation: none; transform: translateX(10px); }
.card-window-seat__shade { transition-duration: 0.01s; }
}
`;
export function WindowSeatCard() {
return (
<>
<style>
{css}
</style>
<div className="card-window-seat" tabIndex={0} role="group" aria-label="Flight San Francisco to Tokyo Haneda, seat 32A by the window, 11 hours 5 minutes">
<div className="card-window-seat__frame" aria-hidden="true">
<div className="card-window-seat__pane">
{CLOUDS.map((c, i) => (
<span key={i} className="card-window-seat__cloud" style={{ top: c.top, width: c.w, animationDelay: `${c.d}s` }} />
))}
<span className="card-window-seat__wing" />
<span className="card-window-seat__shade" />
</div>
</div>
<div className="card-window-seat__info" aria-hidden="true">
<span className="card-window-seat__route">
SFO <span>→</span> HND
</span>
<span>Departs 11:40 · 11 h 05</span>
<span className="card-window-seat__seat">
Seat <b>32A</b> Window
</span>
</div>
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
<div class="card-window-seat" tabindex="0" role="group" aria-label="Flight San Francisco to Tokyo Haneda, seat 32A by the window, 11 hours 5 minutes">
<div class="card-window-seat__frame" aria-hidden="true">
<div class="card-window-seat__pane">
<span class="card-window-seat__cloud" style="top:22px;width:46px;animation-delay:0s"></span>
<span class="card-window-seat__cloud" style="top:48px;width:34px;animation-delay:-3s"></span>
<span class="card-window-seat__cloud" style="top:66px;width:56px;animation-delay:-6.5s"></span>
<span class="card-window-seat__wing"></span>
<span class="card-window-seat__shade"></span>
</div>
</div>
<div class="card-window-seat__info" aria-hidden="true">
<span class="card-window-seat__route">SFO <span>→</span> HND</span>
<span>Departs 11:40 · 11 h 05</span>
<span class="card-window-seat__seat">Seat <b>32A</b> Window</span>
</div>
</div>@keyframes card-window-seat-drift { from { transform: translateX(80px); } to { transform: translateX(-90px); } }
.card-window-seat {
display: flex;
align-items: center;
gap: 16px;
width: 260px;
padding: 14px;
border-radius: 18px;
background: light-dark(#f3f4f7, #1a1c1f);
box-shadow: inset 0 0 0 1px light-dark(#dbdde1, #2e3035), 0 12px 24px -14px light-dark(rgba(0, 0, 0, 0.21), rgba(0, 0, 0, 0.7));
outline: none;
}
.card-window-seat:focus-visible { outline: 2px solid light-dark(#50535b, #c6c8cd); outline-offset: 3px; }
/* The cabin window: a rounded frame with the sky behind and a shade that slides up. */
.card-window-seat__frame {
position: relative;
flex: none;
width: 78px;
height: 104px;
padding: 7px;
border-radius: 40px;
background: light-dark(#dbdde1, #2e3035);
box-shadow: inset 0 2px 4px light-dark(rgba(0, 0, 0, 0.11), rgba(0, 0, 0, 0.35)), 0 0 0 1px light-dark(#cbced3, #393c41);
}
.card-window-seat__pane { position: relative; height: 100%; border-radius: 34px; overflow: hidden; background: linear-gradient(light-dark(#6d7179, #7d8087), light-dark(#50535b, #c6c8cd)); }
.card-window-seat__cloud { position: absolute; left: 0; height: 12px; border-radius: 8px; background: light-dark(#373a40, #f1f2f3); opacity: 0.85; animation: card-window-seat-drift 9s linear infinite; }
.card-window-seat__cloud::before { content: ''; position: absolute; left: 25%; bottom: 5px; width: 50%; height: 12px; border-radius: 50%; background: inherit; }
.card-window-seat__wing { position: absolute; right: -6px; bottom: 10px; width: 64px; height: 30px; background: light-dark(#ebedf0, #232529); clip-path: polygon(0 100%, 100% 30%, 100% 50%, 18% 100%); }
.card-window-seat__shade {
position: absolute;
inset: 0;
border-radius: 34px;
background: linear-gradient(light-dark(#ebedf0, #232529), light-dark(#dbdde1, #2e3035));
box-shadow: inset 0 -2px 0 light-dark(#6d7179, #7d8087);
transition: transform 0.7s cubic-bezier(0.5, 0, 0.2, 1);
}
.card-window-seat__shade::after { content: ''; position: absolute; left: 50%; bottom: 6px; width: 16px; height: 3px; margin-left: -8px; border-radius: 2px; background: light-dark(#6d7179, #7d8087); }
.card-window-seat:is(:hover, :focus-visible) .card-window-seat__shade { transform: translateY(-84%); }
.card-window-seat__info { display: grid; gap: 2px; min-width: 0; color: light-dark(#5f626a, #a7abb2); font-size: 11.5px; }
.card-window-seat__route { color: light-dark(#373a40, #f1f2f3); font-size: 19px; font-weight: 600; letter-spacing: 0.02em; }
.card-window-seat__route span { color: light-dark(#5f626a, #a7abb2); font-weight: 400; }
.card-window-seat__seat { margin-top: 8px; display: inline-flex; gap: 6px; align-items: baseline; color: light-dark(#50535b, #c6c8cd); }
.card-window-seat__seat b { color: light-dark(#373a40, #f1f2f3); font-size: 15px; font-weight: 600; }
@media (prefers-reduced-motion: reduce) {
.card-window-seat__cloud { animation: none; transform: translateX(10px); }
.card-window-seat__shade { transition-duration: 0.01s; }
}Goal: Create a "Window seat" card.
Visual behavior: A flight card with a cabin window: on hover the shade slides up on clouds drifting past the wing.
Technique:
- The window is a rounded frame with the sky behind it; the shade covers it and translates up on hover and focus.
- Clouds drift past on a loop and the wing is a clip-path polygon over the sky.
Accessibility: The card is focusable (tabIndex 0, role="group") with an aria-label that carries its content, so keyboard focus reveals the same state as hover and screen readers get the information in one go; the drawing is aria-hidden. Under prefers-reduced-motion the clouds stay still and the shade opens almost at once.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Window seat" card.
Visual behavior: A flight card with a cabin window: on hover the shade slides up on clouds drifting past the wing.
Technique:
- The window is a rounded frame with the sky behind it; the shade covers it and translates up on hover and focus.
- Clouds drift past on a loop and the wing is a clip-path polygon over the sky.
Accessibility: The card is focusable (tabIndex 0, role="group") with an aria-label that carries its content, so keyboard focus reveals the same state as hover and screen readers get the information in one go; the drawing is aria-hidden. Under prefers-reduced-motion the clouds stay still and the shade opens almost at once.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
A flight card with a cabin window: on hover the shade slides up on clouds drifting past the wing.
- Dependencies
- None
- Difficulty
- Beginner
- Triggers
- hover, card, ambient
- Techniques
- css transition, keyframes, clip path, gradient
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
The card is focusable (tabIndex 0, role="group") with an aria-label that carries its content, so keyboard focus reveals the same state as hover and screen readers get the information in one go; the drawing is aria-hidden. Under prefers-reduced-motion the clouds stay still and the shade opens almost at once.