/*
Theme Name:   Hello Elementor Child — SEMC
Theme URI:    https://superevilmegacorp.com
Description:  Child theme of Hello Elementor for superevilmegacorp.com. Carries the SEMC-owned Adobe Fonts kit, registers Proxima Nova with Elementor, enables excerpts on Pages so the four game pages can hold their genre label, applies the agreed security hardening, and emits VideoGame JSON-LD via Rank Math. See CLAUDE.md sections 4, 5 and 12.
Author:       Super Evil Megacorp
Author URI:   https://superevilmegacorp.com
Template:     hello-elementor
Version:      1.42.0
License:      GNU General Public License v2 or later
License URI:  https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:  hello-elementor-child
*/

/* --------------------------------------------------------------------------
 * SEMC design tokens.
 *
 * Exact values extracted from the live Gatsby stylesheet. These are the whole
 * palette — five colours, one typeface. Mirrored here as custom properties so
 * hand-written CSS can reference them, but Elementor's Global Colors remain the
 * source of truth for anything built in the editor. Keep the two in sync.
 * -------------------------------------------------------------------------- */

:root {
	--se-black:        #000000; /* page background */
	--se-almost-black: #444444; /* secondary text, gradient midpoints */
	--se-grey:         #A6A6A6; /* body text on dark, timeline rails */
	--se-white:        #FFFFFF; /* headings */
	--se-highlight:    #FFE549; /* links, hover, CTAs, accents */
}

/* --------------------------------------------------------------------------
 * Fixed header, and why there is no content offset here.
 *
 * The header container is position: fixed, so it is out of document flow and
 * <header> collapses to 0px. Every page therefore begins at the top of the
 * viewport, underneath the header.
 *
 * That is the design, not a bug. Measured across all 77 mirrored pages, 71 lead
 * with a full-bleed hero band that deliberately runs under the header. The other
 * six — the legal pages, the handbook, and one news article — carry the same band
 * at the same 50vh / 60vh height with no background image, just black with the
 * page title inside it. The band is the clearance.
 *
 * An earlier version of this file added padding-top of --semc-header-height to
 * every non-home page. It fixed nothing and broke 71 pages, because it pushed the
 * hero clear of the header and left a strip of page background above it. Removed
 * 11 August 2026.
 *
 * If a page is ever built WITHOUT a leading full-bleed band, give that page's
 * first container top padding in Elementor. That is a per-page layout decision and
 * belongs in the page, not in a theme-wide rule that has to guess which pages want
 * it.
 *
 * --semc-header-height is still published by functions.php and still used below.
 * -------------------------------------------------------------------------- */

/* Anchor links must not land underneath the fixed header. */
:target {
	scroll-margin-top: var(--semc-header-height, 124px);
}

/* --------------------------------------------------------------------------
 * White surfaces.
 *
 * This build treats #000000 as the page default and paints white where a section
 * needs it. The live Gatsby site does the opposite — a site-wide
 * `bg-se-white text-se-black` wrapper with dark sections painted over it — but the
 * home page and the 59 articles were built black-first, and the rendered result is
 * identical either way. Decided 12 August 2026; noted because reading the live
 * markup will suggest the inverse and it is not worth restructuring to match.
 *
 * The consequence is that any container painted white has invisible headings,
 * because Theme Style sets h1–h6 to the secondary swatch, #FFFFFF. That is correct
 * everywhere else on the site and cannot be expressed conditionally in Theme
 * Style, so white surfaces need the colours put back.
 *
 * Add `semc-on-white` to the container's CSS Classes when you paint one white.
 * The Post Content widget gets it automatically, since the article body is always
 * on white and the widget has no CSS Classes field of its own worth using.
 * -------------------------------------------------------------------------- */

.semc-on-white,
.semc-on-white p,
.semc-on-white li,
.elementor-widget-theme-post-content {
	color: var(--se-black);
}

.semc-on-white :is( h1, h2, h3, h4, h5, h6 ),
.elementor-widget-theme-post-content :is( h1, h2, h3, h4, h5, h6 ) {
	color: var(--se-black);
}

