/* ============================================================
   Pagelove Design System — primitives

   The parts every Pagelove surface is built from: labels, fields,
   buttons, tables, dividers, panels. Values come from tokens.css and
   the shapes come from pagelove.com.

   Load order on every page: tokens.css, fonts.css, primitives.css,
   then the page's own stylesheet, then any sub-app's.

   A page or sub-app stylesheet CONSUMES these. It should not restate a
   font stack, a type size, a radius, a border colour or a button; if it
   needs one, it needs a class from here. What belongs in a page or
   sub-app stylesheet is its own layout — where things sit, how big the
   column is, what reveals on hover.

   Element-level primitives are wrapped in :where() so their specificity
   is zero and any page rule wins without reaching for !important.
   ============================================================ */

/* ── Focus ────────────────────────────────────────────────────
   The design guide marks keyboard focus with a 2px outline in
   --border-focused, 1px outside a control and 2px outside a link. Every
   focusable part gets it here, at zero specificity, so a page rule that
   needs another offset (a row inside a scrolling list) can still say so. */
:where(button, input, select, textarea, summary, [tabindex], [contenteditable]):focus-visible {
  outline: 2px solid var(--border-focused);
  outline-offset: 1px;
}
:where(a):focus-visible {
  outline: 2px solid var(--border-focused);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ── Small labels ─────────────────────────────────────────────
   The site's own label voice (.code-inline on pagelove.com): Fira Code
   at 14px, sentence case, never uppercased and never tracked open. Used
   for field labels, eyebrows, column headings, legends. */
.pl-label,
:where(dl.pl-fields > dt, fieldset > legend) {
  font-family: var(--font-mono);
  font-size: var(--fs-caption-primary, 14px);
  font-weight: 450;
  letter-spacing: -0.6px;
  line-height: var(--lh-caption-primary, 22px);
  color: var(--text-subtle);
}

/* The eyebrow variant carries the brand gradient, as the site's section
   eyebrows do. */
.pl-label--brand {
  background: var(--gradient-main);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ── Text fields ──────────────────────────────────────────────
   The design guide's text field (Storybook: Inputs/Forms), at its small
   size: a pale face with a regular hairline and 8px corners, Inter at
   14px. The hairline firms on hover. On focus it turns the selected blue
   with the focus ring round it. An invalid value is red, and a disabled
   field loses its face. The hairline is a real border, so a page rule
   that sets its own border replaces it rather than doubling it. A page
   that wants the guide's medium size sets 12px by 16px, 16px text and
   12px corners. Fields that hold code set Fira Code themselves. */
:where(
  input:not([type]),
  input[type="text"],
  input[type="email"],
  input[type="search"],
  input[type="password"],
  input[type="url"],
  input[type="tel"],
  input[type="number"],
  textarea,
  select
) {
  box-sizing: border-box;
  margin: 0;
  padding: var(--space-100) var(--space-150);
  font-family: var(--font-family-secondary);
  font-size: var(--font-size-150);
  font-weight: var(--font-weight-400);
  line-height: var(--line-height-225);
  letter-spacing: var(--letter-spacing-100);
  color: var(--text-regular);
  caret-color: var(--text-regular);
  background-color: var(--background-surface-regular);
  border: 1px solid var(--border-regular);
  border-radius: 8px;
  outline: 2px solid transparent;
  outline-offset: 0;
  transition:
    border-color var(--motion-duration-hover) var(--motion-easing-standard),
    outline-color var(--motion-duration-hover) var(--motion-easing-standard),
    background-color var(--motion-duration-hover) var(--motion-easing-standard);
}

:where(
  input:not([type]),
  input[type="text"],
  input[type="email"],
  input[type="search"],
  input[type="password"],
  input[type="url"],
  input[type="tel"],
  input[type="number"],
  textarea,
  select
):hover {
  border-color: var(--border-strong);
}

:where(
  input:not([type]),
  input[type="text"],
  input[type="email"],
  input[type="search"],
  input[type="password"],
  input[type="url"],
  input[type="tel"],
  input[type="number"],
  textarea,
  select
):focus {
  outline: 2px solid var(--border-focused);
  outline-offset: 0;
  border-color: var(--border-selected);
}

:where(
  input:not([type]),
  input[type="text"],
  input[type="email"],
  input[type="search"],
  input[type="password"],
  input[type="url"],
  input[type="tel"],
  input[type="number"],
  textarea,
  select
)[aria-invalid="true"] {
  color: var(--text-light);
  border-color: var(--border-danger-strong);
  outline-color: var(--border-danger-light);
}

:where(
  input:not([type]),
  input[type="text"],
  input[type="email"],
  input[type="search"],
  input[type="password"],
  input[type="url"],
  input[type="tel"],
  input[type="number"],
  textarea,
  select
):disabled {
  color: var(--text-disabled);
  background-color: transparent;
  border-color: var(--border-disabled);
  outline-color: transparent;
  cursor: not-allowed;
}

:where(input, textarea)::placeholder {
  color: var(--text-light);
  opacity: 1;
}
:where(input, textarea):focus::placeholder {
  color: var(--text-disabled);
}

/* A field whose content is prose rather than a value: the guide's
   medium text size. */
.pl-field--prose {
  font-family: var(--font-family-secondary);
  font-size: var(--font-size-200);
}

@media (prefers-reduced-motion: reduce) {
  :where(
  input:not([type]),
  input[type="text"],
  input[type="email"],
  input[type="search"],
  input[type="password"],
  input[type="url"],
  input[type="tel"],
  input[type="number"],
  textarea,
  select
  ) {
    transition: none;
  }
}

/* ── Checkboxes and radio buttons ─────────────────────────────
   The design guide's checkbox and radio button (Storybook: Inputs). A
   20px box with 6px corners, or a 20px circle: a pale fill and a regular
   hairline, blue when chosen. Hovering an empty box shows a faint tick,
   as the guide does. A part-ticked box shows a blue dash. The tick and
   the dot are ::before, so the inputs stay native and keep their
   behaviour. Every state is at zero specificity, so a page can still
   place or size one. */
:where(input[type="checkbox"], input[type="radio"]) {
  --_bg: var(--button-regular-default);
  --_border: var(--border-regular);
  --_glyph: transparent;
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  display: inline-grid;
  place-content: center;
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  padding: 0;
  vertical-align: middle;
  background-color: var(--_bg);
  border: 0;
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px var(--_border);
  outline: 2px solid transparent;
  outline-offset: -1px;
  cursor: pointer;
  transition:
    --_bg var(--motion-duration-hover) var(--motion-easing-standard),
    --_border var(--motion-duration-hover) var(--motion-easing-standard),
    outline-color var(--motion-duration-hover) var(--motion-easing-standard),
    opacity var(--motion-duration-hover) var(--motion-easing-standard);
}
:where(input[type="radio"]) {
  border-radius: 50%;
}
:where(input[type="checkbox"], input[type="radio"])::before {
  content: "";
  display: block;
  background-color: var(--_glyph);
  transition: background-color var(--motion-duration-hover) var(--motion-easing-standard);
}
:where(input[type="checkbox"])::before {
  width: 16px;
  height: 16px;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E") center / contain no-repeat;
}
:where(input[type="checkbox"]:indeterminate)::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 11h14v2H5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 11h14v2H5z'/%3E%3C/svg%3E");
}
:where(input[type="radio"])::before {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}

/* Empty: a faint mark on hover, a stronger one while pressed. */
:where(input[type="checkbox"], input[type="radio"]):hover {
  --_bg: var(--button-regular-hovered);
  --_border: var(--border-strong);
  --_glyph: var(--icon-disabled);
}
:where(input[type="checkbox"], input[type="radio"]):active {
  --_bg: var(--button-regular-pressed);
  --_border: var(--border-regular);
  --_glyph: var(--icon-light);
}

/* Chosen: a blue fill with a pale mark. */
:where(input[type="checkbox"]:checked:not(:indeterminate), input[type="radio"]:checked) {
  --_bg: var(--link-icon-default);
  --_border: transparent;
  --_glyph: var(--background-information-light);
}
:where(input[type="checkbox"]:checked:not(:indeterminate), input[type="radio"]:checked):hover {
  --_bg: var(--link-icon-hover);
  --_border: transparent;
  --_glyph: var(--background-information-light);
}
:where(input[type="checkbox"]:checked:not(:indeterminate), input[type="radio"]:checked):active {
  --_bg: var(--link-icon-pressed);
  --_border: transparent;
  --_glyph: var(--background-information-light);
}

/* Part-ticked: the pale box with a blue dash. */
:where(input[type="checkbox"]:indeterminate) {
  --_glyph: var(--link-icon-default);
}
:where(input[type="checkbox"]:indeterminate):hover {
  --_glyph: var(--link-icon-hover);
}
:where(input[type="checkbox"]:indeterminate):active {
  --_glyph: var(--link-icon-pressed);
}

:where(input[type="checkbox"], input[type="radio"]):focus-visible {
  outline: 2px solid var(--border-focused);
  outline-offset: -1px;
}
:where(input[type="radio"]:checked):focus-visible {
  outline-offset: 1px;
}

/* Disabled: the resting look at a quarter strength, with no hover. */
:where(input[type="checkbox"], input[type="radio"]):disabled {
  --_bg: var(--button-regular-default);
  --_border: var(--border-regular);
  --_glyph: transparent;
  opacity: 0.25;
  cursor: not-allowed;
}
:where(input[type="checkbox"]:checked:not(:indeterminate), input[type="radio"]:checked):disabled {
  --_bg: var(--link-icon-default);
  --_border: transparent;
  --_glyph: var(--background-information-light);
}
:where(input[type="checkbox"]:indeterminate):disabled {
  --_glyph: var(--link-icon-default);
}

@media (prefers-reduced-motion: reduce) {
  :where(input[type="checkbox"], input[type="radio"]),
  :where(input[type="checkbox"], input[type="radio"])::before {
    transition: none;
  }
}
/* Windows high contrast: the system's own controls, which it can
   recolour. */
@media (forced-colors: active) {
  :where(input[type="checkbox"], input[type="radio"]) {
    appearance: auto;
    -webkit-appearance: auto;
  }
  :where(input[type="checkbox"], input[type="radio"])::before {
    content: none;
  }
}

/* ── Buttons ──────────────────────────────────────────────────
   The design guide's button (Storybook: Buttons/Button). A pill with an
   Inter 500 label. Its face is painted by ::after and its hover glow by
   ::before, both behind the label: isolation keeps them inside the
   button, so the label needs no wrapping span. Each state only changes
   the custom properties below, which the face and the label read.

   The console's classes map onto the guide's variants and sizes:
     .btn-primary    filled strong, sm
     .btn-dark       filled strong, lg
     .btn-secondary  outlined, sm
     .btn-quiet      outlined, xs
     .btn-outline    outlined, lg
     .btn-danger     the danger button, sm. The guide has no destructive
                     button, so this is the strong button in the guide's
                     red, with no glow.
   A page sets a different guide size, or the regular fill, by setting
   the same properties in its own rule. */
@property --_bg {
  syntax: "<color>";
  inherits: true;
  initial-value: transparent;
}
@property --_border {
  syntax: "<color>";
  inherits: true;
  initial-value: transparent;
}

.btn-primary,
.btn-secondary,
.btn-quiet,
.btn-danger,
.btn-dark,
.btn-outline {
  --_bg: transparent;
  --_border: transparent;
  --_fg: var(--button-strong-text);
  --_glow: 0;
  --_spinner: var(--link-icon-default);
  position: relative;
  isolation: isolate;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-100);
  margin: 0;
  padding: var(--space-100) var(--space-250);
  appearance: none;
  border: 0;
  border-radius: 100px;
  background: none;
  color: var(--_fg);
  font-family: var(--font-family-secondary);
  font-size: var(--font-size-150);
  font-weight: var(--font-weight-500);
  line-height: var(--line-height-225);
  letter-spacing: var(--letter-spacing-200);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    color var(--motion-duration-hover) var(--motion-easing-standard),
    --_bg var(--motion-duration-hover) var(--motion-easing-standard),
    --_border var(--motion-duration-hover) var(--motion-easing-standard);
}

