.about-page {
  /* The two cards of the page — the live showcase and the profile the wiring
     writes to — are the same object seen twice, so they share one width. */
  --about-card: 460px;
  --about-card-min: 360px;
  gap: var(--space-9);
  padding-block: var(--space-9) var(--space-6);
}

/* The hero: the claim on the left, what the claim already holds on the right.
   The card is the widest fixed element on the page, so the copy takes the
   remaining space rather than a share of it. */
.about-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(var(--about-card-min), var(--about-card));
  align-items: center;
  gap: var(--space-8) var(--space-9);
}

.about-intro {
  max-width: 780px;
}

.about-eyebrow {
  color: var(--brand);
  font: var(--text-meta);
}

/* The landing's opening line is the one marketing-sized role in the system, and
   it now says so through a token rather than an inline clamp only this file
   knew about. */
.about-intro h1 {
  margin: var(--space-5) 0 var(--space-7);
  font: var(--text-hero);
  text-wrap: balance;
}

.about-accent {
  color: var(--text);
}

/* Overlapping grid cells stack the words, but only the visible one keeps its
   place in the flow, so the line fits whenever the shown word fits. Holding
   every word's width reserved the longest translation ("achievements.") and
   wrapped even short pairs — "Different consoles." broke after "Different"
   on a phone. `display` flips discretely inside the keyframes; where that is
   ignored the words keep stacking as before. The line never wraps for the
   long words either: about.js measures the widest pair on the probe copy
   below and shrinks the headline until it fits, so the block under it stays
   put while the words turn.
   The underline belongs to each word, so its length follows the lettering. */
.about-headline-lead { display: block; }
.about-words {
  display: inline-grid;
  vertical-align: baseline;
}
.about-headline { position: relative; }
.about-headline-probe {
  position: absolute;
  top: 0;
  left: 0;
  width: max-content;
  white-space: nowrap;
  visibility: hidden;
  pointer-events: none;
}
.about-headline-probe .about-word {
  display: block;
  animation: none;
}
/* One word holds the line for 3s, so the cycle is the word count times that
   and each word starts one slot behind the one before it. The keyframes below
   are the same 3s slot expressed against this duration: adding a word means
   restating both. */
.about-word {
  grid-area: 1 / 1;
  width: max-content;
  white-space: nowrap;
  opacity: 0;
  animation: about-word-cycle 18s linear infinite;
  animation-play-state: paused;
}
.about-word > span {
  text-decoration: underline;
  text-decoration-color: var(--brand);
  text-decoration-thickness: .055em;
  text-underline-offset: .12em;
  text-decoration-skip-ink: none;
}
.about-word:nth-child(2) { animation-delay: -15s; }
.about-word:nth-child(3) { animation-delay: -12s; }
.about-word:nth-child(4) { animation-delay: -9s; }
.about-word:nth-child(5) { animation-delay: -6s; }
.about-word:nth-child(6) { animation-delay: -3s; }
.about-headline[data-running="true"] .about-word { animation-play-state: running; }
/* 16.667% is the 3s slot; the word reads for 2.52s of it, leaves over .36s,
   and the next one's .45s entrance runs under the tail of this cycle. The
   16.01% and 97.49% stops sit one frame past the fades so the discrete
   `display` flip lands on the invisible stretch, never mid-fade. */
@keyframes about-word-cycle {
  0%, 14% { opacity: 1; transform: translateY(0); display: block; }
  16% { opacity: 0; transform: translateY(-.16em); display: block; }
  16.01%, 97.49% { opacity: 0; transform: translateY(.16em); display: none; }
  97.5% { opacity: 0; transform: translateY(.16em); display: block; }
  100% { opacity: 1; transform: translateY(0); display: block; }
}
@media (prefers-reduced-motion: reduce) {
  .about-word { animation: none; opacity: 0; display: none; }
  .about-word:first-child { opacity: 1; display: block; }
}

/* Three site totals under the headline. They count up from zero once, so the
   row reads as data arriving rather than as a printed claim. The gap answers
   the headline directly now that no sentence stands between them. */
.about-metrics {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6) var(--space-9);
  margin: var(--space-9) 0 0;
}

.about-metric {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-1);
}

.about-metric dt {
  color: var(--muted);
  font: var(--text-meta);
}

.about-metric-value {
  margin: 0;
  font: var(--text-display);
  font-variant-numeric: tabular-nums;
}

.about-metrics-note {
  margin: var(--space-9) 0 var(--space-3);
  color: var(--muted);
  font: var(--text-meta);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

.about-metrics-note:not([hidden]) + .about-metrics {
  margin-top: 0;
}

.about-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5) var(--space-7);
  margin-top: var(--space-7);
}

.about-page a {
  color: var(--text);
  text-underline-offset: .2em;
}

.about-actions .about-primary,
.about-actions .about-secondary {
  padding: var(--space-5) var(--space-6);
  border-radius: var(--radius-control);
  font-weight: var(--weight-bold);
  text-decoration: none;
}

