/* ══════════════════════════════════════════════════════════════════════════
   Prism store — plan chooser.

   Rebuilt rather than patched. The earlier version copied the mockup's
   overlapping-pill tabs literally, and they fought everything around them: the
   row needed a z-index ladder to stop each pill clipping its neighbour, the
   pills grew with their labels so nothing lined up with the card beneath, and
   the whole block could not sit on the same centred measure as the hero.

   This keeps the IDEA (one card, four plans, a live price bar) and drops the
   trick: the tabs are a plain segmented control inside the card, on the same
   grid as everything else, using the same tokens as the hero above so the page
   reads as one thing.
   ══════════════════════════════════════════════════════════════════════════ */
.pp {
  --pp-blue: #1583F2;
  --pp-blue-hi: #2B93FF;
  --pp-navy: #04182B;
  --pp-navy-2: #072339;
  --pp-navy-3: #0B2C48;
  --pp-line: rgba(11,114,234,.42);
  --pp-ink: #EAF2FB;
  --pp-dim: #7FA3C9;
  --pp-gold-a: #F0B23B;
  --pp-gold-b: #9C6B14;
  --pp-r: 16px;
  --pp-font: 'Segoe UI', ui-sans-serif, system-ui, sans-serif;
  --pp-pad: 30px;
  --pp-gap: 14px;

  /* Centred on the same measure as the hero. This is what was missing before:
     the card had a max-width but no auto margin, so it hung to the left while
     the hero sat centred. */
  max-width: 1150px; margin: 0 auto; position: relative;
  font-family: var(--pp-font); color: var(--pp-ink);
  /* The site's global stylesheet sets `section { padding: 90px 44px }`, and
     this block is a <section>. Left alone it inherits 90px of top padding
     that shows up as a large empty band above the card, plus 44px of side
     padding that shrinks it out of alignment with the hero. */
  padding: 0;
}

/* ── Badges: sit on top of the card like tabs, not floating above it ─────
   Rendered in NORMAL FLOW (not position:absolute) and pulled onto the card
   with a negative bottom margin. Absolute positioning was measured against
   .pp's own top edge, which assumed .pp-card started right at that edge —
   true in isolation, but on the real page other content sits above .pp and
   the assumption broke, leaving the badges stranded in whatever gap that
   content added. A negative margin only cares about the box immediately
   below it, so it stays correct no matter what precedes .pp. */
.pp-badges {
  display: flex; gap: 9px; justify-content: flex-end;
  position: relative; z-index: 3;
  /* Bottom edge touches the card's top edge exactly — no negative pull. A
     negative margin here would drag the card up OVER the badges and hide
     both them and the "73 Users" pill beneath it. The right inset keeps
     Featured from hanging off the card's corner, leaving that corner for
     the users pill. */
  margin-bottom: 0; padding-inline-end: 22px;
}
.pp-badge {
  background: var(--pp-navy-2); border: 1px solid var(--pp-line);
  border-radius: 9px; border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  padding: 9px 24px;
  font: 600 13.5px/1 var(--pp-font); color: var(--pp-ink);
}
/* Same treatment as the users pill on the other end of the row: a gold edge
   over a transparent fill, not a solid gold block. Two badges in the same row
   claiming the same colour in two different weights read as a mistake; matched
   like this they read as a pair. */
.pp-badge-featured {
  background: rgba(240,178,59,.07);
  border-color: rgba(240,178,59,.55);
  color: var(--pp-gold-a); font-weight: 700;
  box-shadow: inset 0 1px 0 rgba(255,214,124,.35), 0 0 14px rgba(240,178,59,.14);
}
/* User count: same badge shape as Plugin/New — transparent fill, visible
   edge — but in gold. Sits last in the row rather than floating over the blue
   panel, where it collided with the corner it was pinned to. The inner
   highlight is what gives the edge its raised look; a flat border alone
   reads as a plain outline next to the filled Featured badge beside it. */
.pp-badge-users {
  background: rgba(240,178,59,.07);
  border-color: rgba(240,178,59,.55);
  color: var(--pp-gold-a); font-weight: 700;
  box-shadow: inset 0 1px 0 rgba(255,214,124,.35), 0 0 14px rgba(240,178,59,.14);
  display: inline-flex; align-items: center; gap: 7px;
  padding-inline: 18px;
}
.pp-badge-users svg { width: 15px; height: 15px; flex-shrink: 0; }

/* Figma Community: same badge shape as the rest of the row, but a link, so it
   gets a pointer and a hover state the others do not need. Deliberately not
   loud - it is reassurance next to the price, not a second call to action
   competing with Add to Cart. */
.pp-badge-figma {
  display: inline-flex; align-items: center; gap: 8px;
  text-decoration: none; padding-inline: 18px;
  transition: border-color .18s, color .18s, background .18s;
}
.pp-badge-figma svg { width: 13px; height: 13px; flex-shrink: 0; }
.pp-badge-figma:hover {
  background: rgba(255,255,255,.05);
  border-color: rgba(255,255,255,.28);
  color: #fff;
}
@media (max-width: 620px) {
  /* The row wraps on a phone; the icon alone still identifies it. */
  .pp-badge-figma { padding-inline: 13px; }
}

/* ── Card ──────────────────────────────────────────────────────────────── */
/* Same glass as the hero's apps card, so the two blocks on this page are made
   of the same material: a bloom behind, translucent gradients over it, a
   vignette so the edges stay dark, and a hairline catching the light on top.
   See .ph-hub for the reasoning on the layer order. */
