Controls

Controls on this site live where a book’s imprint page would be: the footer colophon and the side rail. They are colophon furniture, not toolbar buttons. No floating action buttons, no sticky headers, no hamburger menus. The reader finds them when they look for them, and not before.

Every control uses a ghost-button pattern: no background, no border, --text-3 color that warms to --text on hover. They recede into the colophon until engaged.

For exact CSS values, see the implementation spec and tokens.json.


Theme toggle

Purpose

Switches between light and dark mode. Lives in the footer colophon and the side rail. Nowhere else.

Anatomy

PartSelectorDescription
Button.footer-theme-toggleGhost button with icon
Sun icon.footer-theme-toggle .sunShown in dark mode (click to go light)
Moon icon.footer-theme-toggle .moonShown in light mode (click to go dark)

Specs

The toggle is a <button> with no background, no border, padding: 0. Icon visibility swaps per mode: the moon shows in light mode (offering dark), the sun shows in dark mode (offering light).

The toggle appears identically in the footer and side rail. Both instances use the same styling; the side rail version inherits its position from .side-rail-controls.

States

StatePropertyValue
Defaultcolorvar(--text-3)
Hovercolorvar(--text)

Transition: color 0.2s ease.

Icon visibility:

Mode.sun.moon
Lightdisplay: nonedisplay: block
Darkdisplay: blockdisplay: none

Keyboard shortcut

Pressing d toggles the theme. Press it now to try. Disabled when focus is in input, textarea, or [contenteditable].

Responsive behavior

BreakpointChange
<= 1079pxSide rail instance hidden (side rail itself hidden)
PrintFooter settings hidden entirely

Accessibility

The button uses an accessible label for screen readers. Focus is visible via the site’s global focus styling. The icon swap is purely visual; the button’s accessible name communicates the action regardless of which icon is displayed.

Code example

<button class="footer-theme-toggle" aria-label="Toggle theme">
  <svg class="sun"><!-- sun icon --></svg>
  <svg class="moon"><!-- moon icon --></svg>
</button>

Accent picker

Purpose

Lets the reader choose their accent color from the eight Flexoki hues. Progressive disclosure: a small colored dot invites curiosity, expanding on click to reveal the full palette.

Anatomy

PartSelectorDescription
Picker container.footer-accent-picker / .accent-pickerFlex row wrapper
Current dot.accent-currentColored circle showing active accent
Options container.accent-optionsExpandable row/column of color options
Individual option.accent-optionSingle color circle

Specs

Current accent dot:

width: 18px;
height: 18px;
border-radius: 50%;
background: var(--accent);
border: 2px solid var(--ui-2, var(--ui));
cursor: pointer;
padding: 0;

Options container starts collapsed at max-width: 0, opacity: 0, visibility: hidden, clipped with clip-path: inset(-4px 0 -4px -4px). The negative inset values prevent the clip from cutting off the dot borders.

When the .open class is applied, the container expands to max-width: 220px, opacity: 1, visibility: visible, margin-left: 8px, with clip-path: none. The expansion animation uses 0.3s cubic-bezier(0.22, 1, 0.36, 1) for max-width and margin, 0.2s ease for opacity.

Individual options are 16px circles with 2px solid transparent borders. On hover they scale(1.15). The active option gains border-color: var(--text-3).

Variants

Footer variant: Options expand horizontally (default flex direction). Container within .footer-accent-picker.

Side rail variant: Options expand vertically. The side rail overrides the accent options to use flex-direction: column and clip-path: inset(-4px -4px 0 -4px). When open: max-width: none, max-height: 220px, margin-left: 0, margin-top: 8px.

States

Current dot:

StatePropertyValue
Defaultborder-colorvar(--ui-2, var(--ui))
Hoverborder-colorvar(--text-3)

Options container:

Statemax-widthopacitymargin-leftclip-pathvisibility
Closed000inset(-4px 0 -4px -4px)hidden
.open220px18pxnonevisible

Individual option:

StatePropertyValue
Defaultborder-colortransparent
Hovertransformscale(1.15)
.activeborder-colorvar(--text-3)

Onboarding hint

On first visit (no accent preference stored), the current dot pulses twice to draw attention:

@keyframes accent-hint {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.3); }
}

.accent-current.hint {
  animation: accent-hint 0.6s ease 2;
}

Gated behind prefers-reduced-motion: no-preference.

Keyboard shortcut

Pressing c cycles to the next accent color — try it now, or find the colored dot in the footer. Disabled when focus is in input, textarea, or [contenteditable].

Responsive behavior

BreakpointChange
<= 600px.accent-current grows to 22px x 22px
<= 600px.accent-option grows to 20px x 20px
<= 1079pxSide rail variant hidden
PrintHidden entirely

