# Tile Grid Preloader Reveal — Glass Tiles, Flip-Hopping Progress Marker & Masked Hero Text

## Goal
Build a landing-page load sequence. A pale veil covers the page and a grid of **frosted glass tiles** (always an odd number of rows and columns, plus one hidden ring outside the viewport) **fills in random order**. A dark square **progress marker** sits on the middle row and **hops with GSAP Flip** across four stops — showing `25`, `50`, `75`, then a small logo — while the stops it leaves turn back into glass. When the marker lands in the centre, the veil is removed, and every tile **folds down on its top edge** (`scaleY → 0`, random order), briefly **blurring the hero photo** underneath through the glass; the hero then settles from a slight zoom, and the **headline characters, subtitle lines and nav words rise out of their masks**.

## Tech
Vanilla HTML/CSS/JS with ES module imports. `gsap` (npm) plus the plugins **`Flip`** and **`SplitText`**. Fonts: **Barlow Condensed** (800), **DM Mono** (500), **DM Sans** (500–600).

## Layout / HTML
```html
<div class="tg">
  <div class="tg-preloader" aria-hidden="true">
    <div class="tg-preloader__bg"></div>
    <div class="tg-preloader__grid"></div>
    <div class="tg-marker"><span class="tg-marker__value">25</span></div>
  </div>

  <nav class="tg-nav">
    <a class="tg-brand" href="#">Frme</a>
    <div class="tg-links"><a href="#">Work</a><a href="#">Studio</a><a href="#">Process</a><a href="#">Contact</a></div>
  </nav>

  <section class="tg-hero">
    <div class="tg-hero__bg"><img src="…" alt="" /></div>
    <div class="tg-hero__title"><h1>Frme</h1></div>
    <div class="tg-hero__subtitle"><h3>Shaping the world of digital expression</h3></div>
  </section>
</div>
```

## Styling
`:root` variables: `--tg-ink:#f4f3ef`, `--tg-veil:#e9e8e3`, `--tg-tile:rgba(255,255,255,.55)`, `--tg-tile-line:rgba(20,22,27,.08)`, `--tg-marker:#14161b`, `--tg-marker-ink:#f4f3ef`, `--tg-accent:#c4d600`.

- `.tg` — `min-height:100svh; background:#0f1114; color:var(--tg-ink); font-family:"DM Sans"`.
- `.tg-preloader` — `position:fixed; inset:0; z-index:20; overflow:hidden`. `.tg-preloader__bg` — `position:absolute; inset:0; background:var(--tg-veil)`. `.tg-preloader__grid` — `position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); display:flex; flex-wrap:wrap; z-index:1` (JS sets its width/height).
- `.tg-tile` — `position:relative; background:var(--tg-tile); backdrop-filter:blur(10px) saturate(1.2); outline:1px solid var(--tg-tile-line); outline-offset:-.5px`; `::after` is a soft glass sheen `linear-gradient(160deg, rgba(255,255,255,.35), transparent 55%)`. `.tg-tile.is-stop` — `background:var(--tg-marker); outline-color:var(--tg-marker)` with the sheen at 12% opacity.
- `.tg-marker` — `position:absolute; top:0; left:0; z-index:2; display:flex; align-items:center; justify-content:center; background:var(--tg-marker); color:var(--tg-marker-ink); font:500 .85rem "DM Mono"; font-variant-numeric:tabular-nums; box-shadow:0 18px 40px -18px rgba(0,0,0,.55)`; an inline `svg` inside it is `46%` wide/high.
- `.tg-nav` — fixed, `z-index:5; padding:2rem 2.5rem; display:flex; justify-content:space-between; align-items:flex-start`; links `.95rem` weight 500, brand 600; `.tg-links { display:flex; gap:2.5rem }`.
- `.tg-hero` — `height:100svh; padding:2.5rem; display:flex; justify-content:space-between; align-items:flex-end; overflow:hidden`. `.tg-hero__bg` absolute `inset:0` with a `::after` gradient `rgba(0,0,0,.1) → rgba(0,0,0,.45)`; `img` `object-fit:cover`.
- `h1` — `color:var(--tg-accent); font:800 clamp(4rem,20vw,24rem) "Barlow Condensed"; letter-spacing:-.02em; text-transform:uppercase; line-height:.8`. `h3` — `width:20rem; font-weight:500; font-size:clamp(1.4rem,2.6vw,3rem); line-height:1.2`.
- `@media (max-width:1000px)`: nav padding 1.5rem, links stacked right-aligned, hero as a column with `align-items:flex-start`.

## GSAP effect (be exact)
`gsap.registerPlugin(Flip, SplitText)`. Everything in `mount(config)` → `destroy()`.

```js
const DEFAULTS = { startDelay: .6, fillDuration: 2.5, hopDuration: 1, collapseDuration: .75, collapseStagger: .0035, titleStagger: .05, tileSize: 85 };
```

