/* ============================================================
   Kataja Basket — foundation layer

   Reset, element defaults, keyframes and the accessibility helpers.
   Everything that is *designed* lives in Bricks as global classes and
   global variables; this file only holds what the builder has no control
   for. Design tokens (--blue, --ink, --font-display …) are Bricks global
   variables and are referenced, never defined, here.
   ============================================================ */

/* ---- Reset ------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The header is fixed (68px), so an in-page link -- #aitiot,
     #aitiovaraus, #katsomokartta -- would otherwise land under it. */
  scroll-padding-top: 5rem;
  color-scheme: dark;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  background: var(--ink);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* set while the mobile drawer is open */
body.is-locked { overflow: hidden; }

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }

/* Bricks' frontend gives every paragraph a bottom margin:
   `:where(p){margin-block:0 1.2em}`. In this layout a <p> is almost always a
   UI line -- a date, a venue, a score -- spaced by its container's gap, and
   the stray margin stayed invisible only because Bricks also zeroes
   `p:last-of-type` and every container so far happened to hold exactly one
   paragraph. A second one in the same box pushes the whole column down.

   The rhythm is wanted in rich text and nowhere else, so it is switched off
   here and switched back on inside .prose. `:last-child` rather than Bricks'
   `:last-of-type`: a paragraph followed by a list still needs its margin. */
.brx-body p { margin-block: 0; }
.brx-body .prose p { margin-block: 0 1.2em; }
.brx-body .prose p:last-child { margin-block-end: 0; }

a { color: inherit; text-decoration: none; }

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }

ul, ol { list-style: none; padding: 0; }