The size increase at <= 600px ensures touch targets are comfortable on mobile.

Accessibility

Each option button should carry an accessible label naming the color (e.g., “Orange accent”, “Cyan accent”). The current selection is communicated via the .active class and border treatment. The keyboard shortcut provides an alternative to the click-to-expand interaction.

Code example

<div class="footer-accent-picker accent-picker">
  <button class="accent-current" aria-label="Choose accent color"></button>
  <div class="accent-options">
    <button class="accent-option active" style="background: #DA702C" aria-label="Orange accent"></button>
    <button class="accent-option" style="background: #D14D41" aria-label="Red accent"></button>
    <button class="accent-option" style="background: #D0A215" aria-label="Yellow accent"></button>
    <button class="accent-option" style="background: #879A39" aria-label="Green accent"></button>
    <button class="accent-option" style="background: #3AA99F" aria-label="Cyan accent"></button>
    <button class="accent-option" style="background: #4385BE" aria-label="Blue accent"></button>
    <button class="accent-option" style="background: #8B7EC8" aria-label="Purple accent"></button>
    <button class="accent-option" style="background: #CE5D97" aria-label="Magenta accent"></button>
  </div>
</div>

Font size control

Purpose

Cycles through font size options. Lives in the footer settings alongside the theme toggle and accent picker.

Anatomy

PartSelectorDescription
Button.footer-font-sizeGhost button with “Aa” label
Label.footer-font-size .font-size-labelText label that reflects current size

Specs

Same ghost-button pattern as the theme toggle: no background, no border, --text-3 color. Uses Signifier at 0.85rem with padding: 4px. The label text transitions font-size 0.15s ease when cycling to provide subtle visual feedback. Press f to cycle through sizes, or find the “Aa” button in the footer.

States

StatePropertyValue
Defaultcolorvar(--text-3)
Hovercolorvar(--text)

Transition: color 0.2s ease.

Code example

<button class="footer-font-size" aria-label="Change font size">
  <span class="font-size-label">Aa</span>
</button>

Newsletter signup form

Purpose

Email subscription form on the homepage. Connects to Beehiiv via a Vercel serverless function (api/subscribe.ts) that proxies the API key server-side. No iframe, no third-party styling.

Anatomy

PartSelectorDescription
Section.newsletterFull-width section after the two-column grid
Label.section-label”NEWSLETTER” in standard section label style
Blurb.newsletter-blurbSubtitle-style description
Form row.newsletter-rowGrid container aligning input and button to the two-column layout
Email input.newsletter-row inputBottom-border-only text field
Submit button.newsletter-row buttonText-link style button
Status.newsletter-statusSuccess/error feedback

Specs

Form row (.newsletter-row): Mirrors the .home-columns grid so the button aligns with the right column.

display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--space-xl) * 1.5);
align-items: end;

Input (.newsletter-row input): Bottom border only — like writing on a line in a printed subscription card.

font-family: var(--font-serif);
font-size: 0.9rem;
color: var(--text);
background: transparent;
border: none;
border-bottom: 1px solid var(--ui-2);
width: calc(100% + var(--space-xl));  /* extends into grid gap */

Placeholder: italic, --text-3. Focus: border-bottom-color: var(--text). Transition: border-color 0.3s ease.

Button (.newsletter-row button): Text-link pattern matching “All writing →” / “All links →” navigation.

font-family: var(--font-sans);
font-size: 0.8rem;
font-weight: 400;
color: var(--text-2);
background: none;
border: none;

Blurb (.newsletter-blurb): Same treatment as .section-subtitle--font-sans, 0.8rem, --text-2.

States

Input:

Stateborder-bottom-color
Defaultvar(--ui-2)
Focusvar(--text)

Button:

Statecolor
Defaultvar(--text-2)
Hovervar(--text)
Disabledopacity 0.5

Status messages (.newsletter-status):

Statecolor
Successvar(--accent)
Errorvar(--red)

Responsive behavior

BreakpointChange
<= 600pxGrid collapses to single column, input resets to 100% width

Code example

<section class="newsletter" data-animate>
  <p class="section-label">Newsletter</p>
  <p class="newsletter-blurb">A loosely structured newsletter from someone who's never been great at doing just one thing.</p>
  <form class="newsletter-form" id="newsletter-form">
    <div class="newsletter-row">
      <input type="email" name="email" placeholder="your@email.com" required autocomplete="email" />
      <button type="submit">Subscribe →</button>
    </div>
    <p class="newsletter-status" id="newsletter-status"></p>
  </form>
</section>

Toast notification

Purpose

Confirms brief actions. “URL copied to clipboard.” Not a software notification. An editorial aside, set in serif italic on a paper-colored surface with a warm shadow. It appears, delivers its message, and leaves.

