Motion · Component reference

Scroll reveal

Use a brief reveal to reinforce sequence while keeping the content available.

Published and reviewed · Starlit’s implementation notes

Try the live example

Research and our take

We explored the InstaLanding Scroll reveal reference as part of our template research. Its composition was a starting point for Starlit’s palette, system typography, sample content and local interaction treatment. This is an adapted study, not a claim that Starlit invented the underlying pattern.

Our take is described in the reasoning and tradeoffs below. Examples use fictional content and keep their actions local. Read the provenance and reuse notes.

Design reasoning

Use a brief reveal to reinforce sequence while keeping the content available.

  1. Start with the person’s task and the barrier they may encounter.
  2. Use for supporting decoration. Begin with visible content and add enhancement only when the browser supports it.
  3. Keep the ordinary content and action usable before adding enhancement.
  4. Disable JavaScript and enable reduced motion separately. Follow an anchor into a section and check its content is visible.

Usage pattern

Use for supporting decoration. Begin with visible content and add enhancement only when the browser supports it.

Impact and tradeoffs

Subtle motion can establish rhythm. Hidden initial content can become inaccessible if JavaScript or observation fails.

Accessibility solutions

Respect reduced motion and retain all content without scripts. Do not delay reading or focus until an animation finishes.

Build accessible source content first. Personal preferences supplement semantic HTML, useful names, keyboard access and visible focus.

HTML

A concrete usage example. Host these assets on your own origin if you adapt the pattern. Read the source and credits before reuse.

<!-- Load these files from the document head. -->
<link rel="stylesheet" href="/open-ui/brand/tokens.css">
<link rel="stylesheet" href="/open-ui/brand/system.css">
<link rel="stylesheet" href="/open-ui/brand/components.css">
<script defer src="/open-ui/brand/components.js?v=sdou20261029"></script>
<!-- Set data-sd-theme="dark" on the html element. -->
<!-- Place this markup in the document body. -->
<div class="sdou-study-intro">
        <span class="sdou-kicker">The details make the difference</span>
        <h1>Let the story find its pace.</h1>
        <p>
          A quiet sequence that reveals the work as it comes into view. Reduced
          motion shows the whole story immediately.
        </p>
      </div>
      <div class="sdou-stage">
        <section class="sdou-reveal-section" data-stagger>
          <span class="sdou-kicker" data-reveal>01 / The work</span>
          <h2 data-reveal>Begin with a clear purpose.</h2>
          <div class="sdou-grid">
            <article class="sdou-card" data-reveal>
              <h3>Keep the work in view.</h3>
              <p>
                One considered space for context, decisions and the next useful
                action.
              </p>
            </article>
            <article class="sdou-card" data-reveal>
              <h3>Made to connect.</h3>
              <p>A consistent language across the tools and the details.</p>
            </article>
            <article class="sdou-card" data-reveal>
              <h3>Local by design.</h3>
              <p>A visual study in keeping the useful things close.</p>
            </article>
          </div>
        </section>
        <section class="sdou-reveal-section" data-stagger>
          <span class="sdou-kicker" data-reveal>02 / The work</span>
          <h2 data-reveal>Make the useful things feel simple.</h2>
          <div class="sdou-grid">
            <article class="sdou-card" data-reveal>
              <h3>Made to connect.</h3>
              <p>A consistent language across the tools and the details.</p>
            </article>
            <article class="sdou-card" data-reveal>
              <h3>Local by design.</h3>
              <p>A visual study in keeping the useful things close.</p>
            </article>
            <article class="sdou-card" data-reveal>
              <h3>A clear next step.</h3>
              <p>Controls that look and behave the way people expect.</p>
            </article>
          </div>
        </section>
        <section class="sdou-reveal-section" data-stagger>
          <span class="sdou-kicker" data-reveal>03 / The work</span>
          <h2 data-reveal>Leave room for a little delight.</h2>
          <div class="sdou-grid">
            <article class="sdou-card" data-reveal>
              <h3>Local by design.</h3>
              <p>A visual study in keeping the useful things close.</p>
            </article>
            <article class="sdou-card" data-reveal>
              <h3>A clear next step.</h3>
              <p>Controls that look and behave the way people expect.</p>
            </article>
            <article class="sdou-card" data-reveal>
              <h3>Room to explore.</h3>
              <p>A little space for curiosity and a closer look.</p>
            </article>
          </div>
        </section>
      </div>

Open the HTML usage source

CSS

Actual shared component stylesheet. Rules for this example sit alongside the other interface studies. tokens.css and system.css provide the foundation. Any example specific stylesheet is linked in the HTML above.