.pp-card {
  display: grid; grid-template-columns: minmax(0,1fr) minmax(0,.78fr);
  position: relative; isolation: isolate;
  background:
    radial-gradient(120% 85% at 32% 4%, transparent 0%, rgba(2,9,20,.5) 70%, rgba(1,6,14,.8) 100%),
    linear-gradient(168deg, rgba(12,38,74,.55) 0%, rgba(5,18,38,.72) 55%, rgba(2,9,20,.86) 100%);
  border: 1px solid rgba(255,255,255,.10); border-radius: 22px; overflow: hidden;
  box-shadow:
    0 34px 70px -30px rgba(0,0,0,.85),
    0 0 0 1px rgba(37,124,255,.06),
    inset 0 1px 0 rgba(255,255,255,.09);
}
.pp-card::before {
  content: ''; position: absolute; z-index: -1;
  left: -18%; top: -60%; width: 90%; aspect-ratio: 1;
  background: radial-gradient(circle at 50% 50%,
    rgba(64,156,255,.40) 0%, rgba(26,96,224,.16) 36%, transparent 66%);
  filter: blur(52px);
  animation: phBloom 22s ease-in-out infinite;
}
.pp-card::after {
  content: ''; position: absolute; z-index: 3; inset: 0 0 auto; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.45) 50%, transparent);
  pointer-events: none;
}
/* Above the bloom. Without this the light would wash over the controls. */
.pp-left { position: relative; z-index: 1; display: flex; flex-direction: column; min-width: 0; padding: var(--pp-pad) calc(var(--pp-pad) + 8px) var(--pp-pad) var(--pp-pad); }
.pp-right { position: relative; z-index: 1; background: rgba(21,131,242,.10); min-height: 360px; overflow: hidden; }
/* Fills the blue panel edge to edge. object-fit:cover means the clip is
   cropped rather than letterboxed, so it keeps working as the card's height
   changes — design the video with its subject centred and keep the top-right
   and bottom-right corners clear, since the users pill and the app icons sit
   over them. */
.pp-loop { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; z-index: 0; }
.pp-corner-apps { z-index: 2; }

