/*
 * Components — video card + grid.
 *
 * Tokens only. No hardcoded colours (see tokens.css).
 */

/* ---------------------------------------------------------------- section head */

/*
 * Shared, NOT in single.css — search.php and 404.php render sections too, and
 * single.css only loads on is_singular('post').
 */
/*
 * The heading is an anchor, not a label.
 *
 * A 3px tick beside plain text read as dull because nothing about it was
 * deliberate — it was a border pretending to be a design. This gives the row
 * real structure: the title sits on a full rule, with an accent underline that
 * spans only the title itself, so the eye gets a definite start point and the
 * "view all" action has somewhere to live at the far end.
 */
.tt-section-head {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--tt-s4);
	margin: 0 0 var(--tt-s5);
	padding-bottom: var(--tt-s3);
	border-bottom: 1px solid var(--tt-border);
}

/* Accent underline sitting ON the rule, sized to the title */
.tt-section-head::after {
	content: '';
	position: absolute;
	left: 0;
	bottom: -1px;
	width: 64px;
	height: 3px;
	border-radius: 2px;
	background: var(--tt-grad);
}

.tt-section-title {
	margin: 0 0 var(--tt-s4);
	padding-bottom: var(--tt-s2);
	border-bottom: 1px solid var(--tt-border);
	font-size: var(--tt-fs-lg);
	text-transform: uppercase;
	letter-spacing: 0.03em;
}

/* Inside a section head, the border/margin belong to the head, not the title */
.tt-section-head .tt-section-title {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: var(--tt-fs-2xl);
	font-weight: 800;
	text-transform: none;
	letter-spacing: -0.02em;
	color: var(--tt-text);
}

/*
 * "View all" is a real button, not a text link.
 *
 * Flat accent — the SAME red as the active nav pill and the active pagination
 * chip. One red used consistently reads as brand; a gradient here made these a
 * separate visual thing competing with the nav.
 *
 * It was `background: var(--tt-bg-surface)` + muted text, which in light mode
 * resolves to literal #ffffff — a white button with grey text, invisible in the
 * corner it sits in. An action that only reveals itself on hover is one nobody
 * finds, and hover doesn't exist on the phones most of this traffic arrives on.
 *
 * Hover darkens the fill. It can't be a colour SWAP — the resting state is
 * already accent — and it must not move: these sit in a heading row, and a
 * button that jumps on hover drags the eye off the heading it belongs to.
 */
.tt-section-more {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: 5px;
	padding: 6px var(--tt-s4);
	border: 0;
	border-radius: var(--tt-radius-pill);
	/* accent-dark so the white label clears WCAG AA (bright accent = 3.65:1). */
	background: var(--tt-accent-dark);
	color: var(--tt-accent-contrast);
	font-size: var(--tt-fs-xs);
	font-weight: 800;
	white-space: nowrap;
	transition: background var(--tt-speed);
}

.tt-section-more:hover {
	background: color-mix(in srgb, var(--tt-accent-dark), #000 15%);
	color: var(--tt-accent-contrast);
}

/*
 * The /videa/ archive's head. It carries the page's H1, so it needs the top
 * breathing room .tt-home's padding-top gives the homepage — this page has no
 * .tt-home wrapper to inherit it from.
 */
.tt-section-head--page {
	margin-top: var(--tt-s5);
}

.tt-section-head--page .tt-section-title {
	font-size: var(--tt-fs-3xl);
}


.tt-latest {
	margin-top: var(--tt-s6);
}

/* ---------------------------------------------------------------- category slider */

/*
 * Browsing chips, sitting with the content rather than in the nav.
 *
 * Horizontal scroll, never wrap: 14 chips wrapping to three rows would push the
 * video grid below the fold on mobile — the grid is the whole point of the page.
 *
 * The fade on the right edge is the affordance that says "there's more" without
 * needing arrow buttons or JS.
 */
.tt-catslider {
	position: relative;
	margin-top: var(--tt-s5);
}

.tt-catslider::after {
	content: '';
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	width: 48px;
	pointer-events: none;
	background: linear-gradient(90deg, transparent, var(--tt-bg-body));
}

.tt-catslider__list {
	display: flex;
	gap: var(--tt-s2);
	overflow-x: auto;
	padding-bottom: var(--tt-s2);
	white-space: nowrap;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}

.tt-catslider__list::-webkit-scrollbar {
	display: none;
}

.tt-catslider__chip {
	display: inline-flex;
	align-items: center;
	gap: var(--tt-s2);
	padding: 7px var(--tt-s4);
	border: 1px solid var(--tt-border);
	border-radius: var(--tt-radius-pill);
	background: var(--tt-bg-surface);
	color: var(--tt-text);
	font-size: var(--tt-fs-sm);
	font-weight: 700;
	transition: background var(--tt-speed), border-color var(--tt-speed), color var(--tt-speed);
}

.tt-catslider__chip:hover {
	border-color: var(--tt-info);
	background: var(--tt-bg-raised);
	color: var(--tt-info);
}

.tt-catslider__chip.is-active {
	border-color: var(--tt-accent);
	background: var(--tt-accent);
	color: var(--tt-accent-contrast);
}

/* The "all categories" chip is a destination, not a filter — read it as muted */
.tt-catslider__chip--all {
	background: var(--tt-bg-raised);
	color: var(--tt-text-muted);
}

/* ---------------------------------------------------------------- featured section */

/*
 * No top margin: this is the first child of .tt-home__main, and .tt-home
 * already owns the gap under the category slider via its padding-top. Keeping
 * s6 here stacked on top of that and opened a 56px hole above the section.
 *
 * The gap DOWN to "Nejnovější videa" is the .tt-grid margin-bottom inside this
 * section, so the two sections space themselves the same way any two stacked
 * grids do.
 */
.tt-featured {
	margin-top: 0;
}

/* ---------------------------------------------------------------- home layout */

/*
 * Grid + right rail, per the omg.xxx reference.
 * Single column until there's genuinely room — the video grid is why people are
 * here, and squeezing it to fit a rail on a laptop is the wrong trade.
 */
/*
 * padding-top, not margin-top on the section head.
 *
 * The head carries margin:0 (the head owns its own spacing, not the title), so
 * without this the first heading sits flush against the sticky nav while every
 * archive page — which uses .tt-page-title and its margin-top — breathes. The
 * gap belongs to the page region, so both columns start on the same line.
 */
.tt-home {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--tt-s5);
	align-items: start;
	padding-top: var(--tt-s5);
}