Anatomy

PartSelectorDescription
Toast.toastFixed-position notification element

Specs

position: fixed;
bottom: var(--space-xl);
left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--bg);
color: var(--text);
padding: var(--space-sm) var(--space-lg);
border: 1px solid var(--ui);
font-family: var(--font-serif);
font-style: italic;
font-size: 0.9rem;
letter-spacing: 0.01em;
opacity: 0;
pointer-events: none;
z-index: 1000;

The serif italic text and paper background distinguish this from the “software toast” pattern. It reads like a parenthetical remark, not a system alert.

Box shadow is mode-dependent:

Modebox-shadow
Light0 2px 8px hsl(40deg 10% 50% / 0.1), 0 1px 2px hsl(40deg 10% 50% / 0.06)
Dark0 2px 8px hsl(30deg 5% 5% / 0.4), 0 1px 2px hsl(30deg 5% 5% / 0.2)

The warm hue angles in the shadow (40deg light, 30deg dark) match the paper metaphor. No neutral grays.

States

Statetransformopacity
HiddentranslateX(-50%) translateY(20px)0
.showtranslateX(-50%) translateY(0)1

Transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease. The signature easing on the slide-up gives the toast a physical, decelerating settle.

Responsive behavior

BreakpointChange
Printdisplay: none !important

Accessibility

The toast is non-interactive (pointer-events: none). It should be announced via role="status" or an ARIA live region so screen readers catch the confirmation without requiring visual attention. Content is always a brief, complete sentence.

Code example

<div class="toast" id="toast" role="status">URL copied to clipboard</div>

Share modal

Purpose

Overlay for sharing pull quotes as generated PNG images. Contains a preview of the share image and action buttons (copy image, download, close). This is the only modal pattern on the site.

Anatomy

PartSelectorDescription
Overlay.share-modal-overlayFull-screen backdrop
Modal card.share-modalCentered content card
Preview image.share-modal-imageGenerated PNG preview
Actions row.share-modal-actionsButton group
Default button.share-modal-actions buttonSecondary action
Primary button.share-modal-actions button.primaryPrimary action
Close button.share-modal-closeTop-right dismiss
Image template.share-image-templateOffscreen rendering target

Specs

Overlay: Fixed full-screen at z-index: 2000, background: rgba(0, 0, 0, 0.6). Centers the modal card with flexbox.

Modal card: background: var(--bg), border-radius: 8px, padding: var(--space-lg), max-width: 90vw, max-height: 90vh, box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2). The 8px border radius is the only rounded corner on the site, justified by the overlay context separating it from the page’s sharp-corner aesthetic.

Preview image: max-width: 500px, border-radius: 4px, border: 1px solid var(--ui).

Buttons: Montreuil at 0.85rem, padding: var(--space-sm) var(--space-md), border-radius: 4px.

Close button: Positioned absolute at top: var(--space-md), right: var(--space-md). No background, no border, font-size: 1.5rem, --text-2 color.

Image template: Hidden offscreen (left: -9999px), width: 600px, padding: 48px. Used to render the share card PNG with Signifier at 28px italic weight 300.

States

Overlay:

Stateopacityvisibility
Hidden0hidden
.show1visible

Transition: opacity 0.2s ease, visibility 0.2s ease.

Buttons:

VariantStatebackgroundcolorborder-color
DefaultDefaultvar(--bg)var(--text)var(--ui)
DefaultHovervar(--bg)var(--text)var(--accent)
PrimaryDefaultvar(--text)var(--bg)var(--text)
PrimaryHovervar(--accent)var(--bg)var(--accent)

Button transition: border-color 0.25s ease, background 0.25s ease.

Responsive behavior

BreakpointChange
<= 600pxModal padding reduces to var(--space-md)
<= 600pxImage max-width becomes 100%
<= 600pxButtons gain min-height: 44px for touch targets
Printdisplay: none !important

Accessibility

The overlay should trap focus while open and restore it on close. Escape key closes the modal. The close button needs an accessible label. The generated share image should have meaningful alt text describing the quoted content.

The 44px button height at <= 600px meets WCAG 2.5.8 touch target guidance.

Code example

<div class="share-modal-overlay" id="shareModal">
  <div class="share-modal">
    <button class="share-modal-close" aria-label="Close">&times;</button>
    <img class="share-modal-image" src="" alt="Share image preview" />
    <div class="share-modal-actions">
      <button class="primary">Copy image</button>
      <button>Download</button>
    </div>
  </div>
</div>

Specimens

Theme Toggle

Dark mode
Light mode

Press D to toggle

Accent Picker

8 Flexoki hues. Press C to cycle.

Font Size

Cycles through size options

Toast

URL copied to clipboard.