/*
 * Warm Refresh — components.css
 * Shared component vocabulary: buttons, forms, badges, cards, the rating
 * widget, tooltips, comparison-table base styles, and prose/markdown
 * typography. Every value below is a token from tokens.css — no raw
 * hex/px colours or shadows.
 *
 * This file supplies styles only; page bodies are converted to use these
 * classes in later tasks (this task builds the vocabulary, not the pages).
 */

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  display: inline-block;
  padding: 12px 28px;
  border-radius: var(--radius-sm);
  font: 600 14px/1 var(--font-sans);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
  text-align: center;
}

/* base.css's generic "a:hover { text-decoration: underline }" (specificity
   0,1,1) out-specifies .btn's own "text-decoration: none" (0,1,0), so every
   button rendered as an <a> underlined on hover. This restores the intent.
   Deliberately placed BEFORE .btn-link:hover below: that rule has the same
   specificity (0,2,0) and legitimately wants an underline, so it must come
   later in source order to win. */
.btn:hover {
  text-decoration: none;
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  border: none;
}

.btn-primary:hover {
  background: var(--accent-hover);
  transform: translateY(-1px);
}

.btn-primary:active {
  background: var(--accent-active);
  transform: none;
}

.btn-primary:disabled {
  background: color-mix(in srgb, var(--accent) 40%, transparent);
  cursor: not-allowed;
}

.btn-outline-default {
  background: transparent;
  color: var(--ink-strong);
  border: 1.5px solid var(--ink-strong);
}

.btn-outline-default:hover {
  background: var(--ink-strong);
  color: var(--ink-inverse);
}

.btn-outline-primary {
  background: transparent;
  border: 1.5px solid var(--accent);
  color: var(--accent);
}

.btn-outline-primary:hover {
  background: var(--accent-soft);
}

.btn-outline-primary:active {
  background: var(--accent);
  color: var(--accent-ink);
}

.btn-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  text-decoration: none;
}

.btn-link:hover {
  text-decoration: underline;
}

.btn-lg {
  padding: 14px 32px;
  font-size: 16px;
}

.btn-sm {
  padding: 8px 16px;
  font-size: 13px;
}

.btn:focus-visible,
.btn-primary:focus-visible,
.btn-outline-default:focus-visible,
.btn-outline-primary:focus-visible,
.btn-link:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  outline-offset: 2px;
}

/* ==========================================================================
   Forms
   ========================================================================== */

.form-control,
input[type="text"],
input[type="search"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="tel"],
input[type="url"],
textarea,
select {
  background: var(--bg-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  color: var(--ink);
  font: 400 14px/1.4 var(--font-sans);
}

.form-control:focus,
input[type="text"]:focus,
input[type="search"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="number"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
textarea:focus,
select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}

::placeholder {
  color: var(--ink-muted);
}

/* Custom checkbox */
.form-checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
}

.form-checkbox input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--line-strong);
  border-radius: 4px;
  background: var(--bg-surface);
  display: inline-block;
  position: relative;
  cursor: pointer;
  flex: none;
}

.form-checkbox input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}

.form-checkbox input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid var(--accent-ink);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.form-checkbox input[type="checkbox"]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* ==========================================================================
   Badges
   ========================================================================== */

.badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font: 700 10px/1 var(--font-sans);
  letter-spacing: .02em;
}

.badge-new,
.badge-popular {
  background: var(--accent);
  color: var(--accent-ink);
}

/* Blesta-6-compatible */
.badge-success {
  background: var(--success-soft);
  color: var(--success);
}

/* Blesta-5 (not yet compatible) */
.badge-neutral {
  background: var(--neutral-soft);
  color: var(--neutral);
}

.badge-danger {
  background: var(--danger-soft);
  color: var(--danger);
}

/* ==========================================================================
   Doc notice - inline callout above rendered README/markdown content
   ========================================================================== */

.doc-notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin: 0 0 var(--space-5);
}

.doc-notice[hidden] {
  display: none;
}

.doc-notice i {
  color: var(--accent);
  margin-top: 3px;
}