/*
 * 1400px, not 1100px. The grid goes 5-across at 1100 (see the .tt-grid ladder),
 * and it needs the full container to do that at a sane card size — a rail
 * stealing 220px at 1100 would squeeze five cards down to ~149px each.
 *
 * So the rail waits until there's genuinely room for both. The video grid is why
 * people are here; squeezing it to fit a rail on a laptop is the wrong trade.
 */
@media (min-width: 1400px) {
	.tt-home {
		grid-template-columns: minmax(0, 1fr) 220px;
	}
}

.tt-home__main {
	min-width: 0; /* without this the grid refuses to shrink below its content */
}

/* ---------------------------------------------------------------- grid */

/*
 * Row-first CSS grid, NOT columns.
 *
 * `columns` fills top-to-bottom per column, which would stack the newest videos
 * vertically down column 1 instead of left-to-right across the top — wrong
 * reading order for a "latest videos" grid. A sibling project hit exactly this.
 */
/*
 * COLUMN COUNTS MUST DIVIDE EVERY COUNT RENDERED INTO THIS GRID:
 *
 *   10  homepage blocks — featured, latest, related  (2 rows at desktop)
 *   30  category / tag / actor archives              (6 rows at desktop)
 *
 * The ladder is 2 / 5 — divisors 10 and 30 share. Every block is a clean
 * rectangle at every width: at 2 columns that is 5 rows of 10 and 15 rows of 30.
 *
 * ⚠️ There is deliberately NO 3-column step, even though it would suit tablets
 * better: 3 does not divide 10, so it would strand a single orphan on a ragged
 * fourth row in every homepage block. Desktop is pinned at 5 by request and the
 * blocks are 2 rows by request; between them those two choices force this
 * ladder. 4 and 6 are out for the same divisibility reason as 3.
 *
 * ⚠️ The single-column step was REMOVED in 0.35.7 at the owner's request: with the
 * library growing daily, one card per screen showed too little on a phone. 2 is
 * now the base and there is no 600px step, because 2 was already what 600px set.
 * (An earlier version of this comment listed 2 alongside 4 and 6 as "out" while
 * the ladder itself used 2 — the reason it gave, divisibility, never applied to
 * 2. Corrected here.)
 *
 * Columns, posts_per_page and the block counts are ONE decision, not three.
 * Change any of them and re-check the others.
 */
.tt-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--tt-s5) var(--tt-s4);
	margin: 0 0 var(--tt-s6);
}


/*
 * 5 columns at 1100px — the same width the right rail waits for, which is not a
 * coincidence: below 1400px the rail is gone and the grid has the full container,
 * so five cards land at ~200-260px. If 5 columns started while the rail was still
 * stealing 220px, each card would be ~149px — smaller than the phone shows.
 */
@media (min-width: 1100px) {
	.tt-grid {
		grid-template-columns: repeat(5, 1fr);
	}
}

/* ---------------------------------------------------------------- card */

/*
 * The card is a real surface, not loose text under an image.
 *
 * Previously the title and meta floated directly on the page background, so a
 * "card" was just an image with some words beneath it — nothing bound them
 * together and the grid read as unfinished. Now the whole thing is one raised
 * block: image flush to the top, meta on a surface below it.
 *
 * display:flex + height:100% so every card in a row is the same height even
 * when a title wraps to two lines and its neighbour doesn't.
 */
.tt-card {
	position: relative;
	display: flex;
	flex-direction: column;
	height: 100%;
	min-width: 0; /* lets long titles ellipsis instead of blowing out the track */
	overflow: hidden;
	border: 1px solid var(--tt-border);
	border-radius: var(--tt-radius);
	background: var(--tt-bg-surface);

	/* Compositor-only properties. A 24-card grid can't afford a hover that
	   triggers layout or paint — never animate width/height/margin here. */
	transition: transform var(--tt-speed), box-shadow var(--tt-speed), border-color var(--tt-speed);
}

