/* ============================================================
   moxxsc.com front page.

   Blue throughout, in three tones the reader can switch between:
   dark (the default), middle and light. Pastel blue carries
   links, fills and active state; the ground and text shift
   with the theme. Structure still follows the Half-Life 2 site,
   only opened up. Roboto throughout, set larger and mostly in
   sentence case; uppercase is kept for the small section labels
   and nothing else.

   Colour lives only in the theme blocks below, so a new rule
   reaches for a token, never a hex value. Radius, timing and
   letter spacing run off the shared tokens after them.
   ============================================================ */

/* dark: the default, and what shows before any choice is stored */
:root {
  color-scheme: dark;

  --bg:          #0e1622;
  --panel:       #16212f;
  --panel-2:     #1d2a3b;
  --line:        #233246;
  --line-2:      #2f4560;

  --text:        #e4ecf6;
  --text-2:      #b8c6d8;
  --dim:         #8c9db3;

  --accent:      #8dbbf0;  /* links and the active edge */
  --accent-2:    #b3d3f7;
  --accent-soft: #213a5a;  /* fill behind active and hovered items */
  --on-accent:   #0e1622;  /* text set on a solid --accent */
  --label:       #8db2dc;

  /* --bg at reading opacity, for labels laid over artwork */
  --veil:        rgba(14, 22, 34, 0.86);

  --shadow:      0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.25);
  --shadow-sm:   0 1px 2px rgba(0, 0, 0, 0.25);

  /* the corner tag on a project tile sits on artwork, not on the page, so
     it keeps this one look in every theme; the others never override it */
  --tag-bg:      rgba(14, 22, 34, 0.58);
  --tag-line:    #b3d3f7;
  --tag-text:    #f1f5fb;
  --tag-shadow:  0 1px 4px rgba(0, 0, 0, 0.4);
  --shade:       rgba(8, 12, 20, 0.78);  /* foot of a picture, under its label */
}

/* middle: dusk slate, light text on a mid blue ground */
:root[data-theme="middle"] {
  color-scheme: dark;

  --bg:          #2c3c52;
  --panel:       #364963;
  --panel-2:     #3f5470;
  --line:        #455b78;
  --line-2:      #577191;

  --text:        #f1f5fb;
  --text-2:      #d4dfec;
  --dim:         #bacadd;

  --accent:      #a8ccf5;
  --accent-2:    #cbe1fa;
  --accent-soft: #3f5b82;
  --on-accent:   #1b2738;
  --label:       #a9c6e6;

  --veil:        rgba(44, 60, 82, 0.88);

  --shadow:      0 1px 2px rgba(10, 20, 35, 0.2), 0 8px 24px rgba(10, 20, 35, 0.18);
  --shadow-sm:   0 1px 2px rgba(10, 20, 35, 0.18);
}

/* light: white cards on a pale sky wash */
:root[data-theme="light"] {
  color-scheme: light;

  --bg:          #eef4fb;
  --panel:       #ffffff;
  --panel-2:     #e4eef9;
  --line:        #d8e4f1;
  --line-2:      #bdd1e7;

  --text:        #1d2b3c;
  --text-2:      #3a4d63;
  --dim:         #5b6d83;

  --accent:      #2e6aac;  /* deeper than the pastels so links still read */
  --accent-2:    #1f5390;
  --accent-soft: #d3e4f7;
  --on-accent:   #ffffff;
  --label:       #3f6a96;

  --veil:        rgba(238, 244, 251, 0.9);

  --shadow:      0 1px 2px rgba(29, 43, 60, 0.06), 0 6px 20px rgba(46, 106, 172, 0.08);
  --shadow-sm:   0 1px 2px rgba(29, 43, 60, 0.05);
}

:root {
  /* two steps only: controls take the tight one, surfaces the wider */
  --r-1:         6px;
  --r-2:         10px;

  --t:           160ms;  /* hover and state changes */
  --fade:        420ms;  /* shot to shot dissolve */
  --move:        420ms;  /* things that travel: pages, pills, tiles */
  --ease:        cubic-bezier(.2, .7, .2, 1);  /* quick off the mark, soft landing */

  /* the section labels are the only uppercase left, and need the tracking */
  --ls-label:    0.08em;
}

* { box-sizing: border-box; }

/* short pages must not sit wider than tall ones, so the gutter is always held */
html {
  scrollbar-gutter: stable;
  overflow-y: scroll;
}

