Media
Image, video, audio and hero sections - helpers/media.css, components/video.css, components/audio.css, components/hero.css, components/cover.css, components/overlay.css.
Images
| Class | Purpose |
|---|---|
.ncss-ratio-video/-square/-portrait | aspect-ratio via custom property, no padding hack needed |
.ncss-img-cover / .ncss-img-contain | object-fit, -cover including --ncss-focus-point |
.ncss-img-rounded / .ncss-img-circle | Corner radius / full circle shape |
.ncss-img-dim-in-dark | Dims in dark mode (icons/graphics with a hard light background) |
.ncss-figure | Figure+figcaption layout (typography already sits in base.css) |
.ncss-figure--width-25/-50/-75/-full | Width of a standalone image inside flowing text (e.g. from a rich-text editor) |
.ncss-figure--align-left/-right/-center | Text wrap (float) or centering for the same standalone image |
Art direction: <picture><source media="..."> reliably loads a different image file per breakpoint - the native, correct tool for this.
Standalone image in flowing text: .ncss-figure--width-*/-align-* are deliberately separate from the .ncss-width-1-2/-1-3/etc. classes in Layout - those are meant for sibling tiles inside a .ncss-flex-grid, not a single image with text wrap. Markup:
<figure class="ncss-figure ncss-figure--width-50 ncss-figure--align-left">
<img src="..." alt="...">
<figcaption>Caption</figcaption>
</figure>
<p>Text flows around the image ...</p>
Cover (cropping embeds to fill)
For <img>/<video>, .ncss-img-cover above is enough (native object-fit). components/cover.css covers the one case object-fit does NOT reliably solve: <iframe> embeds (YouTube/Vimeo/Google Maps) - object-fit doesn't affect the embedded document in most browsers.
<div class="ncss-cover-container" style="height: 20rem;">
<iframe class="ncss-cover" src="https://www.youtube.com/embed/..." title="..." allowfullscreen></iframe>
</div>
Technique: the classic min-width/min-height:100% + width/height:auto crop pattern (no object-fit) - needs an EXPLICIT aspect-ratio on the embedded element itself for this (default: --ncss-aspect-video, overridable via style="aspect-ratio: ..."). Relying on an <iframe>'s width/height HTML attributes instead gives NO aspect-ratio-accurate result on an absolutely positioned element (confirmed via a real test). .ncss-cover-container needs its own height supplied from outside (fixed, %, or e.g. inside a .ncss-hero) - unlike .ncss-ratio-video, which derives height from width.
Video & Audio
.ncss-video | Aspect-ratio wrapper for <video> |
.ncss-video--responsive-crop | Different crop of the same file via object-fit + breakpoint - there's no reliable native "different file per breakpoint" for video, unlike images (<source media> there is now just Safari legacy) |
.ncss-audio | Consistent width for <audio controls> |
.ncss-audio-card | Audio player with a title |
Overlay (panel over image/video)
components/overlay.css - a panel over an image (caption, CTA, gradient scrim). For an entire page section with a background image: Hero Section below - Overlay is the smaller, media-local variant of the same underlying principle.
<div class="ncss-overlay-container">
<img src="photo.jpg" alt="...">
<div class="ncss-overlay ncss-overlay--bottom ncss-overlay--scrim">
<p class="ncss-text-light">Caption</p>
</div>
</div>
| Class | Purpose |
|---|---|
.ncss-overlay | Default: covers the whole area (inset: 0) |
.ncss-overlay--top / --bottom | Only the top/bottom edge instead of the whole area |
.ncss-overlay--center | Centered, at its own size (no padding) - e.g. for a centered play icon |
.ncss-overlay--scrim | Gradient scrim (dark → transparent) - readable text over varying photo content, mirrors automatically with --top |
.ncss-overlay--tint | Even, semi-transparent fill instead of a gradient (--ncss-color-overlay) |
Combines with existing ncss building blocks instead of duplicating them: .ncss-reveal-on-hover (Scroll & Visibility) for "only visible on hover" - works unchanged, because .ncss-overlay is a direct child of .ncss-overlay-container. .ncss-text-light/-dark (Typography) for text that should stay readable regardless of the image content.
Hero Section
Image background with content on top, components/hero.css.
<section class="ncss-hero">
<div class="ncss-hero-media" style="background-image: url(image.jpg)"></div>
<div class="ncss-hero-content ncss-hero-content--filled">
<h2>Heading</h2>
<p>Text...</p>
</div>
</section>
.ncss-hero | Wrapper, min-height: 22rem, overflow: clip |
.ncss-hero-media | Its own layer for the background image (including a readability scrim) - always set background-image inline, never via a custom property, see Known Limitations |
.ncss-hero-content--transparent | Text directly on the image (default) |
.ncss-hero-content--filled | Text on an opaque card on top |
.ncss-hero--parallax | Image moves slower than the content while scrolling, via animation-timeline: view() - no JS, no background-attachment: fixed (deliberately never supported on iOS Safari) |
Background video instead of an image
The same .ncss-hero-media, now with a real <video> child instead of background-image - object-fit: cover works reliably for <video> (unlike <iframe>, see Cover above), no special cropping needed:
<div class="ncss-hero-media">
<video src="clip.mp4" autoplay muted loop playsinline poster="frame.jpg"></video>
</div>
autoplay NEEDS muted (otherwise virtually every browser blocks autostart), playsinline prevents fullscreen takeover on iOS Safari, poster shows a still frame before loading. For prefers-reduced-motion: pure CSS can't control <video> playback (unlike the animation-timeline used by --parallax above) - js/hero-video-motion.js (opt-in, only include it on pages with an actual background video) removes autoplay and pauses the video when that preference is active; the video stays visible (poster/last frame), just without automatic motion:
<script src="js/hero-video-motion.js" defer></script>