.tt-card:hover,
.tt-card:focus-within {
	transform: translateY(-4px);
	border-color: var(--tt-accent);
	box-shadow: var(--tt-shadow-accent);
}

.tt-card__link {
	display: block;
}

/* Thumbnail scales inside the clipped media box — subtle depth, no reflow */
.tt-card__thumb {
	transition: transform var(--tt-speed);
}

.tt-card:hover .tt-card__thumb {
	transform: scale(1.05);
}

/*
 * The media box.
 *
 * aspect-ratio reserves the space BEFORE the thumbnail loads. Without it the
 * whole grid reflows as ~40 hotlinked CDN images arrive at unpredictable times —
 * the single biggest CLS risk on this site.
 */
/*
 * Flush to the card's top edge — no radius of its own, the card clips it.
 */
.tt-card__media {
	position: relative;
	aspect-ratio: var(--tt-thumb-ratio);
	overflow: hidden;
	background: var(--tt-bg-raised);

	/*
	 * Press and hold on the thumbnail starts the trailer preview (holdPeek in
	 * main.js), and iOS answers a long press on a link with its own preview
	 * popup, which lands right on top of it. Killed here, on the media box
	 * ONLY, so a long press anywhere else on the page still behaves normally.
	 * The JS cancels the matching contextmenu event; this kills the iOS callout
	 * and the drag-the-image selection that comes with it.
	 */
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	user-select: none;
}

.tt-card__thumb,
.tt-card__preview {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * The preview sits on top of the thumbnail and is revealed by opacity rather
 * than display, so there's no layout work when it appears.
 *
 * Opacity is the ONLY transitioned property here — it's compositor-only, so it
 * can't cause a repaint on a grid of 40 cards.
 */
.tt-card__preview {
	opacity: 0;
	z-index: 1;
}

.tt-card.is-previewing .tt-card__preview {
	opacity: 1;
}

/* Duration badge — overlay tokens, mode-independent (always sits on an image) */
.tt-card__duration {
	position: absolute;
	right: var(--tt-s2);
	bottom: var(--tt-s2);
	z-index: 2;
	padding: 2px var(--tt-s2);
	border-radius: var(--tt-radius-sm);
	background: var(--tt-overlay);
	color: var(--tt-overlay-text);
	font-size: var(--tt-fs-xs);
	font-weight: 600;
	line-height: 1.4;
	font-variant-numeric: tabular-nums;
}

/* Everything below the image shares one padded block */
.tt-card__link {
	display: flex;
	flex-direction: column;
}

.tt-card__title {
	margin: 0;
	padding: var(--tt-s3) var(--tt-s3) 0;
	font-size: var(--tt-fs-sm);
	font-weight: 600;
	line-height: 1.4;
	color: var(--tt-text);
	transition: color var(--tt-speed);

	/*
	 * Two-line clamp AND a reserved min-height. The clamp alone still lets a
	 * one-line title sit shorter than a two-line neighbour, so the actor links
	 * below it land at different heights across the row — the single dullest,
	 * most obviously-unfinished thing about a tube grid.
	 */
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	min-height: calc(2 * 1.4 * 13px + var(--tt-s3));
}

.tt-card__link:hover .tt-card__title {
	color: var(--tt-accent);
}

/* ---------------------------------------------------------------- card meta */

/*
 * Actor links under the title, then a stats row (views · ⭐ rating · date),
 * per the omg.xxx reference. The rating is the source's real 0–5 score, not a
 * like-% (dislikes_count is 0 library-wide, so a % would read 100% everywhere
 * and look fabricated).
 */
.tt-card__actors {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tt-s1) var(--tt-s2);
	padding: var(--tt-s2) var(--tt-s3) 0;
}

.tt-card__actor {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	min-width: 0;
	color: var(--tt-text-muted);
	font-size: var(--tt-fs-2xs);
	line-height: 1.4;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 100%;
}

.tt-card__actor:hover {
	color: var(--tt-accent);
}

.tt-card__actor-icon {
	flex: 0 0 auto;
	opacity: 0.6;
}

/* mt:auto pins stats to the card's bottom edge, so they line up across a row
   regardless of how many actor links each card carries */
.tt-card__stats {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tt-s1) var(--tt-s3);
	margin-top: auto;
	padding: var(--tt-s2) var(--tt-s3) var(--tt-s3);
}

.tt-card__stat {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	color: var(--tt-text-muted);
	font-size: var(--tt-fs-2xs);
	font-variant-numeric: tabular-nums;
}

/* The rating is the only coloured stat, so the score reads at a glance. */
.tt-card__stat--rate svg {
	color: var(--tt-star);
}

/* Push the date to the right edge of the row (falls in line when it wraps). */
.tt-card__stat--date {
	margin-left: auto;
}

/* ---------------------------------------------------------------- models row */