/* Highlight yellow fails contrast on white. Links on a white surface take the
 * body colour with an underline instead, and only revert to yellow on hover where
 * the momentary low contrast is not carrying the information. */
.semc-on-white a:not(.elementor-button) {
	color: var(--se-black);
	text-decoration: underline;
}

/* --------------------------------------------------------------------------
 * Article body, inside the Post Content widget.
 *
 * Paragraph spacing. The live site sets 32px between paragraphs; Hello
 * Elementor's default is 14.4px, which bunches the copy in a way that reads as
 * broken next to the original. 161 text-editor widgets across 59 posts, so this
 * cannot be a per-widget setting.
 * -------------------------------------------------------------------------- */

.elementor-widget-theme-post-content p {
	margin-bottom: 2rem;
}

.elementor-widget-theme-post-content ul,
.elementor-widget-theme-post-content ol {
	margin-bottom: 2rem;
}

/*
 * The last block inside a widget drops its trailing margin, because the container
 * gap already provides that space.
 *
 * Elementor containers are flex, and flex does not collapse margins. Without this
 * the 32px paragraph margin and the 32px container gap add to 64px at every widget
 * boundary, which is what made the space around the horizontal rules read as a
 * mistake. One 32px rhythm now governs both paragraph-to-paragraph inside a widget
 * and widget-to-widget across the article.
 */
.elementor-widget-theme-post-content > .elementor-widget-container > :last-child,
.elementor-widget-theme-post-content .elementor-widget-text-editor :last-child {
	margin-bottom: 0;
}

/* --------------------------------------------------------------------------
 * Links inside a news article.
 *
 * Article bodies render inside the Post Content widget on the Single Post
 * template, in a container painted Secondary — white. The site-wide link colour
 * is white, because everything else on this site sits on black. So a link in an
 * article body was white on white: not merely off-brand, invisible.
 *
 * The block above already puts the black back on text and headings inside
 * .elementor-widget-theme-post-content, but its link rule is scoped to
 * .semc-on-white, and the template's container does not carry that class. Links
 * fell through the gap. This closes it.
 *
 * #0000EE is the browser default link colour and the same one semc-legal uses,
 * so articles and legal pages agree. 8.6:1 on white, comfortably AA. Underlined
 * because colour alone must not be the only signal.
 *
 * This is deliberately a theme rule rather than a per-post setting. There are 61
 * posts; the alternative was an inline style on every individual link, which is
 * how the first two were fixed by hand and which already missed one.
 * -------------------------------------------------------------------------- */

.elementor-widget-theme-post-content a:not( .elementor-button ) {
	color: #0000EE;
	text-decoration: underline;
}

.elementor-widget-theme-post-content a:not( .elementor-button ):hover,
.elementor-widget-theme-post-content a:not( .elementor-button ):focus {
	color: #0000EE;
	text-decoration: underline;
}

/* --------------------------------------------------------------------------
 * Legal pages: Privacy Policy, Terms of Use, Cookie Notice, Applicant Notice.
 *
 * These four are long-form documents rather than marketing pages, and they get
 * conventional document styling: white page, black text, blue underlined links.
 *
 * They deliberately do NOT use `semc-on-white`. That class sets links to the
 * body colour with an underline, which is right on the marketing pages where a
 * blue link would fight the brand — but wrong here. In a privacy policy the
 * reader is scanning for the thing they can click, and the single most
 * recognisable signal for that is a blue underlined link. Two different link
 * treatments on one site is a deliberate split, not an inconsistency.
 *
 * Applied by adding the class semc-legal to the body container. The white
 * background is painted on the container itself, not here, so the hero above it
 * stays black.
 * -------------------------------------------------------------------------- */

.semc-legal,
.semc-legal p,
.semc-legal li,
.semc-legal td,
.semc-legal th,
.semc-legal :is( h1, h2, h3, h4, h5, h6 ) {
	color: var(--se-black);
}

/* #0000EE is the browser default link colour — literally the standard. 8.6:1 on
 * white, so it clears AA comfortably. Underlined because colour alone must not
 * be the only signal that something is a link. */
.semc-legal a:not( .elementor-button ) {
	color: #0000EE;
	text-decoration: underline;
}