**Grid build**
- `isDesktop = innerWidth >= 1000`; `maxTile = isDesktop ? tileSize : round(tileSize * .6)`.
- `snapToOdd(v) = v % 2 === 0 ? v - 1 : v`; `cols = max(3, snapToOdd(floor(innerWidth / maxTile)))`, `rows = max(3, snapToOdd(floor(innerHeight / maxTile)))`.
- `tile = min(innerWidth / cols, innerHeight / rows)`; `totalCols = cols + 2`, `totalRows = rows + 2` (one hidden ring around the viewport so the centred grid always overflows the edges).
- Grid element sized `totalCols*tile × totalRows*tile`; create `totalCols*totalRows` `.tg-tile` divs of `tile × tile`.
- `midRow = floor(totalRows/2)`, `midCol = floor(totalCols/2)`, `at(col) = tiles[midRow*totalCols + col]`.
- Stops: `reach = isDesktop ? [5,3,5] : [2,1,2]` → `stops = [at(midCol - reach[0]), at(midCol + reach[1]), at(midCol + reach[2]), at(midCol)]`, each gets `.is-stop`.
- `fillTiles = tiles not in stops` → `gsap.set(fillTiles, { opacity: 0 })`.
- Marker: `gsap.set(marker, { width: tile, height: tile })` and position it on the first stop via `getBoundingClientRect()` offsets relative to the preloader (`left = tileRect.left - preloaderRect.left`, same for top).

**Marker hop** — `hopTo(tile, html)`: `state = Flip.getState(marker)`; `gsap.set(marker, offsetOf(tile))`; replace the marker's inner HTML; `Flip.from(state, { duration: hopDuration, ease: "power2.inOut" })`.

**Text prep** — `SplitText.create(".tg-hero__title h1", { type:"chars", mask:"chars" })`, `…h3 { type:"lines", mask:"lines" }`, `".tg-nav a" { type:"words", mask:"words" }`; `gsap.set([chars, lines, words], { yPercent: 110 })`; `gsap.set(heroBg, { scale: 1.12 })`.

**Timeline** (`delay: startDelay`):
1. `0` — `fillTiles` `opacity: 1`, duration `.125`, `stagger: { each: fillDuration / fillTiles.length, from: "random" }`.
2. `0.25` — callback `hopTo(stops[1], "<span class='tg-marker__value'>50</span>")`.
3. `0.25 + hop*1.25` — `hopTo(stops[2], "75")`.
4. `0.25 + hop*2.5` — `hopTo(stops[3], LOGO_SVG)` and, at the same time, remove `.is-stop` from the first three stops (they melt back into the glass grid). `LOGO_SVG` is a 24×24 stroke icon (a simple "F": `M4 20V4h13` + `M4 12h9`, stroke-width 2.2, round caps).
5. `">${hop}"` — callback: hide `.tg-preloader__bg` (`display:none`) — now the glass tiles blur the hero beneath.
6. `"+=0.35"` — `collapsing = tiles except stops[3]` → `{ scaleY: 0, transformOrigin: "top", duration: collapseDuration, ease: "power3.out", stagger: { each: collapseStagger, from: "random" } }`.
7. `"<"` — `[stops[3], marker]` same `scaleY: 0` collapse (no stagger).
8. `"<"` — hero background `scale: 1`, 2.2s, `power3.out`.
9. callback — hide the whole preloader (`display:none`).
10. `"<0.2"` — title chars `yPercent: 0`, 1s, `power3.out`, `stagger: { each: titleStagger, from: "random" }`.
11. `"<0.5"` — subtitle lines `yPercent: 0`, 1s, `stagger: .1`.
12. `"<0.25"` — nav words `yPercent: 0`, 1s, `stagger: .075`.

**destroy()** — kill the timeline and tweens, `revert()` the three splits, empty the grid, reset the marker's style/HTML, `clearProps` on the hero background, restore the preloader/veil `display`.

## Assets / images
One calm landscape hero (≈16:9, ≥1600px): a misty lake, a field, a coastline. It is seen through frosted tiles for a moment, so it should have soft large shapes rather than fine detail. The logo is an inline SVG (no asset).

## Behavior notes
- The sequence takes ≈7s at defaults and runs once per page load; it is a page-level intro (`position:fixed` veil).
- Grid maths runs at mount; on resize mid-sequence nothing recalculates (the preloader is gone in seconds).
- The tiles use `backdrop-filter`; if you need to support browsers without it, the collapse still reads fine — the blur is a bonus, not the mechanism.
- Keep the prefixed variables on `:root` for theming (`--tg-accent`, `--tg-veil`, `--tg-marker`).

## Images

This component ships with 1 reference asset, served publicly.
Use them as-is to reproduce the demo faithfully, then swap in your own — the layout expects the
same aspect ratios.

```
https://motionprompts.dev/c/tile-grid-preloader-reveal/hero.jpg
```

They are hotlinkable for prototyping. For anything you ship, replace them: they are licensed for
demonstration of this component, not for redistribution.

## Using this outside its demo page

This component is written as a complete page — that is how the demo is meant to look. If you are dropping it into an existing project, or combining it with other components, these are the things it declares at document level and that you need to move or reconcile first.

- **Palette on `:root`** — `--tg-ink`, `--tg-veil`, `--tg-tile`, `--tg-tile-line`, `--tg-marker`, `--tg-marker-ink`, `--tg-accent`. These names are not namespaced and they collide: `--ink` is defined by 164 of the 219 components in this catalogue, `--paper` by 94, `--muted` by 80, each with different values — and they will also collide with whatever your own project defines. Move them onto the component's wrapper (`.my-section { --ink: … }`) or rename them with a prefix.
- **Rules on `html, body`** — the demo owns the whole document, so these set the page background, typography and resets. Dropped into an existing project they restyle the entire page, not just this section. Re-target them at the component's wrapper before using it.
- **Full-screen overlay** — a fixed element covers the viewport (a loader or transition). Only one may exist per page and it must remove itself when done. If your page already has one, keep that and drop this; otherwise the second silently hides the first.