.about-actions .about-primary {
  background: var(--brand);
  color: var(--background);
}

/* The second action is the same shape drawn in outline: the visitor can read
   the page's data without an account, but that is not what the page asks for. */
.about-actions .about-secondary {
  border: var(--border-width) solid var(--line);
  color: var(--text);
}

.about-page a:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* Every section below the hero opens the same way: a hairline across the shell
   and a heading at one size. The hero opens the page and needs neither. */
.about-how,
.about-telegram,
.about-faq {
  padding-top: var(--space-8);
  border-top: var(--border-width) solid var(--line);
}

.about-how > h2,
.about-telegram-copy > h2,
.about-faq > h2 {
  margin: 0 0 var(--space-7);
  font: var(--text-page);
}

/* Every source uses the same 2.4s slot: wire 0–0.9s, hub 0.9–1.08s,
   delivery 1.08–1.7s. Capabilities respond together after all six sources,
   rather than suggesting that each source supplies a particular feature. */
.about-illustration {
  --flow-slot: 2.4s;
  --flow-cycle: calc(var(--flow-slot) * 6);
  margin: 0;
}

.about-merge {
  position: relative;
  display: grid;
  grid-template-columns: minmax(220px, 260px) minmax(280px, 1fr) minmax(var(--about-card-min), var(--about-card));
  align-items: center;
}

.about-sources {
  display: grid;
  grid-template-rows: repeat(6, 60px);
  list-style: none;
  padding: 0;
  margin: 0;
}

.about-source {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  align-self: center;
  min-width: 0;
  width: 100%;
  height: 52px;
  padding: 0 var(--space-6) 0 var(--space-4);
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-surface);
  background: var(--surface);
  font-weight: var(--weight-semibold);
  animation: about-source-active var(--flow-cycle) linear infinite paused;
  animation-delay: calc(var(--flow-slot) * var(--flow-index, 0));
}

.about-source:nth-child(2) { --flow-index: 1; }
.about-source:nth-child(3) { --flow-index: 2; }
.about-source:nth-child(4) { --flow-index: 3; }
.about-source:nth-child(5) { --flow-index: 4; }
.about-source:nth-child(6) { --flow-index: 5; }

.about-source-label { min-width: 0; line-height: 1.3; }
.about-source-label small {
  display: block;
  margin-top: var(--space-1);
  color: var(--muted);
  font: var(--text-meta);
}
.about-source-mark {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.25rem;
  flex-shrink: 0;
}
.about-source img { width: 1.5rem; height: 1.5rem; object-fit: contain; }

.about-source::after {
  content: "";
  position: absolute;
  right: -4px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -3.5px;
  border-radius: var(--radius-round);
  background: var(--line);
  animation: about-emit var(--flow-cycle) linear infinite paused;
  animation-delay: calc(var(--flow-slot) * var(--flow-index, 0));
}

/* All paths are measured in CSS pixels after layout, including mobile's
   central collector. No stretched SVG/non-scaling dash coordinate mismatch. */
.about-flow {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.about-connector {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 360px;
}
.about-hub {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  flex: none;
  width: 176px;
  height: 64px;
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-surface);
  background: var(--surface);
  animation: about-hub-receive var(--flow-slot) linear infinite paused;
}
.about-hub img { display: block; width: 128px; height: auto; }
.about-wire { fill: none; stroke: var(--line); stroke-width: 1.5; }
.about-trail, .about-packet {
  fill: none;
  stroke: var(--brand);
  stroke-linecap: round;
  opacity: 0;
  visibility: hidden;
  animation: about-packet var(--flow-cycle) linear infinite paused;
  animation-delay: calc(var(--flow-slot) * var(--flow-index, 0));
}
/* A gap longer than the complete path prevents a second dash at the far end.
   The tail and dot share the same path and clock. Dashes are normalised to
   pathLength 1000 (set by about.js), so every keyframe below must travel the
   full 1000 — trail 8 to -992, packet 0 to -1000 — or the dot hops partway
   instead of riding the wire end to end. */
.about-trail { stroke-width: 2; stroke-dasharray: 8 1000; animation-name: about-trail; }
.about-packet {
  stroke-width: 7;
  stroke-dasharray: 0.01 1000;
  filter: drop-shadow(0 0 5px color-mix(in srgb, var(--brand) calc(var(--opacity-muted) * 100%), transparent));
}
.about-delivery { animation-duration: var(--flow-slot); animation-delay: 0s; }
.about-trail.about-delivery { animation-name: about-delivery-trail; }
.about-packet.about-delivery { animation-name: about-delivery-packet; }

/* The check stays legible while its row lights: it answers the same wave on
   the same clock, staggered with its own row. Custom properties inherit, so
   the delay reads the row's own index. */
.about-profile-data li::before {
  animation: about-check var(--flow-cycle) linear infinite paused;
  animation-delay: calc(var(--row-index, 0) * 60ms);
}