/* No hover effect. Pinned explicitly rather than left unset, because Elementor's
 * Theme Style defines a link hover colour globally and would otherwise apply it. */
.semc-legal a:not( .elementor-button ):hover,
.semc-legal a:not( .elementor-button ):focus {
	color: #0000EE;
	text-decoration: underline;
}

/* Tables. Three of them in the Privacy Policy, carrying the CCPA and EU
 * disclosures. */
.semc-legal table {
	border-collapse: collapse;
	width: 100%;
}

.semc-legal table :is( th, td ) {
	border: 1px solid var(--se-grey);
	padding: 12px;
	text-align: left;
	vertical-align: top;
}

/* The header row, not "the first tr in the document order of the table".
 * These tables carry <thead> and <tbody>, so a bare tr:first-child matches the
 * thead row AND the first tbody row, shading two rows instead of one. */
.semc-legal table thead tr,
.semc-legal table:not( :has( thead ) ) tr:first-child {
	background-color: #E5E7EB;
}

.semc-legal table thead :is( th, td ),
.semc-legal table:not( :has( thead ) ) tr:first-child :is( th, td ) {
	font-weight: 700;
}

/* A 4-column disclosure table does not fit a 390px screen. Scroll the table
 * rather than the page: a horizontally scrolling document is a defect, and the
 * page-weight verification asserts there is no horizontal overflow. */
.semc-legal .elementor-widget-text-editor:has( table ) {
	overflow-x: auto;
}

/* --------------------------------------------------------------------------
 * Fixed-width carousel slides.
 *
 * Elementor's Loop Carousel only offers "Slides to display" as a whole number,
 * which makes every slide a fraction of the container: three-up on a 1440 screen
 * gives 448px slides, and on a 2000px screen 666px. The live timeline instead uses
 * a fixed 400px slide and shows however many fit, so the card is the same size on
 * every monitor and the number visible changes instead.
 *
 * This is the CSS half. The JS half, in functions.php, flips Swiper to
 * slidesPerView: 'auto' so it measures these widths rather than dividing the
 * container. Both are needed — CSS alone changes the paint but not Swiper's snap
 * grid, and the carousel would scroll to the wrong offsets.
 *
 * Applied by adding the class semc-fixed-slides to the widget.
 * -------------------------------------------------------------------------- */

.semc-fixed-slides .swiper-slide {
	width: var( --semc-slide-width, 400px );
}

@media (max-width: 767px) {
	.semc-fixed-slides .swiper-slide {
		width: var( --semc-slide-width-mobile, 300px );
	}
}

/*
 * Both widths are variables so they can be set per widget from the panel, without
 * touching this file. In the carousel's Advanced > Custom CSS:
 *
 *     selector { --semc-slide-width: 400px; --semc-slide-width-mobile: 300px; }
 */

/* --------------------------------------------------------------------------
 * Fixed-height cards.
 *
 * Min Height is a floor, not a height. The timeline cards carry excerpts of
 * different lengths, so with Min Height 600px the cards render 615-644px and each
 * one puts its rail segment at a different y — the continuous line steps at every
 * card with a longer excerpt.
 *
 * Containers have no Height control at all, only Min Height, so this cannot be
 * done from the panel. The live site sets a hard `h-[600px]`, which is what this
 * reproduces.
 *
 * Deliberately no overflow: hidden. Live lets a long card spill rather than
 * truncating mid-sentence, and clipping would hide the last line of the two or
 * three longest entries with no visible sign that anything was cut.
 *
 * Applied by adding the class semc-fixed-height to the card container.
 * -------------------------------------------------------------------------- */

.semc-fixed-height {
	height: var( --semc-card-height, 600px );
}

@media (max-width: 767px) {
	.semc-fixed-height {
		height: var( --semc-card-height-mobile, 500px );
	}
}