body {
  margin: 0;
  padding: 34px 24px 48px;
  background: var(--bg);
  color: var(--text-2);
  font-family: 'Roboto', Arial, sans-serif;
  font-size: 17px;
  font-weight: 400;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

a, a:visited { color: var(--accent); text-decoration: none; transition: color var(--t) ease; }
a:hover { color: var(--accent-2); }

.page { max-width: 1280px; margin: 0 auto; }

/* ---------- masthead ---------- */

.masthead {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.brand, .brand:visited {
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
}
.brand span { color: var(--accent); }
.brand:hover { color: var(--text); }
.brand:hover span { color: var(--accent-2); }

.burger {
  display: none;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  color: var(--accent);
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: var(--r-1);
  padding: 8px 16px;
  cursor: pointer;
  transition: background-color var(--t) ease, border-color var(--t) ease;
}
.burger:hover { background: var(--accent-soft); border-color: var(--accent); }

/* Three-position theme switch pinned to the right of the masthead: a pill
   track with a knob that slides under the active icon. The knob's position
   follows the data-theme attribute rather than the buttons, so it is right
   from first paint, before app.js has run. */
.theme-switch {
  --pos: 0;
  position: relative;
  display: flex;
  margin-left: auto;
  padding: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: 999px;
}
:root[data-theme="middle"] .theme-switch { --pos: 1; }
:root[data-theme="light"] .theme-switch { --pos: 2; }

.theme-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: var(--shadow-sm);
  transform: translateX(calc(var(--pos) * 34px));
  transition: transform 220ms cubic-bezier(.3, .7, .4, 1), background-color var(--t) ease;
}

.theme-switch button {
  position: relative;  /* above the knob */
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  transition: color var(--t) ease;
}
.theme-switch button:hover { color: var(--text); }
.theme-switch button[aria-pressed="true"] { color: var(--on-accent); }
.theme-switch button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.theme-switch svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.theme-switch button[data-theme-set="dark"] svg,
.theme-switch svg .fill { fill: currentColor; }

/* ---------- two columns ---------- */

.cols { display: flex; gap: 38px; margin-top: 34px; }

.col-nav { width: 200px; flex: none; }
.col-main { flex: 1; min-width: 0; }

.nav { position: relative; list-style: none; margin: 0; padding: 0; }
.nav li { margin-bottom: 4px; }

.nav a {
  position: relative;  /* above the pill */
  display: block;
  padding: 9px 14px;
  border-radius: var(--r-1);
  font-size: 16px;
  font-weight: 500;
  color: var(--text-2);
  transition: color var(--t) ease, background-color var(--t) ease;
}
.nav a:hover { color: var(--text); background: var(--panel-2); }
.nav a.is-on { color: var(--accent-2); background: var(--accent-soft); font-weight: 700; }

/* With JS one pill slides between entries instead of each lit entry painting
   its own fill. It is held still until app.js has placed it once. */
.nav.has-pill a.is-on,
.nav.has-pill a.is-on:hover { background: transparent; }
.nav-pill {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--h, 0);
  margin: 0;
  border-radius: var(--r-1);
  background: var(--accent-soft);
  opacity: 0;
  transform: translateY(var(--y, 0));
  pointer-events: none;
}
.nav-pill.is-on { opacity: 1; }
.nav.is-ready .nav-pill {
  transition: transform var(--move) var(--ease), height var(--move) var(--ease),
              opacity var(--t) ease, background-color var(--t) ease;
}

.note {
  margin: 28px 14px 0;
  padding-top: 18px;
  border-top: 1px solid var(--line-2);
  font-size: 14px;
  line-height: 1.6;
  color: var(--dim);
}

/* ---------- home ---------- */

/* the heading over the featured release, a step up from the h3 labels */
.col-main p.motd-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--label);
}
/* a small triangle turning slowly beside the label, so the panel reads as live */
.motd-mark {
  flex: none;
  width: 13px;
  height: 13px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linejoin: round;
  transform-origin: 50% 58%;   /* the triangle's centre, not its box's */
  animation: turn 9s linear infinite;
}
@keyframes turn { to { transform: rotate(360deg); } }

/* Day and Night of the same street, one frame cut on a diagonal. Each side
   is its own link, clipped to its half, so hovering follows the cut. The
   hovered side pushes the cut across to show more of itself while the other
   dims. The hairline seam is the frame's own fill showing through a sliver
   left between the two clips. */
.motd {
  position: relative;
  aspect-ratio: 16 / 9;
  margin-bottom: 24px;
  overflow: hidden;
  border-radius: var(--r-2);
  background: var(--accent);
  box-shadow: var(--shadow);
}