.about-illustration[data-running="true"] .about-source,
.about-illustration[data-running="true"] .about-source::after,
.about-illustration[data-running="true"] .about-hub,
.about-illustration[data-running="true"] .about-profile-port,
.about-illustration[data-running="true"] .about-avatar,
.about-illustration[data-running="true"] .about-trail,
.about-illustration[data-running="true"] .about-packet,
.about-illustration[data-running="true"] .about-profile-data li,
.about-illustration[data-running="true"] .about-profile-data li::before {
  animation-play-state: running;
}
.about-illustration[data-running="true"] .about-trail,
.about-illustration[data-running="true"] .about-packet { visibility: visible; }

@keyframes about-source-active {
  0% { border-color: var(--line); background: var(--surface); }
  0.7%, 2% { border-color: var(--brand); background: var(--raised); }
  6.25%, 100% { border-color: var(--line); background: var(--surface); }
}
@keyframes about-emit {
  0%, 0.8% { background: var(--brand); box-shadow: var(--shadow-signal); }
  2%, 100% { background: var(--line); box-shadow: 0 0 0 transparent; }
}
@keyframes about-trail {
  0% { stroke-dashoffset: 8; opacity: 0; }
  0.45%, 5.5% { opacity: var(--opacity-muted); }
  6.25%, 100% { stroke-dashoffset: -992; opacity: 0; }
}
@keyframes about-packet {
  0% { stroke-dashoffset: 0; opacity: 0; }
  0.45%, 5.5% { opacity: 1; }
  6.25%, 100% { stroke-dashoffset: -1000; opacity: 0; }
}
@keyframes about-hub-receive {
  0%, 35%, 50%, 100% { border-color: var(--line); box-shadow: 0 0 0 transparent; }
  39%, 44% { border-color: var(--brand); box-shadow: var(--shadow-signal); }
}
@keyframes about-delivery-trail {
  0%, 45% { stroke-dashoffset: 8; opacity: 0; }
  48%, 67% { opacity: var(--opacity-muted); }
  70.833%, 100% { stroke-dashoffset: -992; opacity: 0; }
}
@keyframes about-delivery-packet {
  0%, 45% { stroke-dashoffset: 0; opacity: 0; }
  48%, 68% { opacity: 1; }
  70.833%, 100% { stroke-dashoffset: -1000; opacity: 0; }
}
@keyframes about-receive {
  0%, 69%, 91%, 100% { background: var(--line); box-shadow: 0 0 0 transparent; }
  72%, 78% { background: var(--brand); box-shadow: var(--shadow-signal); }
}
@keyframes about-profile-receive {
  0%, 70%, 94%, 100% { border-color: var(--line); box-shadow: 0 0 0 transparent; }
  75%, 81% { border-color: var(--brand); box-shadow: var(--shadow-signal); }
}

.about-profile {
  position: relative;
  z-index: 1;
  width: 100%;
  padding: var(--space-7);
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-surface);
  background: var(--surface);
}
.about-profile-port {
  position: absolute;
  left: -4px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -3.5px;
  border-radius: var(--radius-round);
  background: var(--brand);
  animation: about-receive var(--flow-slot) linear infinite paused;
}

.about-profile-heading {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

.about-profile-heading > div {
  min-width: 0;
  overflow-wrap: anywhere;
}

.about-avatar {
  position: relative;
  display: grid;
  place-items: center;
  width: 3.25rem;
  height: 3.25rem;
  flex-shrink: 0;
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-round);
  background: var(--raised);
  color: var(--text);
  font: var(--text-icon);
  font-weight: var(--weight-bold);
  animation: about-profile-receive var(--flow-slot) linear infinite paused;
}

/* The card in the drawing is a real profile, so the initial is only a stand-in
   until that person's own avatar arrives (about.js) and covers it. A picture
   that fails to load paints nothing, which leaves the letter standing. */
.about-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}

.about-profile h2 a {
  text-decoration: none;
}

.about-profile h2 a:hover {
  text-decoration: underline;
}

.about-profile h2 {
  margin: var(--space-2) 0 0;
  font: var(--text-title);
}

.about-profile-data {
  list-style: none;
  padding: var(--space-6) 0 0;
  margin: var(--space-6) 0 0;
  border-top: var(--border-width) solid var(--line);
  display: grid;
  gap: var(--space-2);
  color: var(--muted);
}

/* One gentle wave after the complete source loop, not a source-to-feature map. */
.about-profile-data li {
  display: grid;
  grid-template-columns: 1.05em minmax(0, 1fr);
  align-items: center;
  gap: var(--space-5);
  position: relative;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-control);
  animation: about-land var(--flow-cycle) linear infinite;
  animation-delay: calc(var(--row-index, 0) * 60ms);
  animation-play-state: paused;
}