/* --------------------------------------------------------------------------
 * Card titles that line up across a grid row.
 *
 * The Press page media-kit grid puts four cards side by side. Elementor's grid
 * stretches them to equal height, but that only equalises the card BOXES — the
 * contents inside each one are laid out independently, so a one-line title and a
 * three-line title push everything below them to different places.
 *
 * The first version of the Press build reference tried to solve this with
 * Justify Content: Space Between on the card and on its inner container. That is
 * actively wrong and it is what produced the ragged titles. Space Between sends
 * all the leftover vertical space to the MIDDLE of the stack, and the middle is
 * exactly where the title sits — so the more the action block below it varies in
 * height, the further the title drifts. The four cards carry one button, two
 * buttons plus three icons, one button plus four, and two buttons plus five, so
 * they drift by different amounts and no two titles land at the same y.
 *
 * The layout fix is Justify Content: Start on both containers, which is a panel
 * change and needs nothing from this file. That alone aligns every title, because
 * each one then sits immediately under a fixed-height image.
 *
 * This class handles the second-order problem: with Start, the titles all BEGIN
 * at the same y but end at different ones, so the buttons underneath still step.
 * Giving the title a floor of N line-boxes makes the block below it start at the
 * same y in every card.
 *
 * Line-height is 22.5px, from the global H3 of 25px at the site-wide 0.9 ratio.
 * Three lines is the default because the longest title on the page — Teenage
 * Mutant Ninja Turtles: Splintered Fate — wraps to three at the measured 248px
 * card width. Override per grid from the panel when a title needs more:
 *
 *     selector { --semc-card-title-lines: 4; }
 *
 * min-height rather than height, so a title that unexpectedly wraps further
 * grows instead of overlapping what follows it.
 *
 * Applied by adding the class semc-card-title to the Heading widget.
 * -------------------------------------------------------------------------- */

.semc-card-title .elementor-heading-title {
	min-height: calc( var( --semc-card-title-lines, 3 ) * 22.5px );
}

/* Below 768 the grid collapses to a single column, so nothing is being aligned
 * against anything and the floor is just dead space under every short title. */
@media (max-width: 767px) {
	.semc-card-title .elementor-heading-title {
		min-height: 0;
	}
}

/* --------------------------------------------------------------------------
 * Scroll reveal.
 *
 * Live runs a GSAP tween on the alternating feature sections of the About page.
 * Measured off the live DOM 17 August 2026 — the start states are exact, and it
 * is PER ELEMENT rather than per container:
 *
 *     image column        opacity 0, translateX -30px   (image on the left)
 *                         opacity 0, translateX  30px   (image on the right)
 *     h2, body, button    opacity 0, translateY  30px
 *     the 300px divider   not animated at all
 *
 * It plays once and stays. Scrolling back up does not run it backwards; the
 * effect only repeats after a reload.
 *
 * That last part is why this is an IntersectionObserver plus a transition rather
 * than the scroll-driven CSS animation this file carried first.
 * `animation-timeline: view()` is positional by definition — the animation IS the
 * scroll offset — so it always reverses on the way back up, and there is no way
 * to latch it at the end in CSS alone. Live gets the same result with GSAP scrub
 * plus once, which kills the tween on completion.
 *
 * The trade is that this is a timed transition rather than genuinely scrubbed to
 * scroll position. Two things bought with it: correct one-shot behaviour, and
 * Firefox, which has no scroll-driven animation support and would otherwise have
 * shown no effect at all.
 *
 * The from-state is scoped under .semc-reveal-ready, which the script adds to
 * <html> only once it has decided to run. Without that guard anyone with JS off
 * would get permanently invisible copy — the failure mode here has to be "no
 * animation", never "no content".
 *
 * Off below 768 and under prefers-reduced-motion, both decided in the script so
 * the from-state is never applied at all.
 *
 * Apply by adding one class to the widget or container that should move:
 *
 *     semc-reveal-left    image sitting on the left
 *     semc-reveal-right   image sitting on the right
 *     semc-reveal-up      headings, body copy, buttons
 *
 * Distance and timing are variables, so a page can nudge itself from its own
 * Custom CSS without a theme deploy:
 *
 *     selector { --semc-reveal-distance: 40px; --semc-reveal-duration: .8s; }
 * -------------------------------------------------------------------------- */