.motd-side {
  position: absolute;
  inset: 0;
  display: block;
  transition: clip-path 760ms var(--ease);
}
/* --cut is where the seam meets the foot of the frame; it leans 8% to the
   right at the top. app.js moves it while the frame is left alone. */
.motd { --cut: 46%; }
.motd-side.is-day   { clip-path: polygon(0 0, calc(var(--cut) + 8%) 0, var(--cut) 100%, 0 100%); }
.motd-side.is-night { clip-path: polygon(calc(var(--cut) + 8.3%) 0, 100% 0, 100% 100%, calc(var(--cut) + 0.3%) 100%); }

.motd:has(.is-day:hover) .is-day,
.motd:has(.is-day:focus-visible) .is-day     { clip-path: polygon(0 0, 70% 0, 62% 100%, 0 100%); }
.motd:has(.is-day:hover) .is-night,
.motd:has(.is-day:focus-visible) .is-night   { clip-path: polygon(70.3% 0, 100% 0, 100% 100%, 62.3% 100%); }
.motd:has(.is-night:hover) .is-day,
.motd:has(.is-night:focus-visible) .is-day   { clip-path: polygon(0 0, 38% 0, 30% 100%, 0 100%); }
.motd:has(.is-night:hover) .is-night,
.motd:has(.is-night:focus-visible) .is-night { clip-path: polygon(38.3% 0, 100% 0, 100% 100%, 30.3% 100%); }

/* While the cut sweeps on its own, app.js sets it every frame, so nothing
   may trail behind it on a transition. A pointer or focus on the frame drops
   this class, and the hover rules above take over with their easing. */
.motd.is-sweeping .motd-side,
.motd.is-sweeping .motd-label { transition: none; }

.motd-side img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: filter 520ms var(--ease), transform 1400ms var(--ease);
}
.motd-side:hover img,
.motd-side:focus-visible img { transform: scale(1.03); }
.motd:has(.motd-side:hover) .motd-side:not(:hover) img,
.motd:has(.motd-side:focus-visible) .motd-side:not(:focus-visible) img {
  filter: brightness(0.45) saturate(0.7);
}
.motd-side:focus-visible { outline: none; }

/* a shade along the foot of each side so its label reads on any shot */
.motd-side::after {
  content: "";
  position: absolute;
  inset: 45% 0 0;
  background: linear-gradient(to bottom, transparent, var(--shade));
  pointer-events: none;
}

.motd-label {
  position: absolute;
  bottom: 22px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--tag-text);
  transition: opacity 420ms var(--ease), transform 520ms var(--ease);
}
.is-day .motd-label   { left: 24px; align-items: flex-start; opacity: var(--day-a, 1); }
.is-night .motd-label { right: 24px; align-items: flex-end; text-align: right; opacity: var(--night-a, 1); }

/* the side giving way loses its label, which the moving cut would slice */
.motd:has(.motd-side:hover) .motd-side:not(:hover) .motd-label { opacity: 0; }
.motd:has(.is-night:hover) .is-day .motd-label { transform: translateX(-12px); }
.motd:has(.is-day:hover) .is-night .motd-label { transform: translateX(12px); }
.motd-side:hover .motd-label { opacity: 1; transform: translateY(-4px); }
.motd-side:focus-visible .motd-label { opacity: 1; }

.motd-tag {
  padding: 2px 10px;
  border: 1.5px solid var(--tag-line);
  border-radius: 999px;
  background: var(--tag-bg);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}
.motd-name {
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
  text-shadow: var(--tag-shadow);
}
.motd-go {
  font-size: 14px;
  font-weight: 500;
  color: var(--tag-line);
  opacity: 0.8;
  transition: opacity var(--t) ease;
}
.motd-go span { display: inline-block; transition: transform 320ms var(--ease); }
.motd-side:hover .motd-go { opacity: 1; }
.motd-side:hover .motd-go span { transform: translateX(4px); }

/* ---------- screenshot viewer ---------- */

/* picture, control line and strip read as one object */
.viewer {
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  overflow: hidden;
  background: var(--panel);
  box-shadow: var(--shadow);
}

.shotbox {
  position: relative;
  background: var(--panel-2);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  outline: none;
}
.shotbox:focus-visible { box-shadow: inset 0 0 0 3px var(--accent); }

.slide { margin: 0; }

/* With JS the shots are stacked and cross faded, so one dissolves into the
   next instead of cutting. Without JS they stay in flow and all show. */