.about-profile-data li:nth-child(2) { --row-index: 1; }
.about-profile-data li:nth-child(3) { --row-index: 2; }
.about-profile-data li:nth-child(4) { --row-index: 3; }
.about-profile-data li:nth-child(5) { --row-index: 4; }
.about-profile-data li:nth-child(6) { --row-index: 5; }
.about-profile-data li:nth-child(7) { --row-index: 6; }
.about-profile-data li:nth-child(8) { --row-index: 7; }

/* A stable check remains legible while the arriving packet highlights the row. */
.about-profile-data li::before {
  content: "";
  width: 1.05em;
  height: 1.05em;
  background: var(--brand);
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8l3.2 3.2L13 4.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* Last delivery is at 13.7s. The 60ms stagger and 250ms response settle
   before 14.4s, including the eighth row; there is no separate drifting loop. */
@keyframes about-land {
  0%, 95.139%, 96.875%, 100% { color: var(--muted); }
  95.833%, 96.18% { color: var(--text); }
}
@keyframes about-check {
  0%, 95.139%, 96.875%, 100% { transform: scale(1); filter: none; }
  95.833%, 96.18% { transform: scale(1.12); filter: drop-shadow(0 0 3px var(--brand)); }
}

/* Сообщество и бот в Telegram. The section the drawing above hands over to: the
   merged history leaves the site for the chat the community already talks in.
   The copy on the left states what arrives; the card on the right is the bot's
   own answer to the command above it, fetched live, so the claim and its proof
   are the same object — the landing shows no invented message. It takes the
   hero's column widths: the card is the fixed side, the copy takes the rest. */
.about-telegram-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(var(--about-card-min), var(--about-card));
  align-items: center;
  gap: var(--space-8) var(--space-9);
}

.about-telegram-lead,
.about-telegram-kinds {
  max-width: var(--measure);
}

.about-telegram-lead {
  margin: 0;
  color: var(--text);
  font: var(--text-reading);
  text-wrap: pretty;
}

/* Short benefits keep the copy easy to scan beside the chat example. */
.about-telegram-kinds {
  display: grid;
  gap: var(--space-5);
  list-style: none;
  padding: 0;
  margin: var(--space-7) 0 0;
}

.about-telegram-kinds > li {
  position: relative;
  display: grid;
  gap: var(--space-4);
  padding: 0 0 0 var(--space-7);
}

.about-telegram-kinds > li::before {
  content: "";
  position: absolute;
  top: .4rem;
  left: 0;
  width: .3rem;
  height: .3rem;
  border-radius: var(--radius-round);
  background: var(--brand);
}

.about-telegram-kinds strong {
  font: var(--text-item);
  line-height: var(--leading-reading);
  text-wrap: pretty;
}

.about-telegram-kinds span {
  color: var(--muted);
}

.about-chat-sent code {
  padding: var(--pad-chip);
  border-radius: var(--radius-pill);
  font: var(--text-log-strong);
}

/* The chat card. Not a card about the chat — the chat itself, the way the
   community already sees it: the group's own header, two people wondering out
   loud, the question typed as a command, and the bot's answer under it. Only
   the answer is real — it is fetched live, keeps the showcase card's
   placeholder discipline, and an unreachable API collapses the whole thing
   rather than leaving a message the bot never sent. Everything above it — the
   people, and the platinum the bot posted before they spoke — is an example
   written into the markup, which is what keeps it in the reader's language;
   a live trophy arrives named by PlayStation, in PlayStation's locale. */
.about-chat {
  display: grid;
  gap: var(--space-6);
  width: min(100%, var(--about-card));
}

/* The window is the one surface here, so it carries the border and the fill;
   the control and the caption below it belong to the page. */
.about-chat-window {
  overflow: hidden;
  width: 100%;
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-surface);
  background: var(--surface);
}

.about-chat-head {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border-bottom: var(--border-width) solid var(--line);
  background: var(--surface);
}

.about-chat-group-name,
.about-chat-group-meta {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.about-chat-group-name {
  font: var(--text-item);
}

.about-chat-group-meta {
  margin-top: var(--space-1);
  color: var(--muted);
  font: var(--text-meta);
  font-weight: 400;
}

/* A bubble is only a bubble against something else, so the thread takes the
   page's own ground and leaves --background to the messages standing on it —
   the ramp's first step, used the way a chat uses its wallpaper. The header
   and the compose bar keep --background: they are the app's chrome, not the
   conversation. */
.about-chat-thread {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-6);
  background: var(--background);
}

/* Somebody else's message: their initial, their name above the words, and the
   corner nearest the face squared off — the shape a chat gives a sender. */
.about-chat-line {
  display: flex;
  align-items: flex-end;
  gap: var(--space-4);
  max-width: 88%;
}

.about-chat-who {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  background: var(--raised);
  color: var(--muted);
  font: var(--text-meta);
}

.about-chat-said {
  min-width: 0;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-control);
  border-bottom-left-radius: var(--radius-xs);
  background: var(--surface);
}

.about-chat-said p {
  margin: 0;
  font: var(--text-body);
}