/*
 * Horizontal scroll rather than wrap — a wrapping row of 12 avatars would eat
 * the fold on mobile. Matches the xxxu.net reference.
 */
.tt-models {
	margin-top: var(--tt-s7);
}

/*
 * Scrollbar hidden (owner request, v0.21.2), matching .tt-catslider__list above.
 * It was `scrollbar-width: thin`, the only horizontal scroller on the site still
 * painting a visible bar, which made the models strip look unfinished next to the
 * category chips right below it.
 *
 * Both properties are needed: `scrollbar-width` covers Firefox and modern Chrome,
 * the `::-webkit-scrollbar` rule covers Safari and older WebKit. Hiding the bar
 * does NOT disable scrolling: wheel, trackpad, touch and keyboard all still work,
 * and the row keeps its fade-out edge as the affordance that there's more to see.
 */
.tt-models__row {
	display: flex;
	gap: var(--tt-s4);
	overflow-x: auto;
	padding-bottom: var(--tt-s3);
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}

.tt-models__row::-webkit-scrollbar {
	display: none;
}

.tt-models__item {
	flex: 0 0 auto;
	width: 104px;
}

.tt-model {
	display: block;
	text-align: center;
}

.tt-model__avatar {
	position: relative;
	display: grid;
	place-items: center;
	width: 88px;
	height: 88px;
	margin: 0 auto var(--tt-s2);
	overflow: hidden;
	border: 2px solid var(--tt-border);
	border-radius: var(--tt-radius-pill);
	background: var(--tt-bg-raised);
	transition: border-color var(--tt-speed), transform var(--tt-speed), box-shadow var(--tt-speed);
}

.tt-model:hover .tt-model__avatar {
	border-color: var(--tt-accent);
	transform: translateY(-3px);
	box-shadow: var(--tt-shadow-accent);
}

/*
 * object-position: top.
 *
 * These avatars are 16:9 video thumbnails cropped to a circle (see
 * tt_actor_avatar — no real photos exist yet). Centre-cropping a landscape
 * still lands on whatever is mid-frame, which on this library is rarely a face.
 * Biasing to the top of the frame is a meaningful improvement for free, and
 * becomes irrelevant the moment real photos are uploaded.
 */
.tt-model__avatar img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: top center;
}

/* Fallback when an actor has no photo and no videos */
.tt-model__initial {
	color: var(--tt-text-muted);
	font-size: 30px;
	font-weight: 700;
}

.tt-model__name {
	display: block;
	overflow: hidden;
	color: var(--tt-text);
	font-size: var(--tt-fs-sm);
	font-weight: 600;
	white-space: nowrap;
	text-overflow: ellipsis;
}

.tt-model:hover .tt-model__name {
	color: var(--tt-accent);
}

/* ---------------------------------------------------------------- sidebar rail */

.tt-sidebar {
	display: grid;
	gap: var(--tt-s4);
	align-content: start;
}

/* Must stay the mirror of the .tt-home rail breakpoint above — this is the
   "rail is below the grid, not beside it" case. */
@media (max-width: 1399px) {
	/*
	 * Below the grid: two columns so the rail doesn't become a long scroll.
	 *
	 * ⚠️ This was `repeat(auto-fit, minmax(240px, 1fr))`, which DID NOT deliver
	 * two columns on a phone — auto-fit only creates a track once 240px fits, so
	 * at 390px (≈350px of content box) exactly one track fit and the rails
	 * silently stacked into the long scroll this rule exists to prevent. The
	 * comment claimed two columns while the code produced one, and nothing warns
	 * you: auto-fit degrades quietly by design.
	 *
	 * Explicit `repeat(2, …)` guarantees the pair sits in one row at EVERY width.
	 * `minmax(0, 1fr)` rather than plain `1fr` because a grid track's automatic
	 * minimum is its content's intrinsic width — the same min-width:auto trap
	 * documented for flex items in CLAUDE.md — so a long category name like
	 * "Dlouhá porno videa" could otherwise push the track wider than its share.
	 */
	.tt-sidebar {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/*
 * At >=1400px the rail IS the side column (see .tt-home), so let it follow the
 * scroll instead of leaving a tall empty gap next to a long video grid. Only
 * sticky here — below 1400px it's a full-width block under the grid, where
 * sticky would do nothing. Works because .tt-home is align-items:start (a
 * stretched grid item can't stick) and the body uses overflow-x:clip, not
 * hidden, which would otherwise kill sticky.
 */
@media (min-width: 1400px) {
	.tt-sidebar {
		position: sticky;
		top: var(--tt-s5);
	}
}

/*
 * Each rail is one contained panel, not a loose list. Previously the title and
 * rows shared no surface, so the rail read as stray links floating beside the
 * grid rather than a deliberate module.
 */
.tt-rail {
	overflow: hidden;
	border: 1px solid var(--tt-border);
	border-radius: var(--tt-radius);
	background: var(--tt-bg-surface);
}

.tt-rail__title {
	display: flex;
	align-items: center;
	gap: var(--tt-s2);
	margin: 0;
	padding: var(--tt-s3);
	border-bottom: 1px solid var(--tt-border);
	/*
	 * Was bare --tt-bg-raised, which in light mode is near-white (#f4f5f9) so the
	 * header read as washed out. Mixing ~12% text into the raised surface gives a
	 * defined header band that darkens in light mode and lifts in dark — one
	 * token-driven rule, correct in both themes.
	 */
	background: color-mix(in srgb, var(--tt-text) 12%, var(--tt-bg-raised));
	font-size: var(--tt-fs-2xs);
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--tt-text);
}

/* Accent tick, matching the section headings in the main column */
.tt-rail__title::before {
	content: '';
	flex: 0 0 auto;
	width: 3px;
	height: 12px;
	border-radius: 2px;
	background: var(--tt-accent);
}

.tt-rail__list {
	display: grid;
}

.tt-rail__link {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--tt-s2);
	padding: 7px var(--tt-s3);
	border-bottom: 1px solid var(--tt-border);
	color: var(--tt-text-muted);
	font-size: var(--tt-fs-sm);
	transition: background var(--tt-speed), color var(--tt-speed), padding-left var(--tt-speed);
}