.js .slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--fade) ease, visibility 0s linear var(--fade);
}
.js .slide.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  /* the incoming shot sits over the outgoing one, so the dissolve never dips
     through the empty panel behind them */
  z-index: 1;
  transition: opacity var(--fade) ease, visibility 0s;
}

.shot { display: block; width: 100%; height: 100%; object-fit: contain; }

/* a file that will not load leaves a labelled panel rather than a black hole */
.slide.is-missing .shot { visibility: hidden; }
.slide.is-missing::after {
  content: attr(data-label) " (file not found)";
  position: absolute;
  left: 0; top: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--panel-2);
  color: var(--dim);
  font-size: 15px;
}

/* control line welded under the picture */
.shotbar {
  display: flex;
  align-items: baseline;
  gap: 18px;
  flex-wrap: wrap;
  background: var(--panel);
  padding: 10px 16px;
  font-size: 15px;
  font-weight: 500;
  color: var(--dim);
}
.shotbar .pos { margin-left: auto; font-weight: 400; }
.shotbar .pos b { color: var(--text); font-weight: 700; }

/* ---------- thumbnail strip ---------- */

.index {
  position: relative;  /* thumbs measure their offsets against the strip */
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-behavior: smooth;
  padding: 14px 16px;
  border-top: 1px solid var(--line);
  background: var(--panel);
  scrollbar-width: thin;
  scrollbar-color: var(--line-2) transparent;
}
.index::-webkit-scrollbar { height: 8px; }
.index::-webkit-scrollbar-track { background: transparent; }
.index::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: var(--r-1); }

.thumb {
  position: relative;
  flex: none;
  width: 136px;
  height: 77px;
  padding: 0;
  overflow: hidden;
  border: 2px solid transparent;
  border-radius: var(--r-1);
  background: var(--panel-2);
  cursor: pointer;
  transition: border-color var(--t) ease, opacity var(--t) ease;
  opacity: 0.75;
}
.thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 500ms var(--ease);
}
.thumb:hover img { transform: scale(1.07); }
.thumb:hover { opacity: 1; border-color: var(--line-2); }
.thumb.is-on { opacity: 1; border-color: var(--accent); }
.thumb b {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 1px 7px;
  border-radius: 0 var(--r-1) 0 0;
  background: var(--veil);
  color: var(--text);
  font-size: 12px;
  font-weight: 500;
}
.thumb.is-on b { background: var(--accent); color: var(--on-accent); font-weight: 700; }

/* ---------- text sections ---------- */

.view[hidden] { display: none; }

/* A page rises into place as it shows, and its rail follows a beat behind.
   Showing a view restarts these, so every visit gets them. When a view
   transition is already carrying the page in, app.js marks it no-rise. */
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
}
.js .view { animation: rise var(--move) var(--ease) backwards; }
.js .view .store-side { animation: rise calc(var(--move) + 120ms) var(--ease) 90ms backwards; }
.js .view.no-rise,
.js .view.no-rise .store-side { animation: none; }

/* ---------- back ---------- */

.back {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  margin: 0 0 14px;
  padding: 6px 15px 6px 9px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--panel);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  transition: background-color var(--t) ease, border-color var(--t) ease,
              color var(--t) ease, transform var(--t) var(--ease);
}
.back, .back:visited { color: var(--accent-2); }
.back:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-2); }
.back:active { transform: scale(0.96); }
.back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.back svg {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 260ms var(--ease);
}
.back:hover svg { transform: translateX(-3px); }
.back-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* the back button and the page's visitor count share the line over the heading */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  margin: 0 0 14px;
}
.crumbs .back { margin: 0; }
.view-count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: var(--dim);
  animation: rise var(--move) var(--ease) backwards;
}
.view-count[hidden] { display: none; }
.view-count b { font-weight: 700; color: var(--text-2); }
.view-count svg {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

h2 {
  margin: 0 0 18px;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--text);
}

.col-main p { margin: 0 0 16px; max-width: 62ch; }

/* the release-details card from the HL2 game pages */
table.data { border-collapse: collapse; width: 100%; max-width: 660px; }
table.data th,
table.data td {
  border-bottom: 1px solid var(--line);
  padding: 12px 0;
  text-align: left;
  vertical-align: top;
  font-size: 16px;
}
table.data tr:first-child th,
table.data tr:first-child td { border-top: 1px solid var(--line); }
table.data th {
  width: 200px;
  padding-right: 18px;
  font-weight: 500;
  color: var(--dim);
}
table.data td { color: var(--text); font-weight: 400; }