/* A chat gives each person their own colour for their name, which is what keeps
   two neighbouring messages readable as two people. Neither takes the brand
   colour: in this window that one belongs to the bot and to what you sent. */
.about-chat-said-name {
  margin-bottom: var(--space-1);
  color: var(--chart-blue);
  font: var(--text-meta);
}

.about-chat-line.is-alt .about-chat-said-name {
  color: var(--chart-pink);
}

/* Outgoing messages use Telegram's familiar blue cue. */
.about-chat-sent {
  justify-self: end;
  max-width: 85%;
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-control);
  border-bottom-right-radius: var(--radius-xs);
  background: var(--platform-psn-bg);
  color: var(--text);
}

.about-chat-message {
  display: flex;
  align-items: flex-end;
  gap: var(--space-4);
}

.about-chat-avatar {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-round);
  background: var(--raised);
}

.about-chat-avatar img {
  display: block;
  width: .95rem;
  height: auto;
}

.about-chat-bubble {
  display: grid;
  gap: var(--space-4);
  flex: 1;
  min-width: 0;
  padding: var(--space-5);
  border-radius: var(--radius-control);
  border-bottom-left-radius: var(--radius-xs);
  background: var(--surface);
}

/* Decorative app chrome stays out of the tab order: this is a chat preview. */
.about-chat-heading { min-width: 0; flex: 1; }
.about-chat-tools { display: flex; gap: var(--space-5); color: var(--muted); }
.about-chat-tools svg,
.about-chat-compose svg { width: 1.25rem; height: 1.25rem; flex-shrink: 0; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.about-chat-tools svg[stroke="none"] { stroke: none; }
.about-chat-line .about-chat-who { background: var(--platform-psn-bg); color: var(--chart-blue); }
.about-chat-line.is-alt .about-chat-who { background: var(--raised); color: var(--chart-pink); }
.about-chat-time { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-1); color: var(--muted); font: var(--text-meta); font-size: .65rem; font-weight: 400; font-variant-numeric: tabular-nums; }
.about-chat-sent { display: flex; align-items: baseline; gap: var(--space-4); }
.about-chat-sent .about-chat-time { color: var(--chart-blue); }
.about-chat-time svg { width: 1rem; height: .7rem; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.about-chat-compose { display: flex; align-items: center; gap: var(--space-5); padding: var(--space-5) var(--space-6); border-top: var(--border-width) solid var(--line); background: var(--surface); color: var(--muted); }
.about-chat-compose span { flex: 1; font: var(--text-body); }

/* The event the bot posts on its own, before anybody asks: the trophy that was
   earned last, in the same row the trophy card has everywhere else on the
   site — face, what it was, and how rare. */
.about-chat-event {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

/* The platinum is named after its game, so the two lines say the same word in
   English — «Кольцо Элден» is the pair that carries anything. The game line is
   the one to go: the trophy is what the bot posted. */
:root[lang="en"] .about-chat-event .about-trophy-game {
  display: none;
}

.about-chat-trophy-icon {
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  object-fit: cover;
  background: var(--raised);
}

.about-chat-trophy-body {
  min-width: 0;
  flex: 1;
}

.about-chat-trophy-who,
.about-chat-trophy-name {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.about-chat-trophy-who {
  color: var(--muted);
  font: var(--text-meta);
}

.about-chat-trophy-name {
  margin-top: var(--space-1);
  font: var(--text-item);
}

.about-chat-bot-name {
  margin: 0;
  color: var(--brand);
  font: var(--text-meta);
}

.about-chat-title {
  margin: 0;
  font: var(--text-item);
}

.about-chat-meta {
  margin: 0;
  color: var(--muted);
  font: var(--text-meta);
}

.about-chat-players {
  display: grid;
  gap: var(--space-3);
  list-style: none;
  padding: 0;
  margin: 0;
}

.about-chat-player {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.about-chat-rank {
  width: 1.1rem;
  flex-shrink: 0;
  color: var(--muted);
  font: var(--text-meta);
  font-variant-numeric: tabular-nums;
}

.about-chat-face {
  display: grid;
  place-items: center;
  position: relative;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  background: var(--raised);
  color: var(--muted);
  font: var(--text-micro);
}

/* Same rule the drawing's card follows: the picture is laid over the initial,
   so a portrait that never loads leaves the letter standing. */
.about-chat-face img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}

.about-chat-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: var(--text-item);
}

.about-chat-hours {
  flex-shrink: 0;
  color: var(--muted);
  font: var(--text-meta);
  font-variant-numeric: tabular-nums;
}

/* The second half of the answer: what the hours above were spent in. The row is
   the player row again — rank, face, name, hours — so the two lists read as one
   report; the cover is square where a portrait is round, which is the whole
   difference between a person and a game here. */
.about-chat-subtitle {
  /* The two lists share one row rhythm, so the second one needs a beat of its
     own above its heading — otherwise the games read as a fourth player. */
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font: var(--text-meta);
}

.about-chat-games {
  display: grid;
  gap: var(--space-3);
  list-style: none;
  padding: 0;
  margin: 0;
}

.about-chat-game {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.about-chat-cover {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: var(--radius-xs);
  background: var(--raised);
  color: var(--muted);
  font: var(--text-micro);
}

/* Частые вопросы. The page's last block answers what the sections above leave
   open, so it stays a plain list rather than a card: one hairline per row, one
   answer at a time, and no promise the rest of the page has not already made. */
/* The questions run the shell's full width — the row is the hairline table the
   sections above already are — while the answer keeps its own reading measure.
   A hairline separates two questions and nothing else: the list's own top and
   bottom are the section's edges, which the heading and the page already
   draw. */
.about-faq-item + .about-faq-item {
  border-top: var(--border-width) solid var(--line);
}

.about-faq-item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-7);
  padding: var(--space-5) 0;
  font: var(--text-reading);
  font-weight: var(--weight-bold);
  cursor: pointer;
  list-style: none;
}

.about-faq-item > summary::-webkit-details-marker {
  display: none;
}

.about-faq-item > summary:hover .about-faq-sign,
.about-faq-item[open] > summary .about-faq-sign {
  color: var(--text);
}

.about-faq-item > summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-xs);
}