/* The face, and the glow behind it. */
:is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)::after,
:is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}
:is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)::after {
  z-index: -1;
  background: var(--_bg);
  box-shadow: inset 0 0 0 1px var(--_border);
}
:is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)::before {
  z-index: -2;
  background-image: linear-gradient(149deg, var(--color-purple-600) 2.3%, var(--color-old-rose-600) 97.1%);
  filter: blur(12px);
  opacity: var(--_glow);
  /* Safari stops drawing the glow when its fade-in ends unless it keeps
     its own layer. */
  will-change: opacity;
  transition: opacity var(--motion-duration-hover) var(--motion-easing-standard);
}

/* Sizes. sm is the default above. */
.btn-dark,
.btn-outline {
  min-width: 158px;
  padding: var(--space-150) var(--space-400);
  gap: var(--space-150);
  font-size: var(--font-size-200);
  line-height: var(--line-height-250);
}
.btn-quiet,
.btn-danger--quiet {
  padding: var(--space-075) var(--space-200);
  gap: var(--space-075);
}

/* Filled strong. */
.btn-primary,
.btn-dark {
  --_bg: var(--button-strong-default);
  --_fg: var(--button-strong-text);
  --_spinner: var(--link-icon-inverse);
}
.btn-primary:hover,
.btn-dark:hover {
  --_bg: var(--button-strong-hovered);
  --_glow: 1;
}
.btn-primary:active,
.btn-dark:active {
  --_bg: var(--button-strong-pressed);
  --_glow: 0;
}
.btn-primary:disabled,
.btn-dark:disabled {
  --_bg: var(--button-strong-disabled);
}

