/**
 * Gymnastics Plus - Blog+ post, app door
 * ---------------------------------------------------------------------------
 * Pushed-screen chrome + article typography for a Blog+ post on the APP DOOR
 * (inc/gp-gymnast-app-post.php + page-templates/single-post-app.php).
 * Tier-2/3 semantics only, no raw hex, no :root.
 *
 * The chrome numbers are the legal screen's (css/gp-gymnast-app-legal.css),
 * which took them from the Mind Lab lesson, so the back row reads as the same
 * control on every pushed screen. Surfaces are the frozen .gp-card.
 *
 * GUTTER: the pane owns the 16px, and the shell's padding is zeroed below so it
 * cannot become a second owner.
 *
 * That zeroing is NOT belt and braces, it is the fix for a measured bug. I first
 * checked the shipped legal screen, saw css/gp-mobile-portal.css was not loaded
 * there, .gp-content painting padding 0, the card at a clean 16, and concluded
 * this template could rely on the same. WRONG, and the error was assuming one
 * screen's cascade speaks for a template that serves SEVEN post types. MEASURED
 * on dev: a blog post loaded no portal CSS and sat at 16, while a Gym Watch
 * single DID load it and both cards sat at 32, a real double gutter, on the same
 * template, decided by post type. Whether that stylesheet loads is not this
 * screen's to know, so the screen stops depending on the answer: zero the shell,
 * own the 16 here, conform on every type (docs/MOBILE_DESIGN_SYSTEM.md s.12).
 *
 * UPDATE 2026-09-08: the split described above is FIXED AT THE SOURCE, not just
 * defended against here. gp_should_show_mobile_nav() now returns false for this
 * screen, and inc/gp-mobile-nav-loader.php gates the whole mobile CSS enqueue on
 * that function, so css/gp-mobile-portal.css and css/gp-mobile-single.css load on
 * NONE of the seven types now, Gym Watch included. The zeroing rule below is
 * therefore belt and braces today rather than the load-bearing fix it was, and it
 * STAYS: it costs nothing, and it is what keeps this screen honest if a future
 * change puts that stylesheet back. The paragraph above is kept as written
 * because the measurement it records is why the rule exists at all.
 */

.gp-gapost {
	/* HALF THE PICTURE SITS IN THE BLACK, which is the look the owner named on
	   2026-09-09: "half of it's in a hero section, and the other side is in the
	   normal section... it's how it's set up on the website as well."

	   A FLAT 56px DID NOT DO THAT. Measured at 430 it put only 26% of the card in
	   the hero, because the card's height grows with the viewport while 56 does
	   not. The website solves it with a formula, not a number:
	   min(240px, calc((100vw - 48px) * 0.28125)). The 0.28125 is half of 0.5625,
	   and 0.5625 is 9/16, so the expression IS "half the height of a 16:9 image as
	   wide as the column". Its 48 is the website's 24px gutters; this pane's are
	   20, so the same idea reads (100vw - 40px) here.

	   Checked against the website at 430: its overlap is 107.4px on a 215px image,
	   exactly 50%. Ours is 109.7 on 219.4. The cap keeps a tablet from swallowing
	   the headline. */
	--gp-gapost-overlap: min(240px, calc((100vw - 40px) * 0.28125));
	padding: 0 0 14px;
}

/* Zero the shell's gutter so the pane below is the ONE owner of the 16.
   !important because the rule being overridden is itself !important
   (css/gp-mobile-portal.css). Scoped to this screen's body class, so no other
   template is touched. SAFE because every child of .gp-gapost sits inside
   .gp-gapost__backrow or .gp-gapost__pane, both of which carry their own
   horizontal padding; nothing here is full-bleed by design. */
.gp-gapost-page .gp-content.gp-gapost {
	padding-left: 0 !important;
	padding-right: 0 !important;
}

