/* =========================================================================
   Games.app — the ONE definition of the DavidOS Games app's presentation.
   =========================================================================
   Stamped markup: partials/games-app.html   Behaviour: assets/games-app.js

   TWO SURFACES, ONE APP.
   · /about  — the app inside an .os-window (that file owns .gamecenter-window
               and .gamecenter-header: the frame and the traffic lights).
   · /games  — the same app with the frame taken away, filling the viewport,
               exactly like taking a macOS app fullscreen. Its extra chrome
               (the hover-reveal menu bar) is the .gc-fs-* block at the end.

   Everything between those two comments is IDENTICAL on both surfaces by
   construction — there is no second copy to drift.

   THEME: the app is deliberately theme-AGNOSTIC. Its copy is always white and
   its tint is dark glass in BOTH themes (DESIGN.md §4 state matrix, the
   testimonial-card row), so the app already looks the same in day and night
   mode on /about. Anything here that followed the site tokens would make
   /games diverge from /about in day mode — the exact failure this file
   exists to prevent. Do not add html.day-mode variants beyond the one that
   pins the pill tint.
   ========================================================================= */

/* The app root. Deliberately paints and lays out NOTHING: on /about the host
   is an .os-window that starts `display: none` and is switched to `flex` by an
   inline style, so a `display` here would either show the window on load or be
   silently beaten by that inline style. It exists to scope the app's font and
   to give both surfaces one hook. Layout comes from .gamecenter-window
   (/about) and .gc-app--full (/games). */