Read the css implementation (861 lines)
/* Starlit component resources. Layouts share tokens and controls with the full studies. */
.sdou-component {
  overflow-x: clip;
}
.sdou-component .sdou-stage {
  min-height: 380px;
}
.sdou-component h2 {
  font-size: clamp(28px, 4vw, 44px);
}
.sdou-component .sdou-card h3 {
  font-size: 26px;
  margin: 0 0 12px;
}
.sdou-component .sdou-card p {
  color: var(--sdou-muted);
  margin: 0 0 20px;
}
.sdou-example-note {
  font-size: 13px !important;
  color: var(--sdou-muted) !important;
  max-width: 70ch;
}
.sdou-label {
  display: block;
  font-weight: 600;
  font-size: 14px;
  margin: 0 0 8px;
}
.sdou-status {
  min-height: 28px;
  color: var(--sdou-secondary);
  font-size: 14px;
  margin-top: 12px;
}
.sdou-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}
.sdou-marquee {
  overflow: hidden;
  mask-image: linear-gradient(
    90deg,
    transparent,
    #000 12%,
    #000 88%,
    transparent
  );
  padding: 48px 0;
  border-block: 1px solid var(--sdou-line);
}
.sdou-marquee-track {
  display: flex;
  width: max-content;
  animation: sdou-marquee 30s linear infinite;
  align-items: center;
}
.sdou-marquee-group {
  display: flex;
  align-items: center;
  gap: 40px;
  padding-right: 40px;
}
.sdou-marquee span {
  font: 32px var(--sdou-font-display);
  color: var(--sdou-muted);
  white-space: nowrap;
}
.sdou-marquee:hover .sdou-marquee-track,
.sdou-marquee:focus-within .sdou-marquee-track,
.sdou-marquee[data-paused="true"] .sdou-marquee-track {
  animation-play-state: paused;
}
@keyframes sdou-marquee {
  to {
    transform: translateX(-50%);
  }
}
.sdou-pricing-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  margin-bottom: 32px;
  flex-wrap: wrap;
}
.sdou-switch {
  display: inline-flex;
  gap: 4px;
  padding: 5px;
  border: 1px solid var(--sdou-line);
  border-radius: 14px;
  background: var(--sdou-panel);
}
.sdou-switch button {
  border: 0;
  border-radius: 9px;
  min-height: 44px;
  padding: 8px 16px;
  color: var(--sdou-muted);
  background: none;
  cursor: pointer;
}
.sdou-switch button[aria-pressed="true"] {
  background: var(--sdou-accent);
  color: var(--sdou-on-accent);
}
.sdou-plan {
  display: flex;
  flex-direction: column;
  position: relative;
}
.sdou-plan.featured {
  border-color: var(--sdou-gold);
  background: var(--sdou-navy-800);
  color: var(--sdou-cream);
  transform: translateY(-12px);
}
.sdou-plan.featured p {
  color: var(--sdou-muted-dark);
}
.sdou-plan ul {
  padding: 0;
  list-style: none;
  margin: 24px 0 32px;
  flex: 1;
}
.sdou-plan li {
  padding: 8px 0;
}
.sdou-plan li::before {
  content: "✓";
  color: var(--sdou-secondary);
  margin-right: 10px;
}
.sdou-price {
  font: 52px var(--sdou-font-display);
  margin-top: 16px;
}
.sdou-price small {
  font: 14px var(--sdou-font-body);
  color: var(--sdou-muted);
}
.sdou-pill {
  display: inline-flex;
  align-self: start;
  border: 1px solid var(--sdou-line);
  border-radius: 999px;
  font: 11px var(--sdou-font-body);
  padding: 6px 10px;
  color: var(--sdou-accent);
}
.sdou-demo-navbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 16px 24px;
  min-height: 72px;
  border-radius: 18px;
  border: 1px solid transparent;
  background: color-mix(in srgb, var(--sdou-panel) 88%, transparent);
  backdrop-filter: blur(18px);
  transition:
    padding 0.2s,
    min-height 0.2s;
}
.sdou-demo-navbar.compact {
  min-height: 56px;
  padding-block: 8px;
  border-color: var(--sdou-line);
}
.sdou-demo-navbar > a {
  font: 24px var(--sdou-font-display);
  color: var(--sdou-ink);
  text-decoration: none;
}
.sdou-demo-navbar nav {
  display: flex;
  gap: 20px;
  margin-left: auto;
}
.sdou-demo-navbar nav a {
  color: var(--sdou-muted);
  font-size: 13px;
  min-height: 44px;
  display: flex;
  align-items: center;
  text-decoration: none;
}
.sdou-menu-toggle {
  display: none;
}
.sdou-navbar-body {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 40px;
  padding: 96px 0;
  min-height: 500px;
}
.sdou-navbar-body p {
  color: var(--sdou-muted);
}
.sdou-magnetic-stage {
  display: grid;
  place-items: center;
  min-height: 360px;
  border: 1px solid var(--sdou-line);
  border-radius: 28px;
  background: radial-gradient(
    ellipse at 50% 80%,
    var(--sdou-raised),
    var(--sdou-bg)
  );
}
.sdou-magnetic {
  position: relative;
  padding: 20px 34px;
  overflow: hidden;
  will-change: transform;
}
.sdou-magnetic span {
  position: relative;
  z-index: 1;
  display: inline-block;
}
.sdou-magnetic::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    circle at var(--x, 50%) var(--y, 50%),
    #fff8,
    transparent 60%
  );
  opacity: 0;
  transition: opacity 0.2s;
}
.sdou-magnetic:hover::before {
  opacity: 1;
}
.sdou-form-card {
  max-width: 660px;
  margin-inline: auto;
  padding: clamp(24px, 5vw, 56px);
}
.sdou-email-row {
  display: flex;
  gap: 12px;
}
.sdou-email-row .sdou-input {
  flex: 1;
  min-width: 0;
}
.sdou-error {
  color: var(--sdou-error);
  min-height: 24px;
  font-size: 14px;
}
.sdou-success-icon {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--sdou-raised);
  color: var(--sdou-secondary);
  font-size: 28px;
}
.sdou-loading::after {
  content: "";
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: sdou-spin 0.8s linear infinite;
}
@keyframes sdou-spin {
  to {
    transform: rotate(360deg);
  }
}
.sdou-bento {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}
.sdou-bento .sdou-card {
  min-height: 240px;
  position: relative;
  overflow: hidden;
  transition:
    transform 0.2s,
    box-shadow 0.2s;
}
.sdou-bento .sdou-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 18px 40px #09151f22;
}
.sdou-bento .sdou-card:first-child {
  grid-column: span 2;
  grid-row: span 2;
}
.sdou-bento .sdou-card:nth-child(2) {
  grid-column: span 2;
}
.sdou-bento .sdou-card:first-child h3 {
  font-size: 42px;
  max-width: 10ch;
}
.sdou-bento .sdou-card:nth-child(3) {
  background: var(--sdou-navy-800);
  color: var(--sdou-cream);
}
.sdou-bento .sdou-card:nth-child(3) p {
  color: var(--sdou-muted-dark);
}
.sdou-mini {
  height: 100px;
  margin-top: 32px;
  display: flex;
  gap: 8px;
  align-items: flex-end;
}
.sdou-mini span {
  flex: 1;
  border: 1px solid var(--sdou-line);
  border-radius: 7px;
  background: var(--sdou-raised);
}
.sdou-mini span:nth-child(2n) {
  background: var(--sdou-secondary);
}
.sdou-mini span:nth-child(3n) {
  background: var(--sdou-accent);
}
.sdou-reveal-section {
  padding-block: 72px;
  border-top: 1px solid var(--sdou-line);
}
.sdou-reveal-section h2 {
  max-width: 16ch;
}
.sdou-motion-ready [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 0.6s ease-out,
    transform 0.6s ease-out;
}
.sdou-motion-ready [data-reveal].visible {
  opacity: 1;
  transform: translateY(0);
}
.sdou-mega {
  padding: 48px;
  border-radius: 28px;
  background: var(--sdou-panel);
  border: 1px solid var(--sdou-line);
}
.sdou-newsletter {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  align-items: center;
  border-bottom: 1px solid var(--sdou-line);
  padding-bottom: 40px;
}
.sdou-footer-cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  padding-top: 40px;
}
.sdou-footer-cols strong {
  display: block;
  color: var(--sdou-accent);
  font-size: 13px;
  margin-bottom: 16px;
}
.sdou-footer-cols a {
  display: block;
  color: var(--sdou-muted);
  min-height: 36px;
  text-decoration: none;
}
.sdou-outline-word {
  font: clamp(40px, 11vw, 144px)/1.1 var(--sdou-font-display);
  color: transparent;
  -webkit-text-stroke: 1px var(--sdou-muted);
  margin: 60px 0 20px;
  transition: color 0.2s;
}
.sdou-outline-word:hover {
  color: var(--sdou-accent);
}
.sdou-quote {
  position: relative;
}
.sdou-quote::before {
  content: "“";
  position: absolute;
  right: 20px;
  top: 0;
  font: 120px var(--sdou-font-display);
  opacity: 0.1;
}
.sdou-stars {
  color: var(--sdou-accent);
  letter-spacing: 0.2em;
  font-size: 14px;
}
.sdou-quote blockquote {
  font: italic 24px/1.5 var(--sdou-font-display);
  margin: 28px 0;
}
.sdou-quote:nth-child(2) {
  transform: translateY(-12px);
  background: var(--sdou-raised);
}
.sdou-avatar {
  display: flex;
  align-items: center;
  gap: 12px;
}
.sdou-avatar > span {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border: 1px solid var(--sdou-line);
  background: var(--sdou-bg);
  border-radius: 50%;
  color: var(--sdou-accent);
}
.sdou-avatar small {
  display: block;
  color: var(--sdou-muted);
}
.sdou-dashboard {
  max-width: 900px;
  margin: auto;
}
.sdou-dashboard-head {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  align-items: start;
}
.sdou-dashboard-head .sdou-select {
  width: auto;
  min-width: 150px;
}
.sdou-metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  margin-top: 32px;
}
.sdou-metric {
  padding: 18px;
  border: 1px solid var(--sdou-line);
  border-radius: 12px;
}
.sdou-metric small {
  color: var(--sdou-muted);
  font-size: 11px;
}
.sdou-metric strong {
  display: block;
  font: 34px var(--sdou-font-display);
  margin: 12px 0;
}
.sdou-delta {
  font-size: 12px;
  color: var(--sdou-secondary);
}
.sdou-chart {
  display: flex;
  align-items: end;
  gap: 12px;
  height: 180px;
  margin-top: 40px;
  border-bottom: 1px solid var(--sdou-line);
}
.sdou-bar {
  flex: 1;
  border: 0;
  position: relative;
  height: var(--height);
  background: var(--sdou-line);
  border-radius: 6px 6px 0 0;
  cursor: pointer;
  padding: 0;
  min-width: 0;
}
.sdou-bar.active,
.sdou-bar:hover,
.sdou-bar:focus {
  background: var(--sdou-accent);
}
.sdou-bar::after {
  content: attr(data-value);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  color: var(--sdou-ink);
  background: var(--sdou-bg);
  padding: 4px 8px;
  border: 1px solid var(--sdou-line);
  border-radius: 5px;
  font-size: 11px;
  opacity: 0;
  white-space: nowrap;
}
.sdou-bar:hover::after,
.sdou-bar:focus::after {
  opacity: 1;
}
.sdou-dashboard.skeleton .sdou-metric strong {
  color: transparent;
  background: var(--sdou-line);
  border-radius: 6px;
  animation: sdou-skeleton 1s alternate infinite;
}
@keyframes sdou-skeleton {
  to {
    opacity: 0.35;
  }
}
.sdou-sidebar-frame {
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: 640px;
  border: 1px solid var(--sdou-line);
  border-radius: 24px;
  overflow: hidden;
}
.sdou-sidebar {
  display: flex;
  flex-direction: column;
  padding: 28px 20px;
  background: var(--sdou-panel);
  border-right: 1px solid var(--sdou-line);
}
.sdou-sidebar img {
  width: 48px;
  height: 48px;
  margin-bottom: 36px;
}
.sdou-sidebar nav {
  display: grid;
  gap: 4px;
}
.sdou-sidebar a {
  min-height: 48px;
  display: flex;
  align-items: center;
  color: var(--sdou-muted);
  text-decoration: none;
  font: 12px var(--sdou-font-body);
  letter-spacing: 0.1em;
  padding: 12px;
  border-left: 2px solid transparent;
}
.sdou-sidebar a[aria-current="page"] {
  color: var(--sdou-ink);
  border-left-color: var(--sdou-accent);
  background: var(--sdou-raised);
}
.sdou-sidebar a:hover {
  color: var(--sdou-ink);
}
.sdou-sidebar-bottom {
  margin-top: auto;
  padding-top: 40px;
}
.sdou-sidebar-content {
  padding: 48px;
  min-width: 0;
}
.sdou-sidebar-mobile {
  display: none;
}
.sdou-command-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 48px;
  width: min(100%, 560px);
  padding: 20px 24px;
  background: var(--sdou-panel);
  color: var(--sdou-muted);
  border: 1px solid var(--sdou-line);
  border-radius: 14px;
  cursor: pointer;
  font-size: 16px;
}
.sdou-command-trigger kbd {
  font: 12px var(--sdou-font-mono);
  padding: 4px 8px;
  border: 1px solid var(--sdou-line);
  border-radius: 6px;
}
.sdou-dialog {
  background: var(--sdou-panel);
  color: var(--sdou-ink);
  border: 1px solid var(--sdou-line);
  border-radius: 20px;
  width: min(640px, calc(100% - 32px));
  max-height: 80dvh;
  padding: 24px;
}
.sdou-dialog::backdrop {
  background: #09151fcc;
  backdrop-filter: blur(5px);
}
.sdou-command-results {
  list-style: none;
  padding: 0;
  margin: 16px 0;
  max-height: 360px;
  overflow: auto;
}
.sdou-command-results button {
  display: flex;
  justify-content: space-between;
  width: 100%;
  min-height: 52px;
  padding: 12px 16px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--sdou-ink);
  text-align: left;
  cursor: pointer;
}
.sdou-command-results button[aria-selected="true"] {
  background: var(--sdou-raised);
  color: var(--sdou-accent);
}
.sdou-command-results small {
  color: var(--sdou-muted);
}
.sdou-command-results mark {
  background: var(--sdou-gold);
  color: var(--sdou-navy-950);
}
.sdou-masonry {
  columns: 3;
  column-gap: 12px;
}
.sdou-masonry figure {
  break-inside: avoid;
  margin: 0 0 12px;
  position: relative;
  border-radius: 14px;
  overflow: hidden;
  background: var(--sdou-panel);
}
.sdou-masonry img {
  display: block;
  width: 100%;
  transition: transform 0.35s;
}
.sdou-masonry figure:hover img {
  transform: scale(1.03);
}
.sdou-masonry figcaption {
  position: absolute;
  inset: auto 0 0;
  padding: 18px;
  background: linear-gradient(transparent, #09151ff2);
  color: var(--sdou-cream);
  transform: translateY(8px);
  transition: transform 0.2s;
}
.sdou-masonry figure:hover figcaption {
  transform: translateY(0);
}
.sdou-masonry small {
  display: block;
  color: var(--sdou-gold);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.sdou-crt {
  padding: clamp(20px, 4vw, 48px);
  background: var(--sdou-navy-950);
  border-radius: 28px;
  border: 1px solid var(--sdou-navy-600);
  box-shadow:
    inset 0 1px 0 #ffffff20,
    0 28px 60px #09151f44;
}
.sdou-crt canvas {
  display: block;
  width: 100%;
  aspect-ratio: 16/9;
  background: #09151f;
  border-radius: 40%/8%;
  border: 12px solid var(--sdou-navy-900);
  box-shadow:
    0 0 0 2px var(--sdou-navy-600),
    inset 0 0 100px #000;
}
.sdou-crt-controls {
  display: flex;
  gap: 20px;
  align-items: center;
  margin-top: 32px;
  color: var(--sdou-cream);
}
.sdou-crt-controls input {
  flex: 1;
  min-width: 0;
  accent-color: var(--sdou-gold);
}
@media (max-width: 900px) {
  .sdou-bento {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .sdou-bento .sdou-card:first-child {
    grid-row: auto;
  }
  .sdou-bento .sdou-card:nth-child(2) {
    grid-column: auto;
  }
  .sdou-masonry {
    columns: 2;
  }
  .sdou-grid.sdou-quotes {
    grid-template-columns: 1fr;
  }
  .sdou-quote:nth-child(2) {
    transform: none;
  }
}
@media (max-width: 760px) {
  .sdou-plan.featured {
    transform: none;
  }
  .sdou-newsletter,
  .sdou-navbar-body {
    grid-template-columns: 1fr;
  }
  .sdou-footer-cols {
    grid-template-columns: repeat(2, 1fr);
  }
  .sdou-mega {
    padding: 24px;
  }
  .sdou-metrics {
    grid-template-columns: repeat(2, 1fr);
  }
  .sdou-dashboard-head {
    flex-wrap: wrap;
  }
  .sdou-chart {
    gap: 6px;
  }
  .sdou-sidebar-frame {
    grid-template-columns: 1fr;
  }
  .sdou-sidebar {
    display: none;
    border: 0;
  }
  .sdou-sidebar.open {
    display: flex;
  }
  .sdou-sidebar-mobile {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px;
    background: var(--sdou-panel);
    border-bottom: 1px solid var(--sdou-line);
  }
  .sdou-sidebar-content {
    padding: 24px;
  }
  .sdou-menu-toggle {
    display: inline-flex;
    margin-left: auto;
  }
  .sdou-demo-navbar {
    flex-wrap: wrap;
    padding: 12px 16px;
  }
  .sdou-demo-navbar nav {
    display: none;
    flex-basis: 100%;
    margin: 0;
  }
  .sdou-demo-navbar nav.open {
    display: flex;
    flex-direction: column;
    gap: 0;
  }
  .sdou-demo-navbar > .sdou-button:not(.sdou-menu-toggle) {
    display: none;
  }
}
@media (max-width: 520px) {
  .sdou-bento {
    grid-template-columns: 1fr;
  }
  .sdou-bento .sdou-card:first-child,
  .sdou-bento .sdou-card:nth-child(2) {
    grid-column: auto;
  }
  .sdou-email-row {
    flex-direction: column;
  }
  .sdou-masonry {
    columns: 1;
  }
  .sdou-footer-cols {
    gap: 24px;
  }
  .sdou-crt-controls {
    gap: 10px;
  }
  .sdou-crt-controls .sdou-button {
    padding: 10px;
  }
  .sdou-study-intro {
    padding-top: 48px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .sdou-marquee-track {
    animation: none;
  }
  .sdou-motion-ready [data-reveal] {
    opacity: 1;
    transform: none;
  }
  .sdou-magnetic {
    transform: none !important;
  }
  .sdou-bento .sdou-card:hover,
  .sdou-masonry figure:hover img {
    transform: none;
  }
}
figure.sdou-card {
  margin: 0;
}
.sdou-marquee-track > div {
  flex-shrink: 0;
}

JavaScript

Actual shared example controller. Each block activates only when its matching element exists. Layout only components need no custom JavaScript; the bundled file also supports the interactive studies.

Read the javascript implementation (472 lines)
/* Starlit resource interactions. All examples run locally and send no data. */
(() => {
  const $ = (s, r = document) => r.querySelector(s),
    $$ = (s, r = document) => [...r.querySelectorAll(s)];
  const reduced = matchMedia("(prefers-reduced-motion: reduce)");
  const toggle = $("#marquee-toggle");
  if (toggle)
    toggle.onclick = () => {
      const paused = toggle.getAttribute("aria-pressed") !== "true";
      $(".sdou-marquee").dataset.paused = paused;
      toggle.setAttribute("aria-pressed", paused);
      toggle.textContent = paused ? "Resume motion" : "Pause motion";
    };
  $$("[data-billing]").forEach(
    (button) =>
      (button.onclick = () => {
        const period = button.dataset.billing;
        $$("[data-billing]").forEach((b) =>
          b.setAttribute("aria-pressed", b === button),
        );
        $$("[data-monthly]").forEach(
          (p) => (p.textContent = p.dataset[period]),
        );
        $$(".sdou-saving").forEach((b) => (b.hidden = period !== "annual"));
      }),
  );
  $$("[data-plan]").forEach(
    (button) =>
      (button.onclick = () =>
        ($("#plan-status").textContent =
          button.dataset.plan +
          " selected in this example. No purchase or account was created.")),
  );
  const demoNav = $("#demo-nav");
  if (demoNav) {
    const y = demoNav.getBoundingClientRect().top + scrollY;
    const update = () => demoNav.classList.toggle("compact", scrollY > y + 80);
    addEventListener("scroll", update, { passive: true });
    update();
    $(".sdou-menu-toggle").onclick = (e) => {
      const open = e.currentTarget.getAttribute("aria-expanded") !== "true";
      e.currentTarget.setAttribute("aria-expanded", open);
      $("#sample-nav").classList.toggle("open", open);
    };
    $$("#sample-nav a").forEach(
      (a) =>
        (a.onclick = () => {
          $("#sample-nav").classList.remove("open");
          $(".sdou-menu-toggle").setAttribute("aria-expanded", "false");
        }),
    );
  }
  const magnetic = $("#magnetic");
  if (magnetic) {
    let tx = 0,
      ty = 0,
      x = 0,
      y = 0,
      frame = 0;
    const tick = () => {
      x += (tx - x) * 0.16;
      y += (ty - y) * 0.16;
      magnetic.style.transform = `translate(${x}px,${y}px)`;
      $("span", magnetic).style.transform =
        `translate(${x * 0.5}px,${y * 0.5}px)`;
      if (Math.abs(tx - x) + Math.abs(ty - y) > 0.02)
        frame = requestAnimationFrame(tick);
      else frame = 0;
    };
    const go = () => {
      if (!frame) frame = requestAnimationFrame(tick);
    };
    magnetic.parentElement.onpointermove = (e) => {
      if (e.pointerType === "touch" || reduced.matches) return;
      const r = magnetic.parentElement.getBoundingClientRect(),
        dx = e.clientX - r.left - r.width / 2,
        dy = e.clientY - r.top - r.height / 2,
        d = Math.hypot(dx, dy);
      tx = d < 120 ? Math.max(-12, Math.min(12, dx * 0.1)) : 0;
      ty = d < 120 ? Math.max(-12, Math.min(12, dy * 0.1)) : 0;
      magnetic.style.setProperty("--x", 50 + dx * 0.4 + "%");
      magnetic.style.setProperty("--y", 50 + dy * 0.4 + "%");
      go();
    };
    magnetic.parentElement.onpointerleave = () => {
      tx = ty = 0;
      go();
    };
    magnetic.onclick = () =>
      ($("#magnetic-status").textContent =
        "A clear action, with a small moment of delight.");
  }
  $$(".sdou-local-form").forEach(
    (form) =>
      (form.onsubmit = async (e) => {
        e.preventDefault();
        const input = $("input[type=email]", form),
          error = $(".sdou-error", form),
          button = $("button[type=submit]", form);
        if (!input.validity.valid) {
          input.setAttribute("aria-invalid", "true");
          error.textContent =
            "Enter an email address in the form name@example.com.";
          input.focus();
          return;
        }
        input.removeAttribute("aria-invalid");
        error.textContent = "";
        button.disabled = true;
        button.classList.add("sdou-loading");
        button.textContent = "Checking…";
        await new Promise((r) => setTimeout(r, 600));
        const result = form.nextElementSibling;
        result.hidden = false;
        result.innerHTML =
          '<div class="sdou-success-icon" aria-hidden="true">✓</div><h3>A clear next step.</h3><p>The sample form is complete. Your address stayed in this page and was not submitted.</p><button class="sdou-button secondary" type="button">Try again</button>';
        form.hidden = true;
        $("button", result).onclick = () => {
          result.hidden = true;
          form.hidden = false;
          form.reset();
          button.disabled = false;
          button.classList.remove("sdou-loading");
          button.textContent = "Keep me posted";
          input.focus();
        };
      }),
  );
  window.sdou_useReveal = (root = document) => {
    const nodes = $$("[data-reveal]", root);
    if (reduced.matches || !("IntersectionObserver" in window)) {
      nodes.forEach((n) => n.classList.add("visible"));
      return () => {};
    }
    document.documentElement.classList.add("sdou-motion-ready");
    const observer = new IntersectionObserver(
      (entries) =>
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            entry.target.classList.add("visible");
            observer.unobserve(entry.target);
          }
        }),
      { threshold: 0.15 },
    );
    $$("[data-stagger]", root).forEach((parent) =>
      $$("[data-reveal]", parent).forEach(
        (el, i) => (el.style.transitionDelay = i * 80 + "ms"),
      ),
    );
    nodes.forEach((n) => observer.observe(n));
    return () => observer.disconnect();
  };
  if ($("[data-reveal]")) window.sdou_useReveal();
  const loading = $("#show-loading");
  if (loading) {
    let timer;
    const preview = () => {
      clearTimeout(timer);
      $(".sdou-dashboard").classList.add("skeleton");
      $(".sdou-dashboard").setAttribute("aria-busy", "true");
      timer = setTimeout(() => {
        $(".sdou-dashboard").classList.remove("skeleton");
        $(".sdou-dashboard").setAttribute("aria-busy", "false");
      }, 700);
    };
    loading.onclick = preview;
    $("#date-range").onchange = (e) => {
      preview();
      $("#chart-status").textContent =
        "Showing illustrative activity for " +
        e.target.value.toLowerCase() +
        ".";
      $$(".sdou-bar").forEach((b, i) => {
        const value = e.target.selectedIndex
          ? ((i * 17) % 68) + 25
          : ((i * 13) % 70) + 22;
        b.style.setProperty("--height", value + "%");
        b.dataset.value =
          (e.target.selectedIndex ? "Month " : "Week ") +
          (i + 1) +
          ": " +
          value * 12;
        b.setAttribute("aria-label", b.dataset.value + " sample sessions");
      });
    };
    $$(".sdou-bar").forEach(
      (b) =>
        (b.onclick = () => {
          $$(".sdou-bar").forEach((x) => x.classList.toggle("active", x === b));
          $("#chart-status").textContent =
            b.dataset.value + " sample sessions.";
        }),
    );
  }
  const sideToggle = $("#sidebar-toggle");
  if (sideToggle) {
    sideToggle.onclick = () => {
      const open = sideToggle.getAttribute("aria-expanded") !== "true";
      sideToggle.setAttribute("aria-expanded", open);
      $("#sample-sidebar").classList.toggle("open", open);
    };
    $$("[data-panel]").forEach(
      (a) =>
        (a.onclick = (e) => {
          e.preventDefault();
          $$("[data-panel]").forEach((x) =>
            x === a
              ? x.setAttribute("aria-current", "page")
              : x.removeAttribute("aria-current"),
          );
          $("#panel-title").textContent = a.dataset.panel;
          $("#panel-copy").textContent = {
            Overview:
              "Bring the work together, keep the useful context close and make the next step clear.",
            Projects:
              "A place for work in progress, clear checkpoints and useful next actions.",
            Library:
              "Keep reusable patterns, considered details and useful examples within reach.",
            Notes:
              "A readable record of ideas, observations and decisions worth coming back to.",
            Settings:
              "A clear place for the choices that make a workspace feel comfortable.",
          }[a.dataset.panel];
          if (innerWidth < 761) {
            $("#sample-sidebar").classList.remove("open");
            sideToggle.setAttribute("aria-expanded", "false");
          }
        }),
    );
  }
  const command = $("#command-dialog");
  if (command) {
    let index = 0;
    const choices = [
      ["Studio overview", "Recent", "/open-ui/"],
      ["Bento feature grid", "Components", "bento-feature-grid.html"],
      ["A considered invitation", "Components", "email-waitlist-form.html"],
      ["Useful signals", "Components", "stats-dashboard-card.html"],
      ["Studio compositions", "Components", "masonry-gallery.html"],
      ["Source notes", "Pages", "/open-ui/sources/"],
      ["Tool library", "Pages", "/open-ui/components/"],
      ["Design system", "Pages", "/open-ui/guidance/"],
    ];
    let results = choices;
    const fuzzy = (query, value) => {
      let i = 0;
      for (const ch of value.toLowerCase()) if (ch === query[i]) i++;
      return i === query.length;
    };
    const render = () => {
      const query = $("#command-input").value.trim().toLowerCase();
      results = choices.filter((c) => fuzzy(query, c[0] + " " + c[1]));
      index = 0;
      $("#command-results").replaceChildren();
      $("#command-input").removeAttribute("aria-activedescendant");
      $("#command-status").textContent = results.length + " matching resources.";
      if (!results.length) {
        $("#command-status").textContent = "No matching resources. Try another word.";
        return;
      }
      results.forEach((item, i) => {
        const li = document.createElement("li"),
          button = document.createElement("button");
        button.type = "button";
        li.setAttribute("role", "presentation");
        button.setAttribute("role", "option");
        button.tabIndex = -1;
        button.setAttribute("aria-selected", i === index);
        button.id = "command-result-" + i;
        const title = document.createElement("span");
        let q = 0;
        for (const ch of item[0]) {
          const node = document.createElement(
            ch.toLowerCase() === query[q] ? "mark" : "span",
          );
          if (ch.toLowerCase() === query[q]) q++;
          node.textContent = ch;
          title.append(node);
        }
        button.append(title);
        const small = document.createElement("small");
        small.textContent = item[1];
        button.append(small);
        button.onclick = () => (location.href = item[2]);
        li.append(button);
        $("#command-results").append(li);
      });
      $("#command-input").setAttribute(
        "aria-activedescendant",
        "command-result-0",
      );
    };
    let commandTrigger;
    command.addEventListener("close", () => {
      $("#command-input").setAttribute("aria-expanded", "false");
      if (commandTrigger?.isConnected) commandTrigger.focus();
    });
    const open = () => {
      if (!command.open) {
        commandTrigger = document.activeElement;
        $("#command-input").setAttribute("aria-expanded", "true");
        render();
        command.showModal();
        $("#command-input").focus();
      }
    };
    $("#command-open").onclick = open;
    $("#command-close").onclick = () => command.close();
    command.onclick = (e) => {
      if (e.target === command) {
        const r = command.getBoundingClientRect();
        if (
          e.clientX < r.left ||
          e.clientX > r.right ||
          e.clientY < r.top ||
          e.clientY > r.bottom
        )
          command.close();
      }
    };
    addEventListener("keydown", (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        command.open ? command.close() : open();
      }
    });
    $("#command-input").oninput = render;
    $("#command-input").onkeydown = (e) => {
      if (["ArrowDown", "ArrowUp"].includes(e.key) && results.length) {
        e.preventDefault();
        index =
          (index + (e.key === "ArrowDown" ? 1 : -1) + results.length) %
          results.length;
        $$("#command-results button").forEach((b, i) =>
          b.setAttribute("aria-selected", i === index),
        );
        $("#command-input").setAttribute(
          "aria-activedescendant",
          "command-result-" + index,
        );
        $("#command-result-" + index).scrollIntoView({ block: "nearest" });
      }
      if (e.key === "Enter" && results[index]) {
        e.preventDefault();
        location.href = results[index][2];
      }
    };
  }
  const crt = $("#crt-container");
  if (crt) {
    window.sdou_createScene = ({
      width = 1280,
      height = 720,
      container,
      captions = [
        { text: "Thoughtful tools.", start: 0, end: 5 },
        { text: "Made for people.", start: 5, end: 10 },
        { text: "Starlit Digital.", start: 10, end: 16 },
      ],
      duration = 18,
    } = {}) => {
      const canvas = document.createElement("canvas");
      canvas.width = width;
      canvas.height = height;
      canvas.setAttribute("role", "img");
      canvas.setAttribute(
        "aria-label",
        "A Starlit caption rendered as phosphor contours",
      );
      container.append(canvas);
      const ctx = canvas.getContext("2d");
      const seek = (t) => {
        t = Math.max(0, Math.min(duration, t));
        ctx.clearRect(0, 0, width, height);
        ctx.fillStyle = "#09151f";
        ctx.fillRect(0, 0, width, height);
        const caption = captions.find((c) => t >= c.start && t < c.end);
        for (let i = 0; i < 32; i++) {
          ctx.beginPath();
          ctx.strokeStyle = `rgba(121,183,181,${0.03 + (i % 3) * 0.012})`;
          for (let x = 0; x <= width; x += 10) {
            const y =
              height * 0.5 +
              Math.sin(x * 0.006 + t * 0.3 + i * 0.13) * height * 0.2 +
              i * 4;
            x ? ctx.lineTo(x, y) : ctx.moveTo(x, y);
          }
          ctx.stroke();
        }
        if (caption) {
          const age = t - caption.start,
            remaining = caption.end - t,
            fade = Math.min(1, age / 0.7, remaining / 0.6),
            z = Math.sin((Math.min(1, age / 1.5) * Math.PI) / 2);
          ctx.save();
          ctx.textAlign = "center";
          ctx.textBaseline = "middle";
          const size = width / (caption.text.length * 0.64);
          ctx.font = `500 ${size}px Georgia`;
          ctx.translate(width * 0.5, height * 0.48);
          ctx.scale(0.94 + z * 0.06, 0.94 + z * 0.06);
          for (let i = 18; i >= 0; i--) {
            ctx.lineWidth = 0.5;
            ctx.strokeStyle = `rgba(121,183,181,${fade * (0.02 + (18 - i) * 0.004)})`;
            ctx.strokeText(caption.text, i * 0.8, -i * 0.6);
          }
          ctx.shadowColor = "#79b7b5";
          ctx.shadowBlur = 20;
          ctx.fillStyle = `rgba(242,231,212,${fade})`;
          ctx.fillText(caption.text, 0, 0);
          ctx.restore();
          canvas.setAttribute("aria-label", caption.text);
        }
        const vignette = ctx.createRadialGradient(
          width / 2,
          height / 2,
          0,
          width / 2,
          height / 2,
          width * 0.6,
        );
        vignette.addColorStop(0, "transparent");
        vignette.addColorStop(1, "#000b");
        ctx.fillStyle = vignette;
        ctx.fillRect(0, 0, width, height);
        for (let y = 0; y < height; y += 4) {
          ctx.fillStyle = "#0003";
          ctx.fillRect(0, y, width, 1);
        }
        ctx.fillStyle = "#e8c58e";
        ctx.font = "14px monospace";
        ctx.fillText("STARLIT / PHOSPHOR NOTES", width * 0.06, height * 0.9);
      };
      seek(0);
      return { duration, width, height, canvas, seek };
    };
    window.sdou_scene = window.sdou_createScene({ container: crt });
    let playing = false,
      origin = 0,
      at = 0,
      frame;
    const seek = (t) => {
      at = t;
      window.sdou_scene.seek(t);
      $("#crt-seek").value = t;
      $("#crt-time").value = t.toFixed(1) + "s";
    };
    const tick = (now) => {
      if (!playing) return;
      seek(((now - origin) / 1000) % window.sdou_scene.duration);
      frame = requestAnimationFrame(tick);
    };
    $("#crt-play").onclick = () => {
      playing = !playing;
      $("#crt-play").textContent = playing ? "Pause" : "Play";
      $("#crt-play").setAttribute("aria-pressed", playing);
      if (playing) {
        origin = performance.now() - at * 1000;
        frame = requestAnimationFrame(tick);
      } else cancelAnimationFrame(frame);
    };
    $("#crt-seek").oninput = (e) => {
      playing = false;
      cancelAnimationFrame(frame);
      $("#crt-play").textContent = "Play";
      $("#crt-play").setAttribute("aria-pressed", "false");
      seek(+e.target.value);
    };
    if (new URLSearchParams(location.search).has("paused")) seek(0);
  }
})();

