/*! polyfish.org site.css | MIT
 *
 * Site chrome, not framework. Everything here belongs to polyfish.org's
 * documentation rather than to PolyFish, which is why it is a separate file:
 * a project using the framework should never inherit a docs playground.
 *
 * WHAT MAKES THE PLAYGROUND WORK
 *   Radio inputs, :has() and the token architecture, and nothing else. The
 *   controls set no classes and run no script - each rule redeclares a custom
 *   property on the stage, and every component inside re-renders because none
 *   of them reference a raw value.
 *
 *   :has() is Chrome 105, Safari 15.4, Firefox 121 and up. Where it is
 *   missing the stage simply shows its default state and the controls do
 *   nothing, which is a degradation rather than a break.
 *
 * CONTENTS
 *   1. Playground shell
 *   2. Theme switch
 *   3. Shape, density and accent controls
 *   4. Grid column control
 *   5. Changelog timeline
 */

/* ==========================================================================
   #PLAYGROUND SHELL
   ========================================================================== */

.pg {
  display: grid;
  gap: var(--pf-space-5);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg);
  overflow: hidden;
}

.pg__controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pf-space-5);
  background: var(--pf-bg-deep);
  border-bottom: 1px solid var(--pf-border);
  padding: var(--pf-space-4) var(--pf-space-5);
}

.pg__group {
  display: grid;
  gap: var(--pf-space-2);
}

.pg__legend {
  color: var(--pf-text-muted);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
}

/* The control strip reuses the tab pattern: a radio per option, a label
   styled as the button, and no script to keep them in sync. */
