/* zpl-pdf — playground page
 *
 * Playground layout, format picker, preview pane and error box.
 * The editor surface and file-drop overlay live in zpl-editor.css.
 */

/* ─────────────── playground ─────────────── */

/* The two panels pin while the headline scrolls away — added 2026-09-15.
 *
 * Same mechanism as the pricing page's configurator (css/pricing.css, ".studio-detail"), which is
 * itself measured from apple.com/shop/buy-mac/mac-studio: the thing you keep referring to holds
 * still, and the page scrolls past it. The difference is only which part is pinned. On pricing the
 * left pane pins and the chooser scrolls beside it; here there is nothing to scroll past — the
 * editor and the preview ARE the page — so the grid itself pins and the headline above is what
 * scrolls away, leaving the two panels holding the viewport.
 *
 * No JavaScript. `position: sticky` is the whole effect, the same as on pricing, whose pricing.js
 * carries no scroll logic at all.
 */
.pg {
  --error-slot-h: 190px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  gap: 20px;
  position: sticky;
  top: calc(var(--nav-h) + 20px);
  align-items: start;
}

/* The panels fill the window once they are pinned, the way pricing's gallery frame does
   (.plan-slide, "a viewport-relative height with a floor, so it stays large on a laptop and does
   not outgrow a short window"). The floor is 640px there; here it is the natural height of the
   editor stack, so the panels never shrink below what their own controls need. */
.pg .panel {
  height: min(720px, calc(100vh - var(--nav-h) - 60px - var(--error-slot-h)));
  height: min(720px, calc(100dvh - var(--nav-h) - 60px - var(--error-slot-h)));
}
/* Both panels are flex columns, so the height lands in the two regions that should absorb it —
   the editor and the preview — rather than as dead space beneath the controls. */
.pg .play-code-editor,
.pg .preview { min-height: 0; }

.panel {
  display: flex;
  flex-direction: column;
  background: var(--bg-white);
  border-radius: var(--r);
  overflow: hidden;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .05);
}

