Card skeleton
A grid of cards that holds each photo, title, detail and price in place while it loads.
Check your Wi-Fi or mobile data. We'll reconnect on our own as soon as it's back.
Last synced today at 10:42 AM
Live preview: try it.
<!-- Icons: Lucide (lucide.dev), ISC licence -->
<style>
.odl-page-offline { box-sizing: border-box; inline-size: 100%; max-inline-size: 45rem; margin-inline: auto; container-type: inline-size; font: var(--ds-text-body-md); color: var(--ds-color-text-primary); text-wrap: pretty; }
.odl-page-offline *, .odl-page-offline *::before, .odl-page-offline *::after { box-sizing: border-box; }
.odl-page-offline svg { flex: none; inline-size: var(--ds-size-icon-sm); block-size: var(--ds-size-icon-sm); fill: none; stroke: currentColor; stroke-width: var(--ds-icon-stroke-md); }
.odl-page-offline h2, .odl-page-offline h3, .odl-page-offline p, .odl-page-offline ul, .odl-page-offline ol, .odl-page-offline dl, .odl-page-offline dd { margin: 0; }
.odl-page-offline h2, .odl-page-offline h3 { text-wrap: balance; }
.odl-page-offline .odl-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--ds-space-inline-sm); min-block-size: max(var(--ds-size-control-md), var(--ds-size-target-min)); padding: 0 var(--ds-space-inset-md);
border: var(--ds-border-width-default) solid transparent; border-radius: var(--ds-radius-control); background: var(--ds-color-bg-action-primary); color: var(--ds-color-text-on-action);
font: var(--ds-text-label-lg); font-weight: var(--ds-font-weight-label); text-decoration: none; white-space: nowrap; cursor: pointer; transition: background-color var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-page-offline .odl-btn:hover { background: var(--ds-color-bg-action-primary-hover); }
.odl-page-offline .odl-btn:active { background: var(--ds-color-bg-action-primary-pressed); }
.odl-page-offline .odl-btn.is-quiet { border-color: var(--ds-color-border-default); background: var(--ds-color-surface-raised); color: var(--ds-color-text-primary); }
.odl-page-offline .odl-btn.is-quiet:hover { background: var(--ds-color-bg-neutral-subtle); }
.odl-page-offline .odl-btn.is-text { padding-inline: var(--ds-space-inset-sm); background: transparent; color: var(--ds-color-text-link); }
.odl-page-offline .odl-btn.is-text:hover { background: var(--ds-color-bg-neutral-subtle); }
.odl-page-offline .odl-btn[disabled], .odl-page-offline .odl-btn[aria-disabled="true"] { opacity: var(--ds-opacity-disabled-container); cursor: not-allowed; }
.odl-page-offline button:focus-visible, .odl-page-offline a:focus-visible, .odl-page-offline input:focus-visible, .odl-page-offline textarea:focus-visible, .odl-page-offline [tabindex]:focus-visible { outline: var(--ds-focus-ring-width) solid var(--ds-color-border-focus); outline-offset: var(--ds-focus-ring-offset); }
.odl-page-offline .odl-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.odl-page-offline { display: grid; justify-items: center; align-content: center; min-block-size: 32rem; padding: var(--ds-space-section-md) var(--ds-space-inset-xl); text-align: center;
background: var(--ds-color-surface-base); border-radius: var(--ds-radius-container); }
.odl-page-offline .odl-art { display: grid; place-items: center; inline-size: calc(var(--ds-size-control-lg) * 1.5); aspect-ratio: 1; margin-block-end: var(--ds-space-stack-lg);
border: var(--ds-border-width-default) solid var(--ds-color-border-subtle); border-radius: var(--ds-radius-container); background: var(--ds-color-surface-sunken); color: var(--ds-color-icon-default); }
.odl-page-offline .odl-art svg { inline-size: var(--ds-size-icon-lg); block-size: var(--ds-size-icon-lg); stroke-width: var(--ds-icon-stroke-sm); }
.odl-page-offline .odl-code { margin-block-end: var(--ds-space-stack-xs); color: var(--ds-color-text-tertiary); font: var(--ds-text-label-md); font-variant-numeric: tabular-nums; }
.odl-page-offline h2 { font: var(--ds-text-headline-sm); font-weight: var(--ds-font-weight-heading); }
.odl-page-offline .odl-lead { max-inline-size: 44ch; margin-block-start: var(--ds-space-stack-sm); color: var(--ds-color-text-secondary); }
.odl-page-offline .odl-acts { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: var(--ds-space-inline-sm); margin-block-start: var(--ds-space-stack-xl); }
.odl-page-offline .odl-box { inline-size: 100%; max-inline-size: 30rem; margin-block-start: var(--ds-space-stack-xl); padding: var(--ds-space-inset-lg); text-align: start;
border: var(--ds-border-width-default) solid var(--ds-color-border-subtle); border-radius: var(--ds-radius-container); background: var(--ds-color-surface-raised); }
.odl-page-offline .odl-box + .odl-acts { margin-block-start: var(--ds-space-stack-lg); }
.odl-page-offline .odl-box h3 { font: var(--ds-text-label-lg); font-weight: var(--ds-font-weight-label); }
.odl-page-offline .odl-box p { color: var(--ds-color-text-secondary); }
.odl-page-offline .odl-live { min-block-size: 1lh; margin-block-start: var(--ds-space-stack-md); color: var(--ds-color-text-secondary); font: var(--ds-text-body-sm); }
@container (max-width: 30rem) {
.odl-page-offline { padding: var(--ds-space-section-sm) var(--ds-space-inset-lg); }
.odl-page-offline .odl-acts { flex-direction: column; align-self: stretch; inline-size: 100%; }
.odl-page-offline .odl-acts > * { inline-size: 100%; white-space: normal; text-align: center; }
}
.odl-page-offline .odl-spin { display: none; animation: odl-spin calc(var(--ds-motion-duration-long) * 3) var(--ds-motion-easing-linear) infinite; }
.odl-page-offline .is-busy .odl-spin { display: block; }
.odl-page-offline .is-busy .odl-idle { display: none; }
@keyframes odl-spin { to { rotate: 1turn; } }
.odl-page-offline .odl-box ul { display: grid; gap: var(--ds-space-stack-sm); margin-block-start: var(--ds-space-stack-sm); padding: 0; list-style: none; }
.odl-page-offline .odl-box li { display: grid; grid-template-columns: var(--ds-size-icon-sm) minmax(0, 1fr); gap: var(--ds-space-inline-md); align-items: start; }
.odl-page-offline .odl-box li svg { margin-block-start: calc((1lh - var(--ds-size-icon-sm)) / 2); color: var(--ds-color-text-success); }
.odl-page-offline .odl-box li.is-wait svg { color: var(--ds-color-icon-subtle); }
.odl-page-offline .odl-box li.is-wait { color: var(--ds-color-text-secondary); }
.odl-page-offline .odl-box h3 + ul + h3 { margin-block-start: var(--ds-space-stack-lg); }
@media (prefers-reduced-motion: reduce) { .odl-page-offline, .odl-page-offline *, .odl-page-offline *::before, .odl-page-offline *::after { transition: none !important; animation: none !important; } }
</style>
<section class="odl-page-offline" aria-label="You're offline">
<span class="odl-art" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h.01"/><path d="M8.5 16.429a5 5 0 0 1 7 0"/><path d="M5 12.859a10 10 0 0 1 5.17-2.69"/><path d="M19 12.859a10 10 0 0 0-2.007-1.523"/><path d="M2 8.82a15 15 0 0 1 4.177-2.643"/><path d="M22 8.82a15 15 0 0 0-11.288-3.764"/><path d="m2 2 20 20"/></svg></span>
<h2 data-od-slot="title">You're offline</h2>
<p class="odl-lead" data-od-slot="message">Check your Wi-Fi or mobile data. We'll reconnect on our own as soon as it's back.</p>
<div class="odl-box">
<h3>Still works on this device</h3>
<ul data-od-list="works"><li data-od-item><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span data-od-slot="text">Read the 24 notes you opened this week</span></li><li data-od-item><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span>Write new notes. They sync when you're back</span></li><li data-od-item><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span>Search everything saved here</span></li></ul>
<h3>Waits for a connection</h3>
<ul data-od-list="waits"><li class="is-wait" data-od-item><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg><span data-od-slot="text">Sharing, comments and new files from your team</span></li></ul>
</div>
<div class="odl-acts"><button type="button" class="odl-btn" data-od-action="reconnect"><svg class="odl-idle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg><svg class="odl-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>Try again now</button></div>
<p class="odl-live" role="status" data-od-slot="synced">Last synced today at 10:42 AM</p>
</section>
<script>
document.querySelectorAll(".odl-page-offline").forEach(function (root) {
if (root.hasAttribute("data-odl-wired")) return;
root.setAttribute("data-odl-wired", "");
var btn = root.querySelector("[data-od-action='reconnect']"), live = root.querySelector(".odl-live");
if (btn) btn.addEventListener("click", function () {
if (btn.getAttribute("aria-disabled") === "true") return;
btn.setAttribute("aria-disabled", "true"); btn.classList.add("is-busy"); live.textContent = "Checking your connection…";
setTimeout(function () {
btn.removeAttribute("aria-disabled"); btn.classList.remove("is-busy");
live.textContent = navigator.onLine ? "You're back online. Syncing your changes." : "Still offline. We'll keep trying on our own.";
}, 1200);
});
});
</script>
A grid of cards that holds each photo, title, detail and price in place while it loads.
A planned-downtime page that says what is paused.
A 403 page that says who owns the page and lets the person ask for access with an.