.semc-reveal-ready :is( .semc-reveal-left, .semc-reveal-right, .semc-reveal-up ) {
	opacity: 0;
	transition:
		opacity   var( --semc-reveal-duration, .6s ) ease-out,
		transform var( --semc-reveal-duration, .6s ) ease-out;
}

.semc-reveal-ready .semc-reveal-left {
	transform: translateX( calc( -1 * var( --semc-reveal-distance, 30px ) ) );
}

.semc-reveal-ready .semc-reveal-right {
	transform: translateX( var( --semc-reveal-distance, 30px ) );
}

.semc-reveal-ready .semc-reveal-up {
	transform: translateY( var( --semc-reveal-distance, 30px ) );
}

/* Set by the observer, once, and never removed. */
.semc-reveal-ready :is( .semc-reveal-left, .semc-reveal-right, .semc-reveal-up ).semc-revealed {
	opacity: 1;
	transform: none;
}

/* --------------------------------------------------------------------------
 * Games index: hover zoom on each band.
 *
 * Reproduces live, where the rule is:
 *
 *     .se-background-image-wrapper { overflow: hidden; position: relative }
 *     .se-background-image         { position: absolute; inset: 0; transition: all .5s }
 *     .se-background-image-wrapper:hover .se-background-image { transform: scale(1.1) }
 *
 * On live the wrapper IS the band section, and the photo is an absolutely
 * positioned child filling it. That separation is the whole trick and it has to
 * be copied: our first build put the photo on the band container itself as an
 * Elementor background, and a transform on that element would drag the card and
 * its yellow shadow along with it. You cannot scale an element's background
 * without scaling the element.
 *
 * background-size is not a way out either. Elementor sets `cover`, which is a
 * keyword and does not interpolate, so a transition from `cover` to a percentage
 * jumps rather than animates.
 *
 * So the band gets an extra empty child that carries the photo and nothing else,
 * and only that child scales. Everything else in the band is lifted above it.
 *
 * Zoom level is a variable, tunable per page without a theme deploy:
 *
 *     selector { --semc-band-zoom: 1.06; }
 * -------------------------------------------------------------------------- */

.semc-band {
	position: relative;
	overflow: hidden;
}

.semc-band > .semc-band-image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	transition: transform var( --semc-band-zoom-duration, .5s ) ease;
}

/* The card, its shadow wrapper and anything else sit above the photo. */
.semc-band > :not( .semc-band-image ) {
	position: relative;
	z-index: 1;
}

/* Guarded on a real pointer. Live has no guard, but a :hover triggered by a tap
 * sticks on touch devices, which would leave one band zoomed in until you tapped
 * something else. */
@media (hover: hover) {
	.semc-band:hover > .semc-band-image {
		transform: scale( var( --semc-band-zoom, 1.1 ) );
	}
}

/* --------------------------------------------------------------------------
 * Hover zoom, pseudo-element variant.
 *
 * Same effect as semc-band above and the same live rule behind it, but for the
 * two places where the extra child element is not an option:
 *
 *   the home page news section — a BOXED container, so Elementor puts every
 *   child inside .e-con-inner and a photo child would be trapped in the centred
 *   1280 column instead of filling the band
 *
 *   the game carousel card in Loop Item template 507 — its background is a
 *   DYNAMIC tag resolving to each game's featured image, so there is no fixed
 *   image to copy onto a child
 *
 * `background: inherit` solves both: the pseudo picks up whatever the element
 * actually resolved to, dynamic tags included, and only the pseudo scales.
 *
 * ::after rather than ::before, because Elementor already uses ::before on
 * containers for its Background Overlay, and the carousel card has a gradient
 * overlay set.
 *
 * The pseudo sits at z-index 0 and the children are lifted to 1, rather than the
 * more obvious z-index -1 on the pseudo alone. A negative z-index paints behind
 * the ORIGINATING ELEMENT'S OWN BACKGROUND whenever that element establishes a
 * stacking context, which makes the pseudo invisible and the hover silently do
 * nothing. That is exactly what happened on the home page news band, while the
 * carousel card — no stacking context — worked. Lifting the children instead
 * behaves the same either way.
 *
 * The element keeps painting its own background underneath at rest, which is
 * harmless: at scale 1 the two coincide exactly, and at any larger scale the
 * pseudo covers it completely.
 *
 *     selector { --semc-zoom: 1.06; --semc-zoom-duration: .35s; }
 * -------------------------------------------------------------------------- */