/* ── Tabs: a segmented control, inside the card, on the same grid ──────── */
.pp-tabs {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  background: rgba(8,26,50,.55); border: 1px solid rgba(255,255,255,.10);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border-radius: 11px; padding: 4px; margin-bottom: 26px;
}
.pp-tab {
  border: none; cursor: pointer; border-radius: 8px;
  background: transparent; color: var(--pp-dim);
  font: 600 14px/1 var(--pp-font); padding: 12px 6px;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  transition: background .15s, color .15s;
}
.pp-tab:hover { color: var(--pp-ink); background: rgba(255,255,255,.04); }
.pp-tab.is-active { background: var(--pp-blue); color: #fff; }
.pp-tab:focus-visible { outline: 2px solid var(--pp-blue-hi); outline-offset: 2px; }
.pp-gift-ico { width: 16px; height: 16px; }

/* ── Gift ────────────────────────────────────────────────────────────────
   The only tab nobody comes looking for, so it is the only one that asks. A
   slow breathing glow in the gold the gift artwork already uses: enough to
   catch the eye on a page of blue, not enough to compete with Add to Cart.

   box-shadow on a static element rather than an animated filter, and it stops
   the moment the tab is chosen or the pointer is on it — a control that keeps
   pulsing after you have taken it up is nagging, not inviting. */
.pp-tab-gift { position: relative; color: var(--pp-gold-a); }
.pp-tab-gift:not(.is-active) {
  animation: ppGiftGlow 3.2s ease-in-out infinite;
}
.pp-tab-gift:hover, .pp-tab-gift.is-active { animation: none; }
.pp-tab-gift.is-active { color: #fff; }
@keyframes ppGiftGlow {
  0%, 100% { background: rgba(240,178,59,.05); box-shadow: 0 0 0 0 rgba(240,178,59,0); }
  50%      { background: rgba(240,178,59,.13); box-shadow: 0 0 16px 0 rgba(240,178,59,.22); }
}
@media (prefers-reduced-motion: reduce) {
  .pp-tab-gift:not(.is-active) { animation: none; background: rgba(240,178,59,.10); }
}

/* ── Pane ──────────────────────────────────────────────────────────────── */
/* Fixed to the tallest pane so the card never grows or shrinks as the
   customer switches tabs, set to the tallest pane's natural height so no tab
   is left with a blank band under it. Re-measure if any pane gains a row. */
/* Every pane occupies the SAME grid cell, and a hidden one is only made
   invisible rather than removed. So the block is always as tall as its tallest
   pane and the card cannot change size when the customer switches tabs.
   
   This replaces a hand-measured min-height, which was right until the Gift
   pane gained a row and then quietly stopped being right — the card grew a
   little every time that tab was picked. The floor is kept as a floor only. */
.pp-body { flex: 1; min-height: 261px; display: grid; }
.pp-pane { grid-area: 1 / 1; }
.pp-pane[hidden] {
  /* display:none would take it out of the grid and lose its height. Hidden
     this way it still measures, and visibility:hidden keeps it out of the tab
     order and out of the accessibility tree, which display:none also did. */
  display: block; visibility: hidden; pointer-events: none;
}
.pp-title { font: 700 24px/1.2 var(--pp-font); color: #fff; margin: 0 0 6px; }
.pp-sub { font: 400 14px/1.5 var(--pp-font); color: var(--pp-dim); margin: 0 0 22px; }
/* Two columns, both starting at the pane's top line: the heading and picker
   stack inside .pp-main on the left, the code box sits on the right. Putting
   the heading INSIDE the left column is what lets the side block rise to the
   top — it used to sit below the heading, which made the pane
   heading + max(picker, side) tall instead of just max(main, side). */
.pp-cols { display: grid; grid-template-columns: minmax(0,1.2fr) minmax(0,.8fr); gap: 24px; align-items: start; }
.pp-main { display: flex; flex-direction: column; min-width: 0; }

/* ── Selects ───────────────────────────────────────────────────────────── */
.pp-picker { display: flex; flex-direction: column; gap: var(--pp-gap); }
.pp-sel { position: relative; display: flex; align-items: center; }
.pp-sel-icon {
  /* No border-radius here: the icon art already carries its own rounded
     square and a capability badge tucked in the bottom-left corner, so an
     extra radius on this box just clips a sliver off that badge. contain
     (not cover) keeps the whole badge on-screen at this size. */
  position: absolute; inset-inline-start: 12px; width: 28px; height: 28px;
  background-size: contain; background-repeat: no-repeat; background-position: center; pointer-events: none;
  /* A shadow under the tile so its dark edge separates from the dark field
     instead of melting into it. */
  filter: drop-shadow(0 1px 3px rgba(0,0,0,.55));
  z-index: 1;
}
.pp-sel select, .pp-input {
  width: 100%; box-sizing: border-box;
  /* DARK glass, not white glass. Two things broke when this was a white tint:
     the app icons sitting on it are dark artwork and went invisible, and a
     native <select> paints its dropdown from its own background-color, so the
     list came out white with white text on it. Tinting the glass with the
     card's own navy fixes both and still reads as glass against the bloom. */
  /* Light enough for the app icons. Their artwork is a near-black tile with
     a small logo on it, so on a very dark field the tile disappears and only
     the logo is left floating. This is about as dark as it can go and still
     read as a surface the icon is sitting ON. */
  background: rgba(15,52,92,.78); border: 1px solid rgba(255,255,255,.14);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border-radius: 10px; color: #fff;
  /* line-height 1 clipped the descenders: the g in "Figma" lost its tail and
     the word read as "Fiqma". The padding drops by the same amount the line
     box gains, so the control keeps the height it had. */
  font: 600 15px/1.25 var(--pp-font);
  padding: 12px 40px 12px 48px; appearance: none; cursor: pointer;
}
/* The popup is drawn by the OS, not by this stylesheet: it cannot inherit a
   translucent colour, so it gets an opaque one of its own. Without this the
   options are unreadable in Chrome the moment the field is not fully opaque. */
.pp-sel select option {
  background-color: #0B2C48; color: #fff;
}
.pp-sel select option:disabled {
  color: rgba(255,255,255,.45);
}
.pp-sel select:focus, .pp-input:focus { outline: none; border-color: var(--pp-blue); }
.pp-sel::after {
  content: ''; position: absolute; inset-inline-end: 15px; width: 0; height: 0;
  border-inline: 6px solid transparent; border-top: 8px solid #fff; pointer-events: none;
}

.pp-input { padding: 12px 15px; font-weight: 500; font-size: 13.5px; cursor: text; }

/* ── Invite offer, top of the side column ────────────────────────────────
   Compact by design: the referral URL is long, nobody reads it, and a field
   showing it cost more height than the offer is worth. The link rides on the
   button's data-link instead, so this is two short lines on every tab. */
.pp-refer { display: flex; flex-direction: column; gap: 7px; margin-bottom: 4px; }
.pp-refer-head { font: 700 12px/1.35 var(--pp-font); color: var(--pp-gold-a); }
.pp-refer-copy {
  display: block; width: 100%; box-sizing: border-box; text-align: center;
  background: rgba(240,178,59,.10); border: 1px solid rgba(240,178,59,.45);
  color: var(--pp-gold-a); border-radius: 10px; padding: 9px 12px;
  font: 700 12.5px/1 var(--pp-font); cursor: pointer; text-decoration: none;
  transition: background .15s, color .15s;
}
.pp-refer-copy:hover { background: rgba(240,178,59,.20); color: #fff; }
.pp-refer-copy.is-done { background: rgba(74,222,128,.16); border-color: rgba(74,222,128,.5); color: #4ade80; }
.pp-refer-note { font: 500 12px/1.5 var(--pp-font); color: var(--pp-dim); margin: 8px 0 0; }
.pp-refer-note a { color: #4FA9FF; }

/* ── App list (Prism Pro / Studio) ─────────────────────────────────────── */
.pp-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 13px 16px;
  background: var(--pp-navy-3); border: 1px solid var(--pp-line);
  border-radius: 11px; padding: 16px;
}
.pp-app { display: flex; align-items: center; gap: 9px; font: 600 13.5px/1.2 var(--pp-font); }
.pp-app img { width: 28px; height: 28px; flex-shrink: 0; object-fit: contain; }
.pp-app-keys { color: var(--pp-gold-a); font-weight: 700; }
/* An app whose bridge has not shipped yet: dimmed and labelled, so the grid
   can still show it without implying the licence covers it. */
.pp-app.is-soon { color: var(--pp-dim); }
.pp-app.is-soon img { opacity: .55; }
.pp-soon {
  font: 700 9.5px/1 var(--pp-font); font-style: normal; letter-spacing: .04em;
  text-transform: uppercase; color: var(--pp-dim);
  border: 1px solid var(--pp-line); border-radius: 5px; padding: 3px 5px; margin-inline-start: 5px;
}

/* ── Right column of the pane ──────────────────────────────────────────────
   Deliberately narrower and shorter than the picker beside it: this used to
   stretch to match the picker column's width (making the input look like an
   elongated bar right up against the blue media panel) and stack with the
   same gap as the picker's selects (making it taller than the blue panel's
   own content). Capped width + tighter gap + smaller type fixes both. */
.pp-side { display: flex; flex-direction: column; gap: 10px; max-width: 230px; margin-inline: auto; text-align: center; }
.pp-once { font: 600 13px/1.35 var(--pp-font); color: #fff; }
.pp-discount { text-align: center; color: var(--pp-dim); padding: 11px 14px; font-size: 13px; }
.pp-apply {
  background: transparent; border: 1px solid var(--pp-blue); color: #4FA9FF;
  border-radius: 10px; padding: 10px; font: 700 13px/1 var(--pp-font); cursor: pointer;
}
.pp-apply:hover { background: rgba(21,131,242,.14); }
.pp-apply:disabled { opacity: .5; cursor: default; }
/* Reserves a line so a coupon result cannot shift the card's height, which
   is pinned by .pp-body's min-height. */
.pp-coupon-msg { font: 500 11.5px/1.45 var(--pp-font); margin: 0; min-height: 17px; color: var(--pp-dim); }
.pp-coupon-msg.is-ok { color: #4ade80; }
.pp-coupon-msg.is-err { color: #fca5a5; }

/* ── Price bar: inside the card's padding, so it aligns with the pane ──── */
.pp-bar {
  display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 18px;
  margin-top: 24px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,.10);
}
/* The second column holds two readouts that swap per tab: the price lines, and
   the gift tab's claimed/available bar. They are not the same height, so
   swapping them resized the whole card — the same bug the panes above had, in
   the one place the pane fix could not reach, because these live outside
   .pp-body. Same answer: one cell, both stacked in it, the hidden one only
   made invisible so it still sets the height. */
.pp-bar > .pp-totals, .pp-bar > .pp-gift-progress { grid-area: 1 / 2; }
.pp-bar > [hidden] { display: flex; visibility: hidden; pointer-events: none; }
.pp-cta {
  background: var(--pp-blue); border: none; color: #fff; cursor: pointer;
  font: 700 15px/1 var(--pp-font); padding: 15px 26px; border-radius: 10px;
  display: flex; align-items: center; gap: 9px;
}
.pp-cta:hover { background: var(--pp-blue-hi); }
.pp-cart-ico { width: 18px; height: 18px; }
.pp-totals { display: flex; flex-direction: column; gap: 6px; }
.pp-total-row { display: grid; grid-template-columns: 7px 1fr auto; align-items: center; gap: 9px; font: 600 14px/1 var(--pp-font); }
.pp-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pp-blue); }
.pp-was { color: var(--pp-dim); font-weight: 500; }

/* Marketing "was" price, struck through, sitting right before the real total.
   Only ever shown when the plan actually has a genuine higher price on file
   (see prism_db_prices() in includes/prism_bridges_catalog.php) - never a
   made-up reference number. */
.pp-compare { color: var(--pp-dim); text-decoration: line-through; font-size: 13px; opacity: .7; margin-inline-end: 2px; }
.pp-totals strong { font-size: 16px; color: #fff; }
.act-err { color: #fca5a5; font: 500 12px/1.4 var(--pp-font); min-height: 15px; margin-top: 8px; }
/* A Mac-Resolve heads-up is information, not a rejected purchase - the red
   #ppError tone would read as "something went wrong with your order". */
.act-err.is-notice { color: #93c5fd; }

/* ── Gift tab: claimed/available bar instead of a price ─────────────────── */
.pp-gift-progress { display: flex; flex-direction: column; gap: 9px; }
.pp-gift-progress-row { display: grid; grid-template-columns: 7px 1fr auto; align-items: center; gap: 9px; font: 600 14px/1 var(--pp-font); }
.pp-gift-progress-count { color: var(--pp-dim); font-variant-numeric: tabular-nums; }
.pp-gift-progress-track {
  height: 8px; border-radius: 999px; background: var(--pp-navy-3);
  border: 1px solid var(--pp-line); overflow: hidden;
}
.pp-gift-progress-fill {
  height: 100%; border-radius: inherit;
  background: linear-gradient(90deg, var(--pp-blue) 0%, var(--pp-blue-hi) 100%);
}
/* Belt-and-braces on top of the JS hidden-attribute toggle: on the gift tab
   the price text must never be visible next to the bar, full stop.

   visibility, not display. These two share one grid cell so the bar is always
   as tall as the taller of them; taking one out of the layout with display
   made the card shrink by 11px every time the Gift tab was picked, and the
   !important here is what was quietly overriding the fix for that. Invisible
   is what this rule ever needed to guarantee. */
.pp.pp-gift #ppTotals,
.pp:not(.pp-gift) #ppGiftProgress {
  visibility: hidden !important; pointer-events: none;
}

/* ── Media half ────────────────────────────────────────────────────────── */
/* One straight row of five, centred along the bottom of the panel. The old
   2-then-3 stagger crowded the panel's bottom-right corner and clipped
   against its edge; a single row reads as a clean strip and leaves the
   looping video in the middle unobstructed. */
.pp-corner-apps {
  position: absolute; inset-inline: 0; bottom: 20px;
  display: flex; justify-content: center; gap: 12px;
}
.pp-corner-apps img { width: 40px; height: 40px; object-fit: contain; }

/* ── Platform support, under the card ──────────────────────────────────── */
.pp-os { display: flex; gap: 9px; margin-top: 14px; align-items: center; flex-wrap: wrap; }
.pp-os > span:first-child {
  width: 34px; height: 30px; border-radius: 7px;
  background: var(--pp-navy-2); border: 1px solid var(--pp-line);
  display: flex; align-items: center; justify-content: center; color: var(--pp-dim);
  flex: none;
}
.pp-os svg { width: 17px; height: 17px; }
/* Spelled out, not left to an icon. Which platforms a paid tool runs on is the
   fact most likely to cause a refund if the buyer guesses wrong, so it gets
   words. */
.pp-os-label { font-size: 12.5px; font-weight: 600; color: var(--pp-dim); }
/* Quiet by design: it is a way out for Mac visitors, not a second call to
   action competing with Add to Cart. */
.pp-os-soon {
  font-size: 12px; color: var(--pp-dim); opacity: .7;
  text-decoration: underline; text-underline-offset: 3px;
  margin-inline-start: 2px; transition: opacity .2s, color .2s;
}
.pp-os-soon:hover { opacity: 1; color: #fff; }

@media (max-width: 860px) {
  .pp-card { grid-template-columns: 1fr; }
  .pp-cols { grid-template-columns: 1fr; }
  .pp-tabs { grid-template-columns: 1fr 1fr; }
  .pp-bar { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Hero above the card: breadcrumb, title, video, bridge diagram.
   Shares the card's tokens so the two blocks read as one page rather than two
   components that happen to sit near each other.
   ══════════════════════════════════════════════════════════════════════════ */
.ph {
  --pp-blue: #1583F2;
  --pp-navy: #04182B;
  --pp-navy-2: #072339;
  --pp-line: rgba(11,114,234,.42);
  --pp-ink: #EAF2FB;
  --pp-dim: #7FA3C9;
  --pp-font: 'Segoe UI', ui-sans-serif, system-ui, sans-serif;
  /* Bottom margin clears the badge row that sits on top of the card below
     (33.5px tall), leaving a real gap between the two blocks rather than
     the hero appearing to rest on the badges. */
  max-width: 1150px; margin: 0 auto 52px;
  font-family: var(--pp-font); color: var(--pp-ink);
  /* Same reason as .pp: kill the global `section { padding: 90px 44px }`,
     which otherwise wraps the hero in ~90px of dead space top and bottom. */
  padding: 0;
}

/* Chevron breadcrumb: each crumb points into the next. The notch is cut with
   clip-path rather than drawn as an arrow glyph, so the shape survives any
   label length. */
.ph-crumbs { display: flex; gap: 3px; margin-bottom: 18px; }
.ph-crumbs a, .ph-crumbs span {
  background: var(--pp-navy-2); color: var(--pp-dim);
  font: 600 11.5px/1 var(--pp-font); text-decoration: none;
  padding: 7px 20px 7px 26px;
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%, 10px 50%);
}
.ph-crumbs a:first-child { padding-inline-start: 18px; clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%); }
.ph-crumbs a:hover { color: var(--pp-ink); }
.ph-crumbs .is-current { background: var(--pp-blue); color: #fff; }

.ph-title { font: 800 46px/1 var(--pp-font); letter-spacing: .01em; color: #fff; margin: 0 0 10px; }
.ph-tagline { font: 600 13px/1.5 var(--pp-font); color: var(--pp-ink); margin: 0 0 24px; }

/* Same column split as the card below, so the two rows line up. */
.ph-grid { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,.78fr); gap: 20px; align-items: stretch; }

.ph-video {
  position: relative; border-radius: 12px; overflow: hidden;
  /* Was #D9D9D9. On a page this dark, any part of the box the media does not
     cover to the exact pixel reads as a white line down the edge — which is
     what it was doing, because the media below was in normal flow and a 16/9
     box rounded to a fractional height leaves a sliver behind. Dark now, so
     even a rounding error is invisible; the media is pinned as well. */
  background: #05080f; aspect-ratio: 16 / 9;
  /* width:100% is load-bearing, not decoration. As a grid item under the
     default align-items:stretch, this box gets stretched to the row height
     set by the taller hub panel; with only an aspect-ratio and no definite
     width, the browser then derives WIDTH FROM THAT HEIGHT (height x 16/9),
     which made it ~62px wider than its column and spill over the hub, so
     the two panels looked glued together. Pinning the width makes the
     aspect-ratio resolve the other way round, height from width. */
  width: 100%;
}
/* All three pinned to the box, not laid out inside it. An iframe in normal
   flow keeps its default 300x150 until CSS says otherwise, and YouTube reads
   the player's size when it starts to pick a quality — a player it measures as
   300px wide is a player it hands 360p. This is why the clip came up soft as
   well as why the edges showed. Same rule the AE page has always had. */
.ph-video video,
.ph-video img,
.ph-video iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0; display: block; object-fit: cover;
}
.ph-play {
  position: absolute; inset: 0; margin: auto; width: 58px; height: 58px;
  border: none; border-radius: 50%; cursor: pointer;
  background: rgba(4,24,43,.82); color: #fff;
  display: flex; align-items: center; justify-content: center;
  transition: transform .15s, background .15s;
}
.ph-play:hover { transform: scale(1.07); background: var(--pp-blue); }
.ph-play svg { width: 26px; height: 26px; margin-inline-start: 3px; }
.ph-video.is-playing .ph-play { opacity: 0; pointer-events: none; }

/* ── The blue panel pointed at YouTube ───────────────────────────────────
   Same button as the hero, sized down for the square. .pp-yt is a wrapper
   rather than the image itself because the iframe replaces its contents on
   click, and .pp-loop's object-fit means nothing to an <iframe>. */
.pp-yt { position: absolute; inset: 0; z-index: 0; }
.pp-yt iframe { width: 100%; height: 100%; border: 0; display: block; }
.pp-play {
  position: absolute; inset: 0; margin: auto; width: 52px; height: 52px;
  border: none; border-radius: 50%; cursor: pointer; z-index: 1;
  background: rgba(4,24,43,.82); color: #fff;
  display: flex; align-items: center; justify-content: center;
  transition: transform .15s, background .15s;
}
.pp-play:hover { transform: scale(1.07); background: var(--pp-blue); }
.pp-play svg { width: 24px; height: 24px; margin-inline-start: 3px; }

/* ── Hero video control bar ──────────────────────────────────────────────
   Hidden until the pointer is over the video (or it is paused), so a playing
   clip is not permanently covered by chrome. Always visible on touch, where
   there is no hover to reveal it with. */
.ph-ui {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 3;
  display: flex; align-items: center; gap: 10px;
  padding: 22px 14px 12px;
  background: linear-gradient(to top, rgba(2,10,20,.88), transparent);
  opacity: 0; transition: opacity .2s;
}
.ph-video:hover .ph-ui,
.ph-video.is-paused .ph-ui,
.ph-ui:focus-within { opacity: 1; }
@media (hover: none) { .ph-ui { opacity: 1; } }

.ph-btn {
  flex-shrink: 0; width: 30px; height: 30px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 7px; cursor: pointer;
  background: rgba(255,255,255,.1); color: #fff; transition: background .15s;
}
.ph-btn:hover { background: rgba(255,255,255,.2); }
.ph-btn svg { width: 15px; height: 15px; }

/* One button, two glyphs: whichever matches the current state is shown, so
   the icon can never drift out of sync with a separate state variable. */
.ph-i-pause, .ph-i-loud { display: none; }
.ph-video.is-playing .ph-i-play  { display: none; }
.ph-video.is-playing .ph-i-pause { display: block; }
.ph-video.is-loud    .ph-i-muted { display: none; }
.ph-video.is-loud    .ph-i-loud  { display: block; }

.ph-time { font: 600 11.5px/1 var(--pp-font); color: #cfe2f7; font-variant-numeric: tabular-nums; flex-shrink: 0; }

/* A styled range input: native seeking behaviour, custom look. */
.ph-seek {
  flex: 1; min-width: 0; height: 4px; margin: 0; padding: 0;
  appearance: none; -webkit-appearance: none; cursor: pointer;
  border-radius: 100px; background: rgba(255,255,255,.22);
}
.ph-seek::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none;
  width: 12px; height: 12px; border-radius: 50%;
  background: #fff; border: none; box-shadow: 0 1px 4px rgba(0,0,0,.5);
}
.ph-seek::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%;
  background: #fff; border: none; box-shadow: 0 1px 4px rgba(0,0,0,.5);
}
.ph-seek:focus-visible { outline: 2px solid var(--pp-blue); outline-offset: 3px; }

/* ── The apps card: one piece of dark glass ──────────────────────────────
   Was a flat navy box with a solid blue bar capping it, which read as a dialog
   dropped on the page rather than part of it. Rebuilt the way the reference
   work is built: a deep blue ground, a light source behind it, and a hairline
   edge catching that light.

   Three layers, back to front:
     ::before  the light  — a soft blue bloom that drifts, on its own layer, so
                            the drift is a transform and nothing repaints
     the card  the glass  — translucent gradients laid over that bloom
     ::after   the edge   — a bright hairline along the top, strongest in the
                            middle, the way a bevel catches a light above it
   Content sits above all three on z-index 1. */
.ph-hub {
  position: relative; isolation: isolate;
  display: flex; flex-direction: column;
  border-radius: 22px; overflow: hidden;
  background:
    /* The vignette goes ON TOP of the bloom rather than under it: the light is
       behind the glass, and glass is darkest where it curves away at the
       edges. Without this the bloom washes the whole card out to flat blue. */
    radial-gradient(120% 85% at 50% 8%, transparent 0%, rgba(2,9,20,.55) 72%, rgba(1,6,14,.82) 100%),
    linear-gradient(168deg, rgba(12,38,74,.55) 0%, rgba(5,18,38,.72) 55%, rgba(2,9,20,.86) 100%);
  border: 1px solid rgba(255,255,255,.10);
  box-shadow:
    0 34px 70px -30px rgba(0,0,0,.85),
    0 0 0 1px rgba(37,124,255,.06),
    inset 0 1px 0 rgba(255,255,255,.09);
}
.ph-hub::before {
  content: ''; position: absolute; z-index: -1;
  left: -10%; top: -42%; width: 120%; aspect-ratio: 1;
  background: radial-gradient(circle at 50% 50%,
    rgba(64,156,255,.42) 0%, rgba(26,96,224,.17) 36%, transparent 66%);
  filter: blur(52px);
  /* Slow enough to be felt rather than watched. Transform only, so it runs on
     the compositor and costs nothing per frame. */
  animation: phBloom 22s ease-in-out infinite;
}
@keyframes phBloom {
  0%, 100% { transform: translate3d(-5%, -3%, 0) scale(1); }
  50%      { transform: translate3d(5%, 4%, 0) scale(1.14); }
}
@media (prefers-reduced-motion: reduce) {
  .ph-hub::before { animation: none; }
}
.ph-hub::after {
  content: ''; position: absolute; z-index: 1; inset: 0 0 auto; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.45) 50%, transparent);
  pointer-events: none;
}

/* The old blue bar, now an outlined pill: same words, sitting inside the glass
   instead of capping it. */
.ph-hub-head {
  position: relative; z-index: 1;
  align-self: center; margin: 20px 20px 0;
  padding: 8px 18px; border-radius: 100px;
  border: 1px solid rgba(255,255,255,.20);
  background: rgba(255,255,255,.045);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  font: 700 10.5px/1 var(--pp-font); letter-spacing: .15em; text-transform: uppercase;
  color: rgba(255,255,255,.88); text-align: center;
}
.ph-hub-body {
  position: relative; z-index: 1;
  flex: 1; background: none; border: none;
  display: flex; align-items: center; justify-content: center; padding: 14px;
}
.ph-hub-body svg { width: 100%; height: auto; max-height: 290px; }

/* ── Supported-apps folder ────────────────────────────────────────────────
   An iOS home-screen folder: closed it is a small tile with the five icons
   shrunk inside it, open it is the full grid with names.

   There is only ONE set of icons, and only TWO layouts — closed and open,
   each a plain set of numbers below. Nothing here transitions between them.
   The travel is a FLIP in js/prism-pricing.js: it measures both layouts,
   then plays the difference back as transform alone.

   That split is the whole point. Animating padding/gap/width (the obvious
   way, and how this was first built) makes the browser re-lay-out and
   repaint the subtree every single frame, on the main thread, and it stutters
   under any other load on the page. Transform and opacity are the only two
   properties the compositor can animate by itself, so those are the only two
   that move here. */
.ph-folder-body {
  position: relative; overflow: hidden;
  contain: layout size;
  /* The panel is normally stretched to the hero row's height (set by the
     video beside it). This floor is what keeps the open folder from being
     clipped on a page where that row happens to be short — a small hero
     image, or the single-column layout on a phone. */
  min-height: 304px;
}
/* ZERO layout cost, by construction.

   The tile more than doubles in size when it opens. If it did that in normal
   flow, the panel would grow, the hero row would grow with it (both columns
   are stretched to the same row height), and every section below the hero
   would slide down and back up on each click — the whole page breathing in
   time with the folder. Absolute positioning inside a panel that has a height
   of its own means nothing outside can see the tile change size at all. */
.ph-folder {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 6px;
}

.ph-folder-tile {
  /* The knobs both layouts are built from. The JS never writes geometry; it
     flips one class, these change, and it animates the difference. */
  --tile-pad: 12px;
  --icon: 32px;
  --gap: 7px;

  position: relative;
  box-sizing: content-box;
  padding: var(--tile-pad);
  /* NO box behind the icons. The panel already draws a frame, and a second
     rounded rectangle inside it read as a box within a box. The icons and
     their names sit straight on the panel; the padding is still here because
     it is what sets the distance the icons travel. */
  border-radius: 26px;
}

/* Covers the closed tile as one big target. Hidden once open, which is the
   moment the icons underneath become the targets instead. */
.ph-folder-open {
  position: absolute; inset: -6px; z-index: 2;
  border: none; background: none; padding: 0; cursor: pointer;
  border-radius: 30px;
}
.ph-folder-open:focus-visible { outline: 2px solid var(--pp-blue); outline-offset: 2px; }
.ph-folder-body.is-open .ph-folder-open { display: none; }

/* With no box to light up, hover lives on the icons themselves. */
.ph-folder-tile:hover .ph-folder-icon {
  filter: drop-shadow(0 4px 12px color-mix(in srgb, var(--app-glow) 75%, transparent));
}

/* Flex-wrap rather than a 3-column grid: five icons means the second row holds
   two, and a grid would jam them against the left edge. Wrapping at an
   explicit three-icon width centres the short row the way a home screen
   does. */
.ph-folder-grid {
  position: relative; z-index: 1;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--gap);
  width: calc(var(--icon) * 3 + var(--gap) * 2);
}

.ph-folder-app {
  position: relative; display: block; width: var(--icon); height: var(--icon);
  padding: 0; border: none; background: none; font: inherit; color: inherit;
  /* Not clickable while closed: the whole tile is one target then, and a
     stray click on an icon must not select anything behind the cover. */
  pointer-events: none; cursor: default;
}
.ph-folder-body.is-open .ph-folder-app { pointer-events: auto; cursor: pointer; }
.ph-folder-app:focus-visible { outline: 2px solid var(--pp-blue); outline-offset: 5px; border-radius: 24%; }

/* ── Picked ──────────────────────────────────────────────────────────────
   A ring in the app's own colour, drawn as a pseudo-element so it costs no
   layout and cannot disturb the FLIP measurements. Both the ring and the lift
   animate transform/opacity only. */
.ph-folder-app::after {
  content: ''; position: absolute; inset: -7px; border-radius: 26%;
  border: 2px solid var(--app-glow);
  opacity: 0; transform: scale(.82);
  transition: opacity .2s ease, transform .3s cubic-bezier(.22,1.4,.44,1);
  pointer-events: none;
}
.ph-folder-app.is-picked::after {
  opacity: 1; transform: none;
  animation: phPick 1.7s ease-in-out .3s infinite;
}
@keyframes phPick {
  50% { opacity: .4; transform: scale(1.06); }
}
.ph-folder-app.is-picked .ph-folder-icon {
  transform: translateY(-3px) scale(1.05);
  filter: drop-shadow(0 0 16px color-mix(in srgb, var(--app-glow) 85%, transparent))
          drop-shadow(0 6px 14px rgba(0,0,0,.5));
}
.ph-folder-app.is-picked .ph-folder-name { color: #fff; }

/* No pair sold for those two: a shake, then the selection clears itself. */
.ph-folder-app.is-nope { animation: phNope .4s ease; }
@keyframes phNope {
  25% { transform: translateX(-5px); }
  55% { transform: translateX(5px); }
  80% { transform: translateX(-2px); }
}
.ph-folder-icon {
  width: var(--icon); height: var(--icon); display: block;
  /* The app's own brand hue, dialled right down: enough to lift the icons off
     the tile, not enough to read as five coloured blobs. Deliberately NOT
     animated — a filter that changes every frame is a full repaint every
     frame. Left constant, it is rasterised once and the FLIP's transform
     scales the finished layer for free. */
  filter: drop-shadow(0 4px 9px color-mix(in srgb, var(--app-glow) 50%, transparent));
  will-change: transform;
  /* Only the pick state animates these; the FLIP writes its own transform on
     the parent button, so the two never fight over the same property. */
  transition: transform .3s cubic-bezier(.22,1.4,.44,1), filter .25s ease;
}
/* Names are absolutely positioned so showing them costs no layout: the icon
   box stays exactly one icon tall in both layouts, and only the icon size
   feeds the FLIP. */
.ph-folder-name {
  position: absolute; inset-inline: -8px; top: calc(100% + 2px);
  text-align: center; white-space: nowrap;
  font: 600 10px/1 var(--pp-font); color: var(--pp-dim);
  opacity: 0; transform: translateY(-5px);
  /* Closing: short, and stepped in REVERSE order (--ri) so the names leave
     from the far end inwards, mirroring the icons. All five vanishing on the
     same frame was the part that read as broken. */
  transition: opacity .16s ease, transform .16s ease;
  transition-delay: calc(var(--ri, 0) * 15ms);
  pointer-events: none;
}

.ph-folder-label {
  position: relative; display: block; height: 34px; width: 100%; text-align: center;
  will-change: transform;
}
.ph-folder-label > span {
  position: absolute; inset-inline: 0; top: 0;
  font: 700 13px/1.25 var(--pp-font); color: #fff; letter-spacing: .02em;
  opacity: 0; transform: translateY(6px);
  /* The outgoing line fades in .16s with no delay, the incoming one waits
     that long before starting. Cross-fading them on the same frame turned two
     legible sentences into one unreadable overlap. */
  transition: opacity .16s ease, transform .28s cubic-bezier(.22,1.4,.44,1);
}
.ph-folder-label > span.is-live { opacity: 1; transform: none; transition-delay: .16s; }
.ph-folder-label em {
  display: block; font: 600 10.5px/1.4 var(--pp-font); font-style: normal;
  color: var(--pp-dim); letter-spacing: .04em;
}

/* ── The open layout ──────────────────────────────────────────────────────
   Applied instantly. Everything above is measured before and after this lands
   and the difference is played back as transform. */
.ph-folder-body.is-open .ph-folder-tile {
  --tile-pad: 20px;
  --icon: 68px;
  --gap: 18px;
  /* Extra room under the bottom row for its names, which are positioned
     outside the icon box and would otherwise sit on the tile's edge. */
  padding-bottom: 30px;
}
/* Names fade in only once the icons have most of the way arrived, so the text
   is not sliding around underneath a moving icon. */
/* Opening: each name waits for its own icon to arrive, then follows it. */
.ph-folder-body.is-open .ph-folder-name {
  opacity: 1; transform: none;
  transition: opacity .26s ease, transform .3s cubic-bezier(.22,1.4,.44,1);
  transition-delay: calc(170ms + var(--i, 0) * 26ms);
}
/* The bottom row of names would otherwise print on top of the label. */
.ph-folder-body.is-open .ph-folder { gap: 28px; }

/* No FLIP either: the JS checks the same query and just swaps the layout. */
@media (prefers-reduced-motion: reduce) {
  .ph-folder-name, .ph-folder-label > span { transition-duration: .01ms; }
}

/* The open tile is 282px wide, which is wider than the panel's content box on
   the narrowest phones. The panel clips its overflow, so without this the
   outer icons would simply be cut off there. */
@media (max-width: 380px) {
  .ph-folder-body.is-open .ph-folder-tile { --icon: 56px; --gap: 14px; --tile-pad: 16px; }
}

@media (max-width: 860px) {
  .ph-grid { grid-template-columns: 1fr; }
  .ph-title { font-size: 34px; }
}