ul.plain { margin: 0; padding: 0; list-style: none; }
ul.plain li {
  border-bottom: 1px solid var(--line);
  padding: 13px 0;
  font-size: 15px;
  color: var(--dim);
}
ul.plain li:first-child { border-top: 1px solid var(--line); }
/* title on its own line with the description sitting under it; a feature
   list has no page to link to, so its title is a plain span instead */
ul.plain a,
ul.plain .title {
  display: block;
  font-size: 17px;
  font-weight: 500;
}
ul.plain .title { color: var(--text); }
ul.plain .sub {
  display: block;
  margin-top: 3px;
  line-height: 1.55;
  color: var(--dim);
}

/* feature lists run long, so they sit tighter: the title takes a fixed
   column like the facts table and the description reads beside it */
ul.plain.features li {
  display: grid;
  grid-template-columns: 150px 1fr;
  column-gap: 18px;
  align-items: baseline;
  padding: 8px 0;
}
ul.plain.features .title { font-size: 15px; }
ul.plain.features .sub { margin-top: 0; line-height: 1.5; }

/* ---------- feature updates ----------

   One row per update, newest first. Its title is a button; the patch notes
   unroll under it on a 0fr to 1fr grid row, and settle in a beat after the
   row has begun to open. */

.updates { border-top: 1px solid var(--line); }
.update { border-bottom: 1px solid var(--line); }

.update-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  width: 100%;
  padding: 12px 2px;
  border: 0;
  background: transparent;
  font-family: inherit;
  font-size: 15px;
  line-height: 1.4;
  text-align: left;
  color: var(--text);
  cursor: pointer;
  transition: color var(--t) ease;
}
.update-head:hover { color: var(--accent-2); }
.update-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-1); }
.update-ver {
  padding: 1px 9px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-2);
  font-size: 12.5px;
  font-weight: 700;
}
.update-date { font-weight: 500; }
.update-tag {
  padding: 1px 9px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  color: var(--label);
}
.update-count { margin-left: auto; font-size: 13px; color: var(--dim); }
/* the caret turns over as the notes open */
.update-head::after {
  content: "";
  flex: none;
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid var(--dim);
  transition: transform var(--move) var(--ease), border-top-color var(--t) ease;
}
.update-head:hover::after { border-top-color: var(--accent-2); }
.update.is-open .update-head::after { transform: rotate(180deg); }

.js .update-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--move) var(--ease);
}
.js .update.is-open .update-body { grid-template-rows: 1fr; }
.update-inner { min-height: 0; overflow: hidden; }
.update-inner > :last-child { margin-bottom: 16px; }
.js .update-inner > * {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 220ms ease, transform var(--move) var(--ease);
}
.js .update.is-open .update-inner > * {
  opacity: 1;
  transform: none;
  transition-delay: 90ms;
}

.col-main .update-inner p {
  max-width: none;
  margin: 0 0 10px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-2);
}
.col-main .update-inner p.note-group {
  margin: 14px 0 4px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--label);
}
.col-main .update-inner > p.note-group:first-child { margin-top: 2px; }
ul.notes {
  margin: 0;
  padding: 0 0 0 18px;
  font-size: 15px;
  color: var(--text-2);
}
ul.notes li { padding: 3px 0; line-height: 1.5; }
ul.notes li::marker { color: var(--accent); }
/* a titled list opening straight under the row needn't draw a second rule */
.update-inner > ul.plain:first-child > li:first-child { border-top: 0; }
.update-inner > ul.plain:last-child > li:last-child { border-bottom: 0; }

.btn {
  display: inline-block;
  border: 1px solid var(--line-2);
  border-radius: var(--r-1);
  color: var(--accent-2);
  background: var(--accent-soft);
  padding: 13px 26px;
  font-size: 16px;
  font-weight: 500;
  transition: background-color var(--t) ease, color var(--t) ease, border-color var(--t) ease,
              transform var(--t) var(--ease), box-shadow var(--t) ease;
}
.btn, .btn:visited { color: var(--accent-2); }
.btn:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  transform: translateY(-1px);
  box-shadow: var(--shadow);
}
.btn:active { transform: translateY(0) scale(0.98); box-shadow: none; }

.col-main p.lede {
  margin: -6px 0 22px;
  max-width: 68ch;
  font-size: 19px;
  color: var(--text-2);
}

/* ---------- storefront split: media left, file rail right ---------- */