/* Outlined. */
.btn-secondary,
.btn-quiet,
.btn-outline {
  --_border: var(--border-regular);
  --_fg: var(--button-secondary-text-default);
}
.btn-secondary:hover,
.btn-quiet:hover,
.btn-outline:hover {
  --_bg: var(--button-regular-hovered);
  --_border: var(--border-strong);
  --_fg: var(--button-secondary-text-hovered);
}
.btn-secondary:active,
.btn-quiet:active,
.btn-outline:active {
  --_bg: var(--button-regular-pressed);
  --_border: var(--border-regular);
  --_fg: var(--button-secondary-text-pressed);
}
.btn-secondary:disabled,
.btn-quiet:disabled,
.btn-outline:disabled {
  --_bg: transparent;
  --_border: var(--border-regular);
  --_fg: var(--text-disabled);
}

/* The glow's hovered strength is set on the glow itself as well as through
   --_glow, so Safari has a value of its own to draw once the fade ends. */
:is(.btn-primary, .btn-dark, .btn-gradient):hover::before {
  opacity: 1;
}
:is(.btn-primary, .btn-dark, .btn-gradient):is(:active, :disabled, [aria-busy="true"])::before {
  opacity: 0;
}

/* Outlined, gradient appearance: add .btn-gradient to an outlined button.
   The face has the gradient border, the label (a child span) the gradient
   text, and hovering lights the glow. The label sits in a span because the
   face is drawn over the button's own background. */