.doc-notice p {
  margin: 0;
  color: var(--ink);
  font-size: .9375rem;
}

/* ==========================================================================
   Breadcrumbs (src/shared/breadcrumb.php)
   The trail is rendered site-wide by layout.php, but its Argon-era styling
   was removed with the rest of Bootstrap and never replaced, leaving a bare
   unstyled vertical stack (reset.css strips list markers and ol padding).
   This is the Warm Refresh replacement: an inline, slash-separated trail.
   ========================================================================== */

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-2) 0;
  list-style: none;
  font-size: .8125rem;
  color: var(--ink-muted);
}

.breadcrumb-item {
  display: flex;
  align-items: center;
}

.breadcrumb-item + .breadcrumb-item::before {
  content: "/";
  color: var(--line-strong);
  margin-right: var(--space-2);
}

.breadcrumb-item a {
  color: var(--accent);
}

.breadcrumb-item a:hover {
  text-decoration: underline;
}

.breadcrumb-item.active {
  color: var(--ink-muted);
}

/* ==========================================================================
   Cards
   ========================================================================== */

.card {
  background: var(--bg-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: visible;
}

.card--hover,
.card-lift--hover {
  transition: transform var(--duration-base) var(--ease-standard),
    box-shadow var(--duration-base) var(--ease-standard);
}

.card--hover:hover,
.card-lift--hover:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

/* Pricing cards (generalised from the former /access-scoped rules in
   custom.css so any page — /access, /blesta, /managed, the extension
   detail page's "Included In" card — can use the same markup). */
.card-pricing {
  background: var(--bg-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  overflow: visible;
  transition: transform var(--duration-base) var(--ease-standard),
    box-shadow var(--duration-base) var(--ease-standard);
}

.card-pricing:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-xl);
}

.card-pricing--popular {
  border: 2px solid var(--accent);
  position: relative;
}

.badge-popular {
  position: absolute;
  top: -12px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--accent);
  color: var(--accent-ink);
  font: 700 11px/1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 4px 14px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  z-index: 1;
}

/* "Bundled in" style badge used on pricing cards. */
.badge-included {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent-soft-border);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .01em;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
}

/* Optional extra-cost add-on — visually distinct from .badge-included's
   "this is bundled free" styling. Uses the neutral/informational tokens
   rather than the accent so it doesn't read as "included". */
.badge-addon {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--neutral-soft);
  color: var(--neutral);
  border: 1px solid var(--line-strong);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .01em;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
}

/* Equal-height pricing card columns: pin the footer/CTA to the bottom of
   whatever extra space the tallest card in the row creates. */
.card-pricing {
  display: flex;
  flex-direction: column;
}

.card-pricing .card-footer {
  margin-top: auto;
}

/* Pricing card internal structure (header/body/footer typography, price
   display, feature list). Generalised alongside .card-pricing above so any
   page using the card - the homepage, /access (Task 6), /blesta, the
   extension detail page's "Included In" card - shares the same recipe.
   Added in Task 5: the brief describes this as already living here, but it
   wasn't part of Task 4's delivered scope - filling the gap now. */
.card-pricing .card-header {
  padding: var(--space-5) var(--space-5) var(--space-2);
  text-align: center;
}

.card-pricing .card-header h4,
.card-pricing .card-header h5,
.card-pricing .card-header h6 {
  font: 700 .8125rem/1.2 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-strong);
  margin: 0 0 var(--space-2);
}

.card-pricing .card-body {
  padding: 0 var(--space-5) var(--space-4);
  text-align: center;
}

.card-pricing .card-price {
  font-size: 2.5rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink-strong);
}

.card-pricing .card-price-term {
  display: block;
  color: var(--ink-muted);
  font-size: .8125rem;
  margin-top: var(--space-1);
  margin-bottom: var(--space-1)
}

.card-pricing .card-list {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  text-align: left;
}

.card-pricing .card-list li {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .9375rem;
}

.card-pricing .card-list li:last-child {
  border-bottom: none;
}