/* ==========================================================================
   THE WEBSITE'S LAYOUT, ON THE APP DOOR (owner, 2026-09-09: "we already have a
   format and layout of how we would like the blog to look, check out our mobile
   web version for how the layout is supposed to be").

   Mirrored from single-templates/single-post.php, whose rules live INLINE in that
   template rather than in a shared stylesheet. There is nothing to consume, so
   these are the same numbers under this module's own names. Measured at 430 on
   the served website page before writing any of it: hero 371px tall, title 32px
   at 700 starting 24px from the left, topline as a three-up grid, featured image
   overlapping the hero's foot, author row 48px.

   WHAT CHANGED FROM THE APP'S PREVIOUS SHAPE: the title, tag and date moved OUT
   of a white card and into the black hero, where the website puts them; the body
   lost its card border and now runs on a white sheet the way the website's page
   background does; and an author row was added, which the app never had.

   THE WHITE SHEET IS A SURFACE, DELIBERATELY. The website prints body text on the
   page background, which on the app door would be the gray canvas and would break
   the owner's own gray-canvas rule. So the pane below the hero paints white: the
   member sees the website's white column, and the rule is satisfied because the
   text's nearest painted ancestor is a real surface, not the canvas.
   ========================================================================== */

.gp-gapost__masthead {
	background: var(--gp-color-fill-black);
	/* THE HERO RESERVES THE OVERLAP, which is the whole reason the website writes
	   its own padding-bottom as calc(overlap + 32px) rather than a flat number.
	   Without it the media card, pulled up by --gp-gapost-overlap, lands ON the
	   headline: measured at 430, a four-line title had its last line cut in half
	   behind the picture. The numbers did not show it (title height and hero
	   height were both 175, perfectly consistent) because the clipping happens in
	   the space BELOW the hero. It was found by looking at the screen. */
	padding: 0 24px calc(var(--gp-gapost-overlap) + 24px);
}

/* THE NAME MATTERS HERE, and getting it wrong cost the picture.
   `gp-gapost__hero` was ALREADY the featured IMAGE's class (line ~310) when I
   added a <header> with the same name on 2026-09-09. Both then matched every
   rule written for either, so the header's black background and its
   overlap padding landed on the IMG: that is the band of black under the
   picture and the 430px-wide image inside a 390px card, cropped by the card's
   own overflow. Measured before the rename: img box 430x255 in a 390 container.
   The newcomer renamed, not the incumbent. */

/* THE TOPLINE IS THE FROZEN PART, and this is how both of the owner's rulings
   hold at once. On 2026-09-08 he asked for the header to be frozen; on 2026-09-09
   he asked for the website's layout, whose hero is 371px tall and scrolls away.
   Freezing the whole hero would eat half a phone screen; letting it all scroll
   would take back the frozen header he asked for the day before.

   So the TOPLINE freezes and the big title scrolls under it: the way back is
   always one tap away, and the page still reads like the website. That is the
   collapsing-header pattern a phone reader already knows.

   It carries the safe-area inset itself rather than inheriting it from the hero,
   because once stuck it IS the top of the screen, and it paints its own black so
   it never sits transparent over the white sheet below. */
.gp-gapost__topline {
	position: sticky;
	top: 0;
	z-index: 2;
	background: var(--gp-color-fill-black);
	padding: calc(12px + env(safe-area-inset-top, 0px)) 24px 12px;
}

/* Three-up: back left, tag centred, date right. The website measured this at 390
   before shipping it (back 60 + tag 62 + date 93 against 342 of content width) and
   it is the reason the date is not on a row of its own. */
.gp-gapost__topline {
	display: grid;
	grid-template-columns: 1fr auto;   /* back, date. The pill's centre column is gone with it. */
	align-items: center;
	gap: 12px;
}

.gp-gapost__topline > .gp-gapost__back { justify-self: start; }
.gp-gapost__topline > .gp-gapost__date { justify-self: end; }

.gp-gapost__back {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-height: 44px;          /* the tap target the website's text link does not need */
	font-size: 14px;
	/* The website uses rgba(255,255,255,0.6) here. No Tier-2 token means "muted
	   white on ink", and I am not minting one for two elements, so the always-white
	   token is dimmed with opacity instead. That keeps the value inside the token
	   system rather than reintroducing a raw colour. */
	color: var(--gp-nav-ink);
	opacity: 0.62;
	text-decoration: none;
}

.gp-gapost__back:active { opacity: 1; }


.gp-gapost__date {
	font-size: 13px;
	color: var(--gp-nav-ink);
	opacity: 0.62;
	white-space: nowrap;
}

/* 32px, not the website's desktop 44: that is the size its own mobile rules
   resolve to at 430, measured on the served page rather than read off the file. */