.btn-gradient {
  --_bg: var(--button-gradient-bg-default);
}
.btn-gradient:hover {
  --_bg: var(--button-gradient-bg-hovered);
  --_glow: 1;
}
.btn-gradient:active {
  --_bg: var(--button-gradient-bg-pressed);
  --_glow: 0;
}
.btn-gradient:disabled {
  --_glow: 0;
}
.btn-gradient::after {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--_bg), var(--_bg)) padding-box,
    var(--gradient-dawn-bloom) border-box;
  box-shadow: none;
}
.btn-gradient > span {
  background: var(--gradient-brand-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Danger: the strong button in the guide's red. */
.btn-danger {
  --_bg: var(--icon-danger);
  --_fg: var(--button-strong-text);
  --_spinner: var(--link-icon-inverse);
}
.btn-danger:hover {
  --_bg: var(--color-torch-red-800);
}
.btn-danger:active {
  --_bg: var(--color-torch-red-900);
}
.btn-danger:disabled {
  --_bg: var(--background-danger-regular);
}

/* An outlined destructive action, for places where a filled red button
   would shout: a per-row delete, a danger zone inside a panel. */
.btn-danger--quiet {
  --_bg: transparent;
  --_border: var(--border-danger);
  --_fg: var(--text-danger);
  --_spinner: var(--icon-danger);
}
.btn-danger--quiet:hover {
  --_bg: var(--background-danger-light);
  --_border: var(--border-danger-strong);
}
.btn-danger--quiet:active {
  --_bg: var(--background-danger-regular);
}
.btn-danger--quiet:disabled {
  --_bg: transparent;
  --_border: var(--border-danger);
  --_fg: var(--text-disabled);
}

.btn-primary:disabled,
.btn-secondary:disabled,
.btn-quiet:disabled,
.btn-danger:disabled,
.btn-dark:disabled,
.btn-outline:disabled {
  --_glow: 0;
  cursor: not-allowed;
}

/* Busy: the label gives way to the guide's spinner, 24px, in the middle.
   The label is still the button's text, so screen readers keep it. */
:is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)[aria-busy="true"] {
  --_glow: 0;
  color: transparent;
  cursor: progress;
  pointer-events: none;
}
:is(.btn-primary, .btn-dark)[aria-busy="true"] {
  --_bg: var(--button-strong-default);
}
.btn-danger[aria-busy="true"] {
  --_bg: var(--icon-danger);
}
.btn-danger--quiet[aria-busy="true"] {
  --_bg: transparent;
}
:is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)[aria-busy="true"]::before {
  --_size: 24px;
  --_stroke: 2.88px;
  z-index: 1;
  inset: calc(50% - var(--_size) / 2) auto auto calc(50% - var(--_size) / 2);
  width: var(--_size);
  height: var(--_size);
  border-radius: 50%;
  filter: none;
  opacity: 1;
  background:
    radial-gradient(circle at 50% calc(100% - var(--_stroke) / 2), var(--_spinner) calc(var(--_stroke) / 2), transparent calc(var(--_stroke) / 2 + 0.5px)),
    conic-gradient(from 180deg, color-mix(in srgb, var(--_spinner) 0%, transparent), var(--_spinner));
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_stroke) - 0.5px), black calc(100% - var(--_stroke)));
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--_stroke) - 0.5px), black calc(100% - var(--_stroke)));
  animation: pl-spin var(--motion-duration-spin) var(--motion-easing-spin) infinite;
}
.btn-quiet[aria-busy="true"]::before {
  --_size: 16px;
  --_stroke: 2.08px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-primary,
  .btn-secondary,
  .btn-quiet,
  .btn-danger,
  .btn-dark,
  .btn-outline,
  :is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)::before {
    transition: none;
  }
}