.tt-rail__list li:last-child .tt-rail__link {
	border-bottom: 0;
}

/* Nudge right on hover — cheap, and makes a dense list feel responsive */
.tt-rail__link:hover {
	padding-left: calc(var(--tt-s3) + 4px);
	background: var(--tt-bg-raised);
	color: var(--tt-accent);
}

/*
 * Rail footer action — closes the panel off instead of trailing into nothing.
 *
 * Same flat accent as .tt-section-more: one "view all" treatment everywhere, so
 * the button teaches itself once. See that rule for why these stopped being
 * surface-coloured pills.
 */
.tt-rail__all {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 5px;
	padding: var(--tt-s3);
	/* accent-dark so the white label clears WCAG AA (bright accent = 3.65:1). */
	background: var(--tt-accent-dark);
	color: var(--tt-accent-contrast);
	font-size: var(--tt-fs-2xs);
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	transition: background var(--tt-speed);
}

.tt-rail__all:hover {
	background: color-mix(in srgb, var(--tt-accent-dark), #000 15%);
	color: var(--tt-accent-contrast);
}

/* The arrow leads the eye to where the button goes */
.tt-rail__all svg {
	transition: transform var(--tt-speed);
}

.tt-rail__all:hover svg {
	transform: translateX(3px);
}

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

/*
 * Category cards — the /kategorie/ index.
 *
 * ⚠️ FIXED columns, not auto-fill, and the ladder is 1 / 2 / 4.
 *
 * This page paginates at 20 per page, so the same rule as .tt-grid applies: every
 * column count must divide the per-page count or the last row comes out ragged.
 * 20 / 4 = five clean rows; 20 / 2 = ten; 20 / 1 = twenty. auto-fill would pick
 * its own column count based on width and blow that guarantee apart at some
 * viewport nobody tested.
 *
 * A 3-column step is deliberately absent — 3 does not divide 20.
 */
.tt-tiles {
	display: grid;
	grid-template-columns: repeat(1, 1fr);
	gap: var(--tt-s4);
	margin: 0 0 var(--tt-s6);
	padding: 0;
	list-style: none;
}


@media (min-width: 600px) {
	.tt-tiles {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (min-width: 1000px) {
	.tt-tiles {
		grid-template-columns: repeat(4, 1fr);
	}
}

/*
 * The ring matters more than it sounds. These tiles are arbitrary video frames
 * bled to the tile edge, so against a dark page a dark-cornered thumbnail had no
 * edge at all and the grid read as floating rectangles of noise. A 1px border
 * gives every tile the same silhouette whatever its artwork, and picking up the
 * accent on hover is what makes the whole card feel like one clickable object
 * rather than a picture that happens to be a link.
 */
.tt-tile {
	position: relative;
	display: block;
	overflow: hidden;
	border: 1px solid var(--tt-border);
	border-radius: var(--tt-radius);
	background: var(--tt-bg-surface);
	transition:
		transform var(--tt-speed),
		box-shadow var(--tt-speed),
		border-color var(--tt-speed);
}

.tt-tile:hover {
	transform: translateY(-3px);
	border-color: color-mix(in srgb, var(--tt-accent) 55%, transparent);
	box-shadow:
		var(--tt-shadow),
		0 0 0 1px color-mix(in srgb, var(--tt-accent) 25%, transparent);
}

/* Keyboard users get the same affordance the mouse gets. */
.tt-tile:focus-visible {
	outline: 2px solid var(--tt-accent);
	outline-offset: 2px;
}

.tt-tile__media {
	display: block;
	/* Reserve the box before the image lands — same CLS guard as the video card */
	aspect-ratio: var(--tt-thumb-ratio);
	background: var(--tt-bg-raised);
}

.tt-tile__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var(--tt-speed);
}

.tt-tile:hover .tt-tile__media img {
	transform: scale(1.06);
}

.tt-tile__blank {
	display: block;
	width: 100%;
	height: 100%;
	background: var(--tt-bg-raised);
}

/*
 * Name sits ON the image, not in a bar beneath it.
 *
 * A caption below would make every tile ~40px taller and push the grid down for
 * no gain — the artwork is decoration here, the NAME is what's being clicked.
 * The scrim is what keeps the text readable: these are arbitrary video frames,
 * not art-directed covers, so the background behind the text is unknowable and
 * could be anything from black to white.
 */
/*
 * The veil: a full-tile tint, not a bottom scrim.
 *
 * The previous card was a name in the bottom-left over a black gradient, which
 * is what every tube ships and reads as a caption pasted onto a photo. Tinting
 * the WHOLE tile does two things a bottom bar cannot: the artwork becomes a
 * backdrop instead of the subject, and the text is legible no matter what the
 * frame happens to contain, which matters because these are arbitrary video
 * stills, not art-directed covers.
 *
 * The tint carries a touch of accent rather than being neutral black. That is
 * what stops 37 unrelated stills reading as 37 unrelated colour temperatures:
 * a shared hue over all of them makes the grid look designed.
 */
.tt-tile__veil {
	position: absolute;
	inset: 0;
	background:
		linear-gradient(
			to bottom,
			rgba(0, 0, 0, 0.15) 0%,
			rgba(0, 0, 0, 0.55) 55%,
			rgba(0, 0, 0, 0.82) 100%
		),
		color-mix(in srgb, var(--tt-accent) 6%, transparent);
	transition: background var(--tt-speed), opacity var(--tt-speed);
	pointer-events: none;
}

.tt-tile:hover .tt-tile__veil {
	background:
		linear-gradient(
			to bottom,
			rgba(0, 0, 0, 0.25) 0%,
			rgba(0, 0, 0, 0.6) 55%,
			rgba(0, 0, 0, 0.85) 100%
		),
		color-mix(in srgb, var(--tt-accent) 16%, transparent);
}

/*
 * Centred plate. justify-content:center puts the name in the optical middle of
 * the tile, where the eye lands anyway, instead of in a corner it has to hunt
 * for. Padding rather than fixed positioning so a two-word category name can
 * wrap without shoving the count out of the tile.
 */
.tt-tile__body {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: var(--tt-s3);
	text-align: center;
	pointer-events: none;
}

/*
 * The rule under the name is the whole hover gesture: it grows from 24px to the
 * full width of the text. Cheap, and it reads as a deliberate interaction rather
 * than the generic "card lifts slightly" every grid on the web does.
 */
.tt-tile__name {
	position: relative;
	max-width: 100%;
	padding-bottom: 9px;
	color: #fff;
	/* Flat 20px, owner's call — not a clamp. The tile is a fixed 16:9 box in a
	   4-column grid, so the name has the same room at every breakpoint and a
	   viewport-scaled size only made it drift for no reason. */
	font-size: var(--tt-fs-2xl);
	font-weight: 800;
	line-height: 1.15;
	letter-spacing: 0.01em;
	text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8);
	/* "Kompilace Cumshotů" may wrap to two lines here; it must not run to three */
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
}

.tt-tile__name::after {
	content: "";
	position: absolute;
	bottom: 0;
	left: 50%;
	width: 24px;
	height: 2px;
	border-radius: 2px;
	background: var(--tt-accent);
	transform: translateX(-50%);
	transition: width var(--tt-speed);
}

.tt-tile:hover .tt-tile__name::after {
	width: 64px;
}


/* ---------------------------------------------------------------- term index pages */

/*
 * Full model grid — the /modelky/ page.
 *
 * ⚠️ NO LONGER the same unit as the homepage strip. That strip is still the round
 * 88px .tt-model__avatar above; this is .tt-model--card, a 2:3 portrait card
 * sized for the 400x600 photos the owner uploads into the tt_actor_photo field.
 * The two share .tt-model__name and nothing else — they also shared
 * .tt-model__count until 0.35.9 removed the counts. Keep them apart: a change
 * meant for one will otherwise land on the other.
 */
/*
 * A fixed 5-column ladder, NOT auto-fill (owner's call).
 *
 * per_page is 20 in template-actors.php, so 5 columns is exactly 4 full rows with
 * no orphan on the end. auto-fill packed 9 tiny cards into a wide viewport and
 * left a ragged last row. ⚠️ The two numbers are a pair: change per_page and this
 * stops dividing evenly.
 *
 * Same 600/1000 breakpoints as .tt-tiles above — one ladder for every grid on the
 * site.
 */
.tt-model-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--tt-s4);
	margin-bottom: var(--tt-s6);
}


