/*
 * Portal homepage - structure only.
 *
 * Everything that decides how the page *looks* lives in the per-site skin;
 * this file only decides where things sit, so the three sites can share it
 * without ending up looking related.
 */

.ph {
  --ph-gap: 16px;
  --ph-radius: 14px;
  --ph-card-radius: 12px;
  /* Column count follows the container, not the viewport: the same page runs
     both with and without the theme's right sidebar. */
  --ph-min: 152px;
  max-width: 1280px;
  margin: 0 auto;
  padding: 8px 0 40px;
  color: var(--ph-text);
  font-family: var(--ph-font, inherit);
}

.ph *,
.ph *::before,
.ph *::after { box-sizing: border-box; }

.ph a { text-decoration: none; }

/* ---------- intro band ---------- */

.ph-intro {
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: 28px;
  align-items: center;
  padding: 26px 26px 28px;
  border-radius: var(--ph-radius);
  margin: 18px 0 6px;
  background: var(--ph-surface);
  border: 1px solid var(--ph-border);
}

.ph-intro__eyebrow {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ph-accent);
}

.ph-intro__title {
  margin: 0 0 10px;
  font-size: clamp(26px, 3.4vw, 40px);
  line-height: 1.12;
  color: var(--ph-heading, var(--ph-text));
}

.ph-intro__text {
  margin: 0 0 18px;
  max-width: 62ch;
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--ph-muted);
}

.ph-search { display: flex; gap: 8px; max-width: 440px; }

.ph-search input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 11px 14px;
  font-size: 15px;
  color: var(--ph-text);
  background: var(--ph-surface-2);
  border: 1px solid var(--ph-border);
  border-radius: 10px;
}

.ph-search input::placeholder { color: var(--ph-muted); opacity: .8; }
.ph-search input:focus { outline: 2px solid var(--ph-accent); outline-offset: 1px; }

.ph-search button {
  flex: 0 0 auto;
  padding: 11px 20px;
  font-size: 15px;
  font-weight: 700;
  color: var(--ph-on-accent, #fff);
  background: var(--ph-accent);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
}

.ph-facts {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ph-facts li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 16px;
  border-radius: 10px;
  background: var(--ph-surface-2);
  border: 1px solid var(--ph-border);
}

.ph-facts b { font-size: 22px; line-height: 1.1; color: var(--ph-heading, var(--ph-text)); }
.ph-facts span { font-size: 12.5px; color: var(--ph-muted); }

/* ---------- shelves ---------- */

.ph-shelf { margin-top: 30px; }

.ph-shelf__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}

.ph-shelf__title {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-size: 21px;
  line-height: 1.2;
  color: var(--ph-heading, var(--ph-text));
}

.ph-shelf__icon { display: inline-flex; color: var(--ph-accent); }
.ph-shelf__icon svg { width: 20px; height: 20px; }

.ph-shelf__subtitle { margin: 4px 0 0; font-size: 13px; color: var(--ph-muted); }

.ph-shelf__more {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 700;
  color: var(--ph-accent);
  white-space: nowrap;
}

.ph-shelf__more:hover { text-decoration: underline; }

.ph-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--ph-min), 1fr));
  gap: var(--ph-gap);
}

/* ---------- cards ---------- */

.ph-card { display: flex; flex-direction: column; gap: 8px; color: inherit; }

.ph-card__art {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--ph-card-radius);
  background: var(--ph-surface-2);
  border: 1px solid var(--ph-border);
}

.ph-thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.ph-thumb--empty { display: block; width: 100%; height: 100%; }

.ph-card__play {
  position: absolute;
  inset: auto 0 0 0;
  padding: 8px 0;
  text-align: center;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ph-on-accent, #fff);
  background: var(--ph-accent);
  transform: translateY(100%);
  transition: transform .18s ease;
}

.ph-card:hover .ph-card__play,
.ph-card:focus-visible .ph-card__play { transform: translateY(0); }