/* Two questions carry artwork inline instead of a typed word: the drawn
   wordmark and the Telegram glyph. Without a height the wordmark paints at
   its intrinsic 2676px and buries the list, so both stay on the text's
   own line exactly like the lead mark above. */
.about-faq-question {
  flex: 1;
  min-width: 0;
}

.about-faq-mark {
  display: inline-block;
  height: 1.15em;
  width: auto;
  max-width: 100%;
  vertical-align: -0.31em;
}

.about-faq-glyph {
  width: 1em;
  height: 1em;
  margin-right: .2em;
  vertical-align: -0.15em;
  fill: currentColor;
}

/* The only affordance is a hairline cross that drops its vertical stroke once
   the answer is open — no chevron, no icon set. */
.about-faq-sign {
  position: relative;
  flex: none;
  width: .8rem;
  height: .8rem;
  color: var(--muted);
  transition: color var(--dur-1) var(--ease-out);
}

.about-faq-sign::before,
.about-faq-sign::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
  background: currentColor;
}

.about-faq-sign::after {
  transform: rotate(90deg);
  transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-2) var(--ease-out);
}

.about-faq-item[open] .about-faq-sign::after {
  transform: rotate(0deg);
  opacity: 0;
}

.about-faq-item > p {
  max-width: var(--measure);
  margin: 0 0 var(--space-6);
  padding-right: var(--space-8);
  color: var(--muted);
  font: var(--text-reading);
}

/* The live card. Everything in it is the community's own data, fetched once and
   revealed together. The card's own shape is drawn from the first frame — see
   the placeholders below — so the arriving data replaces a card of the same
   height instead of growing one under a page the reader has already settled
   into. An unreachable API is the one case that still collapses it. */
.about-showcase {
  display: grid;
  gap: var(--space-6);
  padding: var(--space-7);
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-surface);
  background: var(--surface);
}

.about-showcase-block {
  display: grid;
  gap: var(--space-4);
  padding-top: var(--space-6);
  border-top: var(--border-width) solid var(--line);
}

.about-showcase-block:first-child {
  padding-top: 0;
  border-top: 0;
}

.about-showcase-label {
  display: flex;
  justify-content: space-between;
  gap: var(--space-6);
  margin: 0;
  color: var(--muted);
  font: var(--text-meta);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

.about-games {
  display: grid;
  gap: var(--space-3);
  list-style: none;
  padding: 0;
  margin: 0;
}

.about-game {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4);
  border-radius: var(--radius-surface);
  background: var(--raised);
}

.about-game-icon {
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  object-fit: cover;
  background: var(--raised);
}

.about-game-body {
  flex: 1;
  min-width: 0;
}

.about-game-title {
  margin: 0;
  font: var(--text-item);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.about-game-track {
  display: block;
  margin-top: var(--space-3);
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--line);
  overflow: hidden;
}

.about-game-bar {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--text);
}

.about-game-hours {
  flex-shrink: 0;
  color: var(--muted);
  font: var(--text-meta);
  font-variant-numeric: tabular-nums;
}

/* One trophy frame at a time. The cards are stacked on top of each other and
   take turns; the container keeps the height so the card below never moves. */
.about-ticker {
  position: relative;
  min-height: 6rem;
}

.about-ticker[data-cycle="off"] {
  display: grid;
  gap: var(--space-3);
  min-height: 0;
}

.about-trophy {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4);
  border-radius: var(--radius-surface);
  background: var(--raised);
}

.about-ticker[data-cycle="on"] .about-trophy {
  position: absolute;
  inset: 0;
  opacity: 0;
}

.about-trophy-icon {
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  object-fit: contain;
}

.about-trophy-body {
  min-width: 0;
}