@media (min-width: 600px) {
	.tt-model-grid {
		grid-template-columns: repeat(3, 1fr);
	}
}

@media (min-width: 1000px) {
	.tt-model-grid {
		grid-template-columns: repeat(5, 1fr);
	}
}

/*
 * One composition for BOTH states: a full-bleed image with the name over a scrim
 * at the foot of it, exactly like the category tiles.
 *
 * ⚠️ The first cut of this card gave the fallback its own layout — 16:9 frame at
 * the top, name in the space below — and two thirds of every card was empty. Keep
 * both states full-bleed. The only thing that should change when a real photo is
 * uploaded is that the blurred backdrop stops being needed.
 */
.tt-model--card {
	position: relative;
	display: block;
	overflow: hidden;
	aspect-ratio: 2 / 3;
	border: 1px solid var(--tt-border);
	border-radius: var(--tt-radius);
	background: var(--tt-bg-surface);
	transition: border-color var(--tt-speed), transform var(--tt-speed), box-shadow var(--tt-speed);
}

.tt-model--card:hover {
	border-color: var(--tt-accent);
	transform: translateY(-3px);
	box-shadow: var(--tt-shadow-accent);
}

.tt-model--card .tt-model__media {
	position: absolute;
	display: grid;
	place-items: center;
	inset: 0;
	overflow: hidden;
}