.card-pricing .card-list strong {
  color: var(--accent);
}

.card-pricing .card-footer {
  padding: 0 var(--space-5) var(--space-5);
}

.card-pricing .card-footer .btn {
  width: 100%;
}

/* Responsive grid for a row of plan/pricing cards (e.g. the four Club
   tiers on the homepage and /access). Added in Task 5 alongside the
   .card-pricing internals above, for the same reason. */
.plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--space-5);
  align-items: stretch;
}

/* ==========================================================================
   Icon chip (feature grid / info cards / contact cards) - Added in Task 5:
   the brief attributes this recipe to Task 4's components.css, but it
   wasn't part of Task 4's delivered scope - filling the gap now.
   ========================================================================== */

.icon-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 1.25rem;
  flex: none;
}

.icon-chip--lg {
  width: 72px;
  height: 72px;
  font-size: 1.75rem;
}

.icon-chip--sm {
  width: 36px;
  height: 36px;
  font-size: .9375rem;
}

/* ==========================================================================
   Feature grid (icon chip + label + optional description/link) - Added in
   Task 5, same reason as the Icon chip section above.
   ========================================================================== */

/* Mobile-first single column. auto-fit's implicit column-count (based on
   container width alone, blind to item count) is what caused uneven last
   rows (3 items -> 2+1, 4 items -> 3+1): a modifier per item-count with
   explicit breakpoints replaces it below, jumping straight between column
   counts that divide the row evenly rather than drifting through one that
   doesn't. */
.feature-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-7) var(--space-6);
}

/* 3-item showcase grid (homepage "What We Offer", 404 category shortcuts):
   1 column -> 3 columns directly. No 2-column stop - 3 items split
   unevenly (2+1) across 2 columns, so skip straight to the count that
   divides evenly. */