PHP integration

In your own application, PHP can render this HTML on the server. Here, the example is escaped text only. The library has no PHP runner or execution endpoint. The browser owns the interaction.

<?php
// Server rendering example, not a payment or mailing endpoint.
declare(strict_types=1);
$heading = 'Scroll reveal';
$e = static fn (string $value): string =>
    htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
<section aria-labelledby="example-heading">
  <h2 id="example-heading"><?= $e($heading) ?></h2>
  <?php
  // Include the trusted component HTML shown above from your own file.
  // Never build this path from a request parameter.
  require __DIR__ . '/partials/scroll-reveal-sections.html';
  ?>
</section>

Open the PHP rendering source

API and dependencies

The HTML section lists the actual selectors, IDs, classes and data attributes used by the example. Preserve IDs referenced by labels and JavaScript, and make them unique when composing a page. The shared controller is a demonstration bundle, not a backend service.

For PHP, save the body markup above in the fixed partial path shown, and load its linked CSS and scripts once in your page head. Escape dynamic text at output. Never insert untrusted HTML, URLs or script fragments into the partial.

Form, plan and dashboard outcomes are local simulations. A production submission needs its own validated endpoint, abuse controls and honest delivery result. Decorative studies need integration specific review before production use.

Testing and limitations

Disable JavaScript and enable reduced motion separately. Follow an anchor into a section and check its content is visible.

These visual studies are learning examples. Their presence here is not a claim that each is deployed across Starlit products or fully audited. Check motion changes, keyboard focus, status announcements and content equivalents in the final host.

Production review still needs assistive technology, touch, zoom, custom styles and representative content. See our testing scope and known gaps. Report a barrier to Starlit.