/* An icon-only control: a close ×, a per-row delete. The guide's light
   icon button, kept at 28px for the tight rows that use it. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  appearance: none;
  font-size: 18px;
  line-height: 1;
  color: var(--button-borderless-icon-default);
  background: transparent;
  border: 0;
  border-radius: 100px;
  cursor: pointer;
  transition:
    color var(--motion-duration-hover) var(--motion-easing-standard),
    background-color var(--motion-duration-hover) var(--motion-easing-standard);
}
.btn-icon:hover {
  color: var(--button-borderless-icon-hovered);
  background: var(--button-regular-hovered);
}
.btn-icon:active {
  color: var(--button-borderless-icon-pressed);
  background: var(--button-regular-pressed);
}
.btn-icon--danger:hover {
  color: var(--icon-danger);
  background: var(--background-danger-light);
}

/* ── Spinner ──────────────────────────────────────────────────
   The guide's radial progress, indeterminate (Storybook: Progress
   indicators/ProgressRadial). A ring that fades from clear to blue with
   a round head, turning every 0.8s. md, 40px, by default. */
.pl-spinner {
  --_size: 40px;
  --_stroke: 4px;
  --_spinner: var(--link-icon-default);
  box-sizing: border-box;
  display: inline-block;
  flex: none;
  width: var(--_size);
  height: var(--_size);
  border-radius: 50%;
  vertical-align: middle;
  background:
    radial-gradient(circle at 50% calc(100% - var(--_stroke) / 2), var(--_spinner) calc(var(--_stroke) / 2), transparent calc(var(--_stroke) / 2 + 0.5px)),
    conic-gradient(from 180deg, color-mix(in srgb, var(--_spinner) 0%, transparent), var(--_spinner));
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_stroke) - 0.5px), black calc(100% - var(--_stroke)));
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--_stroke) - 0.5px), black calc(100% - var(--_stroke)));
  animation: pl-spin var(--motion-duration-spin) var(--motion-easing-spin) infinite;
}
.pl-spinner--xs { --_size: 24px; --_stroke: 2.88px; }
.pl-spinner--xxs { --_size: 16px; --_stroke: 2.08px; }