@media (min-width: 900px) {
  .feature-grid--3 {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* 4-item showcase grid (about page "Our Offerings" / "Why Choose Us"):
   1 -> 2 (2x2) -> 4. Every stop divides 4 evenly, so there's no width
   where a row is left with a lone trailing item. */
@media (min-width: 640px) {
  .feature-grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

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

.feature-grid-item {
  text-align: center;
}

.feature-grid-item .icon-chip {
  margin: 0 auto var(--space-4);
}

.feature-grid-item__label {
  color: var(--ink-strong);
  font-size: 1.0625rem;
  margin: 0 0 var(--space-2);
}

.feature-grid-item__description {
  color: var(--ink-soft);
  font-size: .9375rem;
  line-height: 1.6;
  margin: 0 0 var(--space-3);
}

.feature-grid-item__link {
  color: var(--accent);
  font-weight: 600;
  font-size: .875rem;
}

.feature-grid-item__link i {
  margin-left: 4px;
  font-size: .75rem;
}

/* Dense variant - Added in Task 8 for the extension detail page's Features
   section, which can list dozens of items (shared/features.php's catalog),
   unlike the homepage's small, centred showcase grid above. Left-aligned
   icon+label rows with a tight --space-2 gap so many items read as a
   scannable list rather than a showcase. */
.feature-grid--dense {
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-2);
  /* A .feature-grid-item--wide item (below) spans 2 columns - with the
     default "sparse" auto-placement, one that doesn't fit the columns left
     in its row gets pushed to the next row, permanently leaving that
     leftover space blank. "dense" instead backfills such gaps with whatever
     later item fits, at the cost of some items rendering out of DOM order -
     an acceptable trade for a decorative list like this one. */
  grid-auto-flow: dense;
}

.feature-grid-item--dense {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  text-align: left;
}

.feature-grid-item--dense .icon-chip {
  margin: 2px 0 0;
  flex: none;
}

.feature-grid-item--dense .feature-grid-item__label {
  font-size: .9375rem;
  margin: 0 0 2px;
}

.feature-grid-item--dense .feature-grid-item__description {
  font-size: .8125rem;
  margin: 0;
}

/* A notably longer description gets more width to wrap into, so it
   doesn't stretch its row much taller than its neighbours. */
.feature-grid-item--wide {
  grid-column: span 2;
}

.feature-list__toggle {
  display: block;
  margin-left: auto;
  margin-right: auto;
}

/* ==========================================================================
   Horizontal info card (icon chip beside a title + description) - Added in
   Task 5, same reason as the Icon chip section above.
   ========================================================================== */

.info-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: var(--space-5);
}

.info-card--horizontal {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  background: var(--bg-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
}

.info-card--horizontal .icon-chip {
  margin-top: 2px;
}

.info-card--horizontal h5 {
  margin: 0 0 var(--space-2);
}

.info-card--horizontal p {
  margin: 0;
  color: var(--ink-soft);
  font-size: .9375rem;
  line-height: 1.6;
}

/* ==========================================================================
   Rating widget (.rating-box)
   Consolidates the four previously-duplicated copies (src/body.php,
   src/extensions/index.php, src/extensions/card.php via body.php,
   src/extensions/extension-template.php's inline $head string) into one
   definition. Half-star fill mechanism (width:calc(N*10% - 5px)) is
   unchanged; colour swapped from Bootstrap's .text-warning orange to the
   bronze --accent token.
   ========================================================================== */

.rating-box {
  position: relative;
  vertical-align: middle;
  font-family: 'Font Awesome 5 Free';
  font-weight: 400;
  color: var(--line-strong);
  display: inline-block;
  letter-spacing: 10px;
}

.rating-box::before {
  content: "\f005 \f005 \f005 \f005 \f005";
}

.rating-box .rating {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  overflow: hidden;
  font-weight: 900;
  color: var(--accent);
}

.rating-box .rating::before {
  content: "\f005 \f005 \f005 \f005 \f005";
}

.rating-count {
  color: var(--ink-muted);
  font-size: 13px;
}

/* ==========================================================================
   Extension catalog card (src/extensions/card.php)
   Shared by the extensions catalog (extensions/body.php + the four category
   pages) and the homepage's "Top Rated Extensions" section (src/index.php) -
   both render this same partial, so its styling lives here rather than in
   either page. Added in Task 7.
   ========================================================================== */

.ext-card {
  display: flex;
}

.ext-card .card {
  width: 100%;
  display: flex;
  flex-direction: column;
}

/* Fixed height + flex centring, not just text-align on an auto-height box:
   logos vary in their own natural width/height (up to max-height below), so
   auto-height centring left each one sitting at a different vertical
   position from its neighbours in the same grid row. A shared box height
   with flex centring lines every logo up on the same centre line
   regardless of its own dimensions. */
.ext-card__media {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 160px;
  padding: var(--space-5);
}

.ext-card__logo {
  max-width: 100%;
  max-height: 96px;
  border-radius: var(--radius-sm);
}

/* Matches .badge-popular's prominence (uppercase, wider padding, subtle
   lift) rather than the small inline .badge base — these sit alone on a
   card's artwork, not beside body copy, so they can afford to read as a
   real badge rather than a small pill. Declared here (not on .badge-new/
   .badge-success/.badge-neutral themselves) because those base classes are
   also reused as small inline "eyebrow" labels elsewhere on the site
   (e.g. above section headings) that should stay compact. */
.ext-card__badge {
  position: absolute;
  top: var(--space-3);
  z-index: 2;
  padding: 5px 14px;
  font: 700 11px/1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: .04em;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
}

.ext-card__badge--left {
  left: var(--space-3);
}

.ext-card__badge--right {
  right: var(--space-3);
}

/* Card outline echoing the "New"/"Blesta 6" badge colour — same 2px accent
   border language as .card-pricing--popular, so a stand-out card reads as
   stand-out even at a glance, not just via the small corner badge.
   .ext-card--new/.ext-card--v6 are applied directly on the same element as
   .card (card.php's outer .card div), not an ancestor of it — compound
   selectors, not descendant ones. */
.card.ext-card--new {
  border: 2px solid var(--accent);
}

.card.ext-card--v6 {
  border: 2px solid var(--success);
}

/* Both New and Blesta-6-compatible at once: a single border can only carry
   one colour per side, so split it — top/left in the "New" accent,
   bottom/right in the "Blesta 6" success colour — rather than letting one
   status silently win. Both badges still render too (left/right corners,
   per the markup), this just extends the same signal to the frame. */
.card.ext-card--new.ext-card--v6 {
  border-width: 2px;
  border-style: solid;
  border-top-color: var(--accent);
  border-left-color: var(--accent);
  border-right-color: var(--success);
  border-bottom-color: var(--success);
}

.ext-card__body {
  padding: var(--space-4) var(--space-5) 0;
  flex: 1;
}

.ext-card__category {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
  font: 700 .75rem/1.2 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin: 0 0 var(--space-2);
}

.ext-card__title {
  font-size: 1.125rem;
  margin: 0 0 var(--space-2);
}

.ext-card__title a {
  color: var(--ink-strong);
}

.ext-card__title a:hover {
  color: var(--accent);
}

.ext-card__rating {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 var(--space-3);
}

.ext-card__description {
  color: var(--ink-soft);
  font-size: .9375rem;
  line-height: 1.6;
  margin: 0 0 var(--space-4);
}

.ext-card__compat {
  color: var(--ink-muted);
  font-size: .8125rem;
  margin: 0 0 var(--space-4);
}

.ext-card__footer {
  padding: var(--space-4) var(--space-5) var(--space-5);
  text-align: center;
  border-top: 1px solid var(--line);
  margin-top: auto;
}

.ext-card__price {
  color: var(--ink-strong);
  font-weight: 700;
  margin: 0 0 var(--space-3);
}

.ext-card__price--free {
  color: var(--success);
}

.ext-card__currencies {
  display: block;
  margin: var(--space-2) auto 0;
  font-size: .8125rem;
}

/* ==========================================================================
   Tooltip
   ========================================================================== */

[data-tooltip] {
  position: relative;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  background: var(--ink-strong);
  color: var(--ink-inverse);
  font-size: 12px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  /* Capped + wrapping, not nowrap: this is a generic attribute-driven
     tooltip, and at least one real use (a gateway card's "Supported
     Currencies" list) can hold well over a hundred comma-separated codes
     (Stripe Universal: 136). opacity: 0 below still leaves the element
     occupying real layout space, and nowrap on unbounded content
     stretched some of these to 1000s of px wide - invisible, but wide
     enough to blow out the whole page's scrollWidth and give every page
     a horizontal scrollbar. A capped max-width wraps long ones onto
     several lines instead, and none can inflate layout further than this.
     width: max-content is required alongside max-width, not just the
     latter alone: an absolutely-positioned element with only one of
     left/right set (this one only sets "left", centring via transform
     instead of a matching "right") gets a shrink-to-fit auto width from
     the browser, which for wrappable text resolved to the width of its
     single longest unbreakable "word" (~77px, one "XXX," per line) - not
     up to max-width. width: max-content asks for the content's natural
     (unwrapped) width instead, which max-width then clamps, giving the
     "fill out to max-width, then wrap" sizing actually wanted here. */
  width: max-content;
  max-width: min(190px, 90vw);
  white-space: normal;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard);
  z-index: var(--z-dropdown);
}

[data-tooltip]:hover::after,
[data-tooltip]:focus::after {
  opacity: 1;
}

/* ==========================================================================
   Comparison table (base visual rules; structure is fixed per-page in
   later tasks — the sticky offset is supplied inline per-page since it
   depends on navbar/sale-bar height there).
   ========================================================================== */

/* Base table rules (width/border-collapse/cell padding) - Added in Task 5:
   the brief attributes the full "comparison-table base styles" to Task 4,
   but these foundational rules weren't part of Task 4's delivered scope -
   filling the gap now so any page adding a bare <table class="table-comparison">
   (with or without the section-row/sticky-header structure below) renders
   sensibly. */
.table-comparison {
  width: 100%;
  border-collapse: collapse;
}

/* Horizontal-scroll container for .table-comparison on narrow viewports -
   Added in Task 6, same "gap-closing, not redesign" rationale as the rest
   of this section: any page wrapping a bare <table class="table-comparison">
   needs this to make the sticky row-label column below make sense. */
.table-comparison-wrapper {
  overflow-x: auto;
}

.table-comparison thead th,
.table-comparison .table-comparison__section-row th,
.table-comparison .table-comparison__section-row td {
  background: var(--bg-surface-alt);
  color: var(--ink-strong);
  font-weight: 700;
  position: sticky;
  /* Above the plain row-label column (z-index 1 below) when a sticky-top
     row scrolls past a sticky-left cell in a different table row. */
  z-index: 2;
}

/* Row-label column (first cell of the header row and every data row,
   including section-divider rows - see the markup comment on why those use
   one empty <td> per tier column instead of a single colspanned cell) -
   pinned while the tier columns scroll horizontally. Added in Task 6 to
   close the gap the brief flagged: the base rules above style *what* the
   sticky cells look like, but nothing previously made the leftmost column
   stick horizontally. Lower specificity than the two selectors above on
   purpose, so a header-row label cell (matched by both) keeps the header's
   --bg-surface-alt background instead of this rule's --bg-page. */
.table-comparison__row-label {
  position: sticky;
  left: 0;
  background: var(--bg-page);
  z-index: 1;
}

/* A row-label cell inside thead or a section-row also matches the base
   sticky rule above (higher specificity, so its z-index: 2 wins there,
   not this class's z-index: 1) - which ties it with that same row's other
   cells (also z-index: 2), and CSS resolves stacking ties by DOM order, so
   a later sibling cell's opaque background paints over the label's text
   during horizontal scroll. Break the tie explicitly. */
.table-comparison thead th.table-comparison__row-label,
.table-comparison__section-row th.table-comparison__row-label {
  z-index: 3;
}

.table-comparison th,
.table-comparison td {
  padding: var(--space-2) var(--space-3);
  text-align: left;
  border-bottom: 1px solid var(--line);
  border-left: none;
  border-right: none;
  border-top: none;
  /* Browsers bold <th> by default, and every data row's row-label cell is
     a <th scope="row"> (for accessibility) — with no author rule resetting
     it, every row down the left edge rendered bold, not just the real
     header/section rows. Those get their bold back from the more specific
     "thead th, .table-comparison__section-row th/td" rule below, which
     wins the cascade over this lower-specificity one. */
  font-weight: 400;
}

.table-comparison .icon-check {
  color: var(--success);
}

.table-comparison .icon-cross {
  /* De-emphasised, not --danger — "not included" isn't an error. */
  color: var(--line-strong);
}

/* Optional extra-cost add-on (e.g. Club Select's Blesta licence add-on) -
   distinct from both icon-check ("included") and icon-cross ("not
   included"). fa-adjust (half-filled circle) is the conventional
   "partial/conditional" glyph in feature-comparison tables - a plain "~"
   character sits far smaller than the fa-check/fa-times glyphs beside it,
   so an actual FA icon is used to match their visual weight. Pairs with
   the generic [data-tooltip] pattern above to explain the add-on on
   hover/focus; cursor: help signals it's non-standard without needing a
   native title="" tooltip alongside it. */
.table-comparison .icon-optional {
  color: var(--warning);
  cursor: help;
}

.table-comparison .text-user-choice {
  color: var(--accent);
  font-style: italic;
  font-weight: 600;
}

.table-comparison .table-comparison__col--emphasis {
  background: var(--accent-soft);
}

/* --accent-soft is translucent (rgba) by design — fine for an emphasis tint
   sitting on the page background, but the emphasised tier's column also
   passes through the sticky header/section rows above, where a translucent
   background lets whatever has scrolled underneath show through the "solid"
   header. Give sticky contexts an opaque, solid-blended equivalent instead;
   higher specificity than the plain rule above, so only sticky cells are
   affected — ordinary body-row emphasis cells keep the translucent tint. */
.table-comparison thead th.table-comparison__col--emphasis,
.table-comparison .table-comparison__section-row th.table-comparison__col--emphasis,
.table-comparison .table-comparison__section-row td.table-comparison__col--emphasis {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-surface-alt));
}

