The library, rendered live from the same CSS the website loads. Every component reads the surface contract — nothing below names a palette color for text.
Buttons
Four variants. Each inverts on hover rather than dimming, and lifts .125rem — the motion is the affordance. Minimum height is 3rem so the target clears touch guidance.
Light grounds only. A navy fill has no edge on navy.
Secondary is ground-dependent, and that is the whole choice. There is one secondary variant per ground: outline on dark, ink on light. Putting ink on a dark band produces a button with no visible shape — the failure is silent, because the label still reads.
Section furniture
A label, a heading with one accented clause, and an intro paragraph. The accent color comes from the surface, so this markup is identical on every ground.
Practice areas
We handle the hard ones
The claims insurers fight hardest are the ones that change a family's finances for a decade.
Practice areas
We handle the hard ones
The claims insurers fight hardest are the ones that change a family's finances for a decade.
Poster · the loudest role
We don't settle for their number.
Poster is the top of the scale — up to 9rem — and exists to sit against a .75rem label and a 1rem lead. Under eight words, one accented clause, one per page.
The turn
The brand's central image is a person moving from their worst day to the day it is settled. .turn shows both states in one frame and moves between them. One registered custom property, --turn, runs 0 → 1, and every variant is only a different way of setting it — so a cross-fade, a wipe and a scroll reveal share one implementation and one motion curve.
Turn hero
The showcase composition: a full-bleed turn behind a navy scrim, poster heading seated bottom-left, and the switch in the action row. Hover, keyboard focus, or the switch turns the whole scene. The scrim keeps the copy white-on-navy however bright the after frame is.
North Carolina injury attorneys
They have adjusters. You have us.
A car crash puts you across the table from a company that does this every day. From tonight, so do you.
The same markup, four ways of setting --turn. Every one is keyboard-operable and none needs a script except the wipe, whose range input carries a one-line inline handler.
BeforeAfter
.turn--hoverCross-fades on pointer or focus over --motion-slow. The default; put tabindex="0" on it when nothing inside is focusable.
BeforeAfter
.turn--wipeA transparent range input covers the frame; dragging or arrow keys move the seam. Opens at the midpoint so both states are seen at once.
BeforeAfter
.turn--scrollTurns as it travels up the viewport via animation-timeline: view(). Where the browser can't, it behaves as hover; under reduced motion it resolves to the after.
.turn__switchA Before | After control built on a checkbox and :has(). The reader turns it on purpose. No script.
Diptych
The static form, for anywhere the turn cannot move — email, print, a grid of many pairs. Before then after, each captioned; the after caption takes the accent.
01 · Before01 · After
Markup
<div class="turn turn--hover" tabindex="0">
<img class="turn__before" src="…-sad.jpg" alt="Before: …" />
<img class="turn__after" src="…-happy.jpg" alt="After: …" />
<span class="turn__tag">Before</span>
<span class="turn__tag turn__tag--after">After</span>
</div>
<!-- wipe: add the seam and the range -->
<span class="turn__handle" aria-hidden="true"></span>
<input class="turn__range" type="range" min="0" max="100" value="50"
aria-label="Reveal the after photograph"
oninput="this.closest('.turn').style.setProperty('--turn', this.value / 100)" />
<!-- switch: anywhere inside .turn or .turn-hero -->
<label class="turn__switch"><input type="checkbox" /><span>Before</span><span>After</span></label>
Rule
Why
Both frames, always. The before is never shipped alone.
The pairing is the argument. A before on its own is a sad photograph.
Both frames get real alt text, and it changes between them.
A screen-reader user should get the turn too: “Before: on crutches…” then “After: standing…”.
Rest state is before, except .turn--wipe at the midpoint.
The page opens on the reader's day. The after is earned by moving.
Only --turn moves. Never animate the images' transform as well.
Two motions on one frame reads as an effect; one reads as a change of state.
Text over a turn sits on .turn-hero__scrim, never on the photograph.
The after frame is bright. The scrim is navy on both states so the contract holds through the whole turn.
One moving turn per viewport.
Two frames turning at once compete; the reader watches neither.
Practice cards
A four-across grid with a yellow top rule. The glyph leads at --icon-lg; the heading is pushed to the bottom with margin-block-start: auto so cards align regardless of copy length.
Sits directly under the hero on the deep surface. An intro cell plus three claims, divided by hairlines.
Durham and RaleighNorth Carolina injury counsel
Direct attorney access
No fee unless we win
Same-day response
Form fields
Square by decision — the border-radius reset is deliberate, because browsers supply their own. Focus moves the border to blue and adds an inset ring; the outline is suppressed only because that ring replaces it.
Popup
A native <dialog> for the free-consultation form, ported from piercelaw.com. The dialog is only a frame; the panel inside declares its surface, so the fields read the same contract they read on a band. Behavior ships in /design-system/popup.js — load it with defer.
Opens that dialog on click. Manual opens always work, even after the auto-open has fired. data-cta-id becomes the event's source.
[data-popup-auto="ms"]
On the dialog: opens once per browser, ms after the tab is first visible (the timer does not run in a background tab). Remembered per id in localStorage as wpl-popup:<id>.
[data-popup-close]
The only way out. Escape and backdrop clicks are blocked.
wpl:popupopen / wpl:popupclose
Bubbling events with { id, source }. The website forwards them to analytics; the Design System never calls a vendor.
document.documentElement.wplPopup
open(id, source) and close(id) for a consumer's own script.
One auto-opening popup per page, and none after a lead is captured. The form-success page, careers, and any page that already leads with an intake form leave out data-popup-auto. The auto-open is an interruption; it earns its place only where no other form is in view.
Quote
They took the call at 9pm and had the police report by morning.