Back to the library

Schematic — section hatch

Variant: Section A–A

The cutting plane is declared in one beat — a plane is an instruction, not geometry, so it does not get a draw-on — and then the drawing answers it: sixteen hatch strokes applied lower-left to upper-right, one pen pass per 111ms beat, all clipped to the material and none to the bore. Shading is applied, never faded in, because a section face is a claim about where the metal is. The whole application runs off a single registered progress number that every stroke reads its own start time out of, so the cascade is arithmetic rather than sixteen tuned keyframe blocks. As the shading completes, the plane and its flags withdraw: once the face is hatched the view has become the section, and a drawing cannot carry the plane and its own answer at once. Only then does SECTION A–A land — the title of a finished drawing, not a promise about one in progress. Reach for it where a cutaway or an under-the-surface reveal needs to feel drafted rather than rendered.

svgline-drawingdiagramstaggerloop

The source

A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Schematic — section hatch</title>
    <style>
      /* One registered number drives all sixteen hatch strokes: each stroke
         reads its own start out of the shared progress, so the application
         order is arithmetic, not sixteen hand-tuned keyframe blocks. */
      @property --_p {
        syntax: '<number>';
        initial-value: 0;
        inherits: true;
      }

      :root {
        /* Neutrals — fixed, never remixed. */
        --ivory: #faf9f5;
        --paper: #ffffff;
        --oat: #e3dacc;
        --slate: #141413;

        /* Knobs — the remix API. A knob exists iff declared here and used below. */
        --hue: 38.8;
        --lift: 0;
        --chroma: 1;
        --speed: 1;

        /* Accents — derived from the knobs with the family's offsets baked in,
           so one hue roll moves them together. Defaults reproduce the original hex. */
        --clay: oklch(calc(0.6724 + var(--lift)) calc(0.1308 * var(--chroma)) var(--hue));
        --clay-d: oklch(calc(0.5797 + var(--lift)) calc(0.127 * var(--chroma)) var(--hue));
        --olive: oklch(
          calc(0.6118 + var(--lift)) calc(0.0713 * var(--chroma)) calc(var(--hue) + 88.3)
        );
        --rust: oklch(
          calc(0.5408 + var(--lift)) calc(0.1357 * var(--chroma)) calc(var(--hue) - 10.3)
        );

        --_muted: color-mix(in srgb, var(--slate) 62%, transparent);

        /* Clock — 7s: the cut is declared in the first tenth, the shading is
           applied over the next third at a steady 111ms a stroke, and the
           finished section holds for over two seconds before clearing. */
        --cycle: 7s;
        --t: calc(var(--cycle) / var(--speed));

        --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
      }

      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }

      body {
        min-height: 100vh;
        display: grid;
        place-items: center;
        background: var(--ivory);
        font-family:
          system-ui,
          -apple-system,
          'Segoe UI',
          Roboto,
          sans-serif;
        color: var(--slate);
      }

      .sheet {
        width: min(320px, 90vw);
        height: auto;
        display: block;
      }

      .part {
        fill: var(--paper);
        stroke: var(--slate);
        stroke-width: 2;
      }

      .bore {
        fill: var(--ivory);
        stroke: var(--slate);
        stroke-width: 2;
      }

      .anno {
        animation: anno-clear var(--t) var(--ease-out) infinite;
      }

      /* The plane arrives in one beat rather than drawing on — a cutting plane
         is an instruction, not geometry — and it withdraws as the shading
         completes: once the face is hatched, the view has become the section,
         and a drawing cannot carry the plane and its own answer at once. */
      .plane {
        stroke: var(--slate);
        stroke-width: 1.25;
        stroke-dasharray: 9 3 2 3;
        opacity: 0;
        animation: plane-in var(--t) var(--ease-out) infinite;
      }

      .flag {
        opacity: 0;
        animation: flag-top var(--t) var(--ease-out) infinite;
      }

      .flag.south {
        animation-name: flag-bottom;
      }

      .flag line {
        stroke: var(--slate);
        stroke-width: 1.5;
      }

      .flag path {
        fill: var(--slate);
      }

      .flag text {
        fill: var(--slate);
        font-family: inherit;
        font-size: 13px;
        font-weight: 600;
      }

      .hatchset {
        animation: apply var(--t) linear infinite;
      }

      /* Every stroke is ruled lower-left to upper-right, one pen pass per
         111ms beat — shading is applied, never faded in, because a section
         face is a claim about material. Each segment is pre-trimmed to the
         face's bounding box so ink lands on the first frame of its draw; the
         corner rounds and the bore are left to the clip. Start times are
         --_i * 5.55 in progress units; the *6 gain is one stroke's draw span
         (16.7 units) normalised to a 0-100 dash. */
      .h {
        stroke: var(--clay);
        stroke-width: 1.2;
        opacity: 0.6;
        fill: none;
        stroke-dasharray: 100 200;
        stroke-dashoffset: calc((100 - clamp(0, (var(--_p) - var(--_i) * 5.55) * 6, 100)) * 1px);
      }

      .caption {
        fill: var(--_muted);
        font-family: inherit;
        font-size: 12.5px;
        font-weight: 600;
        letter-spacing: 0.12em;
        opacity: 0;
        animation: caption-in var(--t) var(--ease-out) infinite;
      }

      @keyframes apply {
        0%,
        16% {
          --_p: 0;
        }
        44.5%,
        100% {
          --_p: 100;
        }
      }

      @keyframes plane-in {
        0%,
        5% {
          opacity: 0;
        }
        9%,
        38% {
          opacity: 0.55;
        }
        45%,
        100% {
          opacity: 0;
        }
      }

      /* The flags land top then bottom, the order you read a cutting plane,
         and leave with it. */
      @keyframes flag-top {
        0%,
        9% {
          opacity: 0;
        }
        11.5%,
        38% {
          opacity: 1;
        }
        45%,
        100% {
          opacity: 0;
        }
      }

      @keyframes flag-bottom {
        0%,
        10.5% {
          opacity: 0;
        }
        13%,
        38% {
          opacity: 1;
        }
        45%,
        100% {
          opacity: 0;
        }
      }

      /* The name arrives only once the plane has cleared and the shading is
         done: SECTION A-A is the title of a finished drawing, not a promise
         about one in progress. */
      @keyframes caption-in {
        0%,
        46% {
          opacity: 0;
          transform: translateY(2px);
        }
        50.5%,
        100% {
          opacity: 1;
          transform: translateY(0);
        }
      }

      @keyframes anno-clear {
        0%,
        85% {
          opacity: 1;
        }
        92%,
        100% {
          opacity: 0;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        /* The application is the content; it runs at half rate, which slows
           each pen stroke to about 660ms — an uncover rather than a flick. */
        .anno,
        .plane,
        .flag,
        .hatchset,
        .caption {
          animation-duration: calc(var(--t) * 2);
        }
      }
    </style>
  </head>
  <body>
    <svg
      class="sheet"
      viewBox="0 0 300 170"
      role="img"
      aria-label="A section drawing: a dash-dot cutting plane is declared through a plate, diagonal hatch strokes shade the cut material one by one while skipping the bore, the plane withdraws, and the caption SECTION A–A lands once the shading is complete"
    >
      <defs>
        <clipPath id="face" clip-rule="evenodd">
          <path
            clip-rule="evenodd"
            d="M78 46 H222 A8 8 0 0 1 230 54 V102 A8 8 0 0 1 222 110 H78 A8 8 0 0 1 70 102 V54 A8 8 0 0 1 78 46 Z
               M166 78 A16 16 0 1 1 134 78 A16 16 0 1 1 166 78 Z"
          />
        </clipPath>
      </defs>

      <rect class="part" x="70" y="46" width="160" height="64" rx="8" />
      <circle class="bore" cx="150" cy="78" r="16" />

      <g class="anno">
        <line class="plane" x1="150" y1="26" x2="150" y2="130" />

        <g class="flag">
          <line x1="150" y1="26" x2="143" y2="26" />
          <path d="M134 26 L143 24.5 V27.5 Z" />
          <text x="127" y="31" text-anchor="end">A</text>
        </g>
        <g class="flag south">
          <line x1="150" y1="130" x2="143" y2="130" />
          <path d="M134 130 L143 128.5 V131.5 Z" />
          <text x="127" y="135" text-anchor="end">A</text>
        </g>

        <g class="hatchset" clip-path="url(#face)">
          <path class="h" style="--_i: 0" pathLength="100" d="M70 59 L83 46" />
          <path class="h" style="--_i: 1" pathLength="100" d="M70 72 L96 46" />
          <path class="h" style="--_i: 2" pathLength="100" d="M70 85 L109 46" />
          <path class="h" style="--_i: 3" pathLength="100" d="M70 98 L122 46" />
          <path class="h" style="--_i: 4" pathLength="100" d="M71 110 L135 46" />
          <path class="h" style="--_i: 5" pathLength="100" d="M84 110 L148 46" />
          <path class="h" style="--_i: 6" pathLength="100" d="M97 110 L161 46" />
          <path class="h" style="--_i: 7" pathLength="100" d="M110 110 L174 46" />
          <path class="h" style="--_i: 8" pathLength="100" d="M123 110 L187 46" />
          <path class="h" style="--_i: 9" pathLength="100" d="M136 110 L200 46" />
          <path class="h" style="--_i: 10" pathLength="100" d="M149 110 L213 46" />
          <path class="h" style="--_i: 11" pathLength="100" d="M162 110 L226 46" />
          <path class="h" style="--_i: 12" pathLength="100" d="M175 110 L230 55" />
          <path class="h" style="--_i: 13" pathLength="100" d="M188 110 L230 64" />
          <path class="h" style="--_i: 14" pathLength="100" d="M201 110 L230 73" />
          <path class="h" style="--_i: 15" pathLength="100" d="M214 110 L230 84" />
        </g>

        <text class="caption" x="150" y="158" text-anchor="middle">SECTION A–A</text>
      </g>
    </svg>
  </body>
</html>

The variant family

Same idea, one axis moved. Compare them side by side before you commit.