/* ==========================================================================
   Prose / .card-markdown (README + blog content)
   ========================================================================== */

.card-markdown {
}

.card-markdown p {
  font-size: 1rem;
  line-height: 1.7;
  color: var(--ink);
  margin: 0 0 1em;
}

.card-markdown figure,
.card-markdown table,
.card-markdown hr,
.card-markdown pre,
.card-markdown blockquote,
.card-markdown h1,
.card-markdown h2,
.card-markdown h3,
.card-markdown h4,
.card-markdown h5,
.card-markdown h6 {
  clear: both;
}

.card-markdown h1,
.card-markdown h2,
.card-markdown h3,
.card-markdown h4,
.card-markdown h5,
.card-markdown h6 {
  color: var(--ink-strong);
  margin: 1.6em 0 .5em;
}

.card-markdown a {
  color: var(--accent);
}

/* reset.css zeroes ul/ol padding globally (needed elsewhere - nav menus,
   card lists, etc. all supply their own layout). Markdown content never
   overrides that, so with list-style-position's default of "outside" and
   nowhere to put it, every bullet/number rendered hanging out past the
   left edge of the surrounding paragraph text instead of sitting indented
   above it. */
.card-markdown ul,
.card-markdown ol {
  padding-left: 1.5em;
}

.card-markdown li + li {
  margin-top: .35em;
}