.pg__options {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
}
.pg__radio {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.pg__label {
  border: 1px solid var(--pf-border);
  color: var(--pf-text-muted);
  cursor: pointer;
  font-size: var(--pf-text-xs);
  font-weight: 500;
  padding: 0.5rem 0.85rem;
  min-height: 2.25rem;
  display: inline-flex;
  align-items: center;
  margin-left: -1px;
  transition: background-color var(--pf-speed) var(--pf-ease), color var(--pf-speed) var(--pf-ease);
}
.pg__label:first-of-type { margin-left: 0; border-start-start-radius: var(--pf-radius-sm); border-end-start-radius: var(--pf-radius-sm); }
.pg__label:last-of-type { border-start-end-radius: var(--pf-radius-sm); border-end-end-radius: var(--pf-radius-sm); }
.pg__label:hover { color: var(--pf-text); }
.pg__radio:checked + .pg__label {
  background: var(--pf-accent);
  border-color: var(--pf-accent);
  color: var(--pf-base-950);
}
.pg__radio:focus-visible + .pg__label { outline: 2px solid var(--pf-focus); outline-offset: 2px; }

.pg__stage {
  background: var(--pf-bg);
  color: var(--pf-text);
  padding: var(--pf-space-6) var(--pf-space-5);
  transition: background-color var(--pf-speed) var(--pf-ease);
}

.pg__note {
  border-top: 1px solid var(--pf-border);
  background: var(--pf-bg-deep);
  color: var(--pf-text-muted);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  padding: var(--pf-space-3) var(--pf-space-5);
}

/* ==========================================================================
   #THEME SWITCH
   ========================================================================== */

/**
 * The same palette block the framework ships, scoped to the stage instead of
 * :root. Proof that a theme is a redeclaration and not a fork.
 */
.pg:has(#pg-light:checked) .pg__stage {
  color-scheme: light;
  --pf-base-900: #e7edf3;
  --pf-base-800: #f4f7fa;
  --pf-hue-1: #45702f;
  --pf-hue-1-deep: #35591f;
  --pf-hue-2: #9a5620;
  --pf-base-50: #0d2436;
  --pf-base-300: #4a6072;
  --pf-base-950: #f7fbff;
  --pf-bg: #f4f7fa;
  --pf-bg-deep: #e7edf3;
  --pf-text: #0d2436;
  --pf-text-muted: #4a6072;
  --pf-surface: rgba(13, 36, 54, 0.045);
  --pf-surface-raised: rgba(13, 36, 54, 0.085);
  --pf-border: rgba(13, 36, 54, 0.16);
  --pf-border-strong: rgba(69, 112, 47, 0.45);
  --pf-accent: #45702f;
  --pf-accent-2: #9a5620;
  --pf-focus: #45702f;
  --pf-mesh: none;
}

/* ==========================================================================
   #SHAPE, DENSITY AND ACCENT
   ========================================================================== */

.pg:has(#pg-cut-0:checked) .pg__stage { --pf-cut: 0px; }
.pg:has(#pg-cut-18:checked) .pg__stage { --pf-cut: 18px; }
.pg:has(#pg-cut-36:checked) .pg__stage { --pf-cut: 36px; }

.pg:has(#pg-radius-sharp:checked) .pg__stage { --pf-radius: 2px; --pf-radius-lg: 2px; --pf-radius-sm: 2px; }
.pg:has(#pg-radius-default:checked) .pg__stage { --pf-radius: 12px; --pf-radius-lg: 22px; }
.pg:has(#pg-radius-soft:checked) .pg__stage { --pf-radius: 18px; --pf-radius-lg: 32px; }

.pg:has(#pg-density-tight:checked) .pg__stage {
  --pf-space-4: 0.75rem;
  --pf-space-5: 1rem;
  --pf-space-6: 1.25rem;
  --pf-gap: 0.75rem;
  --pf-text-base: 0.9375rem;
}
.pg:has(#pg-density-airy:checked) .pg__stage {
  --pf-space-4: 1.25rem;
  --pf-space-5: 2rem;
  --pf-space-6: 3rem;
  --pf-gap: 2.5rem;
}

.pg:has(#pg-accent-sage:checked) .pg__stage { --pf-accent: #8fe06a; --pf-accent-2: #e5b189; --pf-focus: #8fe06a; --pf-border-strong: rgba(143, 224, 106, 0.45); }
.pg:has(#pg-accent-teal:checked) .pg__stage { --pf-accent: #79c7c0; --pf-accent-2: #c3a6e0; --pf-focus: #79c7c0; --pf-border-strong: rgba(121, 199, 192, 0.45); }
.pg:has(#pg-accent-amber:checked) .pg__stage { --pf-accent: #e0bd7a; --pf-accent-2: #d59a8a; --pf-focus: #e0bd7a; --pf-border-strong: rgba(224, 189, 122, 0.45); }

/* ==========================================================================
   #GRID COLUMN CONTROL
   ========================================================================== */

.pg:has(#pg-cols-2:checked) .pg__row { --pf-row-cols: 2; }
.pg:has(#pg-cols-3:checked) .pg__row { --pf-row-cols: 3; }
.pg:has(#pg-cols-4:checked) .pg__row { --pf-row-cols: 4; }
.pg:has(#pg-cols-6:checked) .pg__row { --pf-row-cols: 6; }
.pg__row > * { grid-column: span 1; }
.pg__cell {
  background: var(--pf-surface);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-sm);
  color: var(--pf-text-muted);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  padding: var(--pf-space-4);
  text-align: center;
}

/* Gutter control, driven by the same two properties the Grid module reads. */
.pg:has(#pg-gutter-0:checked) .pg__row { --pf-gutter-x: 0; --pf-gutter-y: 0; }
.pg:has(#pg-gutter-2:checked) .pg__row { --pf-gutter-x: var(--pf-space-2); --pf-gutter-y: var(--pf-space-2); }
.pg:has(#pg-gutter-6:checked) .pg__row { --pf-gutter-x: var(--pf-space-6); --pf-gutter-y: var(--pf-space-6); }

/* ==========================================================================
   #DEVICE PREVIEWER
   ==========================================================================
   The one honest way to show responsive behaviour inside a page: a same-origin
   iframe, resized by radio buttons. Media queries inside the frame evaluate
   against the frame's width, so what you see is what a phone sees - not a
   scaled-down screenshot and not a container-query approximation.

   This is why the site's frame-ancestors is 'self' rather than 'none'. Other
   origins still cannot frame these pages.
   ========================================================================== */

.vp { display: grid; gap: var(--pf-space-4); }

.vp__stage {
  display: grid;
  justify-items: center;
  background: var(--pf-bg-deep);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg);
  padding: var(--pf-space-4);
  overflow-x: auto;
}

.vp__frame {
  width: 100%;
  height: 34rem;
  max-height: 70dvh;
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius);
  background: var(--pf-bg);
  transition: width var(--pf-speed) var(--pf-ease);
}

/* Each width is a real CSS pixel width on the frame. 100% is whatever the
   documentation column happens to be. */
.vp:has(#vp-320:checked) .vp__frame { width: 320px; }
.vp:has(#vp-390:checked) .vp__frame { width: 390px; }
.vp:has(#vp-768:checked) .vp__frame { width: 768px; }
.vp:has(#vp-1024:checked) .vp__frame { width: 1024px; }
.vp:has(#vp-full:checked) .vp__frame { width: 100%; }


.vp__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pf-space-2) var(--pf-space-5);
  color: var(--pf-text-muted);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
}
.vp__meta b { color: var(--pf-text); font-weight: 500; }

/* Which framework breakpoints are live at the selected width. Every marker is
   dim until its query would match, which turns the abstract list of numbers
   into something you can watch cross over. */
.vp__bp {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  opacity: 0.35;
}
.vp__bp::before {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--pf-accent);
}
.vp:has(#vp-320:checked) .vp__bp[data-max="620"],
.vp:has(#vp-390:checked) .vp__bp[data-max="620"],
.vp:has(#vp-320:checked) .vp__bp[data-max="860"],
.vp:has(#vp-390:checked) .vp__bp[data-max="860"],
.vp:has(#vp-768:checked) .vp__bp[data-max="860"],
.vp:has(#vp-320:checked) .vp__bp[data-max="900"],
.vp:has(#vp-390:checked) .vp__bp[data-max="900"],
.vp:has(#vp-768:checked) .vp__bp[data-max="900"],
.vp:has(#vp-768:checked) .vp__bp[data-min="576"],
.vp:has(#vp-768:checked) .vp__bp[data-min="768"],
.vp:has(#vp-1024:checked) .vp__bp[data-min="576"],
.vp:has(#vp-1024:checked) .vp__bp[data-min="768"],
.vp:has(#vp-1024:checked) .vp__bp[data-min="992"],
.vp:has(#vp-full:checked) .vp__bp[data-min="576"],
.vp:has(#vp-full:checked) .vp__bp[data-min="768"],
.vp:has(#vp-full:checked) .vp__bp[data-min="992"],
.vp:has(#vp-full:checked) .vp__bp[data-min="1200"] { opacity: 1; }

/* ==========================================================================
   #SIDE BY SIDE
   ==========================================================================
   Two panes of identical markup, one with a property switched off, so the
   difference is the only variable. Used for the rules whose value is only
   obvious when they are missing.
   ========================================================================== */

.sbs { display: grid; gap: var(--pf-space-4); grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr)); }
.sbs__pane {
  display: grid;
  gap: var(--pf-space-3);
  align-content: start;
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius);
  padding: var(--pf-space-4);
  min-width: 0;
  overflow: hidden;
}
.sbs__label {
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  color: var(--pf-text-muted);
}
.sbs__pane--bad .sbs__label { color: var(--pf-accent-2); }
.sbs__demo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--pf-space-2); }
.sbs__demo > * { min-width: 0; }
/* The broken pane: the guard removed, so the wide child stretches its track. */
.sbs__demo--nofix > * { min-width: auto; }
.sbs__cell {
  background: var(--pf-surface);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-sm);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  padding: var(--pf-space-3);
  white-space: nowrap;
  overflow-x: auto;
}
.sbs__measure { max-width: var(--pf-measure); }
.sbs__measure--off { max-width: none; }

/**
 * Used once, on the accessibility page, to demonstrate what removing the
 * focus ring costs. It lives here rather than in the framework because a
 * utility that deletes focus visibility is not something a framework should
 * hand you.
 */
.pf-no-ring:focus,
.pf-no-ring:focus-visible { outline: none; box-shadow: none; }

/* ==========================================================================
   #CHANGELOG TIMELINE
   ========================================================================== */

.cl { display: grid; gap: var(--pf-space-8); }

.cl__entry {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: var(--pf-gap-lg);
}
@media (max-width: 860px) {
  .cl__entry { grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-4); }
}

.cl__meta { display: grid; gap: var(--pf-space-2); align-content: start; }
.cl__meta--sticky { position: sticky; top: 6rem; align-self: start; }
@media (max-width: 860px) {
  .cl__meta--sticky { position: static; }
}

.cl__version {
  font-family: var(--pf-font-display);
  font-size: var(--pf-text-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
}
.cl__date { color: var(--pf-text-muted); font-size: var(--pf-text-sm); }

.cl__body { display: grid; gap: var(--pf-space-5); border-left: 1px solid var(--pf-border); padding-left: var(--pf-gap); }
@media (max-width: 860px) {
  .cl__body { border-left: 0; padding-left: 0; }
}

.cl__group { display: grid; gap: var(--pf-space-3); }
.cl__label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  color: var(--pf-text-muted);
}
.cl__label::before {
  content: "";
  width: 0.6rem;
  height: 0.6rem;
  background: var(--pf-accent);
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.cl__label--fixed::before { background: var(--pf-accent-2); }
.cl__label--changed::before { background: var(--pf-text-muted); }
.cl__list { display: grid; gap: var(--pf-space-2); margin: 0; }
.cl__list li {
  color: var(--pf-text-muted);
  font-size: var(--pf-text-sm);
  padding-left: 1rem;
  position: relative;
  max-width: 68ch;
}
.cl__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.65em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--pf-border-strong);
}
.cl__list strong { color: var(--pf-text); font-weight: 600; }

/* ==========================================================================
   #PAGE ANATOMY PLAYGROUND
   ==========================================================================
   The controls on /docs/playground swap whole page regions, not just tokens.
   CSS cannot change a class, so every variant is present in the markup and
   the selected one is revealed: `display: none` on all of them, then a
   :has() rule turns one back on.

   That means all variants ship in the HTML. For a documentation page showing
   four navigation bars that is the right trade; it would not be for a real
   product page, where you would render the one you chose.
   ========================================================================== */

.pa { display: grid; gap: 0; border: 1px solid var(--pf-border); border-radius: var(--pf-radius-lg); overflow: hidden; }

.pa__controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: var(--pf-space-4) var(--pf-space-5);
  background: var(--pf-bg-deep);
  border-bottom: 1px solid var(--pf-border);
  padding: var(--pf-space-4) var(--pf-space-5);
}

.pa__stage {
  background: var(--pf-bg);
  container-type: inline-size;
  max-height: 34rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

/* Region labels, so it is obvious which part of the page each control drives. */
.pa__region { position: relative; }
.pa__region::before {
  content: attr(data-pa-region);
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  background: var(--pf-accent);
  color: var(--pf-base-950);
  font-family: var(--pf-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  padding: 0.15rem 0.5rem;
  border-end-end-radius: var(--pf-radius-sm);
  opacity: 0;
  transition: opacity var(--pf-speed) var(--pf-ease);
}
.pa:has(#pa-labels:checked) .pa__region::before { opacity: 1; }
.pa:has(#pa-labels:checked) .pa__region { outline: 1px dashed var(--pf-border-strong); outline-offset: -1px; }

/* Every variant hidden, then one revealed. */
.pa__variant { display: none; }

.pa:has(#pa-nav-sticky:checked)  .pa__variant--nav-sticky,
.pa:has(#pa-nav-flush:checked)   .pa__variant--nav-flush,
.pa:has(#pa-nav-bar:checked)     .pa__variant--nav-bar,
.pa:has(#pa-hero-split:checked)  .pa__variant--hero-split,
.pa:has(#pa-hero-center:checked) .pa__variant--hero-center,
.pa:has(#pa-hero-page:checked)   .pa__variant--hero-page,
.pa:has(#pa-body-cards:checked)  .pa__variant--body-cards,
.pa:has(#pa-body-split:checked)  .pa__variant--body-split,
.pa:has(#pa-body-table:checked)  .pa__variant--body-table,
.pa:has(#pa-foot-columns:checked) .pa__variant--foot-columns,
.pa:has(#pa-foot-slim:checked)   .pa__variant--foot-slim,
.pa:has(#pa-foot-accent:checked) .pa__variant--foot-accent { display: block; }

/* Token controls, scoped to the stage exactly as a theme file would be. */
.pa:has(#pa-light:checked) .pa__stage {
  color-scheme: light;
  --pf-base-900: #e7edf3;
  --pf-base-800: #f4f7fa;
  --pf-bg: #f4f7fa;
  --pf-bg-deep: #e7edf3;
  --pf-text: #0d2436;
  --pf-text-muted: #3f5568;
  --pf-base-50: #0d2436;
  --pf-accent: #45702f;
  --pf-accent-2: #9a5620;
  --pf-base-950: #f7fbff;
  --pf-surface: rgba(13, 36, 54, 0.045);
  --pf-surface-raised: rgba(13, 36, 54, 0.085);
  --pf-border: rgba(13, 36, 54, 0.16);
  --pf-border-strong: rgba(69, 112, 47, 0.45);
  --pf-focus: #45702f;
  --pf-mesh: none;
}

.pa:has(#pa-cut-0:checked) .pa__stage { --pf-cut: 0px; }
.pa:has(#pa-cut-30:checked) .pa__stage { --pf-cut: 30px; }
.pa:has(#pa-radius-sharp:checked) .pa__stage { --pf-radius: 2px; --pf-radius-lg: 2px; --pf-radius-sm: 2px; }
.pa:has(#pa-radius-soft:checked) .pa__stage { --pf-radius: 18px; --pf-radius-lg: 32px; }
.pa:has(#pa-rhythm-tight:checked) .pa__stage { --pf-section-pad: 1.75rem; --pf-gap: 0.75rem; --pf-gap-lg: 1.25rem; }
.pa:has(#pa-rhythm-airy:checked) .pa__stage { --pf-section-pad: 5rem; --pf-gap: 2.5rem; --pf-gap-lg: 4rem; }
.pa:has(#pa-accent-teal:checked) .pa__stage { --pf-accent: #79c7c0; --pf-accent-2: #c3a6e0; --pf-focus: #79c7c0; --pf-border-strong: rgba(121, 199, 192, .45); }
.pa:has(#pa-accent-amber:checked) .pa__stage { --pf-accent: #e0bd7a; --pf-accent-2: #d59a8a; --pf-focus: #e0bd7a; --pf-border-strong: rgba(224, 189, 122, .45); }

/* The stage is a page in miniature: scale the display type down so a hero
   headline does not fill the whole frame. */
.pa__stage .pf-hero { padding-block: var(--pf-space-6); }
.pa__stage .pf-hero__title { font-size: var(--pf-text-2xl); max-width: 18ch; }
.pa__stage .pf-hero__lede,
.pa__stage .pf-lede { font-size: var(--pf-text-base); }
.pa__stage .pf-section { padding-block: var(--pf-section-pad, var(--pf-space-6)); }
.pa__stage .pf-pagehead { padding-block: var(--pf-space-5); }
.pa__stage .pf-pagehead__title { font-size: var(--pf-text-xl); }
.pa__stage .pf-footer { padding-block: var(--pf-space-5) var(--pf-space-4); }
.pa__stage .pf-nav { position: static; }
.pa__stage h2 { font-size: var(--pf-text-lg); }
.pa__stage h3 { font-size: var(--pf-text-base); }

.pa__code {
  border-top: 1px solid var(--pf-border);
  background: var(--pf-bg-deep);
  color: var(--pf-text-muted);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  padding: var(--pf-space-3) var(--pf-space-5);
}
.pa__code b { color: var(--pf-accent); font-weight: 500; }

/* Each control's current selection, echoed in the readout line. */
.pa__echo { display: none; }
.pa:has(#pa-nav-sticky:checked) .pa__echo--nav-sticky,
.pa:has(#pa-nav-flush:checked) .pa__echo--nav-flush,
.pa:has(#pa-nav-bar:checked) .pa__echo--nav-bar,
.pa:has(#pa-hero-split:checked) .pa__echo--hero-split,
.pa:has(#pa-hero-center:checked) .pa__echo--hero-center,
.pa:has(#pa-hero-page:checked) .pa__echo--hero-page,
.pa:has(#pa-foot-columns:checked) .pa__echo--foot-columns,
.pa:has(#pa-foot-slim:checked) .pa__echo--foot-slim,
.pa:has(#pa-foot-accent:checked) .pa__echo--foot-accent { display: inline; }

/* ==========================================================================
   #CLASS REFERENCE
   ==========================================================================
   The generated index at /docs/reference. Chips rather than table rows for
   families of near-identical classes: eighty rows saying "sets padding" is a
   phone book, eighty names in a paragraph is a glance.
   ========================================================================== */

.ref__head { margin-bottom: var(--pf-space-5); }
.ref__family { margin-top: var(--pf-space-6); }
.ref__family h3 {
  font-family: var(--pf-font-body);
  font-size: var(--pf-text-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  margin-bottom: var(--pf-space-3);
}
.ref__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pf-space-2);
  max-width: none;
}
.ref__chips code {
  background: var(--pf-surface);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-sm);
  color: var(--pf-text-muted);
  font-size: var(--pf-text-xs);
  padding: 0.15em 0.5em;
  white-space: nowrap;
}


/* ==========================================================================
   #CHANGELOG ENTRIES
   ==========================================================================
   Tagged bullets rather than prose sections: a reader scanning for what
   broke should be able to find it without reading a paragraph.
   ========================================================================== */

.cl__summary { color: var(--pf-text); font-weight: 500; }

.cl__items { display: grid; gap: var(--pf-space-4); margin: var(--pf-space-5) 0 0; }
.cl__items li {
  color: var(--pf-text-muted);
  font-size: var(--pf-text-sm);
  line-height: 1.6;
  list-style: none;
  max-width: 78ch;
}
.cl__items code {
  background: var(--pf-surface);
  border-radius: var(--pf-radius-sm);
  color: var(--pf-text);
  font-size: 0.92em;
  padding: 0.1em 0.35em;
}

/* The tag is the scanning target, so it is set on its own baseline-aligned
   chip rather than run into the sentence. */
.cl__tag {
  display: inline-block;
  min-width: 5.5rem;
  margin-right: 0.6rem;
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-sm);
  color: var(--pf-text-muted);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  letter-spacing: 0.02em;
  padding: 0.05em 0.45em;
  text-align: center;
  vertical-align: 0.05em;
}
.cl__tag--added { border-color: color-mix(in srgb, var(--pf-accent) 45%, transparent); color: var(--pf-accent); }
.cl__tag--changed { border-color: color-mix(in srgb, var(--pf-accent-2) 45%, transparent); color: var(--pf-accent-2); }
.cl__tag--fixed { border-color: var(--pf-border-strong); color: var(--pf-text); }
.cl__tag--removed { border-color: rgba(224, 143, 143, 0.45); color: #e08f8f; }
.cl__tag--security { border-color: var(--pf-border-strong); color: var(--pf-brand-2); }

/* A migration note is the one thing in an entry that costs the reader work,
   so it gets its own line and its own rule. */
.cl__migration {
  display: block;
  margin: var(--pf-space-3) 0 0 6.1rem;
  border-left: 2px solid var(--pf-accent-2);
  padding-left: var(--pf-space-3);
  color: var(--pf-text);
}
.cl__migration strong { color: var(--pf-accent-2); font-weight: 600; }

@media (max-width: 620px) {
  .cl__tag { min-width: 0; }
  .cl__migration { margin-left: 0; }
}

/* ==========================================================================
   #VIEWPORT SIMULATOR
   ==========================================================================
   A real iframe at a controlled width, not a scaled screenshot. Media queries
   inside an iframe resolve against the iframe's own width, so a page framed
   at 320px behaves exactly as it would on a 320px phone — which a CSS
   transform or a container query cannot reproduce for rules the framework
   actually uses.

   Cost, stated plainly: the site's Content-Security-Policy moved from
   frame-ancestors 'none' to 'self', and X-Frame-Options from DENY to
   SAMEORIGIN. Third-party framing is still refused.
   ========================================================================== */

.vp { display: grid; gap: 0; border: 1px solid var(--pf-border); border-radius: var(--pf-radius-lg); overflow: hidden; }

.vp__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--pf-space-4);
  background: var(--pf-bg-deep);
  border-bottom: 1px solid var(--pf-border);
  padding: var(--pf-space-4) var(--pf-space-5);
}

.vp__stageback {
  display: grid;
  justify-items: center;
  background:
    repeating-linear-gradient(45deg, var(--pf-surface) 0 8px, transparent 8px 16px);
  padding: var(--pf-space-5) var(--pf-space-4);
  overflow-x: auto;
}

.vp__frame {
  width: 100%;
  max-width: 100%;
  height: 34rem;
  border: 1px solid var(--pf-border-strong);
  border-radius: var(--pf-radius);
  background: var(--pf-bg);
  transition: width var(--pf-speed) var(--pf-ease);
}

/* Each width is a device people actually hold. */
.vp:has(#vp-320:checked) .vp__frame { width: 320px; }
.vp:has(#vp-390:checked) .vp__frame { width: 390px; }
.vp:has(#vp-430:checked) .vp__frame { width: 430px; }
.vp:has(#vp-768:checked) .vp__frame { width: 768px; }
.vp:has(#vp-1024:checked) .vp__frame { width: 1024px; }
.vp:has(#vp-full:checked) .vp__frame { width: 100%; }

.vp:has(#vp-tall:checked) .vp__frame { height: 46rem; }

.vp__readout {
  border-top: 1px solid var(--pf-border);
  background: var(--pf-bg-deep);
  color: var(--pf-text-muted);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  padding: var(--pf-space-3) var(--pf-space-5);
}
.vp__note { display: none; }
.vp:has(#vp-320:checked) .vp__note--320,
.vp:has(#vp-390:checked) .vp__note--390,
.vp:has(#vp-430:checked) .vp__note--430,
.vp:has(#vp-768:checked) .vp__note--768,
.vp:has(#vp-1024:checked) .vp__note--1024,
.vp:has(#vp-full:checked) .vp__note--full { display: inline; }

/* ==========================================================================
   #TOKEN EXPLORER
   ==========================================================================
   Same mechanism as the theming playground, applied to the scale rather than
   the palette: the controls set type, measure and radius tokens on a stage
   and the specimen below re-renders.
   ========================================================================== */

.tk:has(#tk-type-compact:checked) .pg__stage {
  --pf-text-base: 0.9375rem;
  --pf-text-lg: 1.05rem;
  --pf-text-xl: 1.3rem;
  --pf-text-2xl: 1.6rem;
  --pf-text-3xl: clamp(1.6rem, 1.3rem + 1.2vw, 2.1rem);
}
.tk:has(#tk-type-large:checked) .pg__stage {
  --pf-text-base: 1.125rem;
  --pf-text-lg: 1.35rem;
  --pf-text-xl: 1.7rem;
  --pf-text-2xl: 2.2rem;
  --pf-text-3xl: clamp(2.4rem, 1.9rem + 2.4vw, 3.4rem);
}
.tk:has(#tk-measure-narrow:checked) .pg__stage { --pf-measure: 52ch; }
.tk:has(#tk-measure-wide:checked) .pg__stage { --pf-measure: 84ch; }
.tk:has(#tk-leading-tight:checked) .pg__stage { --pf-leading: 1.45; }
.tk:has(#tk-leading-loose:checked) .pg__stage { --pf-leading: 1.85; }

.tk__ruler {
  display: grid;
  gap: var(--pf-space-2);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
  color: var(--pf-text-muted);
}
.tk__bar {
  height: 0.6rem;
  background: var(--pf-accent);
  border-radius: 2px;
  opacity: 0.85;
}

/* ==========================================================================
   #MODULE PICKER
   ==========================================================================
   Checkboxes reveal the link lines a page would need. Each line carries its
   own size, because CSS cannot add numbers and a fake total would be worse
   than none.
   ========================================================================== */

.mp__line { display: none; }
.mp:has(#mp-headers:checked) .mp__line--headers,
.mp:has(#mp-content:checked) .mp__line--content,
.mp:has(#mp-forms:checked) .mp__line--forms,
.mp:has(#mp-components:checked) .mp__line--components,
.mp:has(#mp-footers:checked) .mp__line--footers,
.mp:has(#mp-grid:checked) .mp__line--grid,
.mp:has(#mp-space:checked) .mp__line--space,
.mp:has(#mp-effects:checked) .mp__line--effects,
.mp:has(#mp-utilities:checked) .mp__line--utilities { display: block; }

/* Past eight modules the bundle is the better answer, so say so when the
   picker crosses that line. */
.mp__advice { display: none; color: var(--pf-accent-2); }
.mp:has(#mp-headers:checked):has(#mp-content:checked):has(#mp-forms:checked):has(#mp-components:checked):has(#mp-footers:checked):has(#mp-grid:checked):has(#mp-space:checked) .mp__advice { display: block; }

.mp__options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: var(--pf-space-3);
}

/* ==========================================================================
   #ACCESSIBILITY DEMOS
   ========================================================================== */

/* Touch-target overlay: draws the 44px minimum around every control so the
   claim can be checked rather than believed. */
.ax:has(#ax-targets:checked) .ax__stage :where(.pf-btn, .pf-nav__links a, .pf-pagination__item) {
  outline: 1px dashed var(--pf-accent-2);
  outline-offset: 0;
  position: relative;
}
.ax:has(#ax-targets:checked) .ax__stage :where(.pf-btn, .pf-pagination__item)::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
  border: 1px solid color-mix(in srgb, var(--pf-accent) 60%, transparent);
  pointer-events: none;
}

/* Contrast preview: the prefers-contrast: more values, applied on demand. */
.ax:has(#ax-contrast:checked) .ax__stage {
  --pf-border: rgba(233, 238, 250, 0.55);
  --pf-border-strong: var(--pf-accent);
  --pf-text-muted: #cfe0f5;
}

/* Motion preview: what the reduced-motion block does, without changing your
   operating system to see it. */
.ax:has(#ax-motion:checked) .ax__stage :where(*, *::before, *::after) {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}

.ax__stage { background: var(--pf-bg); padding: var(--pf-space-6) var(--pf-space-5); }


/* ==========================================================================
   #DOCS DEMO HELPERS
   ==========================================================================
   Classes that exist only to demonstrate something on this site. They keep
   the `demo-` prefix rather than `pf-`, because site chrome squatting in the
   framework's namespace is how a reader ends up copying a class that does
   not ship.
   ========================================================================== */

/* Shows what a control looks like with its focus ring suppressed — the thing
   the framework deliberately does not do. */
.demo-no-ring:focus-visible { outline: none; box-shadow: none; }

/* ==========================================================================
   #CHANGELOG FILTER
   ==========================================================================
   Radio per tag, then hide the bullets that do not carry it. :has() on the
   list item is what makes this possible without a script: "an <li> that does
   not contain a tag chip of this kind".
   ========================================================================== */

.cl__filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--pf-space-4);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius);
  background: var(--pf-bg-deep);
  padding: var(--pf-space-4) var(--pf-space-5);
  margin-bottom: var(--pf-space-8);
}

.cl:has(#cf-added:checked) .cl__items li:not(:has(.cl__tag--added)),
.cl:has(#cf-changed:checked) .cl__items li:not(:has(.cl__tag--changed)),
.cl:has(#cf-fixed:checked) .cl__items li:not(:has(.cl__tag--fixed)),
.cl:has(#cf-removed:checked) .cl__items li:not(:has(.cl__tag--removed)) { display: none; }

/* A release with nothing left to show says so rather than rendering a gap. */
.cl__empty { display: none; color: var(--pf-text-muted); font-size: var(--pf-text-sm); }
.cl:has(#cf-added:checked) .cl__entry:not(:has(.cl__tag--added)) .cl__empty,
.cl:has(#cf-changed:checked) .cl__entry:not(:has(.cl__tag--changed)) .cl__empty,
.cl:has(#cf-fixed:checked) .cl__entry:not(:has(.cl__tag--fixed)) .cl__empty,
.cl:has(#cf-removed:checked) .cl__entry:not(:has(.cl__tag--removed)) .cl__empty { display: block; }

.cl:has(#cf-added:checked) .cl__entry:not(:has(.cl__tag--added)) .cl__summary,
.cl:has(#cf-changed:checked) .cl__entry:not(:has(.cl__tag--changed)) .cl__summary,
.cl:has(#cf-fixed:checked) .cl__entry:not(:has(.cl__tag--fixed)) .cl__summary,
.cl:has(#cf-removed:checked) .cl__entry:not(:has(.cl__tag--removed)) .cl__summary { opacity: 0.45; }

/* ==========================================================================
   #MODULE COMPOSER
   ==========================================================================
   Tick modules; every component that needs one you have not loaded is marked
   unavailable. The framework's dependency graph, made pokeable.
   ========================================================================== */

.mx__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: var(--pf-space-4);
}
.mx__tile {
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius);
  padding: var(--pf-space-4);
  transition: opacity var(--pf-speed) var(--pf-ease), border-color var(--pf-speed) var(--pf-ease);
}
.mx__tile h3 { font-family: var(--pf-font-body); font-size: var(--pf-text-sm); font-weight: 600; }
.mx__tile p { color: var(--pf-text-muted); font-size: var(--pf-text-xs); margin-top: var(--pf-space-2); }
.mx__need {
  display: block;
  margin-top: var(--pf-space-3);
  font-family: var(--pf-font-mono);
  font-size: var(--pf-text-xs);
}

/* Default state: unavailable. A tile lights up only when its module is on. */
.mx__tile { opacity: 0.38; }
.mx__tile .mx__need { color: #e08f8f; }
.mx:has(#mx-headers:checked) .mx__tile--headers,
.mx:has(#mx-content:checked) .mx__tile--content,
.mx:has(#mx-forms:checked) .mx__tile--forms,
.mx:has(#mx-components:checked) .mx__tile--components,
.mx:has(#mx-footers:checked) .mx__tile--footers,
.mx:has(#mx-grid:checked) .mx__tile--grid,
.mx:has(#mx-space:checked) .mx__tile--space,
.mx:has(#mx-effects:checked) .mx__tile--effects { opacity: 1; border-color: var(--pf-border-strong); }
.mx:has(#mx-headers:checked) .mx__tile--headers .mx__need,
.mx:has(#mx-content:checked) .mx__tile--content .mx__need,
.mx:has(#mx-forms:checked) .mx__tile--forms .mx__need,
.mx:has(#mx-components:checked) .mx__tile--components .mx__need,
.mx:has(#mx-footers:checked) .mx__tile--footers .mx__need,
.mx:has(#mx-grid:checked) .mx__tile--grid .mx__need,
.mx:has(#mx-space:checked) .mx__tile--space .mx__need,
.mx:has(#mx-effects:checked) .mx__tile--effects .mx__need { color: var(--pf-accent); }

/* Components that only need Core are always available, and say so. */
.mx__tile--core { opacity: 1; border-color: var(--pf-border-strong); }
.mx__tile--core .mx__need { color: var(--pf-accent); }

/* ==========================================================================
   #PERFORMANCE SCENARIOS
   ==========================================================================
   Pre-measured numbers revealed per scenario. CSS cannot add, so nothing here
   is computed — the figures come from the build and are shown, not derived.
   ========================================================================== */

.ps__panel { display: none; }
.ps:has(#ps-signin:checked) .ps__panel--signin,
.ps:has(#ps-marketing:checked) .ps__panel--marketing,
.ps:has(#ps-docs:checked) .ps__panel--docs,
.ps:has(#ps-app:checked) .ps__panel--app,
.ps:has(#ps-bundle:checked) .ps__panel--bundle { display: block; }

.ps__bars { display: grid; gap: var(--pf-space-3); margin-top: var(--pf-space-4); }
.ps__row { display: grid; grid-template-columns: minmax(0, 9rem) 1fr auto; align-items: center; gap: var(--pf-space-3); }
.ps__row span:first-child { font-family: var(--pf-font-mono); font-size: var(--pf-text-xs); color: var(--pf-text-muted); }
.ps__track { height: 0.55rem; background: var(--pf-surface-raised); border-radius: var(--pf-radius-pill); overflow: hidden; }
.ps__fill { display: block; height: 100%; background: var(--pf-accent); border-radius: inherit; }
.ps__fill--alt { background: var(--pf-accent-2); }
.ps__num { font-family: var(--pf-font-mono); font-size: var(--pf-text-xs); color: var(--pf-text); }

/* ==========================================================================
   #ENHANCEMENT BUILDER
   ==========================================================================
   Same shape as the module picker, for the optional script: tick the
   enhancements you want and the attributes appear.
   ========================================================================== */

.jb__line { display: none; }
.jb:has(#jb-menu:checked) .jb__line--menu,
.jb:has(#jb-tabs:checked) .jb__line--tabs,
.jb:has(#jb-dialog:checked) .jb__line--dialog,
.jb:has(#jb-copy:checked) .jb__line--copy,
.jb:has(#jb-counter:checked) .jb__line--counter,
.jb:has(#jb-dismiss:checked) .jb__line--dismiss,
.jb:has(#jb-sort:checked) .jb__line--sort,
.jb:has(#jb-spy:checked) .jb__line--spy,
.jb:has(#jb-theme:checked) .jb__line--theme { display: block; }

/* Nothing ticked is a legitimate answer, and the most common one. */
.jb__none { color: var(--pf-text-muted); }
.jb:has(.jb__toggle:checked) .jb__none { display: none; }

/* ==========================================================================
   #COMPONENT STATES
   ==========================================================================
   Every control has states you cannot see by looking at a default render.
   These apply them to a copy of the component rather than asking you to
   imagine them.
   ========================================================================== */

.cs__row { display: none; }
.cs:has(#cs-default:checked) .cs__row--default,
.cs:has(#cs-focus:checked) .cs__row--focus,
.cs:has(#cs-disabled:checked) .cs__row--disabled,
.cs:has(#cs-busy:checked) .cs__row--busy,
.cs:has(#cs-invalid:checked) .cs__row--invalid { display: block; }

/* The focus row draws the ring the framework would draw, so it can be seen
   without holding the tab key. */
.cs:has(#cs-focus:checked) .cs__row--focus :where(.pf-btn, .pf-input) {
  outline: 2px solid var(--pf-focus);
  outline-offset: 2px;
}