/*
 * The blurred backdrop — the same frame blown up behind itself so the card is
 * filled edge to edge instead of letterboxed. Scaled past the edges because
 * blurring samples past the boundary and would otherwise feather the corners.
 */
.tt-model__blur {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	transform: scale(1.25);
	filter: blur(22px) saturate(1.3) brightness(0.55);
	object-fit: cover;
}

.tt-model__img {
	position: relative;
	transition: transform var(--tt-speed);
}

/* A real 400x600 upload already matches the card — it just fills it. */
.tt-model--photo .tt-model__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * The fallback frame stays sharp at its native 16:9 across the middle of the
 * card, floating on its own blurred backdrop. Never cropped to portrait: that
 * keeps only the middle 38% of the width, where this library's frames rarely have
 * a face, and the 640x360 static-cdn avatars would upscale on top of it.
 */
.tt-model--thumb .tt-model__img {
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
	object-fit: cover;
	object-position: top center;
}

.tt-model--card:hover .tt-model__img {
	transform: scale(1.04);
}

.tt-model__veil {
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to bottom,
		rgba(0, 0, 0, 0) 45%,
		rgba(0, 0, 0, 0.55) 72%,
		rgba(0, 0, 0, 0.88) 100%
	);
	pointer-events: none;
}

.tt-model__body {
	position: absolute;
	right: 0;
	bottom: 0;
	left: 0;
	padding: var(--tt-s3) var(--tt-s2);
	text-align: center;
}

.tt-model--card .tt-model__name {
	color: #fff;
	font-size: var(--tt-fs-md);
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
}

.tt-model--card:hover .tt-model__name {
	color: #fff;
}

.tt-model--card .tt-model__initial {
	font-size: 44px;
}

/* A-Z groups on the tags index */
.tt-az {
	margin-bottom: var(--tt-s5);
}

.tt-az__letter {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 28px;
	height: 28px;
	margin: 0 0 var(--tt-s3);
	padding: 0 var(--tt-s2);
	border-radius: var(--tt-radius-sm);
	background: var(--tt-grad);
	color: #fff;
	font-size: var(--tt-fs-sm);
	font-weight: 800;
}

/*
 * Content pages (privacy, DMCA, kontakt, provozní podmínky…) render through the
 * generic page.php — a .tt-page article wrapping a .tt-container. The index pages
 * (/kategorie/ /modelky/ /tags/) use their OWN template-*.php, so narrowing the
 * container here can't touch their grids.
 *
 * Without this, .tt-prose's 70ch cap left the text hugging the left of a full
 * ~1280px container with a vast empty right half — it read as broken. Narrowing
 * the whole content column and centring it turns that into a balanced, deliberate
 * reading column: title and body share one width and sit centred on the page.
 */
.tt-page .tt-container {
	max-width: 860px;
}

/*
 * Long-form page content (privacy policy, legal, article bodies).
 *
 * ⚠️ --tt-text, NOT --tt-text-muted (owner request, v0.19.1).
 *
 * The muted token (#b8b8c8 dark / #5a5a6e light) is sized for CARD META — a view
 * count or a date, read in a glance. Running it under two thousand words of body
 * copy is a different job entirely, and it read as washed out and tiring. Body
 * text now shares the heading colour: near-white on dark (#f2f2f5), near-black on
 * light (#15151c) — high contrast without the harshness of pure #fff / #000.
 *
 * Keep muted for meta. Use this for anything anyone actually READS.
 */
.tt-prose {
	max-width: 70ch;
	margin-bottom: var(--tt-s6);
	color: var(--tt-text);
	font-size: var(--tt-fs-base);
	line-height: 1.7;
}

/* Inside a .tt-page the container already caps the width — let prose fill it so
   the body lines up with the H1 instead of stopping short at 70ch. */
.tt-page .tt-prose {
	max-width: none;
}

/*
 * Links inside any reading block — ONE definition for both long-form prose
 * (articles, legal pages) and the homepage SEO block.
 *
 * ⚠️ Accent, NOT --tt-info (owner, v0.19.2).
 *
 * These were two separate rules that disagreed: the homepage block went accent +
 * bold + underline in v0.8.6 (its links were invisible before that), while
 * .tt-prose stayed on the blue info token. Same kind of block, same kind of link,
 * two different colours — the article looked like it belonged to another site.
 * Merged into one selector so the next change can only ever hit both.
 */