.card-markdown ul ul,
.card-markdown ul ol,
.card-markdown ol ul,
.card-markdown ol ol {
  margin-top: .35em;
}

.card-markdown li::marker {
  color: var(--ink-muted);
}

.card-markdown code {
  background: var(--accent-soft);
  color: var(--ink-strong);
  padding: 2px 6px;
  border-radius: 4px;
}

.card-markdown pre {
  background: var(--bg-inverse);
  color: var(--ink-inverse-soft);
  border-radius: 8px;
  padding: 16px;
  overflow-x: auto;
}

.card-markdown pre code {
  background: transparent;
  color: inherit;
  padding: 0;
  border-radius: 0;
}

.card-markdown blockquote {
  border-left: 3px solid var(--accent);
  background: var(--bg-surface-alt);
  padding: 12px 20px;
  color: var(--ink-soft);
  font-style: italic;
}

.card-markdown table {
  border-color: var(--line);
  border-collapse: collapse;
  width: 100%;
}

.card-markdown table td,
.card-markdown table th {
  border: 1px solid var(--line);
  padding: var(--space-2) var(--space-3);
  text-align: left;
}

.card-markdown table th {
  background: var(--bg-surface-alt);
  font-weight: 600;
}

.card-markdown img {
  max-width: 100%;
  border-radius: var(--radius-sm);
}