/* Every section runs through this shell, so the main column is the same
   width on all of them. The rail column is held open even when a page has
   no rail, which is what keeps the content edge from shifting. */
.store {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  align-items: start;
  gap: 32px;
}
.store-main { min-width: 0; }
.store-side { min-width: 0; }

.side-card {
  padding: 22px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-sm);
}
.col-main .side-card p.release-name {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  word-break: break-all;
}
.col-main .side-card p.release-meta {
  margin: 4px 0 0;
  font-size: 15px;
  color: var(--dim);
}
.side-card .btn {
  display: block;
  margin-top: 14px;
  text-align: center;
  padding: 12px 10px;
}

/* a map can ship for more than one game, so the card carries one block per
   release rather than a single button */
.side-card .release + .release {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
}

/* The rail stacks headed blocks and cards down one column, so the gap between
   them is set here in one place rather than per block. Whichever lands first
   loses its top margin, keeping the rail level with the column beside it. */
.store-side > h3:first-child { margin-top: 0; }

/* The square Workshop preview and the overview head the rail as one card:
   the art takes a third of it on the left and the description reads beside
   it, running on underneath when it is longer than the art is tall. */
.intro {
  display: flow-root;
  padding: 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-sm);
}
.intro .workshop-art {
  float: left;
  width: calc(33.333% - 6px);
  max-width: 150px;
  aspect-ratio: 1;
  margin: 0 16px 6px 0;
  object-fit: cover;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r-1);
}
.intro h3 { margin: 0 0 6px; }
.col-main .store-side .intro p.lede { font-size: 15px; line-height: 1.6; }
.store-side > .side-card { margin-top: 24px; }
.store-side > .side-card:first-child { margin-top: 0; }

/* the map description reads as rail copy here, not as a page lede */
.col-main .store-side p.lede {
  margin: 0;
  max-width: none;
  font-size: 16px;
  line-height: 1.65;
}
/* Stats in the rail, condensed the way Steam lists them: the figure, then
   what it counts, one tight row each, a short table per store. The figures
   line up in a column, so they take tabular digits. */
.stats { animation: rise var(--move) var(--ease) backwards; }
.col-main p.stat-src {
  margin: 16px 0 6px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
}
.col-main .stats p.stat-src:first-child { margin-top: 0; }
table.stat-list { width: 100%; border-collapse: collapse; }
table.stat-list td,
table.stat-list th {
  padding: 5px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14.5px;
  line-height: 1.4;
  text-align: left;
}
table.stat-list tr:first-child td,
table.stat-list tr:first-child th { border-top: 1px solid var(--line); }
/* one shared width, so both stores' figures sit on the same right edge */
table.stat-list td {
  width: 5.2em;
  padding-right: 14px;
  white-space: nowrap;
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
table.stat-list th { font-weight: 400; color: var(--dim); }
.col-main p.stat-note {
  margin: 10px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--dim);
}

.store-side table.facts th { width: 118px; }
.store-side table.facts th,
.store-side table.facts td { padding: 10px 0; font-size: 15px; }

h3 {
  margin: 30px 0 12px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--label);
}

/* ---------- projects ---------- */

/* filter bar: a type switch, a search box and a running count, all live */
.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 14px;
  margin: 0 0 22px;
}
html:not(.js) .filters { display: none; }

/* the type switch is the theme switch laid flat: a pill track with a knob
   that slides under the pressed button. app.js measures the button, since
   the labels are not one width. */
.seg {
  --x: 0px;
  --w: 0px;
  position: relative;
  display: inline-flex;
  padding: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: 999px;
}
.seg-knob {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 0;
  width: var(--w);
  border-radius: 999px;
  background: var(--accent);
  box-shadow: var(--shadow-sm);
  transform: translateX(var(--x));
}
.seg.is-ready .seg-knob {
  transition: transform var(--move) var(--ease), width var(--move) var(--ease),
              background-color var(--t) ease;
}
.seg button {
  position: relative;  /* above the knob */
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 15px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--text-2);
  cursor: pointer;
  transition: color var(--t) ease;
}
.seg button:hover { color: var(--text); }
.seg button[aria-pressed="true"] { color: var(--on-accent); }
.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.seg .count {
  min-width: 1ch;
  font-size: 12px;
  font-weight: 700;
  opacity: 0.7;
}

