Subframe
Animation & motionLogo zoom transition

Mark focus

An architectural monogram moves from a complete lockup to a precisely framed close-up.

Enter or leave the mark.

Subframe Original · MIT

Add to your project

Component source

Standalone React component. Styles are included.

React only. No additional packages.

Render the default export inside a container with a defined height. Its optional active prop stops movement when false. This example works with React 19; Next.js projects can use it as a client component.

View source code
/**
 * MIT License
 *
 * Copyright (c) 2026 Subframe
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

"use client"

import { useEffect, useRef, useState } from "react"

export default function MarkFocus({ active = true }: { active?: boolean }) {
  const root = useRef<HTMLElement>(null),
    mark = useRef<HTMLDivElement>(null),
    words = useRef<HTMLDivElement>(null)
  const [focused, setFocused] = useState(false),
    [reduced, setReduced] = useState(true)
  const target = useRef(false),
    value = useRef(0),
    wake = useRef<() => void>(() => {}),
    immediate = useRef(false)
  useEffect(() => {
    const q = matchMedia("(prefers-reduced-motion: reduce)")
    const update = () => setReduced(q.matches)
    update()
    q.addEventListener("change", update)
    return () => q.removeEventListener("change", update)
  }, [])
  useEffect(() => {
    const el = root.current,
      m = mark.current,
      w = words.current
    if (!el || !m || !w) return
    let frame = 0,
      last = 0,
      width = 1,
      height = 1
    const draw = () => {
      const p = value.current,
        part = 1 - Math.pow(1 - p, 3),
        size = Math.min(width * 0.25, 136),
        startX = width * 0.72,
        endX = width * 0.5,
        startY = height * 0.43,
        endY = height * 0.43
      m.style.width = `${size}px`
      m.style.height = `${size}px`
      m.style.transform = `translate(${startX + (endX - startX) * part - size / 2}px,${
        startY + (endY - startY) * part - size / 2
      }px) scale(${1 + 2.9 * p}) rotate(${p * 45}deg)`
      w.style.opacity = String(Math.max(0, 1 - p * 2.5))
      w.style.transform = `translateX(${-p * width * 0.22}px)`
    }
    const tick = (now: number) => {
      frame = 0
      const dt = last ? Math.min((now - last) / 1000, 0.04) : 1 / 60
      last = now
      const goal = Number(target.current)
      value.current += (goal - value.current) * (1 - Math.exp(-dt * 7))
      if (Math.abs(goal - value.current) < 0.001) value.current = goal
      draw()
      if (value.current !== goal) frame = requestAnimationFrame(tick)
      else last = 0
    }
    const schedule = () => {
      if (!active || reduced || immediate.current) {
        cancelAnimationFrame(frame)
        frame = 0
        last = 0
        value.current = Number(target.current)
        draw()
      } else if (!frame) frame = requestAnimationFrame(tick)
    }
    wake.current = schedule
    const resize = () => {
      width = el.clientWidth
      height = el.clientHeight
      draw()
    }
    const observer = new ResizeObserver(resize)
    observer.observe(el)
    resize()
    if (active || reduced) schedule()
    return () => {
      cancelAnimationFrame(frame)
      observer.disconnect()
      wake.current = () => {}
    }
  }, [active, reduced])
  const change = (keyboard: boolean) => {
    immediate.current = keyboard
    target.current = !target.current
    setFocused(target.current)
    wake.current()
  }
  return (
    <section ref={root} className="sf-mark-focus">
      <div ref={words} className="mf-wordmark" aria-hidden={focused}>
        <span>NEW</span>
        <span>MONUMENT</span>
        <small>EDITIONS</small>
      </div>
      <div ref={mark} className="mf-mark" aria-hidden="true">
        <svg viewBox="0 0 120 120">
          <path d="M0 0h40v40h40V0h40v120H80V80H40v40H0Z" fill="currentColor" />
        </svg>
      </div>
      <button type="button" aria-pressed={focused} onClick={(e) => change(e.detail === 0)}>
        {focused ? "Whole mark" : "Into the mark"}
        <span aria-hidden="true">{focused ? "↙" : "↗"}</span>
      </button>
      <style>{`.sf-mark-focus{position:relative;height:100%;width:100%;min-height:280px;background:#e02d23;color:#171411;overflow:hidden;isolation:isolate;box-sizing:border-box;container-type:inline-size}.sf-mark-focus *{box-sizing:border-box}.sf-mark-focus .mf-wordmark{position:absolute;left:7%;top:26%;display:flex;flex-direction:column;font:900 clamp(20px,6cqw,38px)/.92 Arial,sans-serif;letter-spacing:-.065em;will-change:transform,opacity}.sf-mark-focus .mf-wordmark small{font:10px/1 monospace;letter-spacing:.15em;margin-top:16px}.sf-mark-focus .mf-mark{position:absolute;left:0;top:0;transform-origin:center;will-change:transform}.sf-mark-focus svg{display:block;width:100%;height:100%}.sf-mark-focus button{position:absolute;bottom:18px;left:7%;right:7%;height:44px;border:0;border-top:1px solid #17141155;background:#e02d23;color:inherit;font:11px Arial;display:flex;align-items:center;justify-content:space-between;padding:0;cursor:pointer}.sf-mark-focus button span{font-size:24px}.sf-mark-focus button:focus-visible{outline:2px solid #171411;outline-offset:4px}`}</style>
    </section>
  )
}

Where it works well

Direct attention from an identity system to its defining shape.

Reduced motion

The framing change resolves immediately for keyboard and reduced-motion users.

Things to know

Independent fictional identity artwork. A bounded 2D transform study, not a camera or arbitrary element focus manager.

More examples