.card-markdown hr {
  border-color: var(--line);
  width: 100%;
}

/* ---- Rich-text editor content classes (figures, image styles, font
   sizes, highlights) - values match the editor's own defaults. ---- */

.card-markdown figure {
  margin: 1.5em 0;
}

.card-markdown figure img {
  display: block;
  max-width: 100%;
  margin: 0 auto;
}

.card-markdown figure.table {
  overflow-x: auto;
}

.card-markdown figure.table table {
  margin: 0;
}

.card-markdown figcaption {
  color: var(--ink-soft);
  font-size: .875rem;
  text-align: center;
  margin-top: .5em;
}

.card-markdown .image-style-side {
  float: right;
  max-width: 50%;
  margin: 0 0 1em 1.5em;
}

.card-markdown .text-tiny {
  font-size: .7em;
}

.card-markdown .text-small {
  font-size: .85em;
}

.card-markdown .text-big {
  font-size: 1.4em;
}

.card-markdown .text-huge {
  font-size: 1.8em;
}

.card-markdown mark.marker-yellow {
  background-color: #fdfd77;
}

.card-markdown mark.marker-green {
  background-color: #62f962;
}

.card-markdown mark.marker-pink {
  background-color: #fc7999;
}

.card-markdown mark.marker-blue {
  background-color: #72ccfd;
}

