:root {
--bg: #000000;
--card: #17130e;
/* White, and white held back. Read off Netflix's own profile screen: the
   heading there is rgb(255,255,255) at weight 900, and the name under each
   face is rgba(255,255,255,0.7). The same idea here at 0.8, which is what
   the wall's brightest patch will still carry at 4.5:1. */
--text: #ffffff;
--muted: rgba(255, 255, 255, 0.8);
--accent: #fcb900;

/* Netflix's stack, without the face that is theirs: "Netflix Sans" is
   licensed to them and would never resolve here, so this starts where their
   list falls back to — Helvetica Neue on a Mac, Segoe UI on Windows, Roboto
   on Android — with system-ui after it for whatever a platform calls its
   own, and Arial last, which is where this page started. */
--sans: "Helvetica Neue", "Segoe UI", Roboto, Ubuntu, system-ui,
        -apple-system, Arial, sans-serif;

}

/* The wall is as wide as the window, and so is the work on it. A column
   capped at 1280 left a small page marooned in the middle of a large screen
   while the lines above and below it ran the whole width — the page looked
   like it had stopped growing. What stays fixed is the margin, not the
   content: a gutter that grows with the window and then settles. */
.field {
width: 100%;
padding-inline: clamp(20px, 4vw, 72px);
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: var(--sans);
}

/* Tall enough to reach the bottom of the window even with one game on it,
   with the footer pushed down to meet it: otherwise the footer's band ended
   in mid-air and the bare wall carried on below it. */
body {
min-height: 100vh;
min-height: 100lvh;
display: flex;
flex-direction: column;
background: var(--bg);
color: var(--text);
-webkit-font-smoothing: antialiased;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

/* The reference gradient with its channels reversed: the red's 111,24,29
   read back as 29,24,111. */
.backdrop {
position: fixed;
inset: 0;
z-index: -1;
background:
  radial-gradient(
    circle max(1600px, 125vmax) at 50% -54dvh,
    rgba(29, 24, 111, 0.8) 19%,
    rgba(15, 12, 56, 0.4) 60%,
    transparent 100%
  ),
  rgb(0, 0, 0);
pointer-events: none;
}

/* HEADER
   The veil earns its place twice over. It was put here when the flame hung
   above the wall and the brightest plaster on the page was the strip the
   menu stood on; the flame has since come down into the frame, and the bar
   is no longer the worst case — measured on the bare wall inside the links'
   own boxes, the menu now reads 11.8:1. It stays because it is still what
   holds that number steady wherever the pool lands, and because the page
   reads better with it: Netflix does the same, 80px tall with
   linear-gradient(rgba(0,0,0,.8), transparent) behind it, and it fades out
   rather than ending on a line, so it is the top of a lit wall rather than
   a band laid over one. */
/* The bar paints above the rows. Without a z-index it paints in document
   order, and a card with one — the open panel sits at 10 — comes out on top
   of the menu hanging from the bar. */
header {
position: relative;
z-index: 50;
min-height: 80px;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0));
}

/* Gap, not a margin on every link: a margin also trailed the last one, so
   the menu stopped 20px short of the edge the logo is inset from. Wrapping
   is the fallback for screens too narrow even for the phone sizes below —
   the menu takes a second row and the header grows to hold it, rather than
   the page scrolling sideways. */
nav {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
column-gap: 6px;
margin-right: -14px;
}

/* Netflix's menu, measured: 16px, links at rgba(255,255,255,0.7), the page
   you are on in full white on a rgba(255,255,255,0.2) pill with a 24px
   radius, each link 41px tall from its own padding. The last of those is
   what ours was missing — four links that look identical say nothing about
   where you are — and the padding is the touch target as well. */
nav a,
.more-btn {
color: rgba(255, 255, 255, 0.8);
padding: 10px 14px;
border: 0;
background: none;
border-radius: 24px;
text-decoration: none;
font-family: inherit;
font-size: 16px;
line-height: 1.25;
cursor: pointer;
transition: color 0.2s ease;
}

nav a:hover,
.more-btn:hover,
.more-btn[aria-expanded="true"] {
color: var(--text);
}

nav a[aria-current="page"] {
color: var(--text);
background-color: rgba(255, 255, 255, 0.2);
}

/* What is left over when the menu will not fit. Netflix hands the items it
   cannot show to a "More" button and, below about 800px, stops showing any
   of them inline and calls the button "Browse" — measured on their page at
   1371, 1067 and 800px, where the count goes 4, 2, 0. The type never gets
   smaller, which is the point: shrinking it buys one more link and costs
   every one of them. */
.more {
position: relative;
}

.more-btn::after {
content: "";
display: inline-block;
margin-left: 7px;
border: 4px solid transparent;
border-top: 5px solid currentColor;
transform: translateY(2px);
}

/* display on a class beats the hidden attribute's own display: none, and the
   menu sat open until this said otherwise. */
.more[hidden],
.more-menu[hidden] {
display: none;
}

/* Right at 14, not 0: the bar carries margin-right: -14px so that the last
   link's letters, not its padding, land on the page's gutter. A panel has no
   such slack — its border is where its edge looks like it is — so pulling it
   back by that same 14 puts it on the gutter with everything else. Measured
   at 375: it stood 6px off the window where the poster below it stands 20. */