.about-trophy-name {
  margin: 0;
  font: var(--text-item);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.about-trophy-game,
.about-trophy-meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-1) 0 0;
  color: var(--muted);
  font: var(--text-meta);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.about-trophy-grade {
  font-weight: var(--weight-bold);
}

.about-trophy-grade.is-bronze { color: var(--trophy-bronze); }
.about-trophy-grade.is-silver { color: var(--trophy-silver); }
.about-trophy-grade.is-gold { color: var(--trophy-gold); }
.about-trophy-grade.is-platinum { color: var(--trophy-platinum); }

.about-trophy-rarity.ultra,
.about-trophy-rarity.veryrare { color: var(--warning); }
.about-trophy-rarity.rare { color: var(--text); }

/* Motion. The two entrances play once when the card scrolls in; the ticker
   loops for as long as it is on screen. */
.about-showcase .about-game {
  opacity: 0;
  transform: translateY(8px);
  animation: about-row-in .6s var(--ease-out) both;
}

.about-showcase .about-game-bar {
  animation: about-bar-grow 1.2s var(--ease-out) both;
}

.about-ticker[data-cycle="on"] .about-trophy {
  animation: about-trophy-cycle var(--about-ticker, 9s) linear infinite both;
}

/* The gate is data-running, so a hidden tab, an offscreen card or a stated
   preference parks all of it. It has to come after the declarations above: the
   `animation` shorthand resets play-state, so an earlier pause would be lost. */
.about-showcase .about-game,
.about-showcase .about-game-bar,
.about-ticker[data-cycle="on"] .about-trophy {
  animation-play-state: paused;
}

.about-showcase[data-running="true"] .about-game,
.about-showcase[data-running="true"] .about-game-bar,
.about-showcase[data-running="true"] .about-ticker[data-cycle="on"] .about-trophy {
  animation-play-state: running;
}

@keyframes about-row-in {
  to { opacity: 1; transform: translateY(0); }
}

@keyframes about-bar-grow {
  to { width: var(--about-bar, 0%); }
}

@keyframes about-trophy-cycle {
  0% { opacity: 0; transform: translateY(12px); }
  4% { opacity: 1; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(0); }
  34% { opacity: 0; transform: translateY(-12px); }
  100% { opacity: 0; transform: translateY(12px); }
}

/* Placeholders. The metric row and the live card are the only blocks on this
   page that wait for the network, and both used to arrive by appearing: the
   hero grew a beat after the reader had settled into it. They now stand in
   their own shape from the first frame.

   Every bar is sized in em against the line it stands in and aligned to that
   line's middle, so the parent's strut — not the bar — sets the height and the
   real text lands exactly where the placeholder held. `status-pulse` is the
   dashboard's own skeleton beat, from the stylesheet this page already shares
   with it. */
.about-page .is-skeleton i,
.about-page .about-game-skeleton i,
.about-page .about-trophy-skeleton i {
  display: inline-block;
  vertical-align: middle;
  width: 100%;
  height: .6em;
  border-radius: var(--radius-xs);
  background: var(--line);
  animation: status-pulse 1.2s ease-in-out infinite alternate;
}

.about-metrics-note.is-skeleton i { width: 6.5rem; }

.about-metrics.is-skeleton dt i { width: 2.6rem; }
.about-metrics.is-skeleton .about-metric-value i { width: 4.4rem; }
.about-metrics.is-skeleton .about-metric:nth-child(2) dt i { width: 3.4rem; }
.about-metrics.is-skeleton .about-metric:nth-child(2) .about-metric-value i { width: 5.4rem; }
.about-metrics.is-skeleton .about-metric:nth-child(3) dt i { width: 3.1rem; }
.about-metrics.is-skeleton .about-metric:nth-child(3) .about-metric-value i { width: 3.8rem; }

/* The entrance belongs to a row that arrives; a placeholder is simply there. */
.about-showcase .about-game-skeleton,
.about-trophy-skeleton {
  pointer-events: none;
  animation: none;
  opacity: 1;
  transform: none;
}

.about-game-skeleton .about-game-icon,
.about-trophy-skeleton .about-trophy-icon {
  background: var(--raised);
  border-radius: var(--radius-round);
  animation: status-pulse 1.2s ease-in-out infinite alternate;
}

.about-game-skeleton .about-game-title i { width: 62%; }
.about-game-skeleton:nth-child(2) .about-game-title i { width: 48%; }
.about-game-skeleton:nth-child(3) .about-game-title i { width: 54%; }
.about-game-skeleton .about-game-hours i { width: 2.1rem; }

/* A descending run, because that is what a top three looks like. */
.about-showcase .about-game-skeleton .about-game-bar {
  width: 100%;
  background: var(--line);
  animation: status-pulse 1.2s ease-in-out infinite alternate;
}
.about-showcase .about-game-skeleton:nth-child(2) .about-game-bar { width: 72%; }
.about-showcase .about-game-skeleton:nth-child(3) .about-game-bar { width: 51%; }

/* Flex rows have no strut to hold the line, so the two metadata lines state the
   height the reading font would have given them. */