.ph-card__rank {
  position: absolute;
  top: 8px;
  left: 8px;
  min-width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0 6px;
  font-size: 13px;
  font-weight: 800;
  border-radius: 8px;
  color: var(--ph-on-accent, #fff);
  background: var(--ph-accent);
}

.ph-card__label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.ph-card__title {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ph-text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ph-card__plays { font-size: 12px; color: var(--ph-muted); }

/* ---------- written block ---------- */

.ph-about {
  margin-top: 40px;
  padding: 26px;
  border-radius: var(--ph-radius);
  background: var(--ph-surface);
  border: 1px solid var(--ph-border);
  display: grid;
  gap: 26px;
}

.ph-about__block h2 {
  margin: 0 0 10px;
  font-size: 20px;
  color: var(--ph-heading, var(--ph-text));
}

.ph-about__block p,
.ph-about__block li { font-size: 15px; line-height: 1.7; color: var(--ph-muted); }
.ph-about__block p { margin: 0 0 12px; }
.ph-about__block ul, .ph-about__block ol { margin: 0 0 12px; padding-left: 22px; }
.ph-about__block li { margin-bottom: 6px; }
.ph-about__block strong { color: var(--ph-text); }
.ph-about__block a { color: var(--ph-accent); text-decoration: underline; }

.ph-faq__item {
  border-top: 1px solid var(--ph-border);
  padding: 12px 0;
}

.ph-faq__item summary {
  cursor: pointer;
  font-size: 15.5px;
  font-weight: 700;
  color: var(--ph-text);
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 12px;
}

.ph-faq__item summary::-webkit-details-marker { display: none; }
.ph-faq__item summary::after { content: "+"; color: var(--ph-accent); font-size: 20px; line-height: 1; }
.ph-faq__item[open] summary::after { content: "\2212"; }
.ph-faq__item > div { padding-top: 8px; }

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  /* `1fr` alone floors the track at the item's min-content width, so the copy
     column measured 330px inside a 288px content box and `.ph-intro`'s
     overflow:hidden cut the last ~40px off every line of the intro text on a
     phone. minmax(0,1fr) lets the track shrink; min-width:0 does the same for
     the grid items themselves. */
  .ph-intro { grid-template-columns: minmax(0, 1fr); padding: 22px; }
  .ph-intro > * { min-width: 0; }
  .ph-facts { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 640px) {
  .ph { --ph-min: 120px; --ph-gap: 10px; }
  .ph-shelf__head { align-items: flex-start; }
  .ph-shelf__title { font-size: 18px; }
  .ph-about { padding: 20px; }
  .ph-search { max-width: none; }
}

@media (max-width: 420px) {
  .ph { --ph-min: 108px; }
  .ph-facts { grid-template-columns: 1fr 1fr; }
}

/* ---------------------------------------------------------------------------
   Border diet (2026-08-24)

   Owner's brief: fewer borders. Every panel on the homepage was a box with a
   hairline around it — intro, facts pills, cards, about, FAQ — and on a page
   that is already a grid of framed thumbnails the lines, not the games, were
   the first thing the eye landed on. What survives is the border that is doing
   real work: the search field (it is a form control), one faint rule between
   FAQ questions, and the paper skin's ink outline on its cards, which is that
   skin's whole idea.

   This block lives in base.css, which is enqueued *after* the skin
   (`w3ph-home` depends on `w3ph-skin`), so a `.ph`-scoped selector here beats
   the skin's own `.ph--<variant>` rule at equal specificity.
--------------------------------------------------------------------------- */
.ph .ph-intro,
.ph .ph-about,
.ph .ph-facts li {
  border: 0;
}

/* Cards keep their fill, their shadow and their hover lift; the outline goes.
   The paper skin is the exception — its 2px ink outline is the sticker. */
.ph:not(.ph--paper) .ph-card__art {
  border: 0;
}

/* One faint rule between questions instead of a full-width 1-2px border. */
.ph .ph-faq__item {
  border-top: 1px solid rgba(128, 128, 128, .16);
}

/* ---------------------------------------------------------------------------
   Spacing after the border diet (2026-08-24)

   A hairline was doing two jobs: drawing an edge and, by drawing it, making the
   gap between two panels read as a gap. With the lines gone the blocks ran into
   each other — a shelf's tiles ended 30px above the next shelf's heading, on a
   page where nothing else separates them any more. Whitespace has to take over
   the job the border was doing, so the rhythm is bigger everywhere and the
   panels carry more of their own padding.
--------------------------------------------------------------------------- */
.ph {
  --ph-gap: 20px;
}

.ph .ph-intro {
  padding: 32px 30px 30px;
}

/* The counter pills lost their outline; they need their own inner room or the
   number sits on the edge of its fill. */
.ph .ph-facts li {
  padding: 15px 18px;
}

/* One clear break between shelves, and room under the heading rule. */
.ph .ph-shelf {
  margin-top: 46px;
}

.ph .ph-shelf__head {
  margin-bottom: 18px;
}

.ph .ph-about {
  margin-top: 56px;
  padding: 32px 30px;
}

.ph .ph-about__block + .ph-about__block {
  margin-top: 26px;
}

/* A FAQ row is now separated by one faint line, so it needs the height to make
   that read as a list rather than as crowded text. */
.ph .ph-faq__item {
  padding: 15px 0;
}

.ph .ph-faq__item > div {
  padding-top: 10px;
}

/* The title under a tile was 6px from the artwork and 20px from the next row's
   artwork; even that reads as belonging to the row below on a dense grid. */
.ph .ph-card__label {
  margin-top: 2px;
}

@media (max-width: 640px) {
  .ph .ph-intro { padding: 24px 20px 22px; }
  .ph .ph-about { margin-top: 40px; padding: 24px 20px; }
  .ph .ph-shelf { margin-top: 34px; }
}

/* ---------------------------------------------------------------------------
   Reserved ad frames (2026-08-24)

   Measured CLS on the home page was 0.208, and 0.129 of it was one shift at
   1.6s: AdSense stamps `height: 280px` on the <ins>, comes back "unfilled", and
   the skin's `display:none` then yanked those 280px out from under the article.
   Every visitor got the page moving under them once, whether or not an ad ever
   showed.

   So the slot is furniture: it holds one fixed box from first paint, before the
   network answers and whether or not it answers, and nothing below it ever
   moves. `min-height` (not `height`) is what survives — AdSense writes its own
   inline `height` onto the element when it fills, and an inline style beats any
   author rule.

   The empty state is deliberately a labelled frame rather than blank space:
   per [[adsense-unfilled-eats-the-fold]] a reserved-but-blank band is the worst
   of both, but an owner-visible placeholder that never moves is the tradeoff
   asked for here.
--------------------------------------------------------------------------- */
.site-main > ins.adsbygoogle,
.w3ph-side-ad > ins.adsbygoogle {
  /* Beats the skins' `[data-ad-status="unfilled"] { display: none }`. */
  display: block !important;
  position: relative;
  width: 100%;
  min-height: 280px;
  margin: 22px 0;
  border-radius: 12px;
  background: var(--ph-surface-2, rgba(127, 127, 127, .08));
  box-shadow: inset 0 0 0 1px var(--ph-border, rgba(127, 127, 127, .18));
  overflow: hidden;
}

/* The label sits behind the creative: an ad that fills paints over it, an empty
   frame explains itself. It is a pseudo-element so it can never add height. */
.site-main > ins.adsbygoogle::before,
.w3ph-side-ad > ins.adsbygoogle::before {
  content: "Advertisement";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ph-muted, #8b8b8b);
  opacity: .5;
  pointer-events: none;
}

.site-main > ins.adsbygoogle[data-ad-status="filled"]::before,
.w3ph-side-ad > ins.adsbygoogle[data-ad-status="filled"]::before {
  content: none;
}

@media (max-width: 640px) {
  /* A responsive unit at phone width picks a SQUARE (390x390 measured at
     390px), so a 250px reservation was 140px short and the fill pushed the page
     down twice — mobile CLS 0.51-0.61 on two of the four sites. Reserving by
     aspect-ratio instead of by pixels makes the box exactly the size the unit
     is going to take, whatever the screen is. */
  .site-main > ins.adsbygoogle,
  .w3ph-side-ad > ins.adsbygoogle {
    min-height: 0;
    /* AdSense writes its chosen creative height inline (330px for one of the
       formats it picks at 390px wide) — that is 60px shorter than the square it
       reserved space for, so the box SHRANK on fill and everything below jumped
       up: 0.48 of mobile CLS. `height: auto !important` is the one thing that
       outranks its inline style, and aspect-ratio then keeps the frame the same
       square from first paint through fill. A shorter creative letterboxes
       inside it; nothing moves. */
    height: auto !important;
    aspect-ratio: 1 / 1;
    margin: 16px 0;
  }
}

/* ---------------------------------------------------------------------------
   Fixed ad frames (2026-08-24, replaces the reservation above)

   `w3ph-fixed-ads.php` now wraps every unit in `.w3ph-adbox` and gives the
   <ins> the frame's own measured box before pushing, so the reserved space and
   the unit are the same number by construction — no responsive unit deciding
   its size at answer time, nothing to shift. The two earlier
   `.site-main > ins.adsbygoogle` rules no longer match (the <ins> has a wrapper
   now) and are left in place only as documentation of what came before.

   `height` carries !important because AdSense writes a plain inline
   `height: 0px` onto the wrapper of an empty unit, and only an author
   !important outranks an inline declaration.
--------------------------------------------------------------------------- */
.w3ph-adbox {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 728px;
  max-width: 100%;
  height: 90px !important;
  margin: 22px auto;
  border-radius: 12px;
  overflow: hidden;
  background: var(--ph-surface-2, rgba(127, 127, 127, .08));
  box-shadow: inset 0 0 0 1px var(--ph-border, rgba(127, 127, 127, .18));
}

/* A pseudo-element props the box open from the inside: AdSense stamps
   `height: auto !important; min-height: 0 !important` on every ancestor of its
   <ins>, but it cannot reach ::before. */
.w3ph-adbox::before {
  content: "";
  flex: 0 0 0px;
  width: 0;
  height: 90px;
}

.w3ph-adbox__label {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ph-muted, #8b8b8b);
  opacity: .45;
  pointer-events: none;
}

.w3ph-adbox .adsbygoogle {
  display: inline-block !important;
  margin: 0 !important;
}

/* The rail unit is a half page and fits a 390px phone as it is. */
.w3ph-side-ad .w3ph-adbox,
.inside-right-sidebar .w3ph-adbox {
  width: 300px;
  height: 600px !important;
}

.w3ph-side-ad .w3ph-adbox::before,
.inside-right-sidebar .w3ph-adbox::before {
  height: 600px;
}

@media (max-width: 819px) {
  .w3ph-adbox {
    width: 320px;
    height: 100px !important;
    margin: 16px auto;
  }

  .w3ph-adbox::before {
    height: 100px;
  }
}