.gp-gapost__title {
	/* FLUID, AND BALANCED, because "match the website's wrapping" turned out not to
	   be a reachable target and chasing it would have been chasing one headline.
	   MEASURED: the website's hero title falls back to the SYSTEM font while this
	   screen uses the brand font Poppins, which is wider, so the same 32px wraps to
	   four lines here against the website's three. No single size fixes that: 30px
	   gives three lines at 430 and still four at 375, and a different headline
	   would land somewhere else again.
	   What the eye actually objected to is the ORPHAN, a last line holding one
	   word. text-wrap:balance fixes that for ANY headline rather than this one:
	   measured at 430 the last line went from 193px of 382 to 381px of 382, and the
	   block from 175px tall to 131. The clamp keeps it off the small screen's
	   throat (27px at 375) without shrinking it on a large phone. */
	font-size: clamp(26px, 7.2vw, 32px);
	font-weight: 700;
	line-height: 1.15;
	letter-spacing: -0.02em;
	text-wrap: balance;
	color: var(--gp-nav-ink);
	margin: 0;
	padding-bottom: 24px;
}

/* THE LEGACY SIDEBAR HEADER, AND THE 60px IT CLAIMS.
   Deleted by my own block rewrite on 2026-09-09 and restored here after the
   rendered numbers showed the hero starting at y=60 again. It is the same defect
   as 2026-09-08, from the same cause, and the reason it came back is that I
   replaced a range of this file rather than editing inside it.

   page-templates/single-post-app.php includes inc/gp-sidebar-academy.php for the
   desktop rail, and that file also prints a LEGACY phone header
   (.gp-portal-mobile-header, line 105). Two files used to deal with it and both
   are gated on the mobile nav being ON: css/gp-mobile-portal.css:52 hides it,
   css/gp-portal-sidebar.css:489 reserves 60px for it on .gp-portal-main. This
   screen turns the mobile chain off, which removes the HIDE and keeps the
   RESERVE. */
.gp-gapost-page .gp-portal-mobile-header {
	display: none !important;
}

.gp-gapost-page .gp-portal-main {
	padding-top: 0 !important;
}

/* The [hidden] attribute always wins (module-wide guard). */
.gp-gapost [hidden] {
	display: none !important;
}

/* ==========================================================================
   SURFACES. The pane owns the 16px gutter; cards carry none.
   ========================================================================== */

.gp-gapost__pane {
	/* The white sheet. See the note above: the website's body sits on its page
	   background, and on this door that background is the gray canvas, which the
	   owner's gray-canvas rule forbids for text. Painting the sheet keeps the
	   website's look AND keeps every word on a real surface. */
	background: var(--gp-color-surface);
	padding: 0 20px calc(env(safe-area-inset-bottom, 0px) + 32px);

	/* flow-root IS THE OVERLAP. Without it the picture's negative top margin
	   COLLAPSES THROUGH this pane and moves the PANE itself up instead of moving
	   the picture within it, because the pane has no padding-top, no border and no
	   formatting context of its own. The white then starts at the picture's top
	   edge and paints over the masthead's lower padding, so the black stops dead
	   where the picture begins and there is no overlap to see.

	   The owner said "it's still not overlapping" three times and was right every
	   time. My element-box numbers said 50% and were measuring the wrong thing: the
	   card's top WAS inside the masthead's BOX, but the masthead's PAINT had been
	   covered. Reading the pixels settled it, at x=6 down the left gutter:
	     before  black ends at y=223, exactly the picture's top. No overlap.
	     after   black ends at y=332, 109px past it. The picture's top half sits in
	             black, which is what the website does (its black ends 107px below
	             its picture's top).
	   Measured, not inferred: paneTop went 223 -> 332 while cardTop stayed 223. */
	display: flow-root;
}

/* The featured media OVERLAPS the hero's foot, which is the website's signature
   for this page. Its own rule uses a viewport-derived overlap; 28% of the media's
   height is the same relationship expressed against the element that is actually
   moving, so it holds at any width without a second breakpoint. */
.gp-gapost__mediacard {
	/* ONE definition of the overlap, consumed by both the pull here and the
	   hero's reserve above. Two numbers that must agree are a defect waiting to
	   happen, and this pair already produced one. */
	margin-top: calc(-1 * var(--gp-gapost-overlap));
	position: relative;
	z-index: 1;
}