@keyframes pl-spin {
  to { transform: rotate(1turn); }
}
@media (prefers-reduced-motion: reduce) {
  .pl-spinner,
  :is(.btn-primary, .btn-secondary, .btn-quiet, .btn-danger, .btn-dark, .btn-outline)[aria-busy="true"]::before {
    animation-duration: 2.4s;
    animation-timing-function: linear;
  }
}

/* ── Links ────────────────────────────────────────────────────
   The design guide's link (Storybook: Buttons/Link). Standalone by
   default: Inter 500 in the guide's blue, no underline. --subtle is the
   guide's grey link. --inline underlines, for a link in running text. */
.pl-link {
  --_fg: var(--link-text-default);
  --_fg-hover: var(--link-text-hover);
  --_fg-pressed: var(--link-text-pressed);
  display: inline-flex;
  align-items: center;
  gap: var(--space-050);
  border-radius: 6px;
  color: var(--_fg);
  font-family: var(--font-family-secondary);
  font-weight: var(--font-weight-500);
  letter-spacing: var(--letter-spacing-200);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--motion-duration-hover) var(--motion-easing-standard);
}
.pl-link--subtle {
  --_fg: var(--button-borderless-text-default);
  --_fg-hover: var(--button-borderless-text-hovered);
  --_fg-pressed: var(--button-borderless-text-pressed);
}
.pl-link--inline {
  display: inline;
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-position: from-font;
}
.pl-link:hover {
  color: var(--_fg-hover);
}
.pl-link:active {
  color: var(--_fg-pressed);
}
.pl-link[aria-disabled="true"] {
  color: var(--text-disabled);
  pointer-events: none;
  cursor: not-allowed;
}
@media (prefers-reduced-motion: reduce) {
  .pl-link { transition: none; }
}

/* ── Tables ───────────────────────────────────────────────────
   Hairline rows, no fills, mono sentence-case column headings. */
.pl-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body, 16px);
}
.pl-table th {
  text-align: left;
  padding: 0 16px 12px 0;
  border-bottom: 1px solid var(--border-subtle);
  font-family: var(--font-mono);
  font-weight: 450;
  font-size: var(--fs-caption-primary, 14px);
  letter-spacing: -0.6px;
  color: var(--text-subtle);
}
.pl-table td {
  padding: 16px 16px 16px 0;
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-body);
  vertical-align: baseline;
}
.pl-table tbody tr {
  transition: background-color var(--motion-duration-hover) var(--motion-easing-standard);
}
.pl-table tbody tr:hover {
  background: var(--accent-wash);
}

/* ── Dividers ─────────────────────────────────────────────────
   The site's hairline dissolves at both ends. border-image paints it on
   whichever edge has a width, so one class covers a rule above or below. */
.pl-divider-top {
  border-top: 1px solid var(--border-subtle);
  border-image: var(--gradient-divider) 1;
}
.pl-divider-bottom {
  border-bottom: 1px solid var(--border-subtle);
  border-image: var(--gradient-divider) 1;
}
hr.pl-divider {
  height: 1px;
  width: 100%;
  border: none;
  margin: 0;
  background: var(--gradient-divider);
}

/* ── Panels ───────────────────────────────────────────────────
   A floating surface: a dialog, a tile, a notice. White, a hairline, and
   a soft lift. Flat sections of a page do not need this — a hairline and
   whitespace are how the site separates those. */
.pl-panel {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg, 16px);
  box-shadow: var(--shadow-panel);
}
.pl-panel--tile {
  border-radius: var(--radius-md, 12px);
  box-shadow: var(--shadow-tile);
}

/* A frosted bar over content: a sticky header, a floating toolbar. */
.pl-mask {
  background: var(--colours-background-mask);
  backdrop-filter: blur(var(--blur-strong, 24px));
  -webkit-backdrop-filter: blur(var(--blur-strong, 24px));
}