.semc-zoom-bg {
	position: relative;
	overflow: hidden;
}

.semc-zoom-bg::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 0;
	background: inherit;
	transition: transform var( --semc-zoom-duration, .5s ) ease;
}

.semc-zoom-bg > * {
	position: relative;
	z-index: 1;
}

@media (hover: hover) {
	.semc-zoom-bg:hover::after {
		transform: scale( var( --semc-zoom, 1.1 ) );
	}
}

/* --------------------------------------------------------------------------
 * Pop reveal.
 *
 * The three cards in the home page's BE SUPER EVIL section, which zoom in from
 * almost nothing, fast. The heading and body copy above them use plain
 * semc-reveal-up instead — they slide, they do not pop.
 *
 * Rides the same IntersectionObserver as the other reveals, so it is one-shot
 * and holds, and it inherits the same .semc-reveal-ready guard: nothing is
 * hidden until the script has decided to run.
 *
 * The overshoot in the timing function is what reads as a pop rather than a
 * fade — it passes 1 and settles back. Drop it to ease-out for a plain zoom.
 *
 * The three fire left to right rather than together, matching live. The delay
 * comes off nth-child on the popping elements themselves, so it needs no marker
 * on the row and stays right if a card is added or reordered.
 *
 *     selector { --semc-pop-from: .4; --semc-pop-duration: .55s; --semc-pop-stagger: .18s; }
 * -------------------------------------------------------------------------- */

.semc-reveal-ready .semc-reveal-pop {
	opacity: 0;
	transform: scale( var( --semc-pop-from, .6 ) );
	transition:
		opacity   var( --semc-pop-duration, .45s ) ease-out,
		transform var( --semc-pop-duration, .45s ) cubic-bezier( .34, 1.4, .64, 1 );
}

.semc-reveal-ready .semc-reveal-pop:nth-child( 2 ) {
	transition-delay: var( --semc-pop-stagger, .14s );
}

.semc-reveal-ready .semc-reveal-pop:nth-child( 3 ) {
	transition-delay: calc( var( --semc-pop-stagger, .14s ) * 2 );
}

.semc-reveal-ready .semc-reveal-pop:nth-child( 4 ) {
	transition-delay: calc( var( --semc-pop-stagger, .14s ) * 3 );
}

.semc-reveal-ready .semc-reveal-pop.semc-revealed {
	opacity: 1;
	transform: none;
}

/* --------------------------------------------------------------------------
 * Deliberately almost empty.
 *
 * The non-obvious CSS in CLAUDE.md section 14 — the clip-path angled news card,
 * the drop-shadow yellow edge, the carousel centre-slide scale — must NOT live
 * here. Those snippets use the literal keyword `selector`, which Elementor
 * substitutes for the widget's generated class at render time. It has no meaning
 * in a theme stylesheet and would silently do nothing.
 *
 * They belong in Elementor's per-widget Advanced > Custom CSS panel, which is
 * why the licence has to be Advanced Solo or above.
 *
 * Two standing rules worth repeating where someone editing this file will see
 * them:
 *   - Do not use `background-attachment: fixed`. It fails on iOS Safari and is
 *     one of the measured defects being removed.
 *   - #FFE549 on #FFFFFF fails contrast. Highlight yellow is for use on black.
 * -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
 * Consent banner: show the opt-out control on the US banner.
 *
 * Complianz builds two banners and picks one by geo-IP. The EU/UK one (optin)
 * offers Accept / Deny / View preferences. The US one (optout) offers only
 * Accept, because under an opt-out regime nothing is being consented to — its
 * `optout.css` hides .cmplz-deny, .cmplz-view-preferences and the categories
 * block outright.
 *
 * The banner's own setting says otherwise: `dismiss` is {text: "Deny", show: 1}.
 * Complianz has no option to honour that in optout mode, so this re-shows it.
 *
 * This is not cosmetic. The click handler is unconditional —
 *
 *     cmplz_add_event( 'click', '.cmplz-deny', function ( e ) {
 *         cmplz_set_banner_status( 'dismissed' );
 *         cmplz_deny_all();
 *     } );
 *
 * — so the button genuinely revokes consent and blocks scripts from that point
 * on. What it changes is that a US visitor's opt-out right is offered up front
 * instead of buried behind the Manage Consent tab. The consent model is
 * unchanged: scripts still run on first load, which is what opt-out means.
 *
 * Regions are eu/uk/us with other_region_behaviour = us, so Canada, LatAm and
 * everywhere else get this banner too.
 *
 * Deliberately NOT re-showing .cmplz-view-preferences: in optout mode every
 * category is already enabled, so opening it shows a set of on switches that
 * look like choices the visitor made. One unambiguous Deny is clearer.
 * -------------------------------------------------------------------------- */