.search {
  position: relative;
  flex: 1 1 220px;
  max-width: 340px;
}
.search svg {
  position: absolute;
  top: 50%;
  left: 13px;
  width: 16px;
  height: 16px;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--dim);
  stroke-width: 2.2;
  stroke-linecap: round;
  pointer-events: none;
  transition: stroke var(--t) ease, transform 260ms var(--ease);
}
.search:focus-within svg { stroke: var(--accent); transform: translateY(-50%) scale(1.1); }
.search input {
  width: 100%;
  height: 40px;
  padding: 0 14px 0 38px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--panel);
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  outline: none;
  transition: border-color var(--t) ease, box-shadow 260ms var(--ease), background-color var(--t) ease;
}
.search input::placeholder { color: var(--dim); }
.search input:hover { border-color: var(--accent); }
.search input:focus { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }

.results {
  margin-left: auto;
  font-size: 14px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

/* every map and tool as its square Workshop art, four across at most; the
   tag in the corner says which kind it is */
.projects {
  position: relative;  /* ghosts of filtered-out tiles are pinned against it */
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.projects > .ghost {
  position: absolute;
  margin: 0;
  pointer-events: none;
}

.project {
  position: relative;
  display: block;
  height: 100%;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--t) ease, transform 320ms var(--ease), box-shadow 320ms var(--ease);
}
.project:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow);
  transform: translateY(-4px);
}
.project:active { transform: translateY(-1px) scale(0.985); }
.project:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.project .art {
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
  background: var(--panel-2);
}
.project .art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 700ms var(--ease);
}
.project:hover .art img { transform: scale(1.06); }

/* Left alone, each tile's art drifts slowly about inside its frame, odd and
   even tiles along opposite diagonals. The drift runs on scale and translate
   so the hover zoom on transform still stacks on top. app.js gives every
   tile its own --drift and --lag, so the grid never moves in step. */
.js .project .art img {
  animation: drift var(--drift, 18s) ease-in-out var(--lag, 0s) infinite alternate;
}
.js .projects > li:nth-child(even) .art img { animation-name: drift-b; }
/* kept small: most of the art has its title lettered right to the edges */
@keyframes drift {
  from { scale: 1.03; translate: -1.2% 1%; }
  to   { scale: 1.07; translate: 1.2% -1%; }
}
@keyframes drift-b {
  from { scale: 1.07; translate: 1.2% 1%; }
  to   { scale: 1.03; translate: -1.2% -1%; }
}

/* A small round tag hung slightly askew in the corner, like a price tag.
   It swings when the tile is hovered and settles back to its tilt. */
.project .kind {
  position: absolute;
  top: 9px;
  left: 9px;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--tag-line);
  border-radius: 50%;
  background: var(--tag-bg);
  color: var(--tag-text);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  box-shadow: var(--tag-shadow);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
  transform: rotate(-14deg);
  transform-origin: 50% -40%;
}
.project:hover .kind { animation: swing 1100ms var(--ease); }
@keyframes swing {
  0%   { transform: rotate(-14deg); }
  22%  { transform: rotate(10deg); }
  46%  { transform: rotate(-22deg); }
  68%  { transform: rotate(-6deg); }
  86%  { transform: rotate(-17deg); }
  100% { transform: rotate(-14deg); }
}

/* every twelve seconds or so the tag gives a smaller swing of its own,
   each tile at its own moment */
.js .project .kind { animation: nudge 12s ease-in-out var(--swing-lag, 0s) infinite; }
.js .project:hover .kind { animation: swing 1100ms var(--ease); }
@keyframes nudge {
  0%        { transform: rotate(-14deg); }
  2%        { transform: rotate(-3deg); }
  4.2%      { transform: rotate(-22deg); }
  6.2%      { transform: rotate(-10deg); }
  7.9%      { transform: rotate(-16deg); }
  9.2%, to  { transform: rotate(-14deg); }
}

.project .name {
  display: block;
  padding: 11px 13px 12px;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.35;
  color: var(--text);
  transition: color var(--t) ease;
}
.project:hover .name { color: var(--accent-2); }

/* the letters a search matched, lit as they are typed */
.project .name mark {
  padding: 0 1px;
  border-radius: 3px;
  background: var(--accent-soft);
  color: var(--accent-2);
}