:focus-visible {
  outline: 2px solid var(--blue-hot);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection { background: var(--blue-lift); color: #fff; }

/* ---- Display type ------------------------------------------ */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 0.9;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  text-wrap: balance;
}

/* Bricks renders the page in .brx-body; it must not clip the fixed header
   or trap the hero's 100svh. */
.brx-body { overflow-x: clip; }

/* ---- Bricks structural adjustments ------------------------- */
/* Bricks renders every image inside a <figure class="brxe-image">. These
   containers were authored against a bare <img> and size the photo with
   height:100%, max-height or a flex rule, so the wrapper has to carry that
   sizing through to the image.

   `display: contents` looks like the tidy answer and is a trap: an element
   with no box is never reported by IntersectionObserver, so Bricks' lazy
   loader never swaps data-src and every photo stays a blank placeholder.
   The wrapper therefore keeps its box and is stretched to fit instead. */
.hero__bg > .brxe-image,
.post__media > .brxe-image,
.offer__media > .brxe-image,
.partner > .brxe-image {
  display: block;
  width: 100%;
  height: 100%;
}

/* The logos are centred inside a fixed-height cell, and the <img> resolves
   its max-height against this wrapper. A linked logo sits one level deeper,
   inside .partner__link, and needs the same pass-through there or the image
   sizes against an auto height and blows the row open. */
.partner > .brxe-image,
.partner__link > .brxe-image {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}

/* The arena photo positions itself against .arena__media, so the wrapper only
   needs to stay out of the way. */
.arena__media > .brxe-image { display: block; }

/* Pagination markup is generated by Bricks, so the builder has no element to
   style — only the .news-pager wrapper around it is a real element. */
.news-pager .bricks-pagination ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.news-pager .bricks-pagination a,
.news-pager .bricks-pagination span {
  display: grid;
  align-items: center;
  justify-items: center;
  min-width: 44px;
  height: 44px;
  padding-inline: 0.75rem;
  border-radius: var(--r-sm);
  font-family: var(--font-display);
  font-size: 0.9375rem;
  letter-spacing: 0.06em;
  color: var(--text-soft);
  box-shadow: inset 0 0 0 1px var(--surface-line);
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.news-pager .bricks-pagination a:hover,
.news-pager .bricks-pagination .current {
  color: #fff;
  background: var(--blue);
  box-shadow: inset 0 0 0 1px var(--blue);
}

.news-pager .bricks-pagination .dots {
  box-shadow: none;
  color: var(--text-faint);
}

/* A query loop renders one card template for every match, and Bricks cannot
   add a class conditionally — so the home-game treatment keys off the Koti
   tag that the element condition actually rendered.

   The resting background is named as well as painted, because a card that is
   not a link has to put it back when .fix:hover tries to wash over it, and
   the gradient should be written once. Bricks' own .fix rule paints the
   plain one; --fix-bg only has to agree with it. */
.fix {
  --fix-bg: var(--surface-raised);
}

.fix:has(.fix__tag--home) {
  --fix-bg: linear-gradient(180deg, var(--tint), transparent 62%), var(--surface-raised);
  background: var(--fix-bg);
}

.fix:has(.fix__tag--home)::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.4rem;
  bottom: 1.4rem;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
}

/* The fixture line is the card's link — Bricks' Text element becomes an <a>
   as soon as one is set — and this stretches that anchor over the whole card.
   The layout's fixture cards were static markup and had no link to cover, so
   the rule only exists once the query loop gives each card a match page. */
.fix__teams::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* A fixture whose match page has nothing on it is not a link. Bricks still
   renders the Text element as an <a> — that is not configurable — but
   {kataja_match_url} resolves empty and Bricks then writes no href, which
   leaves an inert <a> the browser treats as ordinary text. What is left is
   the affordances, and those have to go with it. Both places the fixture
   line appears are covered: the rail cards on the front page and the rows
   in the season archive. */
.fix__teams:not([href])::after {
  content: none;
}

/* Chrome's UA sheet gives a bare <a> a pointer even with no href, which is
   the one affordance the missing href does not take away by itself. */
.fix__teams:not([href]) {
  cursor: auto;
}

/* The card's hover is a lift, a blue ring and a wash — the loudest "this
   goes somewhere" on the front page. Cancelling it means restating the three
   resting values; only the background varies, which is what --fix-bg holds. */
.fix:has(.fix__teams:not([href])):hover {
  background: var(--fix-bg);
  transform: none;
  box-shadow: inset 0 0 0 1px var(--surface-line);
}

.mrow:has(.fix__teams:not([href])):hover {
  background: none;
}

/* The opponent crest is a Bricks Image, so it arrives wrapped in a <figure>
   that has to pass the sizing through — see the note above .hero__bg. */
.match-head__logo > .brxe-image {
  display: block;
  width: 100%;
  height: 100%;
}

/* The next-game card's action box holds only conditional elements: "Osta
   liput" and "Varaa aitio" on home games only, and "Ottelusivulle" only while
   the match page carries a preview, a report or a body. An away game with
   nothing written yet has none, so Bricks leaves the box out — and the card's third grid
   column would still claim the 48px gap, standing the countdown that far
   short of the card's edge. Same species of rule as .fix__teams::after: it
   only exists because the elements are conditional, which the static layout's
   cards never were. */
@media (min-width: 1181px) {
  .nextgame__grid:not(:has(.nextgame__cta)) {
    grid-template-columns: minmax(0, 1fr) auto;
  }
}

/* ---- Accessibility helpers --------------------------------- */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: var(--edge); top: -100px;
  z-index: 200;
  background: var(--blue-lift);
  color: #fff;
  padding: 0.75rem 1.25rem;
  border-radius: var(--r-sm);
  font-weight: 600;
  transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus { top: 1rem; }

/* Motion durations are Bricks global variables; this is the one override the
   builder has no control for, so it is collapsed here instead. */
@media (prefers-reduced-motion: reduce) {
  :root { --dur: 0.01ms; --dur-fast: 0.01ms; }
}

/* ---- Keyframes --------------------------------------------- */
/* Referenced by Bricks global classes; keyframes have no builder control,
   so they are declared once here. */
@keyframes heroRise {
  from { opacity: 0; transform: translateY(0.24em); }
  to   { opacity: 1; transform: none; }
}

@keyframes pulse {
  70%  { box-shadow: 0 0 0 11px rgba(75, 60, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(75, 60, 255, 0); }
}

/* ---- Nodes that cannot be Bricks elements ------------------ */
/* Two kinds of markup end up here. Inline nodes -- the season label inside a
   section heading, the "vs" inside the fixture title, the arrow <svg> inside a
   button -- live inside another element's rich text, so the builder has no
   element to hang a global class on. The countdown cells do not exist at all
   until the script builds them. Both are styled in code by necessity, not by
   choice; everything the builder *can* own is a Bricks global class. */
.sec-head__count {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  color: var(--text-faint);
  text-transform: none;
  font-weight: 400;
}

.btn__arrow {
  transition: transform var(--dur-fast) var(--ease);
  flex: none;
}

/* The news listing's row arrow, same story: an <svg>'s class stays inside the
   markup during conversion, so no element carries it at import time. */
.news-item__go {
  flex: none;
  color: var(--accent);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}

@media (max-width: 700px) {
  .news-item__go {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
    opacity: 1;
    transform: none;
  }
}

/* The fixture archive's row arrow, same story as .news-item__go: the class
   sits on an <svg> in the markup, so no element carries it when the converter
   builds the document and a rule for it would have nowhere to attach. */
.mrow__go {
  grid-column: 4;
  flex: none;
  color: var(--accent);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}

/* Only on a row that leads somewhere. The arrow keeps its column either way:
   .mrow__score sits in column 3 and each row is its own grid, so taking
   column 4 off some rows would step the score column down the season. */
.mrow:has(.fix__teams[href]):hover .mrow__go {
  opacity: 1;
  transform: none;
}

/* The row already stacks on a phone; a hover affordance there is dead weight
   and the arrow would fight the score for the second column. */
@media (max-width: 760px) {
  .mrow__go { display: none; }
}

/* The season links do not exist until kataja_season_nav_html() draws them,
   so there is no Bricks element to hang a global class on. */
.seasons__link {
  display: inline-flex;
  align-items: center;
  padding: 0.45rem 0.8rem;
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.12em;
  color: var(--text-soft);
  box-shadow: inset 0 0 0 1px var(--surface-line);
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.seasons__link:hover {
  color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.seasons__link.is-current {
  color: #fff;
  background: var(--blue);
  box-shadow: inset 0 0 0 1px var(--blue);
}

/* The fan shop tiles, same story: kataja_shop_products_html() draws them from
   the products read off Tukkutiimi (inc/fan-shop.php). */
.shop-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem) clamp(0.75rem, 1.4vw, 1.25rem);
}

/* Four across a phone is four stamps; two rows of two stay legible. */
@media (max-width: 700px) {
  .shop-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.shop-item__link {
  display: grid;
  gap: 0.3rem;
  color: var(--text);
}

/* Tukkutiimi's photos are on pure white and come in any shape: the cap is
   landscape, the shirts portrait. A square paper tile with the photo fitted
   inside gives four different crops one outline, and multiply drops the white
   so the garment sits on the paper instead of on a white box. */
.shop-item__media {
  display: block;
  aspect-ratio: 1;
  margin-bottom: 0.55rem;
  padding: 11%;
  border-radius: var(--r);
  background: #fff;
  overflow: hidden;
  transition: box-shadow var(--dur-fast) var(--ease);
}

.shop-item__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  mix-blend-mode: multiply;
  transition: transform var(--dur) var(--ease);
}

.shop-item__link:hover .shop-item__media,
.shop-item__link:focus-visible .shop-item__media {
  box-shadow: inset 0 0 0 2px var(--blue-lift);
}

.shop-item__link:hover .shop-item__media img,
.shop-item__link:focus-visible .shop-item__media img {
  transform: scale(1.05);
}

.shop-item__name {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.shop-item__price {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.12em;
  color: var(--text-soft);
}

/* No products to show -- only possible if Tukkutiimi was unreachable on the
   very first fetch. The lift is then its copy and button alone, full width,
   rather than a card with an empty half. */
.shop:not(:has(.shop-grid)) { grid-template-columns: minmax(0, 1fr); }
.shop__products:empty { display: none; }

/* The arena page's box photos fill whatever the offer card has left once it
   is stretched to the booking card's height, so the two cards end on one
   line at every width. The strip is Bricks' Image Gallery markup, so it is
   styled here. Stacked, at 900px and down, the gallery keeps its own 4:3. */
@media (min-width: 901px) {
  .box__media {
    display: flex;
    flex: 1 1 auto;
    min-height: 11rem;
  }

  .box-gallery { flex: 1; grid-template-rows: 1fr; }
  .box-gallery .bricks-layout-item { position: relative; }

  .box-gallery figure {
    position: absolute;
    inset: 0;
    margin: 0;
  }

  .box-gallery a,
  .box-gallery img {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
  }
}

/* The arena page's box booking request is a Bricks Form element
   (dynamic-content.php), and its fields, labels and button are drawn by the
   element, so none of them is there to carry a global class. Bricks' own form
   rules are all :where(), so these win without !important. */
.booking-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}

.booking-form .form-group {
  grid-column: 1 / -1;
  padding: 0;
}

/* The first six fields sit two by two: name and company, email and phone,
   box type and match (the order is set in dynamic-content.php). */
.booking-form .form-group:nth-child(-n+6) { grid-column: auto; }

@media (max-width: 600px) {
  .booking-form .form-group:nth-child(-n+6) { grid-column: 1 / -1; }
}

/* The field's own label only -- a <label>, or a div.label for the radio
   group. The options' labels sit inside .options-wrapper and stay body text. */
.booking-form .form-group > :is(label, .label) {
  margin-bottom: 0.45rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.booking-form .required::after { color: var(--accent); }

.booking-form input:not([type=radio]),
.booking-form textarea {
  width: 100%;
  height: auto;
  padding: 0.7rem 0.9rem;
  background: var(--surface);
  border: 1px solid var(--surface-line);
  border-radius: var(--r-sm);
  font-size: 1rem;
  line-height: 1.4;
  color: var(--text);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.booking-form textarea { min-height: 5.5rem; }

.booking-form ::placeholder { color: var(--text-faint); opacity: 1; }

.booking-form input:not([type=radio]):focus,
.booking-form textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.booking-form .options-wrapper {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  min-height: 2.9rem;
  align-items: center;
}

.booking-form .options-wrapper li { align-items: center; }

/* Drawn from the surface tokens: the site's dark color-scheme paints a native
   unchecked radio as a solid dark dot, even on the paper band. */
.booking-form input[type=radio] {
  appearance: none;
  display: grid;
  place-content: center;
  flex: none;
  top: 0;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  background: var(--surface-raised);
  border: 1px solid var(--rule-strong);
  border-radius: 50%;
  cursor: pointer;
}

.booking-form input[type=radio]::before {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--accent);
  transform: scale(0);
  transition: transform var(--dur-fast) var(--ease);
}

.booking-form input[type=radio]:checked { border-color: var(--accent); }
.booking-form input[type=radio]:checked::before { transform: scale(1); }

/* .btn's treatment; the submit button is Bricks' own <button>. */
.booking-form .submit-button-wrapper {
  justify-self: start;
  margin-top: 0.25rem;
}

.booking-form button[type=submit] {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  padding: 0.95rem 1.6rem;
  background: var(--button);
  color: var(--button-fg);
  border-radius: var(--r-sm);
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  transition: background var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.booking-form button[type=submit]:hover {
  background: var(--button-hover);
  transform: translateY(-2px);
  box-shadow: 0 10px 30px -10px rgba(42, 28, 232, 0.8);
}

/* The thank-you (and the error, if the send fails) replaces Bricks' green and
   red slabs with the page's own tint. */
.booking-form .message {
  grid-column: 1 / -1;
  padding: 0;
  background: var(--chip);
  color: var(--text);
  border-radius: var(--r-sm);
}

.booking-form .message.error { background: rgba(198, 40, 40, 0.1); }

/* Surface tokens, not --blue-hot / --fg-faint: the front page's next-game
   card is a light island on the hero and the match page's head is dark, and
   both draw this "vs" and the countdown below. */
.nextgame__vs {
  font-size: 0.42em;
  font-family: var(--font-mono);
  font-weight: 400;
  letter-spacing: 0.14em;
  color: var(--accent);
  text-transform: uppercase;
}

/* Bricks exposes row/column gap only on its layout elements (container,
   section, div, block). A Heading or Text element can be display:flex but has
   nowhere to store a gap, so the converter drops it. These rows are laid out
   as flex on purpose, so the gaps are declared here. */
.sec-head__title { gap: 0.6em; }
/* The season picker is a Text element holding kataja_season_nav_html()'s
   markup, so it lost both of these: twelve seasons in one unwrapped row ran
   457px past the right edge of a phone. */
.seasons { gap: 0.5rem; flex-wrap: wrap; }
.homegame__teams { gap: 0.4em; flex-wrap: wrap; }
.homegame__venue { gap: 0.45em; }
.nextgame__label { gap: 0.55em; }
.nextgame__teams { gap: 0.4em; flex-wrap: wrap; }
.nextgame__meta { gap: 0.5rem 1.35rem; flex-wrap: wrap; }
.nextgame__meta span { gap: 0.45em; }
.fix__venue { gap: 0.45em; }
.match-meta__item { gap: 0.5em; }
.post__kicker { gap: 0.75rem; }
.drawer__link { gap: 0.9rem; }

/* Built by the countdown module once it has a target date. */
.countdown__unit { text-align: center; min-width: 3.1rem; }

.countdown__num {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.65rem, 2.8vw, 2.35rem);
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}

.countdown__word {
  display: block;
  margin-top: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ---- Scroll reveal ----------------------------------------- */
/* The hidden starting state is gated behind .js, which the inline head
   script only sets once it knows the bundle can run. Content must never
   depend on JavaScript to be visible. */
.reveal {
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

.js .reveal { opacity: 0; transform: translateY(22px); }
.js .reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; }
}

/* The builder must never hide elements behind an un-fired reveal. */
.bricks-is-frontend.iframe .reveal,
body.brx-body.iframe .reveal { opacity: 1 !important; transform: none !important; }