.about-trophy-skeleton .about-trophy-game,
.about-trophy-skeleton .about-trophy-meta {
  min-height: 1.25em;
}

.about-trophy-skeleton .about-trophy-name i { width: 8.5rem; }
.about-trophy-skeleton .about-trophy-game i { width: 6rem; }
.about-trophy-skeleton .about-trophy-meta i { width: 7rem; }
.about-trophy-skeleton .about-trophy-date i { width: 4.5rem; }

/* Three placeholder frames, not one: the ticker shows a single card at a time
   and stacks all three under a stated motion preference, so the placeholder has
   to answer both of the heights the real feed will take. Stacked, only the top
   frame is drawn — three translucent cards on one spot are three shades too
   bright. */
.about-ticker.is-skeleton .about-trophy {
  animation: none;
  opacity: 1;
}

.about-ticker.is-skeleton .about-trophy + .about-trophy {
  opacity: 0;
}

/* One width decides the whole page. The hero's card stops fitting beside the
   headline at about 1000px and the drawing runs out of room at its own width —
   the source rail, flexible wiring and the profile card — so the drawing is the
   first to go. Both blocks turn there rather than each on its own number: a
   page that changed shape twice on the way down read as two pages.
   Below it the sources pair up, the wiring turns through a quarter and the
   profile sits under it, so the drawing still reads top to bottom as one flow,
   and the card becomes another block of the page taking the page's own column —
   capped at 460px it sat short of every section under it and the left rail was
   the only thing lining up. */
@media (max-width: 1180px) {
  .about-hero { grid-template-columns: minmax(0, 1fr); }
  .about-merge {
    grid-template-columns: minmax(0, 1fr);
    justify-content: stretch;
    gap: 0;
  }
  .about-sources,
  .about-connector,
  .about-profile {
    width: 100%;
    max-width: var(--about-card);
    justify-self: center;
  }
  /* The central gutter connects every chip at its own inner edge. Packets
     from upper rows never appear to originate from the bottom platform. */
  .about-sources {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(3, minmax(64px, auto));
    gap: var(--space-4) var(--space-6);
  }
  .about-source { width: 100%; height: 100%; min-height: 64px; padding: var(--space-3); }
  .about-source-mark { width: 2rem; }
  .about-source:nth-child(even)::after { right: auto; left: -4px; }
  .about-connector { height: 160px; }
  .about-profile-port { left: 50%; top: -4px; margin: 0 0 0 -3.5px; }
  /* The chat card is the drawing's card seen again, so it takes the same column
     the moment the page stops being two columns wide. */
  .about-telegram-grid { grid-template-columns: minmax(0, 1fr); }
  .about-chat {
    width: 100%;
    max-width: var(--about-card);
    justify-self: center;
  }
}

@media (max-width: 760px) {
  .about-page { padding-top: var(--space-7); gap: var(--space-9); }
  .about-metrics { gap: var(--space-5) var(--space-8); }
  .about-faq-item > summary { gap: var(--space-6); }
  .about-faq-item > p { padding-right: var(--space-8); }
}

@media (prefers-reduced-motion: reduce) {
  /* The wiring is still worth drawing; nothing has to travel along it. */
  .about-trail, .about-packet { display: none; }
  .about-hub, .about-avatar { animation: none; }
  .about-profile-port { animation: none; }
  .about-source, .about-source::after { animation: none; }
  /* Nothing arrives, so every row is simply already ticked. */
  .about-profile-data li,
  .about-profile-data li::before,
  .about-profile-data li::after { animation: none; }
  /* No entrance, no loop: the rows sit where they land and every trophy stays
     readable in one stack instead of taking turns. */
  .about-showcase .about-game { animation: none; opacity: 1; transform: none; }
  .about-showcase .about-game-bar { animation: none; width: var(--about-bar, 0%); }
  .about-ticker { min-height: 0; }
  .about-ticker[data-cycle="on"] { display: grid; gap: var(--space-3); }
  .about-ticker[data-cycle="on"] .about-trophy {
    position: static;
    animation: none;
    opacity: 1;
    transform: none;
  }
  /* The sign still changes, it just stops turning to get there. */
  .about-faq-sign,
  .about-faq-sign::after { transition: none; }
  /* Placeholders keep their shape and lose their beat. Stacked rather than
     cycled, every trophy frame is drawn, so the placeholder is as tall as the
     feed that replaces it. */
  .about-page .is-skeleton i,
  .about-page .about-game-skeleton i,
  .about-page .about-trophy-skeleton i,
  .about-game-skeleton .about-game-icon,
  .about-trophy-skeleton .about-trophy-icon,
  .about-showcase .about-game-skeleton .about-game-bar { animation: none; }
  .about-ticker.is-skeleton .about-trophy + .about-trophy { opacity: 1; }
}

.about-trophy-date {
  display: block;
  margin-top: var(--space-2);
  color: var(--muted);
  font: var(--text-meta);
}