.gc-app { font-family: var(--system-font); }
/* Never define --system-font on .gc-app itself: /about already sets it on
   :root and a local copy here would shadow it, so the two surfaces could drift
   apart on a token change. Only the fullscreen host — which loads no site
   stylesheet at all — declares it, with /about's exact value. */

  /* macOS uses overlay scrollbars that fade away — a persistent track is wrong
     here, so the body scrolls with no visible bar at all */
  .gc-body {
    flex: 1; min-height: 0; overflow-y: auto; position: relative; padding: 0;
    scrollbar-width: none; -ms-overflow-style: none;
  }
  .gc-body::-webkit-scrollbar { display: none; }

  /* ---- floating pill tab bar + avatar (both ride above the hero) ---- */
  .gc-chrome {
    position: absolute; top: 9px; left: 0; right: 0; z-index: 7;
    display: flex; align-items: center; justify-content: center;
    padding: 0 14px 0 92px; pointer-events: none;
  }
  /* The pill is REAL liquid glass — the dock's three-layer stack (DESIGN.md
     §4). Its three children are written into partials/games-app.html with the
     dock's own data-glass values (the canonical markup recipe, so both
     surfaces get byte-identical layers with no JS) and upgraded once by
     liquid-glass.js. Host paints no fill; it keeps only the rim and the drop
     shadow. No dividers: the real Games app has none. */
  .gc-tabs {
    pointer-events: auto; position: relative; isolation: isolate;
    display: flex; align-items: center; gap: 0;
    padding: 3px; border-radius: 999px;
    background: transparent;
    /* NO drawn border. House rule: glass edges are a hairline plus a subtle
       inset top-highlight, never an outline — and this pill previously
       carried a full 1px perimeter border AND the shine's inset ring stacked
       on top of each other, which read as a drawn chip. The shine below is
       what defines the form now. */
    border: none;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
  }
  .gc-tabs > .glass-effect {
    position: absolute; inset: 0; border-radius: inherit; z-index: 0; overflow: hidden;
    pointer-events: none;
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  }
  /* Tint whitelist, BOTH themes (DESIGN.md §4). The labels are white whatever
     the theme and the pill floats over hero art that swings from a bright
     illustration to a dark gradient, so it takes the dark-glass-in-both-themes
     treatment the testimonial cards use rather than a theme-following tint. */
  .gc-tabs > .glass-effect-tint {
    position: absolute; inset: 0; border-radius: inherit; z-index: 0; overflow: hidden;
    pointer-events: none; background-color: rgba(18, 20, 26, 0.38);
  }
  html.day-mode .gc-tabs > .glass-effect-tint { background-color: rgba(18, 20, 26, 0.38); }
  .gc-tabs > .glass-effect-shine {
    position: absolute; inset: 0; border-radius: inherit; z-index: 1; overflow: hidden;
    pointer-events: none;
    /* hairline top-edge catch only — no spread, no bottom ring */
    box-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.13);
  }
  /* lift the controls above the decorative layers */
  .gc-tabs > .gc-tab, .gc-tabs > .gc-search { position: relative; z-index: 2; }
  .gc-tab {
    border: none; background: transparent; cursor: pointer;
    font-family: var(--system-font); font-size: 11.5px; font-weight: 600;
    color: rgba(255, 255, 255, 0.78); padding: 5px 15px; border-radius: 999px;
    transition: color 0.16s ease, background-color 0.16s ease;
  }
  .gc-tab.is-active { color: #fff; }
  .gc-tab:hover:not(.is-active) { color: #fff; }
  /* The selection is a separate MOVING glass thumb, not a fill on the active
     tab — the same lift-and-drop the nav's day/night switch uses. These rules
     out-specify .liquid-glass, which loads LATER and sets position: relative;
     without the extra specificity the thumb drops into the flex flow and
     shoves the tabs sideways (the same trap theme.css documents). */
  .gc-tabs .gc-thumb {
    position: absolute; top: 3px; left: 0; width: 0; height: calc(100% - 6px);
    z-index: 1; border-radius: 999px; overflow: hidden; pointer-events: none;
    transform: translateX(0);
    /* the tabs have DIFFERENT widths, so the thumb resizes as it glides —
       real width, never scaleX(), which would stretch the lens and the radii */
    transition: transform 0.38s cubic-bezier(0.34, 1.2, 0.64, 1),
                width 0.38s cubic-bezier(0.34, 1.2, 0.64, 1);
  }
  /* Lift-and-drop while it travels. `scale` is the INDEPENDENT property on
     purpose: it composes with the transform: translateX glide above instead of
     clobbering it, which is what keeps the thumb sliding rather than jumping. */
  .gc-tabs.is-lifting .gc-thumb {
    animation: gc-thumb-lift 0.42s cubic-bezier(0.3, 0.9, 0.45, 1);
  }
  @keyframes gc-thumb-lift {
    0%   { scale: 1;    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28); }
    40%  { scale: 1.16; box-shadow: 0 9px 22px rgba(0, 0, 0, 0.34); }
    100% { scale: 1;    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28); }
  }
  @media (prefers-reduced-motion: reduce) {
    .gc-tabs .gc-thumb { transition: none; }
    .gc-tabs.is-lifting .gc-thumb { animation: none; }
  }
  /* a little more tint than the nav thumb's 0.22: this one floats over hero
     art that swings from a bright illustration to a dark gradient */
  .gc-tabs .gc-thumb > .glass-effect-tint { background-color: rgba(255, 255, 255, 0.28); }
  /* liquid-glass.css gives --thumb a deliberately bright rim
     (inset -1px -1px 1px 1px / 2px 2px 1px at 0.42–0.50) — precisely the
     1px-spread white ring the house rule rules out. Replaced with a single
     top-edge catch so the thumb reads as a lifted piece of glass rather than
     an outlined chip. Motion is untouched. */
  .gc-tabs .gc-thumb > .glass-effect-shine {
    box-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.30);
  }
  .gc-tab:focus-visible, .gc-search:focus-visible, .gc-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
  .gc-search {
    border: none; background: transparent; cursor: pointer;
    padding: 5px 13px 4px; border-radius: 999px; color: rgba(255, 255, 255, 0.8);
    display: flex; align-items: center;
  }
  .gc-search:hover { color: #fff; }
  .gc-search svg { width: 14px; height: 14px; display: block; }
  .gc-avatar {
    pointer-events: auto; position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
    width: 28px; height: 28px; padding: 0; border-radius: 50%; overflow: hidden; cursor: pointer;
    border: 1px solid rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.1);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  }
  .gc-avatar img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center 18%; }

  /* ---- HERO: full-bleed, two-thirds tall, TWO variants ----
     A "editorial"    — palette gradient + three tilted icons on the right.
     B "illustration" — full-bleed game artwork running edge to edge (and
                        behind the pill tab bar), a directional left scrim in
                        the artwork's own colour so the copy stays readable,
                        and a vertical fade that dissolves into the dark
                        shelves below with no seam.
     A slide declares its variant; B silently renders as A whenever its
     artwork isn't on disk yet, so nothing ever shows an empty hero. */
  /* Home is a flex column and the hero is the growing element, so deleting
     the second shelf can't leave dead space at ANY window height — the hero
     simply absorbs the slack. That is why the window height stays put:
     shortening it would have cost Arcade and Scores visible rows for a gap
     that only ever existed on this one tab. */
  .gc-pane[data-gc-pane="home"] { display: flex; flex-direction: column; min-height: 100%; }
  .gc-pane[data-gc-pane="home"] > .gc-shelf { flex: 0 0 auto; }
  .gc-hero {
    position: relative; flex: 1 1 auto; overflow: hidden;
    /* 320px of content zone + a 52px TAIL that exists purely to carry the
       dissolve into the shelves. The copy and art stay exactly where they
       were because .gc-hero-inner is inset by the tail. */
    height: 372px; min-height: 372px;
    /* NO fill of its own: wherever the masked layers below have faded out,
       the window's own #0b0d14 shows through, so the blend target is the
       section colour itself and a seam is impossible by construction. */
    background: transparent;
  }
  /* The dissolve is a MASK on the painted layers, not another gradient on
     top: an overlay only matches when the colour behind it is exactly what
     you blend to, and over an illustration it fights the artwork's own
     colours. Masking works identically for variant A's CSS gradient and
     variant B's <img> backdrop, and it removes alpha instead of adding ink,
     so it cannot darken the midtones on the way down. Starts at 58% (y=216),
     well above the dots at y=284, and finishes at the hero's bottom edge. */
  .gc-hero-bg, .gc-hero-scrim {
    -webkit-mask-image: linear-gradient(to bottom, #000 58%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 58%, transparent 100%);
  }
  /* the backdrop layer: gradient in variant A, artwork in variant B */
  .gc-hero-bg {
    position: absolute; inset: 0; z-index: 0;
    background-color: #07080c;
    /* Layered TOP-first. The accent is a GLOW behind the tilted icons, never a
       flat field: the dark base is the bed, a radial accent bloom sits on the
       right where the art is, and a global darkener over both keeps the whole
       hero deep and moody so white type and icons carry the contrast. Plain
       rgba throughout — no color-mix — so nothing depends on engine support,
       and it reads deliberately whether a palette's base is near-black
       (#08124c) or near-neutral (#423b3c). */
    background-image:
      linear-gradient(180deg, rgba(0, 0, 0, 0.30) 0%, rgba(0, 0, 0, 0.40) 45%, rgba(0, 0, 0, 0.46) 100%),
      radial-gradient(115% 85% at 74% 30%, var(--gc-accent, #e05a93) 0%, transparent 60%),
      linear-gradient(180deg, var(--gc-base, #423b3c) 0%, var(--gc-base, #423b3c) 100%);
    background-size: cover; background-position: center;
    transition: opacity 0.4s ease;
  }
  /* scrims are variant-B only — variant A's gradient already darkens itself */
  .gc-hero-scrim { position: absolute; inset: 0; z-index: 1; opacity: 0; transition: opacity 0.4s ease; }
  .gc-hero.is-illustration .gc-hero-scrim { opacity: 1; }
  /* variant B paints artwork into the same layer: drop the bed and the bloom */
  .gc-hero.is-illustration .gc-hero-bg { background-image: none; }
  /* horizontal: the artwork's dominant colour at full strength on the left,
     gone by the midpoint — a directional wash, never a flat overlay */
  .gc-hero-scrim--h {
    background-image: linear-gradient(90deg,
      var(--gc-base, #423b3c) 0%,
      color-mix(in srgb, var(--gc-base, #423b3c) 72%, transparent) 26%,
      color-mix(in srgb, var(--gc-base, #423b3c) 28%, transparent) 42%,
      transparent 56%);
  }
  /* vertical: TOP vignette only, so the tab pill has something to sit on.
     The old bottom ramp to #07080c is gone — the mask does the dissolve, and
     leaving the ramp in would have darkened the artwork through the band. */
  .gc-hero-scrim--v {
    background-image: linear-gradient(180deg, rgba(7, 8, 12, 0.30) 0%, rgba(7, 8, 12, 0) 30%, rgba(7, 8, 12, 0) 100%);
  }
  .gc-hero-inner {
    position: absolute; inset: 0 0 52px; z-index: 2; display: flex; align-items: center;
    padding: 46px 54px 0;
  }
  /* variant B's copy block: icon + "Game • Category", then the headline */
  .gc-hero-tag { display: flex; align-items: center; gap: 8px; }
  .gc-hero-tag img { width: 24px; height: 24px; border-radius: 7px; display: block; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4); }
  .gc-hero-tag span { font-size: 11.5px; font-weight: 650; color: rgba(255, 255, 255, 0.86); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .gc-meta { display: flex; align-items: center; gap: 7px; margin-top: 9px; font-size: 12px; color: rgba(255, 255, 255, 0.78); }
  .gc-meta svg { width: 14px; height: 14px; flex: 0 0 auto; display: block; }
  .gc-meta span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .gc-hero-copy { width: 40%; min-width: 0; position: relative; z-index: 2; }
  .gc-kicker {
    font-size: 9.5px; font-weight: 800; letter-spacing: 0.18em; text-transform: uppercase;
    color: rgba(255, 255, 255, 0.72);
  }
  .gc-h1 { margin: 9px 0 0; font-size: 30px; line-height: 1.08; font-weight: 800; letter-spacing: -0.02em; color: #fff; }
  .gc-sub { margin: 8px 0 0; font-size: 13px; line-height: 1.45; color: rgba(255, 255, 255, 0.78); }
  /* ONE button for the whole app — hero CTA, Arcade tiles, Scores cards.
     Apple's translucent capsule: light fill at ~35%, fully rounded, white
     label. Three near-misses is what it replaced. */
  .gc-pill {
    border: none; cursor: pointer;
    font-family: var(--system-font); font-size: 12px; font-weight: 700; color: #fff;
    background: rgba(255, 255, 255, 0.35); border-radius: 999px; padding: 7px 17px;
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
    transition: background-color 0.16s ease, transform 0.16s ease;
    white-space: nowrap; flex: 0 0 auto;
  }
  .gc-pill:hover { background: rgba(255, 255, 255, 0.46); transform: scale(1.04); }
  .gc-pill:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
  /* spacing only — .gc-pill supplies the material; never used alone */
  .gc-cta { margin-top: 17px; padding: 8px 19px; }
  @media (prefers-reduced-motion: reduce) { .gc-pill { transition: none; } .gc-pill:hover { transform: none; } }

  /* ================= shared key art, used by tiles AND scores =================
     Three stacked images so a missing hero degrades instead of blanking: a
     blurred, over-scaled copy of the square icon sits underneath as the bed,
     the wide key art covers it when it loads, and a crisp centred mark
     appears only when the art 404s. Files auto-upgrade the moment they land. */
  .gc-art { position: relative; display: block; overflow: hidden; background: var(--gc-tile-base, #16181f); }
  .gc-art__blur {
    position: absolute; inset: -8%; width: 116%; height: 116%; object-fit: cover;
    filter: blur(22px) saturate(130%); opacity: 0.85;
  }
  .gc-art__hero { position: relative; z-index: 1; display: block; width: 100%; height: 100%; object-fit: cover; }
  .gc-art__mark { display: none; }
  .gc-art.is-fallback .gc-art__hero { display: none; }
  .gc-art.is-fallback .gc-art__mark {
    display: block; position: absolute; z-index: 2; top: 50%; left: 50%;
    transform: translate(-50%, -50%); width: 42%; max-width: 92px; border-radius: 18%;
    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.5);
  }
  /* PER-GAME CROP — ARCADE TILES ONLY. Six of the ten arts were composed for
     the hero BANNER, so their left third is deliberately empty for the text
     scrim. Nothing overlays a tile, so there that margin is pure dead weight
     and each art is biased toward its own subject, measured off the artwork
     rather than one global guess. SCORES does the opposite (below): it fills
     that margin with the game icon, so it must NOT crop it away. */
  .gc-art .gc-art__hero { object-position: 66% 50%; }
  .gc-tile-art[data-art="doom"] .gc-art__hero       { object-position: 72% 50%; }
  .gc-tile-art[data-art="johnpork"] .gc-art__hero   { object-position: 62% 42%; }
  .gc-tile-art[data-art="crossyroad"] .gc-art__hero { object-position: 72% 62%; }
  .gc-tile-art[data-art="pacman"] .gc-art__hero     { object-position: 72% 55%; }
  .gc-tile-art[data-art="snake"] .gc-art__hero      { object-position: 68% 52%; }
  .gc-tile-art[data-art="sahur-run"] .gc-art__hero  { object-position: 66% 48%; }
  /* These four were regenerated to the HERO standard (calm left 40%), so they
     now carry a larger empty margin than the original six and need a stronger
     bias — values picked by rendering real 16:10 tile crops at 50-100%. The Y
     component is a no-op (2:1 art in a 16:10 box overflows horizontally only)
     and is kept only for consistency with the rules above. */
  .gc-tile-art[data-art="blackjack"] .gc-art__hero { object-position: 80% 50%; }
  .gc-tile-art[data-art="roulette"] .gc-art__hero { object-position: 88% 50%; }
  .gc-tile-art[data-art="slots"] .gc-art__hero { object-position: 88% 50%; }
  .gc-tile-art[data-art="gbda-simulator"] .gc-art__hero { object-position: 80% 50%; }

  /* three icons, tilted and overlapping — the hero art */
  .gc-hero-art { position: absolute; right: 8%; top: 50%; transform: translateY(-46%); width: 300px; height: 190px; z-index: 1; }
  .gc-hero.is-illustration .gc-hero-art { display: none; }
  .gc-hero-art img {
    position: absolute; top: 50%; border-radius: 22%; display: block;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
    transition: transform 0.45s cubic-bezier(0.2, 0.9, 0.3, 1), opacity 0.45s ease;
  }
  .gc-hero-art .gc-art--back  { width: 104px; height: 104px; left: 2px;   transform: translateY(-50%) rotate(-13deg) scale(0.9); opacity: 0.75; }
  .gc-hero-art .gc-art--front { width: 104px; height: 104px; right: 2px;  transform: translateY(-50%) rotate(12deg) scale(0.9);  opacity: 0.75; }
  .gc-hero-art .gc-art--main  { width: 132px; height: 132px; left: 50%; margin-left: -66px; transform: translateY(-50%) rotate(-5deg); z-index: 2; }

  /* carousel arrows pinned to the hero edges */
  .gc-arrow {
    /* centred on the CONTENT zone, tracking it as the hero flex-grows —
       a fixed offset drifted off-centre once the hero absorbed slack */
    position: absolute; top: calc((100% - 52px) / 2); transform: translateY(-50%); z-index: 4;
    width: 34px; height: 34px; padding: 0; border: none; border-radius: 50%; cursor: pointer;
    background: transparent; color: rgba(255, 255, 255, 0.62);
    display: flex; align-items: center; justify-content: center;
    transition: color 0.16s ease, background-color 0.16s ease;
  }
  .gc-arrow:hover { color: #fff; background: rgba(255, 255, 255, 0.14); }
  .gc-arrow:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
  .gc-arrow svg { width: 22px; height: 22px; display: block; }
  .gc-arrow--prev { left: 8px; }
  .gc-arrow--next { right: 8px; }

  /* 88px overshot — at the hero's real grown height that read as floating
     mid-panel. 44px puts them at ~89% down with artwork still beneath, and
     still inside the fade band (which starts at 58% and ends at the bottom). */
  .gc-dots { position: absolute; left: 0; right: 0; bottom: 44px; z-index: 4; display: flex; justify-content: center; gap: 6px; }
  .gc-dot {
    width: 6px; height: 6px; padding: 0; border: none; border-radius: 50%; cursor: pointer;
    background: rgba(255, 255, 255, 0.3); transition: background-color 0.16s ease, transform 0.16s ease;
  }
  .gc-dot.is-active { background: #fff; transform: scale(1.15); }
  .gc-dot:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

  @media (prefers-reduced-motion: reduce) {
    .gc-hero-bg, .gc-hero-scrim, .gc-hero-art img, .gc-cta, .gc-dot, .gc-tile-link { transition: none; }
    .gc-cta:hover, .gc-tile-link:hover { transform: none; }
  }

  /* ---- shelf under the hero ---- */
  /* The row is full-bleed and carries the gutters itself, so the first and
     last tile scroll fully clear of the window edges instead of being chopped
     against them; the heading keeps the gutter as padding. */
  .gc-shelf { padding: 20px 0 24px; }
  .gc-shelf-title { margin: 0; padding: 0 22px; font-size: 17px; font-weight: 800; letter-spacing: -0.01em; color: #fff; }
  .gc-row {
    display: flex; gap: 12px;
    /* overflow-x:auto forces overflow-y to compute to auto as well, so a
       hover transform would be clipped by the scroll box. The headroom lives
       INSIDE the scroller as padding (top for the lift, bottom for the drop
       shadow) and negative margins cancel it, so the layout never shifts. */
    margin: -1px 0 -26px; padding: 14px 22px 30px;
    overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
    scroll-padding-inline: 22px;
    /* soft right edge so an overflowing row reads as "more", not a hard chop */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
  }
  .gc-row::-webkit-scrollbar { display: none; }
  .gc-row-item {
    flex: 0 0 auto; padding: 0; border: none; background: transparent; cursor: pointer;
    width: 72px; border-radius: 18px; overflow: hidden; line-height: 0;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
    transition: transform 0.18s ease;
  }
  .gc-row-item img { display: block; width: 72px; height: 72px; object-fit: cover; }
  .gc-row-item:hover { transform: translateY(-3px) scale(1.03); }
  .gc-row-item:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
  @media (prefers-reduced-motion: reduce) { .gc-row-item { transition: none; } .gc-row-item:hover { transform: none; } }

  /* ---- ARCADE tab: key-art tiles ---- */
  /* 56px of top padding clears the transparent 46px chrome strip: the traffic
     lights and the floating tab pill both sit over the pane, so anything
     starting at y=0 lands underneath them. The panes carry NO heading — the
     active tab in the pill already names the section, exactly as Apple does. */
  .gc-pane { padding: 56px 22px 24px; }
  .gc-pane[hidden] { display: none; }
  .gc-pane-sub { margin: 0 0 15px; font-size: 12px; color: rgba(255, 255, 255, 0.5); }
  .gc-grid {
    list-style: none; margin: 0; padding: 0;
    display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
    gap: 14px; align-content: start;
  }
  .gc-tile { min-width: 0; }
  .gc-tile-link {
    position: relative; display: flex; flex-direction: column; height: 100%; width: 100%;
    padding: 0; text-align: left; font: inherit; color: inherit; cursor: pointer;
    border: 1px solid rgba(255, 255, 255, 0.09); border-radius: 16px; overflow: hidden;
    background: var(--gc-tile-base, #16181f);
    transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
  }
  @media (hover: hover) {
    .gc-tile-link:hover { transform: translateY(-3px); border-color: rgba(255, 255, 255, 0.24); box-shadow: 0 18px 38px rgba(0, 0, 0, 0.55); }
  }
  .gc-tile-link:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.9); outline-offset: 2px; }
  /* top ~72%: full-bleed key art, no text over it */
  .gc-tile-art { flex: 0 0 auto; aspect-ratio: 16 / 10; width: 100%; }
  .gc-badge {
    position: absolute; top: 9px; right: 9px; z-index: 3;
    padding: 3px 7px; border-radius: 999px;
    font-size: 8px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase;
    color: #fff; background: rgba(4, 8, 16, 0.62); border: 1px solid rgba(255, 255, 255, 0.24);
    -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  }
  .gc-badge::before { content: "\1F3C6"; margin-right: 4px; font-size: 9px; }
  /* bottom ~28%: an info bar tinted FROM the artwork — the palette's dark
     stop, darkened and desaturated by a flat black wash so it reads as a
     translucent shade of the art rather than a neutral grey bar. Works for
     the near-neutral palettes (#423b3c, #3c4241) as well as the saturated. */
  .gc-tile-bar {
    flex: 1 0 auto; display: flex; align-items: center; gap: 10px;
    padding: 11px 12px; min-height: 72px;
    background-color: var(--gc-tile-base, #22252c);
    background-image: linear-gradient(rgba(0, 0, 0, 0.26), rgba(0, 0, 0, 0.26));
  }
  .gc-tile-icon { width: 48px; height: 48px; border-radius: 11px; flex: 0 0 auto; display: block; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.38); }
  .gc-tile-copy { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .gc-tile-name { margin: 0; font-size: 13.5px; font-weight: 700; letter-spacing: -0.01em; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* two lines, not one truncated one — the copy is a joke and "A raycast hell
     of d…" throws it away. The bar's min-height already covers two lines. */
  .gc-tile-sub {
    margin: 0; font-size: 11px; line-height: 1.35; color: rgba(255, 255, 255, 0.62);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  @media (prefers-reduced-motion: reduce) {
    .gc-tile-link { transition: none; }
    .gc-tile-link:hover { transform: none; }
  }

  /* ---- SCORES tab: tiered, art-led leaderboards ----
     Two kinds of hierarchy. ACROSS games: a lead card spanning the grid,
     chosen from live data (most entries, tie-broken on the highest score) so
     it is never hardcoded. WITHIN a card: #1 gets a bigger medal, a heavier
     name and a brighter score; the runners-up recede. */
  .gc-note {
    padding: 14px 16px; border-radius: 12px; margin-bottom: 14px;
    background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.1);
    color: rgba(255, 255, 255, 0.66); font-size: 12.5px;
  }
  .gc-scores-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 236px), 1fr)); gap: 14px;
  }
  .gc-scores-grid[hidden] { display: none; }
  .gc-card {
    position: relative; min-width: 0; display: flex; flex-direction: column; overflow: hidden;
    border: 1px solid rgba(255, 255, 255, 0.09); border-radius: 16px;
    background: var(--gc-tile-base, #16181f);
  }
  .gc-card[hidden] { display: none; }
  /* The art is shown AS COMPOSED — no subject bias — because the empty left
     third is now doing a job. 16:9 against the 2:1 source crops ~11%
     horizontally, split evenly, so the margin survives intact. */
  .gc-card-art { position: relative; flex: 0 0 auto; aspect-ratio: 16 / 9; width: 100%; }
  .gc-card-art .gc-art__hero { object-position: 50% 50%; }
  /* the big icon replaces the fallback's centred mark on these cards */
  .gc-card-art.is-fallback .gc-art__mark { display: none; }
  /* A left wash so the icon reads over ANY composition. Invisible on the six
     banner arts (that corner is already dark) but essential on the four
     tile-composed ones, which put real subject matter — and in GBDA's case a
     bright grey road — exactly where the icon lands. */
  .gc-card-art::after {
    content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
    background-image: linear-gradient(90deg, rgba(6, 8, 12, 0.62) 0%, rgba(6, 8, 12, 0.30) 20%, rgba(6, 8, 12, 0) 42%);
  }
  /* on the lead the icon straddles the art's LEFT edge, so the wash is a band
     BEHIND THE ICON rather than a field over the picture: the overhang is only
     40px — 9.9% of a 406px art column — so the gradient is finished by 24% */
  .gc-card.is-lead .gc-card-art::after {
    background-image: linear-gradient(90deg, rgba(6, 8, 12, 0.74) 0%, rgba(6, 8, 12, 0.34) 11%, rgba(6, 8, 12, 0) 24%);
  }
  .gc-card-head {
    position: relative; display: flex; align-items: center; gap: 9px;
    padding: 10px 12px 10px 92px;   /* clears the overhanging icon */
    background-image: linear-gradient(rgba(0, 0, 0, 0.26), rgba(0, 0, 0, 0.26));
  }
  /* THE MOVE: the icon sits in the artwork's empty left margin and straddles
     the seam between the art and the headline box, so it reads as deliberately
     breaking the boundary rather than sitting in either zone. Its own shadow +
     rim carry it over photographic backdrops.
     WHERE it stops is no longer a taste percentage: its bottom edge lands ON
     the game title's baseline. The sum, in the system UI font (hhea ascent
     0.952em over a 1.165em content box — which is exactly why `normal` renders
     at ~1.16em — and cap height 0.714em):
       head padding-top                                  10.00
       + (pill 23 − name line 17) / 2   [the PILL is the
         tallest item here, so the name is centred]       3.00
       + (17 − 1.165 × 13) / 2          [half-leading]    0.93
       + 0.952 × 13                     [ascent]         12.38
       = 26.3px below the head's top edge, so a 62px icon starts at
       26.3 − 62 = −35.7px. The old −58% landed at 26.0 by luck and stated
       nothing; every seam below now shows its own arithmetic. */
  .gc-card-icon {
    position: absolute; z-index: 5; left: 16px; top: -35.7px;
    width: 62px; height: 62px; border-radius: 15px; flex: 0 0 auto; display: block;
    box-shadow: 0 12px 26px rgba(0, 0, 0, 0.58), 0 2px 6px rgba(0, 0, 0, 0.45);
    outline: 1.5px solid rgba(255, 255, 255, 0.24); outline-offset: -1.5px;
  }
  .gc-card-titles { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
  /* explicit line-heights: the icon is positioned off these numbers, so they
     may not be left to whatever `normal` happens to resolve to */
  .gc-card-name { min-width: 0; font-size: 13px; line-height: 17px; font-weight: 700; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .gc-card-play { font-size: 11px; line-height: 13px; padding: 5px 13px; }
  .gc-podium { list-style: none; margin: 0; padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 4px; }
  .gc-pod { display: flex; align-items: center; gap: 9px; padding: 4px 6px; border-radius: 9px; font-size: 11.5px; }
  /* the #1 holder is elevated; 2nd and 3rd recede */
  .gc-pod--1 {
    background: rgba(255, 255, 255, 0.10); padding: 7px 8px; font-size: 13.5px;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  }
  .gc-pod--1 .gc-pod-medal { width: 27px; height: 27px; }
  .gc-pod--1 .gc-pod-name { font-weight: 800; }
  .gc-pod--1 .gc-pod-score { font-size: 14px; color: #fff; }
  .gc-pod--2, .gc-pod--3 { opacity: 0.72; }
  .gc-pod.is-empty { opacity: 0.4; }
  .gc-pod-medal { width: 18px; height: 18px; object-fit: contain; flex: 0 0 auto; }
  .gc-pod-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 650; color: #fff; }
  /* flex: 0 0 auto so a five-digit score can never be shrunk or clipped by a
     long name — the NAME is the part that gives way (it already ellipses) */
  .gc-pod-score { flex: 0 0 auto; font-weight: 800; font-variant-numeric: tabular-nums; color: rgba(255, 255, 255, 0.62); }
  /* THE LEAD CARD IS SIDE-BY-SIDE, standings LEFT and art RIGHT.
     A full-width column could not work here: at 772px wide, art tall enough
     to avoid heavy cropping pushed the standings off a 558px pane entirely —
     the Scores tab showed no scores. Art on the RIGHT specifically so its
     deliberately empty left margin lands against the standings box — the icon
     still sits in that margin and still straddles the boundary, just a
     vertical seam instead of a horizontal one.
     TWO THINGS CHANGED after the first pass read as odd:
     (a) THE CARD'S HEIGHT IS ITS STANDINGS' HEIGHT, not its art's. The hero
         used to size itself from the file's intrinsic 2:1, which made the card
         211px tall while the standings needed only 174 — that leftover 37px
         was the flat empty field under the last score. The hero is absolutely
         positioned now, so the art contributes no height, stretches to the
         column, and the column has nothing left over.
     (b) AT 2:1 THE ART WAS ITS OWN SOURCE RATIO, so object-fit had nothing to
         crop and every game's empty banner margin survived at full size next
         to a wash. The column is sized to ~16:10 instead — the Arcade tile's
         ratio — so the lead shows each game the framing already tuned for its
         tile rather than a letterboxed strip with the subject at one end. */
  .gc-card.is-lead {
    grid-column: 1 / -1; flex-direction: row-reverse; align-items: stretch;
    min-height: 200px;
  }
  .gc-card.is-lead .gc-card-art { flex: 0 0 50%; aspect-ratio: auto; align-self: stretch; }
  /* absolute: the hero fills whatever height the standings ask for instead of
     dictating one (406 x 256 = 1.59:1, the tile's 16:10 to within a percent) */
  .gc-card.is-lead .gc-card-art .gc-art__hero {
    position: absolute; inset: 0; object-position: 66% 50%;
  }
  .gc-card.is-lead .gc-card-side {
    display: flex; flex-direction: column; justify-content: center;
    flex: 1 1 50%; min-width: 0;
    /* THE SEAM BAND, reserved ONCE for the whole column. It was previously
       padding on the head alone, so the title cleared the icon but the score
       rows ran straight under it and the headline number — the most important
       value on the screen — was hidden. Head and rows now inherit the same
       clearance from their container and cannot drift apart again.
       56px = the icon's 40px overhang into this column (80 / 2) + 16px gap. */
    padding-right: 56px;
    /* one graded panel, not a flat field: the first pass darkened the head
       alone and left the raw brand colour everywhere else, which read as an
       empty panel parked beside a picture */
    background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(0, 0, 0, 0.12) 52%, rgba(0, 0, 0, 0.26) 100%);
  }
  /* the head is the TOP OF THAT PANEL now, not a strip under artwork, so it
     drops its own band — and it goes back to being the icon's containing block
     so the icon's offsets are stated against the type it aligns to, immune to
     however the column happens to distribute leftover height */
  .gc-card.is-lead .gc-card-head { position: relative; padding: 30px 0 14px 16px; background-image: none; }
  .gc-card.is-lead .gc-card-titles { gap: 2px; }
  .gc-card.is-lead .gc-card-name { font-size: 17px; line-height: 22px; }
  /* ICON ↔ TITLE, the vertical-seam version. An 80px icon cannot put its
     bottom edge on a baseline only ~61px down without leaving the card, so
     here the relationship is centre-to-centre: the icon's centre sits on the
     title's CAP MIDLINE — the optical centre of the words themselves.
       head padding-top                                  30.00
       + eyebrow line 12 + 2 gap                         14.00
       + (22 − 1.165 × 17) / 2          [half-leading]    1.10
       + 0.952 × 17                     [ascent]         16.18
       = 61.28  the title's baseline
       − 0.714 × 17                     [cap height]     12.14
       = 49.14  cap top,  so the midline is 55.21
     and an 80px icon starts at 55.21 − 40 = 15.2px.
     Horizontally the head's right edge IS the reserved band's inner edge, so
     −96px (56 band + 40 half-icon) centres the icon on the seam. */
  .gc-card.is-lead .gc-card-icon {
    top: 15.2px; left: auto; right: -96px;
    width: 80px; height: 80px; border-radius: 19px;
  }
  /* the lead's standings are the biggest numbers in the pane — that is what a
     lead card is FOR — and they are scaled to fill its height exactly:
       head    30 + (12 + 2 + 22) + 14                       =  80
       podium  (36+22) + 8 + (24+16) + 8 + (24+16) + 22      = 176
       = 256, which is then also the art's height. */
  .gc-card.is-lead .gc-podium { padding: 0 0 22px 16px; gap: 8px; }
  .gc-card.is-lead .gc-pod { font-size: 13px; padding: 8px 10px; }
  .gc-card.is-lead .gc-pod-medal { width: 24px; height: 24px; }
  .gc-card.is-lead .gc-pod--1 { padding: 11px 12px; font-size: 16px; }
  .gc-card.is-lead .gc-pod--1 .gc-pod-medal { width: 36px; height: 36px; }
  .gc-card.is-lead .gc-pod--1 .gc-pod-score { font-size: 16.5px; }
  .gc-lead-tag {
    display: none; font-size: 8.5px; line-height: 12px; font-weight: 800; letter-spacing: 0.16em;
    text-transform: uppercase; color: rgba(255, 255, 255, 0.55);
  }
  .gc-card.is-lead .gc-lead-tag { display: block; }
  /* a card that is NOT the lead keeps its side wrapper transparent to layout */
  .gc-card-side { display: contents; }

/* ---------------- phone sheet / small viewport ---------------- */
@media screen and (max-width: 768px) {
    /* Games.app on a phone sheet: the hero loses a third of its height and
       drops to a single column, the tab pill tightens, and the hero art /
       carousel arrows step out of the way of the copy. */
    .gc-hero, .gc-hero-inner { height: auto; }
    /* 232px content zone + a 36px tail, so the fade band scales with the
       sheet instead of swallowing it */
    .gc-hero { height: 268px; min-height: 268px; }
    .gc-hero-bg, .gc-hero-scrim {
      -webkit-mask-image: linear-gradient(to bottom, #000 56%, transparent 100%);
      mask-image: linear-gradient(to bottom, #000 56%, transparent 100%);
    }
    .gc-hero-inner { inset: 0 0 36px; padding: 52px 20px 0; }
    .gc-dots { bottom: 34px; }
    .gc-hero-copy { width: 100%; }
    .gc-hero-art { display: none; }
    .gc-h1 { font-size: 22px; }
    .gc-sub { font-size: 12px; }
    .gc-arrow { display: none; }
    .gc-chrome { padding: 0 10px 0 84px; }
    .gc-tab { padding: 5px 11px; font-size: 11px; }
    .gc-pane { padding-left: 14px; padding-right: 14px; }
    .gc-shelf-title { padding-left: 14px; padding-right: 14px; }
    .gc-row { padding-left: 14px; padding-right: 14px; scroll-padding-inline: 14px; }
    .gc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* a phone sheet is too narrow for the side-by-side lead: it returns to the
       column, i.e. exactly the small-card treatment one size up, so the icon
       goes back to straddling a horizontal seam */
    .gc-card.is-lead { flex-direction: column; min-height: 0; }
    .gc-card.is-lead .gc-card-art { flex: 0 0 auto; aspect-ratio: 16 / 9; }
    .gc-card.is-lead .gc-card-side { display: contents; }
    /* back to a strip under artwork, so the head takes its own band back */
    .gc-card.is-lead .gc-card-head {
      position: relative; padding: 12px 14px 10px 104px;
      background-image: linear-gradient(rgba(0, 0, 0, 0.26), rgba(0, 0, 0, 0.26));
    }
    /* horizontal seam again → bottom edge on the title's baseline again:
       12 head pad + 12 eyebrow + 2 gap + 1.10 half-leading + 16.18 ascent
       = 43.28, so a 72px icon starts at 43.28 − 72 = −28.7px */
    .gc-card.is-lead .gc-card-icon {
      top: -28.7px; left: 20px; right: auto;
      width: 72px; height: 72px; border-radius: 17px;
    }
    .gc-card.is-lead .gc-podium { padding: 10px 14px 14px; }
}

/* =========================================================================
   FULLSCREEN SURFACE (/games) — the same app with the window taken away
   =========================================================================
   Everything above this line is shared. Nothing below it changes a single
   value of the app: it removes the frame and adds back the one affordance a
   real fullscreen macOS app still has — the menu bar that slides down when
   you push the pointer at the top edge of the screen, carrying the traffic
   lights that put you back in the window. */
html.gc-fs, html.gc-fs body {
  height: 100%; margin: 0; padding: 0;
  /* the .gamecenter-window fill, now edge to edge — the app's own colour is
     the page canvas, so there is no seam anywhere and no theme to follow */
  background: #0b0d14; color: #fff;
  /* the PANE scrolls (.gc-body), never the document: a fullscreen app has no
     page scrollbar, and letting the document scroll would drag the floating
     tab pill off the top of the screen */
  overflow: hidden; overscroll-behavior: none;
}
.gc-app--full {
  /* fixed, not absolute: iOS Safari's collapsing URL bar changes the layout
     viewport mid-scroll, and 100dvh on an absolutely-positioned root reflows
     the hero every frame while it does */
  position: fixed; inset: 0;
  display: flex; flex-direction: column;
  --system-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
  background: #0b0d14;
}
/* NO cap on the hero here. The Home pane is a flex column whose hero is the
   one growing element precisely so a single shelf cannot leave dead space at
   ANY host height (see the note on .gc-pane[data-gc-pane="home"] above) —
   capping it reintroduces exactly that hole under Continue Playing. A taller
   hero is what fullscreen looks like; a gap is what a broken layout looks
   like. */

/* ---- THE FULLSCREEN SCALE ----------------------------------------------
   A macOS app taken fullscreen does not keep its window-sized furniture and
   stand in a bigger frame — its content grows with the screen. Every length
   in the app above is tuned for the 860×560 window, so fullscreen multiplies
   them by --gc-scale, which games/index.html measures from the real viewport
   (floor 1, so nothing ever renders smaller than the windowed design;
   ceiling 1.5, so a 27" display gets a bigger app rather than a comic book).

   IT IS APPLIED WITH `zoom`, AND ONLY ON .gc-body. Not on .gc-app--full:
   the tab pill is in .gc-chrome, a SIBLING of the body, and zooming a
   liquid-glass host desynchronises its SVG displacement map from its element
   box — verified in the browser, the pill renders as a hard-edged light
   rectangle instead of a rounded pill. Not `transform: scale()` either: that
   makes a new backdrop root and kills the pill's backdrop-filter outright.
   The chrome therefore scales through REAL lengths below, which
   liquid-glass.js repaints correctly through its own ResizeObserver.
   `zoom` is inherited-by-layout, so percentages, flex and the pane's
   min-height:100% all keep working — the body still measures exactly the
   viewport, just in smaller local units. An engine without `zoom` ignores it
   and gets today's 1:1 app. */
.gc-app--full > .gc-body { zoom: var(--gc-scale, 1); }

/* The chrome, in real lengths so the lens survives. Numbers are the windowed
   values above × the scale; at --gc-scale: 1 every one resolves to exactly
   what .gc-chrome/.gc-tabs/.gc-tab already say. */
.gc-app--full .gc-chrome {
  top: calc(9px * var(--gc-scale, 1));
  /* TRULY centred here, unlike the window: the 92px left inset exists to
     clear the traffic lights, and fullscreen has none. */
  padding: 0 calc(14px * var(--gc-scale, 1));
}
.gc-app--full .gc-tabs { padding: calc(3px * var(--gc-scale, 1)); }
.gc-app--full .gc-tab {
  font-size: calc(11.5px * var(--gc-scale, 1));
  padding: calc(5px * var(--gc-scale, 1)) calc(15px * var(--gc-scale, 1));
}
.gc-app--full .gc-search {
  padding: calc(5px * var(--gc-scale, 1)) calc(13px * var(--gc-scale, 1)) calc(4px * var(--gc-scale, 1));
}
.gc-app--full .gc-search svg {
  width: calc(14px * var(--gc-scale, 1)); height: calc(14px * var(--gc-scale, 1));
}
/* the thumb is measured from the live tabs by games-app.js, so only its own
   inset has to follow the scale */
.gc-app--full .gc-tabs .gc-thumb {
  top: calc(3px * var(--gc-scale, 1)); height: calc(100% - 6px * var(--gc-scale, 1));
}
.gc-app--full .gc-avatar {
  right: calc(14px * var(--gc-scale, 1));
  width: calc(28px * var(--gc-scale, 1)); height: calc(28px * var(--gc-scale, 1));
}
/* Screen-reader-only: /games needs a real <h1> (Apple's Games app shows no
   title text, and neither does this one — see .gc-chrome). */
.gc-sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---- the hover-reveal menu bar ---------------------------------------
   macOS keeps the menu bar and the window controls hidden in fullscreen and
   slides them down over the app when the pointer reaches the top edge. The
   strip is always in the DOM (so it is focusable and announced); only its
   transform changes. */
.gc-fs-bar {
  position: fixed; top: 0; left: 0; right: 0; height: 30px; z-index: 9000;
  display: flex; align-items: center; gap: 10px;
  padding: 0 12px 0 12px;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
  font-size: 13px; color: #fff;
  background: rgba(8, 10, 16, 0.72);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.07), 0 8px 24px rgba(0, 0, 0, 0.45);
  transform: translateY(-100%);
  transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}
/* ONE source of truth for the revealed state, set on <html> by the page's
   own script (pointer in the hot zone, pointer on the bar, or keyboard focus
   inside it) — not a bare :hover, because the app's tab pill has to travel
   with the bar and a sibling cannot be selected from a :hover on it. */
html.gc-fs-menu .gc-fs-bar { transform: translateY(0); }
/* macOS slides the app's own toolbar down under the revealed menu bar rather
   than burying it. .gc-chrome is an absolutely-positioned overlay, so this
   moves the pill and the avatar ALONE — the hero and the panes never reflow,
   and no transform is introduced that would break the pill's backdrop-filter
   by creating a new backdrop root. */
.gc-app--full .gc-chrome { transition: top 0.26s cubic-bezier(0.32, 0.72, 0, 1); }
html.gc-fs-menu .gc-app--full .gc-chrome { top: calc(30px + 9px * var(--gc-scale, 1)); }
/* the trap: a 6px ribbon along the very top edge whose only job is to catch
   the pointer, exactly like macOS' own hot zone. It sits ABOVE the app but
   the app has nothing in its top 6px — the floating tab pill starts at 9px —
   so it can never eat a click on a control. */
.gc-fs-hot {
  position: fixed; top: 0; left: 0; right: 0; height: 6px; z-index: 8999;
}
.gc-fs-bar .window-controls { display: flex; align-items: center; gap: 8px; }
.gc-fs-light {
  width: 12px; height: 12px; padding: 0; border: none; border-radius: 50%;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.18), 0 1px 2px rgba(0, 0, 0, 0.12);
  transition: filter 0.15s ease;
}
.gc-fs-light:active { filter: brightness(0.85); }
.gc-fs-light--close { background: #ff5f57; }
.gc-fs-light--min   { background: #febc2e; }
.gc-fs-light--max   { background: #28c840; }
/* macOS greys out minimize while an app is fullscreen — it is drawn, inert,
   and never focusable, rather than lying about working */
.gc-fs-light--min { background: #4a4a4f; cursor: default; }
.gc-fs-light::after {
  font-size: 9px; font-weight: 700; line-height: 1;
  color: rgba(0, 0, 0, 0.55); opacity: 0; transition: opacity 0.15s ease;
}
.gc-fs-bar .window-controls:hover .gc-fs-light::after { opacity: 1; }
.gc-fs-light--close::after { content: "\00D7"; }
.gc-fs-light--max::after { content: "\21F2"; font-size: 8px; }
.gc-fs-light:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.92); outline-offset: 2px; }
.gc-fs-app { font-weight: 700; margin-left: 4px; }
.gc-fs-spacer { flex: 1; }
.gc-fs-link {
  color: rgba(255, 255, 255, 0.82); text-decoration: none; padding: 3px 7px; border-radius: 6px;
}
.gc-fs-link:hover { color: #fff; background: rgba(255, 255, 255, 0.12); }
.gc-fs-link:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.92); outline-offset: 1px; }
.gc-fs-hint {
  color: rgba(255, 255, 255, 0.5); font-size: 11px;
  /* the keyboard route is a hint, not a control — no room for it on a phone */
}
/* The chrome rules above out-specify the phone-sheet tightening in the shared
   @media block (0,2,0 beats 0,1,0), so the phone's tighter pill has to be
   re-asserted here, after them. The scale is pinned at 1 this small anyway —
   these are the shared mobile values verbatim, minus the 84px inset that only
   exists to clear the window's traffic lights. */
@media screen and (max-width: 768px) {
  .gc-app--full .gc-chrome { padding: 0 10px; }
  .gc-app--full .gc-tab { padding: 5px 11px; font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  .gc-fs-bar, .gc-app--full .gc-chrome { transition: none; }
}
/* Touch has no hover, so the bar cannot hide behind a pointer gesture: it is
   simply always down and the app starts below it — the same deal as the phone
   sheet on /about, which also sits under the desktop's menu bar. */
@media (hover: none) {
  .gc-fs-bar { transform: translateY(0); }
  .gc-fs-hot { display: none; }
  .gc-app--full { top: calc(30px + env(safe-area-inset-top, 0px)); }
  .gc-fs-bar { padding-top: env(safe-area-inset-top, 0px); height: calc(30px + env(safe-area-inset-top, 0px)); }
  /* the bar is already out of the way, so the pill never steps down */
  html.gc-fs-menu .gc-app--full .gc-chrome { top: calc(9px * var(--gc-scale, 1)); }
  .gc-fs-hint { display: none; }
}