.more-menu {
position: absolute;
top: calc(100% + 6px);
right: 14px;
min-width: 190px;
padding: 6px 0;
display: flex;
flex-direction: column;
background: rgba(10, 8, 6, 0.96);
border: 1px solid rgba(255, 255, 255, 0.13);
border-radius: 4px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
}

.more-menu a {
border-radius: 0;
padding: 10px 18px;
text-align: left;
}

.more-menu a[aria-current="page"] {
background-color: rgba(255, 255, 255, 0.12);
}

/* A thumb is not a cursor. Measured at 375: every one of these was 40px tall,
   and both Apple and Google put the floor at 44 — two more pixels of padding
   is the whole difference, and on a mouse it would only make the bar heavy,
   so it is asked for where the pointer is coarse and nowhere else. The one
   place it matters most is this panel, where the rows touch each other and a
   miss lands on the neighbour rather than on nothing.

   The panel's own rows are named here as well: "nav a" is two element names
   and loses to ".more-menu a", which carries a class, whatever order they
   are written in — the rows stayed at 40 until this said their name. */
@media (pointer: coarse) {
  nav a,
  .more-btn,
  .more-menu a {
    padding-block: 12px;
  }
}

/* ROWS
   Netflix names its rows and stacks them: a title, then the tiles under it.
   Ours are "Video Games" and "Production". The tiles wrap instead of
   scrolling sideways, because there are three of them and a carousel with
   nothing to carousel is furniture. */
.row {
padding-block: clamp(14px, 2.5vh, 28px);
}

.rows {
margin-bottom: auto;
}

/* 24px, measured on their own row titles. */
.row-title {
font-size: 24px;
font-weight: 500;
color: var(--text);
margin-bottom: 14px;
}

/* A work has a size. It does not grow because the window did — the tile is
   256px on a phone and on a wall-sized monitor alike, and what changes is
   how many stand side by side. min() is only there for windows narrower
   than one tile, where the alternative is to hang off the edge. */
.row-items {
display: grid;
grid-template-columns: repeat(auto-fill, min(256px, 100%));
justify-content: start;
gap: 24px;
}

.card {
position: relative;
background: none;
cursor: pointer;
}

/* Netflix lights the one you are pointing at rather than moving it: a pale
   veil behind it, and the name under it goes to full white. Nothing lifts,
   nothing scales. The veil is a layer behind the card, not padding on it —
   padding would have taken 24px out of the tile, and the tile's size is
   fixed. */
.card::before {
content: "";
position: absolute;
inset: -12px;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0);
transition: background-color 0.2s ease;
z-index: -1;
}

.card:hover::before {
background-color: rgba(255, 255, 255, 0.06);
}

.card:hover .card-title {
color: var(--text);
}

/* Square, and rounded the way their own tiles are: read off Netflix's
   computed styles, a 181.42px tile carries a 21.6px radius and a 1px
   rgba(128,128,128,0.4) hairline — 11.9% of the width, which on 256 is
   30px. No drop shadow: theirs have none, and on a dark page a shadow only
   smears the edge the hairline is drawing. */
.card-art {
position: relative;
aspect-ratio: 1;
overflow: hidden;
border-radius: 11.9%;
border: 1px solid rgba(128, 128, 128, 0.4);
background: #000;
}

/* A twenty-pixel-wide copy of the artwork, blurred back up to size, carried
   on the card itself so each one holds its own. It costs no request and is
   on screen in the first paint — before the real one has been asked for.
   The scale hides the soft edge blur leaves behind. */
.card-art::before {
content: "";
position: absolute;
inset: 0;
background: #000 center / cover no-repeat;
background-image: var(--lqip, none);
filter: blur(12px);
transform: scale(1.08);
}

.card-art img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}

/* Sharpening, rather than swapping. Only armed when there is script to
   disarm it, so with none the artwork simply appears. */
.js .card-art img { opacity: 0; transition: opacity 0.32s ease; }
.js .card-art img.ready { opacity: 1; }

header img { height: 50px; width: auto; }

.card-info {
padding: 12px 0 0;
}

/* The name at the weight Netflix gives the name under a face (500), and the
   facts under it smaller. Both held back to 0.8 white so the artwork stays
   the brightest thing in the row. */
.card-title {
font-size: 18px;
font-weight: 500;
margin-bottom: 4px;
color: var(--muted);
transition: color 0.2s ease;
}

.card-meta {
font-size: 12px;
color: var(--muted);
opacity: 0.75;
}

/* THE DETAIL PANEL
   Netflix opens one of these when you rest on a tile: the art, the name, what
   it is, and the way in. Ours carries the same four things and nothing it
   cannot back up — no invented blurb, no rating it does not have.

   It is written into the page rather than built by script, so with no script
   at all it is simply a block of text under the tile that a reader can still
   use. The script's whole job is to hide it until it is wanted. */