.gp-gapost__mediacard {
	overflow: hidden;
	border-radius: var(--gp-r-lg, 12px);
	padding: 0;
	background: var(--gp-color-surface);
}

/* No card chrome on the body any more: on the website the article is the page,
   not a panel inside it. The sheet above supplies the surface. */
.gp-gapost__card {
	background: none;
	border: 0;
	box-shadow: none;
	padding: 0;
	margin: 0;
}

/* Author row: avatar, name, reading time. The website places it UNDER the image
   on purpose, so the page reads "what this is, what it looks like, who says so". */
.gp-gapost__author {
	display: flex;
	align-items: center;
	gap: 14px;
	padding: 22px 0 18px;
}

.gp-gapost__avatar {
	width: 44px;
	height: 44px;
	border-radius: var(--gp-r-full, 999px);
	object-fit: cover;
	flex: none;
}

.gp-gapost__authorinfo { flex: 1; min-width: 0; }

.gp-gapost__authorname {
	font-size: 15px;
	font-weight: 600;
	color: var(--gp-color-text);
	margin: 0 0 2px;
}

.gp-gapost__reading {
	font-size: 13px;
	color: var(--gp-color-text-muted);
	margin: 0;
}

/* ==========================================================================
   MEDIA. 16:9 so the card never jumps when the poster loads.
   ========================================================================== */

.gp-gapost__media {
	position: relative;
	width: 100%;
	aspect-ratio: 16 / 9;
	background: var(--gp-color-surface-2);
	overflow: hidden;
}

/* A Presto embed brings its own player chrome and sizes itself. */
.gp-gapost__media--presto {
	aspect-ratio: auto;
	background: none;
}