.panel-label {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.play-code-editor {
  flex: 1;
  min-height: 290px;
  display: flex;
  position: relative;
  background: var(--bg-white);
  border: none;
  overflow: hidden;
}

.meta-limit {
  margin-left: auto;
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.meta-limit.over { color: var(--danger); }
.label-select {
  width: 5.2ch;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.label-select:focus { outline: 2px solid var(--accent); outline-offset: 2px; }

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: 22px 34px;
  padding: 20px 18px;
  border-top: 1px solid var(--line-soft);
  background: var(--bg-tint);
}
.ctrl { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* `display:flex` above outranks the host stylesheet's [hidden] rule. */
.ctrl[hidden] { display: none; }

/* Format picker — built on Apple's dotnav mechanism (ui-research/apple/measurements.md §3C.1).
 *
 * The selected option animates its own `min-width` from a compact state to an expanded one, and
 * because min-width participates in layout, the whole row reflows around it: the neighbours slide
 * aside rather than being covered. That reflow is what makes Apple's carousel indicator feel
 * playful instead of merely lit-up, and it is the reason this is not done with transform.
 *
 * Their own timing is used too: 0.4s cubic-bezier(.33,0,.67,1), slower and more symmetric than
 * the page's 0.32s house curve, because the eye is tracking a shape change, not a state change.
 */
.segmented {
  display: inline-flex;
  flex: 0 0 419px;
  width: 419px;
  padding: 3px;
  background: #E9E9EB;
  border-radius: 13px;
  gap: 3px;
}

.seg {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 76px;
  height: 30px;
  padding: 0 16px;
  border-radius: 8px;
  border: none;
  background: transparent;
  color: var(--ink-dim);
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.3;
  cursor: pointer;
  white-space: nowrap;
  transition:
    min-width .4s cubic-bezier(.33, 0, .67, 1),
    background-color var(--dur) var(--ease),
    color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}

.seg svg {
  width: 13px;
  height: 13px;
  flex: none;
}

.seg:hover { color: var(--ink); }

.seg[data-format="multi-label-pdf"] {
  min-width: 136px;
}

.seg[aria-checked="true"] {
  min-width: 116px;
  background: #fff;
  color: var(--accent);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .1), 0 0 0 .5px rgba(0, 0, 0, .04);
}

.seg[data-format="multi-label-pdf"][aria-checked="true"] {
  min-width: 176px;
}

.run {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: nowrap;
  height: 50px;
  box-sizing: border-box;
  padding: 8px 18px;
  border-top: 1px solid var(--line-soft);
}
.run .btn-primary,
.run .btn-ghost {
  flex: 0 1 124px;
  min-width: 0;
  height: 34px;
  padding: 0 18px;
  border-radius: 999px;
  border-width: 1px;
  font-size: 15px;
  line-height: 1;
  letter-spacing: 0;
  justify-content: center;
}
.run .btn-primary {
  background: #0071e3;
  border-color: #0071e3;
  color: #fff;
}
.run .btn-primary:hover {
  background: #0071e3;
  border-color: #0071e3;
}
.run .btn-ghost {
  background: transparent;
  border-color: #0071e3;
  color: #0071e3;
}
.run .btn-ghost:hover {
  background: rgba(0, 113, 227, .06);
  color: #0071e3;
}
.result-run {
  justify-content: space-between;
  flex-wrap: nowrap;
}
.run-status { font-size: 14px; color: var(--ink-faint); }
.run-status.busy { color: var(--accent); }
.run-status.ok   { color: #248A3D; }
.run-status.err  { color: var(--danger); }
.result-measure {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.result-measure::-webkit-scrollbar { height: 0; }
.result-measure > * {
  flex: none;
  white-space: nowrap;
}
.result-measure select,
.result-measure input {
  height: 24px;
  font-family: var(--sans);
  font-size: 13px;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 0 5px;
}
.result-measure input {
  width: 72px;
  padding-inline: 3px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.result-measure select:hover,
.result-measure input:hover {
  border-color: var(--line);
  background: #fff;
}
.result-measure select:focus,
.result-measure input:focus {
  outline: none;
  border-color: var(--accent);
  background: #fff;
}
.measure-x {
  color: var(--ink-faint);
  font-size: 13px;
}

/* Secondary navigation in the run row. */
.btn-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--accent);
  padding-left: 10px;
  padding-right: 10px;
}
.btn-quiet:hover { color: var(--accent-hover); background: rgba(0, 113, 227, .06); }

/* The chevron: a rotated square corner, drawn from two borders rather than set as a glyph, so it
   takes currentColor and needs no font. (base.css's .disclosure rows used the same construction
   until that block was removed on 2026-09-16; this is now the site's only copy of it.) */
.btn-chev {
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-top: 1.5px solid currentColor;
  transform: rotate(45deg);
  margin-left: -2px;
  transition: transform var(--dur-fast) var(--ease);
}
.btn-quiet:hover .btn-chev { transform: rotate(45deg) translate(1px, -1px); }

/* Inline text links with a chevron, for the closing section below the editor.
 *
 * `home.css` carries the same construction, but this page loads only base.css and this file, so
 * the rule is defined here rather than reached for across a stylesheet it never loads. Kept
 * identical to home.css's so the two pages' links behave the same; if one changes, change both.
 * The chevron is a glyph here (not the bordered square .btn-chev uses) because this one sits in
 * running prose, where a box drawn from borders does not sit on the text baseline. */
.textlink {
  color: var(--link-blue);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.textlink::after {
  content: "›";
  font-size: 1.15em;
  line-height: 1;
  transform: translateY(-1px);
  transition: transform var(--dur-fast) var(--ease);
}
.textlink:hover { text-decoration: underline; }
.textlink:hover::after { transform: translate(3px, -1px); }

/* The Designer card that stacks two .mini tables — the promise, then the ceilings it runs under.
/* ─────────────── the Designer shelf ───────────────
 *
 * The commerce-page shelf from ui-research/apple/page-patterns.md §1.1: a header row with the
 * headline left and one link right, then a horizontal scroller of cards. A shelf costs one
 * screen-height regardless of how many cards it holds, which is why Apple's store page fits 42
 * cards in nine of them.
 *
 * On the dark ground. Grounds, not borders (measurements.md §3.3): the cards are one step off the
 * section's black — #1C1C1E, the same card ground .section--dark .doc uses in base.css — and
 * nothing here carries an outline.
 */

/* The header row. .headline centres its contents, so this section opts out of it: at this size a
   headline and its link read as one line, the way a shelf header does, not as a centred stack. */
/* The header carries the section's full three-line opening — eyebrow, headline, one sentence —
   left-aligned, with the link held on the last baseline at the right. `.headline` is not used
   here because it centres its contents: a shelf header reads as a column of text with the shelf's
   own left edge, not as a centred stack above it. */
.shelf-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  /* The 1180px box and its centring come from .wrap--wide in the markup, the same class the card
     row carries, so the header and the cards resolve their left edge identically at every width.
     `width: 100%` is what makes that hold: this is a flex container whose contents are narrower
     than 1180px, and without it the box shrink-wraps the text and `margin: 0 auto` then centres
     that shorter box — pushing the eyebrow ~150px right of the first card. The card row never
     showed the bug because four 340px cards overflow 1180px and pin it to the left edge. */
  width: 100%;
  margin-bottom: 26px;
}
.shelf-head-text { min-width: 0; }
/* The headline's own size, weight, tracking and 16px bottom margin come from `.headline h2` in
   base.css, which this header's h2 is named in — see the note there. Nothing is restated here.
   The sentence runs on one line: no measure cap, and `text-wrap: balance` is cancelled because it
   splits a line that fits into two shorter ones. */
.shelf-head .section-sub {
  max-width: none;
  text-wrap: nowrap;
  white-space: nowrap;
}
.shelf-lead-link { flex: none; font-size: 17px; }

.shelf { position: relative; }

/* The shelf owns a full screen: header, one row of tall cards, and the arrows under them, with
   the section's own padding making up the rest. Centred rather than top-aligned so the row sits
   in the middle of the viewport the way Apple's does. */
.section--shelf {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  /* Sits above centre rather than on it: with the arrows below the row, dead-centring the three
     parts leaves the header floating low with a band of empty ground above it. */
  justify-content: center;
  padding-top: 72px;
  padding-bottom: 96px;
}

/* The track.
 *
 * `scroll-snap` so a flick lands on a card edge rather than mid-card, and the scrollbar is hidden
 * because the arrows and the card peeking at the right edge already say it scrolls. The trailing
 * card keeps its own right padding via scroll-padding, so the last card never sits flush against
 * the viewport edge when scrolled to the end. */
.shelf-track {
  display: flex;
  gap: 18px;
  margin: 0;
  padding: 0 0 4px;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.shelf-track::-webkit-scrollbar { width: 0; height: 0; }

/* Four cards across at --maxw-wide, and the fifth edge peeking is what tells the reader there is
   more. Below the desktop breakpoint they get wider as fewer fit, which is the point of a
   scroller: the card size is fixed, the number visible is not. */
/* Tall cards, close to the viewport's own height: this is the shelf Apple runs on a product page
   rather than the short one on the store grid, and the card is mostly artwork with the copy
   sitting over the top of it. `overflow: hidden` is what lets the media bleed to three edges and
   still take the card's corner radius. */
.shelf-card {
  position: relative;
  flex: none;
  width: 340px;
  height: min(760px, 75vh);
  display: flex;
  flex-direction: column;
  padding: 30px 28px 0;
  background: #1C1C1E;
  border-radius: var(--r-lg);
  overflow: hidden;
  scroll-snap-align: start;
}

/* §2: the eyebrow carries state or category, set as text above the title — no icon, no chip. */
.shelf-eyebrow {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink-faint);
}
.shelf-card h3 {
  margin: 0 0 12px;
  font-size: 21px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink-on-dark);
}
.shelf-desc {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-on-dark-dim);
  letter-spacing: -0.01em;
}

/* The media frame, empty for now — artwork drops in here later.
 *
 * It bleeds to the card's left, right and bottom edges rather than sitting inside the padding,
 * which is what makes the card read as an image with copy over it instead of a text card with a
 * picture attached. The negative margins undo the card's own side padding; `margin-top: auto`
 * pushes it below however much copy each card carries, so the frames still line up across the
 * row. A faint ground marks the area while there is nothing in it. */
.shelf-media {
  margin: auto -28px 0;
  min-height: 150px;
  flex: 1;
  background: rgba(255, 255, 255, .04);
}

/* Paging arrows, after the track and right-aligned. */
.shelf-nav {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  max-width: var(--maxw-wide);
  margin: 24px auto 0;
}
.shelf-arrow {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .1);
  color: var(--ink-on-dark);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.shelf-arrow svg { width: 17px; height: 17px; }
.shelf-arrow:hover { background: rgba(255, 255, 255, .16); }
/* At either end of the track the arrow is disabled rather than hidden: a control that vanishes
   makes the row jump, and the pair is what tells the reader the shelf pages at all. */
.shelf-arrow[disabled] {
  opacity: .35;
  cursor: default;
}
.shelf-arrow[disabled]:hover { background: rgba(255, 255, 255, .1); }
.shelf-arrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The two breakpoints the whole site runs on (page-patterns.md §6). */
@media (max-width: 1068px) {
  /* Below --maxw-wide the three 1180px boxes all meet the viewport edge, so each needs the
     section's gutter back. .shelf-head and the card row take it from .section's own 22px
     padding; .shelf-nav sits inside .shelf and does not, so it carries the 22px itself. */
  .shelf-nav { padding: 0 22px; }
}
@media (max-width: 734px) {
  /* The header text and its link stop sharing a row: at phone width the link would either crowd
     the headline or strand itself on a line of its own with a huge gap above it. */
  .shelf-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
    margin-bottom: 24px;
  }
  .shelf-card {
    width: 82vw;
    height: min(520px, 64vh);
    padding: 24px 22px 0;
  }
  .shelf-media { margin-inline: -22px; min-height: 110px; }
}