.card-markdown .pen-red {
  color: #e71313;
}

.card-markdown .pen-green {
  color: #12a800;
}

.card-markdown .oembed-video {
  position: relative;
  aspect-ratio: 16 / 9;
  margin: 1.5em 0;
}

.card-markdown .oembed-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: var(--radius-sm);
}

/* ==========================================================================
   Shared page-layout families
   ==========================================================================
   The recipes below were duplicated verbatim across ~13 pages' page-scoped
   <style>/$head blocks during the Warm Refresh conversion. That duplication
   was originally deliberate: the obvious shared class name ("page-header-mini")
   collided with a legacy custom.css rule whose padding stacked rather than
   overrode. Task 15 deleted that legacy rule, so the justification is gone -
   and the duplication had by then caused two real bugs (.section-intro was
   used but never defined on /access and the extension detail page; the
   /extensions/<unknown-key> path renders 404/body.php from a controller whose
   $head has none of 404/index.php's CSS). One canonical copy each, here,
   loaded globally, fixes both.
   ========================================================================== */

/* ---- Reduced-height page header (hero-mini) -----------------------------
   Used by /access, /about, /blesta, /dev, /faq, /managed, /privacy, /terms,
   /404, the extensions catalog and the extension detail page. */

.hero-mini {
  background: linear-gradient(135deg, var(--bg-sunken), var(--bg-page));
  padding: var(--space-8) var(--space-4) var(--space-7);
}

.hero-mini .lead {
  max-width: 640px;
  margin: var(--space-3) auto 0;
}

/* Accent-coloured phrase inside the headline (/dev, /managed) - matches the
   homepage's .hero__headline span treatment. */
.hero-mini h1 span {
  color: var(--accent);
}

.hero-mini__ctas {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

/* ---- Centred section intro (eyebrow + heading + lead) ------------------- */

.section-intro {
  max-width: 640px;
  margin: 0 auto;
}

/* ---- Alternating image/text rows ---------------------------------------
   feature-row--reverse flips which column the image sits in on desktop;
   markup order already puts content first for that row, so nothing changes
   below the breakpoint. Used by /about, /blesta, /managed. */

.feature-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  align-items: center;
}

@media (min-width: 992px) {
  .feature-row {
    grid-template-columns: 1fr 1fr;
  }

  .feature-row--reverse .feature-row__media {
    order: 2;
  }
}

.feature-row__media img {
  width: 100%;
}

.feature-row__content h3 {
  margin: var(--space-4) 0 var(--space-3);
}

.feature-row__content p {
  color: var(--ink-soft);
  font-size: 1rem;
  line-height: 1.7;
  margin: 0;
}

/* ---- Licence teaser: one priced card beside one descriptive card --------
   Used by the homepage's "Blesta Licences" section and /managed. */

.licence-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  align-items: stretch;
}

@media (min-width: 768px) {
  .licence-grid {
    grid-template-columns: 5fr 7fr;
  }
}

.licence-list {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  text-align: left;
}

.licence-list li {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
}

/* ---- Contact section (homepage #contact, /dev, /managed) ---------------- */

.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-6);
  text-align: center;
}

.contact-card__title {
  margin: var(--space-4) 0 var(--space-2);
}

.contact-card__link {
  color: var(--accent);
  font-weight: 600;
  font-size: .9375rem;
}

/* ---- 404 category-shortcut cards ---------------------------------------
   The card itself is the feature-grid-item + icon-chip recipe above; this
   just makes the whole card an unstyled <a>. Shared (not page-scoped to
   404/index.php) because 404/body.php is also rendered by other controllers
   - /extensions/<unknown-key> and /blog/<unknown-slug>. */

.not-found-card {
  display: block;
  padding: var(--space-6);
  text-decoration: none;
  color: inherit;
}