.cmplz-cookiebanner.optout .cmplz-buttons .cmplz-btn.cmplz-deny {
	display: block;
}

/* --------------------------------------------------------------------------
 * Scroll reveal: stop the from-state creating a horizontal scrollbar.
 *
 * .semc-reveal-right starts an element at translateX(+30px). Every element it
 * is applied to on the game pages is an `e-con-full` container — full viewport
 * width — so shifting it right pushes 30px past the right edge and the viewport
 * grows a horizontal scrollbar.
 *
 * QA saw exactly the signature that implies: a small horizontal scroll from
 * page load, a black band to the right of the hero and any right-aligned image,
 * the scroll disappearing about 90% of the way down, and never returning when
 * scrolling back up. That last part is the observer setting .semc-revealed
 * "once, and never removed" — by the bottom of the page every element has
 * snapped to transform:none and the overflow is gone for good.
 *
 * `clip` rather than `hidden`: hidden would make body a scroll container and
 * break position:sticky. Nothing on the site is sticky today, but clip costs
 * nothing and removes the trap for whoever adds a sticky header later.
 *
 * Scoped to .semc-reveal-ready, which the script only puts on <html> when it
 * has decided to run — so this is inert below 768px and under
 * prefers-reduced-motion, where the from-state is never applied anyway.
 * -------------------------------------------------------------------------- */
.semc-reveal-ready body {
	overflow-x: clip;
}

/* --------------------------------------------------------------------------
 * Headings may break a word rather than run off the page.
 *
 * Measured 26 August 2026: 16 of the 59 news articles overflowed their <h1> at
 * 375px, worst +119px on the GamingOnPhone finalists post. Mobile h1 is 50px
 * Proxima 900 uppercase in a 343px column, which fits about ten characters — so
 * any single word longer than that runs off the right edge with nowhere to
 * wrap. GAMINGONPHONE, CHROMEBOOK, PARTNERSHIP, GAMESCOM. Nothing overflows at
 * 768, 1024 or 1440, so it is a small-screen problem only.
 *
 * `overflow-wrap: break-word` and NOT `hyphens: auto`. Both were tested against
 * all 16. break-word fixed every one; hyphens changed nothing — but that null
 * result is untrustworthy, because this headless Chrome has no hyphenation
 * dictionary at all (a control with plain lowercase words wrapped identically
 * with and without it). What decides it is that hyphenation could not fix the
 * worst cases even with a dictionary: GAMINGONPHONE, GAMESCOM and TMNT are not
 * words, and they are the large overflows. Adding `hyphens: auto` alongside is
 * harmless and would soften the break on real words, but its effect is
 * invisible here — so it would be shipping unseen behaviour into 50px display
 * type. That is a design call with eyes on it, not a bug fix.
 *
 * `break-word` and NOT `anywhere`: anywhere also feeds into min-content
 * intrinsic sizing, so it can let a flex or grid track collapse narrower than
 * intended. break-word only acts at the point the word would otherwise
 * overflow, and is inert whenever the text already fits — which is why this is
 * left unscoped rather than wrapped in a max-width query. It costs nothing on
 * the pages that are fine and guards the next long title at any width.
 * -------------------------------------------------------------------------- */
h1,
h2,
h3,
h4,
h5,
h6 {
	overflow-wrap: break-word;
}
