ph/components

Tile

Square image tile with a caption.

a pen plotter that draws in Bayer-dithered strokes.

pixel plotter ada & grace
glider clock alan

Code

Copy into src/components/ph/ .

tile.css
/* Crossfade only, so it never reads as the image still loading. */
.ph-tile__desc {
  position: absolute;
  inset: 0;
  overflow: auto;
  margin: 0;
  padding: 14px;
  background: var(--background);
  opacity: 0;
  filter: blur(3px);
  pointer-events: none;
  transition:
    opacity 100ms var(--ph-ease),
    filter 100ms var(--ph-ease);
}

@media (hover: hover) and (pointer: fine) {
  .ph-tile:is(:hover, :has(:focus-visible)) .ph-tile__desc {
    opacity: 1;
    filter: blur(0);
    transition-duration: 160ms;
  }
}

@media (hover: none) {
  .ph-tile__desc {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ph-tile__desc {
    filter: none;
    transition: opacity 100ms linear;
  }
}
Tile.astro
---
import Link from "./Link.astro";
import "./tile.css";

type Props = {
  title: string;
  href?: string;
  meta?: string;
  image?: string;
  alt?: string;
  description?: string;
  size?: string;
  class?: string;
};

const { title, href, meta, image, alt = title, description, size = "232px", class: className } = Astro.props;
const hasImage = image || Astro.slots.has("image");
---

<figure class:list={["ph-tile flex w-[var(--tile)] max-w-full flex-col gap-2", className]} style={`--tile: ${size}`}>
  <div class="relative aspect-square w-full overflow-hidden border border-border">
    {
      hasImage ? (
        <slot name="image">
          <img class="size-full object-cover" src={image} alt={alt} loading="lazy" decoding="async" />
        </slot>
      ) : (
        <div class="dither-field size-full" />
      )
    }
    {description && <p class="ph-tile__desc text-[13px] leading-[1.4]">{description}</p>}
  </div>

  <figcaption class="flex min-w-0 flex-col gap-0.5">
    <span class="truncate leading-[1.35]">{href ? <Link href={href}>{title}</Link> : title}</span>
    {meta && <span class="truncate text-[13px] leading-[1.35]">{meta}</span>}
  </figcaption>
</figure>

Also needs