.tt-prose a,
.tt-seo__text a {
	color: var(--tt-accent);
	font-weight: 700;
	text-decoration: underline;
	text-underline-offset: 2px;
	text-decoration-thickness: 1px;
	transition: color var(--tt-speed);
}

.tt-prose a:hover,
.tt-seo__text a:hover {
	color: var(--tt-accent-2);
}

.tt-prose h2,
.tt-prose h3 {
	margin-top: var(--tt-s5);
	color: var(--tt-text);
}

/* ---------------------------------------------------------------- tag cloud */

.tt-tagcloud {
	margin-top: var(--tt-s7);
}

.tt-tagcloud__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tt-s2);
}

.tt-tag {
	display: inline-flex;
	align-items: center;
	gap: var(--tt-s2);
	padding: 6px var(--tt-s3);
	border: 1px solid var(--tt-border);
	border-radius: var(--tt-radius-pill);
	background: var(--tt-bg-surface);
	color: var(--tt-text);
	font-size: var(--tt-fs-sm);
	font-weight: 600;
}

.tt-tag:hover {
	border-color: var(--tt-accent);
	color: var(--tt-accent);
}

/* ---------------------------------------------------------------- partners */

/*
 * Partner directory: plain text links that wrap, each sized to its text (0.37.5 to 0.37.7).
 *
 * ⚠️ Owner: "keep it minimalistic". Anchor text only. Rejected on the way here:
 * white wordmark plates (0.32.x), big cards with a monogram icon (0.37.2), rows
 * with the domain (0.37.3), gradient rows with a description (0.37.4). Do not
 * add decoration back. Wrapped flex keeps ~100 partners compact.
 */
.tt-partners {
	/* Flex, not grid: each link is as wide as its own text (owner, 0.37.7). */
	display: flex;
	flex-wrap: wrap;
	gap: var(--tt-s2);
	margin-top: var(--tt-s5);
}

.tt-partners__link {
	max-width: 100%;
	overflow: hidden;
	padding: var(--tt-s2) var(--tt-s3);
	/*
	 * The site red, softened (owner, 0.37.8). 0.37.6 used 60% of --tt-border and
	 * it vanished against the body; 0.37.7 was grey. Hover goes to full accent.
	 */
	border: 1px solid color-mix(in srgb, var(--tt-accent) 50%, transparent);
	border-radius: var(--tt-radius-sm);
	color: var(--tt-text);
	font-weight: 600;
	text-overflow: ellipsis;
	white-space: nowrap;
	transition: color var(--tt-speed), border-color var(--tt-speed);
}

.tt-partners__link:hover,
.tt-partners__link:focus-visible {
	border-color: var(--tt-accent);
	color: var(--tt-accent);
}

/* ---------------------------------------------------------------- seo block */

.tt-seo {
	margin-top: var(--tt-s7);
	padding-top: var(--tt-s5);
	border-top: 1px solid var(--tt-border);
}

.tt-seo__title {
	margin: 0 0 var(--tt-s3);
	font-size: var(--tt-fs-xl);
}

/*
 * Two columns on wide screens. A 550-word block at one column reads as a wall
 * of grey and gets skipped; split, it scans. Crawlers see identical markup
 * either way.
 */
.tt-seo__text {
	/* Reading text — see the note on .tt-prose. Muted is for meta, not for prose. */
	color: var(--tt-text);
	font-size: var(--tt-fs-sm);
	line-height: 1.75;
}

@media (min-width: 900px) {
	.tt-seo__text {
		column-count: 2;
		column-gap: var(--tt-s7);
	}
}

.tt-seo__text p {
	margin: 0 0 var(--tt-s3);
}

/* Link styling for this block moved up to the shared `.tt-prose a, .tt-seo__text a`
   rule (v0.19.2) — it was duplicated here and had drifted out of sync with prose. */

.tt-seo__text h3 {
	margin: var(--tt-s4) 0 var(--tt-s2);
	color: var(--tt-text);
	font-size: var(--tt-fs-md);
	font-weight: 700;

	/* Stops a heading stranding itself at the foot of a column */
	break-after: avoid;
	break-inside: avoid;
}

.tt-seo__text h3:first-child {
	margin-top: 0;
}

/* ---------------------------------------------------------------- player shell */

/*
 * The box the source's own <iframe> sits in. Same aspect-ratio contract as the
 * card, so the page does not shift while the embed loads.
 *
 * ⚠️ This was the CLICK-TO-LOAD FACADE until v0.5.0, when the owner dropped it so
 * visitors get the source player's native UI and press its own play button. There
 * is no poster, and main.js injects nothing — the embed renders directly. The old
 * comment here still described the facade, which is worse than no comment: it sent
 * a reader looking for JS that has not existed for 30 versions.
 * Modifiers live in single.css: .tt-player--raw (the real embed) and
 * .tt-player--empty / .tt-player__msg (a source-dead placeholder).
 */
.tt-player {
	position: relative;
	aspect-ratio: var(--tt-thumb-ratio);
	overflow: hidden;
	border-radius: var(--tt-radius);
	background: var(--tt-bg-raised);
}