/* the counts under a tile's name: Workshop and GameBanana visitors, Workshop subscribers */
.project:has(.meta) .name { padding-bottom: 4px; }
.project .meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  padding: 0 13px 12px;
  font-size: 13px;
  color: var(--dim);
  animation: rise 360ms var(--ease) backwards;
}
.project .meta-item { display: inline-flex; align-items: center; gap: 5px; }
.project .meta-item b { font-weight: 500; color: var(--text-2); }
.project .meta svg {
  flex: none;
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.empty {
  padding: 46px 20px;
  border: 1px dashed var(--line-2);
  border-radius: var(--r-2);
  text-align: center;
  color: var(--dim);
  animation: rise var(--move) var(--ease) backwards;
}
.col-main .empty p { margin: 0 auto 14px; }
.empty button {
  padding: 8px 18px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-2);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--t) ease, color var(--t) ease, border-color var(--t) ease;
}
.empty button:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* read out by screen readers, never drawn */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 1100px) {
  .projects { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---------- view transitions ----------

   Moving between sections: the main column is captured on its own, the old
   page fades out quickly while the new one rises in, the project art flies
   from its tile to the top of its page, and the footer slides to its new
   height. Theme changes use the browser's plain cross fade.

   The nav and the theme switch are captured whole and shown live, old copy
   dropped, so their sliding pill and knob keep moving under their labels
   rather than being lifted out above them. */

.col-main { view-transition-name: main; }
.footer { view-transition-name: footer; }
.nav { view-transition-name: nav; }
.theme-switch { view-transition-name: theme-switch; }

::view-transition-old(nav),
::view-transition-old(theme-switch) { animation: none; opacity: 0; }
::view-transition-new(nav),
::view-transition-new(theme-switch) { animation: none; }

::view-transition-group(*) {
  animation-duration: var(--move);
  animation-timing-function: var(--ease);
}

@keyframes vt-out { to { opacity: 0; transform: translateY(-6px); } }
@keyframes vt-in { from { opacity: 0; transform: translateY(14px); } }

.vt-nav::view-transition-old(main) { animation: vt-out 200ms ease both; }
.vt-nav::view-transition-new(main) { animation: vt-in var(--move) var(--ease) 60ms both; }

/* the art flies as one picture rather than cross fading two */
.vt-nav::view-transition-old(art) { animation: none; opacity: 0; }
.vt-nav::view-transition-new(art) { animation: none; }
.vt-nav::view-transition-group(art) {
  animation-duration: 520ms;
  border-radius: var(--r-2);
  overflow: hidden;
}

.vt-theme::view-transition-group(*),
.vt-theme::view-transition-old(*),
.vt-theme::view-transition-new(*) { animation-duration: 360ms; }

/* ---------- footer ---------- */

.footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  margin-top: 50px;
  text-align: center;
  font-size: 14px;
  color: var(--dim);
}
.footer .visitors { animation: rise var(--move) var(--ease) backwards; }

/* ---------- narrow ---------- */

@media (max-width: 780px) {
  body { padding: 16px 16px 30px; font-size: 16px; }
  .burger { display: block; }
  .brand { font-size: 26px; }
  h2 { font-size: 28px; }

  .cols { display: block; margin-top: 18px; }
  .col-nav {
    display: none;
    width: auto;
    padding-bottom: 18px;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--line-2);
  }
  .col-nav.is-open { display: block; animation: drop 280ms var(--ease) backwards; }
  .note { margin-left: 0; margin-right: 0; }

  .store { display: block; }
  .store-side { width: auto; margin-top: 30px; }
  .thumb { width: 108px; height: 61px; }
  table.data th { width: 136px; }
  ul.plain.features li { grid-template-columns: 1fr; row-gap: 1px; }
  .projects { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .seg { flex: 1 1 100%; }
  .seg button { flex: 1; justify-content: center; }
  .search { max-width: none; }
  .shotbar { gap: 14px; padding: 11px 14px; }
  .motd-label { bottom: 12px; gap: 3px; }
  .is-day .motd-label { left: 13px; }
  .is-night .motd-label { right: 13px; }
  .motd-name { font-size: 15px; }
  .motd-go { font-size: 12px; }
  .motd-tag { padding: 1px 8px; font-size: 10px; }
}

@keyframes drop {
  from { opacity: 0; transform: translateY(-8px); }
}

/* ---------- reduced motion ---------- */

/* The dissolve is decoration; anyone who asks for less movement gets the
   plain cut back. */
@media (prefers-reduced-motion: reduce) {
  :root { --fade: 1ms; }
  * , *::before, *::after {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
    transition-delay: 0ms !important;
    animation-delay: 0ms !important;
  }
  /* a loop at 1ms would strobe, so the triangle and the tiles simply hold still */
  .motd-mark,
  .js .project .art img,
  .js .project .kind { animation: none; }
}