.gp-gapost__poster,
.gp-gapost__hero,
.gp-gapost__video {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* A still image is content, not a crop: let it keep its own shape. */
.gp-gapost__media:has(> .gp-gapost__hero) {
	aspect-ratio: auto;
}

.gp-gapost__hero {
	height: auto;
	object-fit: contain;
}

.gp-gapost__posterfallback {
	position: absolute;
	inset: 0;
	background: var(--gp-color-surface-2);
}

.gp-gapost__media iframe {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
}

.gp-gapost__play {
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	width: 56px;
	height: 56px;
	border: 0;
	border-radius: var(--gp-r-full, 999px);
	background: var(--gp-color-fill-ink, var(--gp-color-text));
	color: var(--gp-color-bg);
	display: grid;
	place-items: center;
	cursor: pointer;
	transition: transform var(--gp-motion-duration-fast, 160ms) var(--gp-motion-ease-out);
}

.gp-gapost__play svg {
	width: 26px;
	height: 26px;
	margin-left: 3px; /* optical centring of a triangle in a circle */
}

.gp-gapost__play:active {
	transform: translate(-50%, -50%) scale(0.94);
}

.gp-gapost__player.is-playing .gp-gapost__play {
	display: none;
}

/* ==========================================================================
   HEADER + BODY
   ========================================================================== */

.gp-gapost__head {
	padding: 18px 18px 0;
}

/* The type pill's rule is deleted with the pill itself (owner, 2026-09-09:
   "remove the pill and just have the post with the date"). Deleted rather than
   left orphaned: a rule for markup that no longer exists is the thing the next
   reader trusts and the next duplicate-selector bug hides behind. */


/* The card title and its meta line are GONE, not restyled: the website puts both
   in the hero and this screen now follows it. Leaving the old rules here is what
   made the hero headline render at 22px instead of 32 on the first run, because
   they sat LATER in the file and won on order. Same cascade mistake as the black
   bar's first attempt; deleting beats overriding. */

.gp-gapost__body {
	padding: 14px 18px 24px;
	color: var(--gp-color-text);
	font-size: var(--gp-t-sub);
	line-height: 1.6;
	overflow-wrap: anywhere;
}

.gp-gapost__body > *:first-child {
	margin-top: 0;
}

.gp-gapost__body > *:last-child {
	margin-bottom: 0;
}

.gp-gapost__body h1,
.gp-gapost__body h2,
.gp-gapost__body h3,
.gp-gapost__body h4 {
	margin: 22px 0 8px;
	font-weight: 700;
	line-height: 1.3;
	color: var(--gp-color-text);
}

.gp-gapost__body h1,
.gp-gapost__body h2 {
	font-size: var(--gp-t-h3, 18px);
}

.gp-gapost__body h3,
.gp-gapost__body h4 {
	font-size: var(--gp-t-sub);
}

.gp-gapost__body p {
	margin: 0 0 14px;
}

.gp-gapost__body ul,
.gp-gapost__body ol {
	margin: 0 0 14px;
	padding-left: 22px;
}

.gp-gapost__body li {
	margin: 0 0 6px;
}

.gp-gapost__body a {
	color: var(--gp-color-link);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.gp-gapost__body strong {
	font-weight: 700;
}

.gp-gapost__body blockquote {
	margin: 0 0 14px;
	padding: 2px 0 2px 14px;
	border-left: 3px solid var(--gp-color-border-strong);
	color: var(--gp-color-text-muted);
}

/* Media inside the body must never push the card wide. */
.gp-gapost__body img,
.gp-gapost__body iframe,
.gp-gapost__body video {
	max-width: 100%;
	height: auto;
	border-radius: var(--gp-r-md, 12px);
}

/* Wide content scrolls inside itself rather than scrolling the screen. */
.gp-gapost__body table,
.gp-gapost__body pre {
	display: block;
	max-width: 100%;
	overflow-x: auto;
}

.gp-gapost__empty {
	margin: 0;
	color: var(--gp-color-text-muted);
}

/* Reduced motion: no press scales. */
@media (prefers-reduced-motion: reduce) {
	.gp-gapost__play,
	.gp-gapost__back {
		transition: none;
	}
	.gp-gapost__play:active {
		transform: translate(-50%, -50%);
	}
}

/* ==========================================================================
   BELOW THE ARTICLE (owner, 2026-09-09): the guest, the audio, and more of the
   same thing. Mirrors what the website's podcast template puts under its hero.
   ========================================================================== */

/* A guest's picture is larger than the author avatar it replaces, because it is a
   photograph of the person the episode is about rather than a byline chip. It
   stays a CIRCLE (owner, 2026-09-09: "the guest should be in a circle, not a
   square"): the base .gp-gapost__avatar is already round, and my first version
   overrode that radius to a soft square, which is the whole of what he saw. The
   override is gone rather than restated, so the shape now comes from one place
   and a future change to the avatar carries the guest with it. */
.gp-gapost__author--guest .gp-gapost__avatar {
	width: 56px;
	height: 56px;
}

.gp-gapost__sectiontitle {
	margin: 0 0 12px;
	font-size: var(--gp-t-sub);
	font-weight: 700;
	color: var(--gp-color-text);
}

/* Buzzsprout injects its own player into the container, so this section owns the
   spacing around it and nothing else. No fixed height: the player sizes itself
   and pinning it would clip the episode title on a narrow phone. */
.gp-gapost__listen {
	padding: 20px 0 4px;
	border-top: 1px solid var(--gp-color-border);
	margin-top: 20px;
}

.gp-gapost__more {
	padding: 20px 0 8px;
	border-top: 1px solid var(--gp-color-border);
	margin-top: 20px;
}

.gp-gapost__morelist {
	list-style: none;
	margin: 0;
	padding: 0;
}

.gp-gapost__moreitem + .gp-gapost__moreitem {
	margin-top: 4px;
}

/* A row, not a card grid: three 16:9 cards stacked on a phone would push the
   next post two screens down, and this sits at the FOOT of a long article where
   the reader is deciding whether to carry on, not browsing. */
.gp-gapost__morelink {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 8px 0;
	text-decoration: none;
	color: inherit;
}

.gp-gapost__morethumb {
	width: 84px;
	height: 56px;
	flex: none;
	object-fit: cover;
	border-radius: var(--gp-r-sm, 12px);
	background: var(--gp-color-surface-2);
}

.gp-gapost__morethumb--empty {
	display: block;
}

.gp-gapost__moretext {
	min-width: 0;
}

.gp-gapost__moretitle {
	display: block;
	font-weight: 600;
	color: var(--gp-color-text);
	line-height: 1.3;
	/* Two lines, then ellipsis: a long episode title must not make one row taller
	   than the three rows together. */
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.gp-gapost__moredate {
	display: block;
	margin-top: 2px;
	font-size: var(--gp-t-cap);
	color: var(--gp-color-text-muted);
}