/* --shift is set by the script after the panel is measured: centred on its
   card until that would carry it past the page's gutter, then held at the
   gutter instead. A card at the left edge of a phone is the case — 320 of
   panel centred on 256 of tile reaches 32px further left than the tile does,
   and the tile is already at the margin. */
.card-pop {
position: absolute;
left: 50%;
top: -14px;
transform: translateX(calc(-50% + var(--shift, 0px)));
/* Wider than the tile it stands on, which is what makes it read as
   something that opened rather than something that grew. 100% would have
   pinned it to the tile's own 256. */
width: min(320px, calc(100vw - 32px));
z-index: 5;
background: #141414;
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: 6px;
box-shadow: 0 16px 44px rgba(0, 0, 0, 0.78);
overflow: hidden;
animation: pop 0.16s ease-out;
}

@keyframes pop {
from { opacity: 0; transform: translateX(calc(-50% + var(--shift, 0px))) scale(0.97); }
to   { opacity: 1; transform: translateX(calc(-50% + var(--shift, 0px))) scale(1); }
}

/* display on a class beats the hidden attribute's own display: none — the
   same thing that once left the menu standing open. */
.card-pop[hidden] { display: none; }

/* The strip is not the tile. A square cropped to 16:9 loses whatever sits
   outside the band, and where to take the band from turned out to depend on
   the picture — a poster wants its top, an app icon wants its middle. One
   rule cannot know, and a rule per picture is not a rule.

   So each product brings a second asset for this: a 16:9 still or a clip.
   Whatever is there plays. Nothing there, no strip — the panel is then the
   name, the facts and the ways in, which is already the part that does the
   work. */
.pop-art {
aspect-ratio: 16 / 9;
background: #000;
}

.pop-art img,
.pop-art video {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}

.pop-body {
padding: 14px 16px 16px;
}

.pop-title {
font-size: 17px;
font-weight: 500;
color: var(--text);
margin-bottom: 6px;
}

.pop-meta {
font-size: 12px;
color: var(--muted);
opacity: 0.75;
margin-bottom: 14px;
}

/* The way in, in each store's own badge rather than in words. They come from
   the stores themselves — Apple's from developer.apple.com, Microsoft's from
   get.microsoft.com — and they are not touched: no recolouring, no hover
   effect, no cropping, because both sets of guidelines ask for exactly that.
   Whoever is reading picks the one they are on.

   The browser badge is ours. There is no official one for "it runs in a
   browser", so it is built to the same 40px height and the same two-line
   shape, and it borrows no mark from anybody: a globe and a word. */
.pop-ways {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}

/* The badges themselves must not be touched, so the hover lives outside
   them: a ring around the badge, not a filter on it. Google's file carries
   its own clear space — 41px of the 250 at each edge — so a ring at the
   element's border box would float away from the artwork; that one is pulled
   inward by the same amount instead. */
/* No frame: there is not the room for one, and a ring around a badge that
   already has a border is a border around a border. The badge sits a little
   back instead and comes forward under the pointer. This is a change of
   appearance, which Apple's and Google's guidelines would rather you did not
   make; it is opacity only — no tint, no crop, no recolouring — and it is
   the affordance the page uses everywhere else. */
.pop-ways a {
display: block;
line-height: 0;
opacity: 0.72;
transition: opacity 0.16s ease;
}

.pop-ways a:hover,
.pop-ways a:focus-visible {
opacity: 1;
}

.pop-ways img {
height: 36px;
width: auto;
display: block;
}

.pop-ways img[src$="google-play.png"] { height: 54px; }

/* Nothing to link to yet, so it says so rather than pretending. */
.pop-soon {
font-size: 14px;
font-weight: 500;
color: var(--muted);
opacity: 0.6;
}


/* While one panel is open its card has to sit above the rest of the row. */
.card.open { z-index: 10; }

/* FOOTER
   The studio's name in the same hand as the heading, smaller — a mason's
   signature at the foot of the wall. */
footer {
min-height: 70px;
display: flex;
align-items: center;
color: var(--muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.10em;
}

/* RESPONSIVE */
/* Capitals are wide, and four of these words set in them do not fit beside
   the logo on a phone. The menu keeps the wall's hand there but drops the
   capitals for it, which is narrower by a third and holds one row down to
   360px. */
@media (max-width: 768px) {
}

/* CONTACT
   Two ways out, and the reader picks which. Neither fires on arriving here:
   a mail client opening by itself is a door opened on somebody's behalf, and
   the nav used to do exactly that. */
.contact {
display: grid;
grid-template-columns: repeat(auto-fill, min(320px, 100%));
justify-content: start;
gap: 16px;
}

.contact-card {
display: block;
padding: 18px 20px;
background: #141414;
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: 6px;
text-decoration: none;
outline: 2px solid rgba(255, 255, 255, 0);
outline-offset: 3px;
transition: outline-color 0.16s ease;
}

.contact-card:hover,
.contact-card:focus-visible {
outline-color: rgba(255, 255, 255, 0.62);
}

.contact-kind {
display: block;
font-size: 12px;
color: var(--muted);
opacity: 0.75;
margin-bottom: 6px;
}

.contact-value {
display: block;
font-size: 18px;
font-weight: 500;
color: var(--text);
}