.preview-stats {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.preview-stats.ok { color: var(--accent); }

.preview {
  flex: 1;
  min-height: 450px;
  position: relative;
  background: var(--bg-tint);
}

.preview-empty {
  position: absolute;
  inset: 0;
  background: #fff;
}

.files {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #fff;
}
.files[hidden] { display: none; }

.preview-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #fff;
}

.error-slot {
  grid-column: 1 / -1;
  min-height: 170px;
  padding-top: 0;
}

.errbox {
  max-width: var(--maxw-wide);
  margin: 0 auto;
  background: #FFF5F5;
  border-radius: var(--r-sm);
  padding: 20px 24px;
}
.err-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.err-code { font-family: var(--mono); font-size: 14px; font-weight: 500; color: var(--danger); }
.err-http { font-family: var(--mono); font-size: 12px; color: var(--ink-faint); }
.err-msg  { margin: 8px 0 0; font-size: 17px; }
.err-ctx {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 5px 20px;
  margin: 14px 0 0;
  font-family: var(--mono);
  font-size: 12px;
}
.err-ctx dt { color: var(--ink-faint); }
.err-ctx dd { margin: 0; color: var(--ink-dim); }
.err-hint { margin: 14px 0 0; font-size: 14px; color: var(--ink-dim); }
.err-hint:empty { display: none; }

/* ─────────────── advanced page ───────────────
   .backlink and .adv-stack were removed 2026-09-16. They styled a documentation-shaped version of
   playground/designer/ — a back link plus a column of .doc cards — that the designer replaced when
   it became the full-viewport three-pane IDE it is now. That page loads css/designer.css and does
   not load this file at all, so the two rules matched nothing anywhere on the site. */

/* Stacked, the panels must unpin — the same reason pricing unpins its detail pane below 980px
   (css/pricing.css): a pinned grid would hold the editor over the preview it belongs with. The
   breakpoint is the site's own 860px step, which is also where .pg stops fitting two 400px
   columns. Their natural heights come back with it. */
@media (max-width: 860px) {
  .pg { position: static; }
  .pg .panel { height: auto; }
  .pg .play-code-editor { min-height: 290px; }
  .pg .preview   { min-height: 450px; }
}
