{ "$description": "Wolfskin TV design tokens, extracted by script from the locked prototype (originally data/tv-v2/opus-final-2026-11-05/index.html, sha256 ee8844fc...; now amended in data/tv-v2/spacing-2026-11-06/index.html, sha256 fcc5d879...). Values are CSS px on the 1920x1080 stage; the box renders 1920x1080 at 320 dpi = density 2.0, so dp/sp = px / 2. Format follows the W3C design-tokens draft ($type/$value); extra keys: px, dp|sp, source (page file:line), usedBy, page (the page value where q1 consolidated it). Amended 2026-11-06 (spec revision 3, Level 2 spacing + card ring; data/tv-card-spacing/answers.md): card, layout.row*/gridRowGap and focus.ringCard changed; every other token is as extracted. The prototype source line numbers in `source` fields predate the amendment and drift by a few lines after index.html:8.", "meta": { "density": 2.0, "stagePx": [ 1920, 1080 ], "stageDp": [ 960, 540 ], "source": "data/tv-v2/spacing-2026-11-06/index.html", "sourceSha256": "fcc5d879c34da868b817f8d9d306193743d3933db3096da605bda659d8e633b2", "generator": "data/tv-spec-lane/scratch/extract.py + build_tokens.py" }, "color": { "bg": { "$type": "color", "$value": "#07070a", "css": "#07070a", "source": "index.html:10", "usedBy": "app background; stage; focus-ring gap colour; hero/detail scrim end; nav band when solid" }, "surface": { "$type": "color", "$value": "#13131a", "css": "#13131a", "source": "index.html:10", "usedBy": "sheets (side panels), state panels, See all card, skeleton blocks, Try again/Show more tail cards" }, "surface2": { "$type": "color", "$value": "#1b1b25", "css": "#1b1b25", "source": "index.html:10", "usedBy": "card art placeholder behind images; menus; toast; person-page portrait placeholder" }, "surface3": { "$type": "color", "$value": "#252532", "css": "#252532", "source": "index.html:10", "usedBy": "hero radial placeholder; Coming-in thumbnail placeholder" }, "fg": { "$type": "color", "$value": "#f4f4f6", "css": "#f4f4f6", "source": "index.html:11", "usedBy": "primary text; primary button fill; focused nav blob; focused chip/row/list fill (inverse focus); white focus ring on avatars and the gear" }, "muted": { "$type": "color", "$value": "#f4f4f6bd", "css": "rgb(244 244 246 / .74)", "source": "index.html:11", "usedBy": "secondary text: facts lines, overviews, captions under cards, unfocused nav labels, row descriptions" }, "dim": { "$type": "color", "$value": "#f4f4f68f", "css": "rgb(244 244 246 / .56)", "source": "index.html:11", "usedBy": "tertiary text: sheet labels, small print, pill text, the resting gear" }, "border": { "$type": "color", "$value": "#ffffff1a", "css": "rgb(255 255 255 / .10)", "source": "index.html:11", "usedBy": "hairline borders on See all/tail cards and state panels" }, "accent": { "$type": "color", "$value": "#cc9fff", "css": "oklch(78% 0.14 305)", "source": "index.html:12", "usedBy": "lilac focus ring on cards and buttons; progress fills; current-destination underline; See all glyph; requested badge ring; Downloading/Almost ready stage words" }, "accentSoft": { "$type": "color", "$value": "#2c1a3f", "css": "oklch(26% 0.07 305)", "source": "index.html:12", "usedBy": "selected chip fill (chip.on)" }, "accentText": { "$type": "color", "$value": "#e4cbff", "css": "oklch(88% 0.08 305)", "source": "index.html:12", "usedBy": "kicker line above hero titles; d-line on title pages; availability words in captions; selected chip text; wordmark \"skin\"" }, "ok": { "$type": "color", "$value": "#89da9b", "css": "oklch(82% 0.12 150)", "source": "index.html:13", "usedBy": "held badge (green check); Ready stage word; readiness Done" }, "warn": { "$type": "color", "$value": "#f4c26a", "css": "oklch(84% 0.12 80)", "source": "index.html:13", "usedBy": "Saved HH:MM chip; warning state titles and icons; toast icon; Try again tail glyph" }, "fail": { "$type": "color", "$value": "#f97770", "css": "oklch(72% 0.16 25)", "source": "index.html:13", "usedBy": "failed-request badge ring; Failed stage word; Refused receipt title" }, "onFocus": { "$type": "color", "$value": "#0b0b10", "css": "#0b0b10", "source": "index.html:84", "usedBy": "text and icons on any white focus fill: focused nav item, primary button label, focused chip/row/list item, avatar letters" }, "buttonSecondary": { "$type": "color", "$value": "#ffffff24", "css": "rgb(255 255 255 / .14)", "source": "index.html:107", "usedBy": "secondary (non-primary) pill button at rest on hero rows" }, "buttonSecondaryFocused": { "$type": "color", "$value": "#ffffff42", "css": "rgb(255 255 255 / .26)", "source": "index.html:111", "usedBy": "secondary pill button while focused (plus the accent ring)" }, "titleButtonSecondary": { "$type": "color", "$value": "#2c2c38c7", "css": "rgb(44 44 56 / .78)", "source": "index.html:279", "usedBy": "title-page secondary and icon buttons at rest (dark backing so labels read over bright art)" }, "titleButtonSecondaryFocused": { "$type": "color", "$value": "#464654e6", "css": "rgb(70 70 84 / .9)", "source": "index.html:280", "usedBy": "title-page secondary and icon buttons while focused" }, "chipRest": { "$type": "color", "$value": "#ffffff1a", "css": "rgb(255 255 255 / .10)", "source": "index.html:185", "usedBy": "filter/sort/season chips at rest" }, "navBlobRest": { "$type": "color", "$value": "#ffffff1a", "css": "rgb(255 255 255 / .10)", "source": "index.html:76", "usedBy": "nav indicator resting behind the current destination while focus is elsewhere" }, "listRowRest": { "$type": "color", "$value": "#ffffff0d", "css": "rgb(255 255 255 / .05)", "source": "index.html:231", "usedBy": "sheet rows (person, version, track) at rest; Settings rows (.srow) use the same value" }, "sheetScrim": { "$type": "color", "$value": "#0000009e", "css": "rgb(0 0 0 / .62)", "source": "index.html:226", "usedBy": "full-screen scrim behind every side sheet" }, "badgeRest": { "$type": "color", "$value": "#07070abf", "css": "rgb(7 7 10 / .75)", "source": "index.html:141", "usedBy": "badge disc behind the requested/failed glyph" }, "badgeWatched": { "$type": "color", "$value": "#f4f4f6d9", "css": "rgb(244 244 246 / .85)", "source": "index.html:145", "usedBy": "watched badge disc (defined in CSS; no surface draws it in the locked page) [unused]" }, "progressTrack": { "$type": "color", "$value": "#ffffff38", "css": "rgb(255 255 255 / .22)", "source": "index.html:138", "usedBy": "progress-bar track on wide cards; title-page progress track uses rgb(255 255 255 / .2) (.d-prog i)" }, "railEmpty": { "$type": "color", "$value": "#f4f4f638", "css": "rgb(244 244 246 / .22)", "source": "index.html:206", "usedBy": "letter rail: letters with no titles (rail shown only at >= 200 titles)" } }, "personColor": { "justin": { "$type": "color", "$value": "#62c4ff", "initial": "J", "source": "fixture/fixture.json profiles[].color", "usedBy": "Justin: avatar fill in Who's watching (220 px), nav avatar (64 px), person rows in sheets (60 px), library/settings headers (52/56 px); letter in color.onFocus" }, "mindel": { "$type": "color", "$value": "#ff9d72", "initial": "M", "source": "fixture/fixture.json profiles[].color", "usedBy": "Mindel: avatar fill in Who's watching (220 px), nav avatar (64 px), person rows in sheets (60 px), library/settings headers (52/56 px); letter in color.onFocus" }, "shared": { "$type": "color", "$value": "#9be38f", "initial": "S", "source": "fixture/fixture.json profiles[].color", "usedBy": "Shared: avatar fill in Who's watching (220 px), nav avatar (64 px), person rows in sheets (60 px), library/settings headers (52/56 px); letter in color.onFocus" } }, "gradient": { "heroScrim": { "$type": "gradient", "$value": "linear-gradient(90deg,rgb(7 7 10 / .92) 0%,rgb(7 7 10 / .55) 42%,rgb(7 7 10 / 0) 70%),linear-gradient(0deg,var(--bg) 0%,rgb(7 7 10 / .6) 26%,rgb(7 7 10 / 0) 48%)", "source": "index.html:99", "usedBy": "Home hero and Discover picks hero: left-to-right darkening (92% at 0, 55% at 42%, 0 at 70%) plus bottom fade into bg (0..48%)" }, "detailScrim": { "$type": "gradient", "$value": "linear-gradient(90deg,rgb(7 7 10 / .94) 0%,rgb(7 7 10 / .6) 46%,rgb(7 7 10 / .05) 75%),linear-gradient(0deg,var(--bg) 0%,rgb(7 7 10 / .35) 34%,rgb(7 7 10 / 0) 55%)", "source": "index.html:210", "usedBy": "title page backdrop scrim (94% / 60% at 46% / 5% at 75%; bottom fade to 55%)" }, "navBand": { "$type": "gradient", "$value": "linear-gradient(180deg,rgb(7 7 10 / .72),rgb(7 7 10 / 0))", "source": "index.html:71", "usedBy": "nav band at scrollTop <= 40: 72% bg fading to 0 over 150 px" }, "navBandSolid": { "$type": "gradient", "$value": "linear-gradient(180deg,var(--bg) 0%,var(--bg) 84%,rgb(7 7 10 / 0) 100%)", "source": "index.html:72", "usedBy": "nav band once the frame scrolled > 40 px: solid bg to 84% of 150 px, then fades" }, "ratingPlate": { "$type": "gradient", "$value": "linear-gradient(180deg,rgb(7 7 10 / .9) 0%,rgb(7 7 10 / .72) 52%,rgb(7 7 10 / 0) 100%)", "source": "index.html:162", "usedBy": "rating plate on the focused card: 90% bg at top, 72% at 52%, 0 at bottom" }, "tileOverlay": { "$type": "gradient", "$value": "linear-gradient(0deg,rgb(7 7 10 / .92),rgb(7 7 10 / 0))", "source": "index.html:135", "usedBy": "bottom gradient under tile/wide titles" }, "fallbackArt": { "$type": "gradient", "$value": "linear-gradient(160deg,hsl(var(--h,260) 22% 22%),hsl(var(--h,260) 26% 10%))", "source": "index.html:130", "usedBy": "no-art card: 160deg two-tone gradient, hue = hash(title) (hueOf, index.html:744)" }, "entryTile": { "$type": "gradient", "$value": "linear-gradient(150deg,hsl(var(--h) 30% 26%),hsl(var(--h) 30% 12%))", "source": "index.html:149", "usedBy": "service entry tiles: 150deg gradient, hue per service (SERVICE_HUE, index.html:663)" }, "initialsCircle": { "$type": "gradient", "$value": "linear-gradient(160deg,hsl(var(--h) 24% 30%),hsl(var(--h) 26% 14%))", "source": "index.html:288", "usedBy": "cast circle / person portrait without a photo: initials on a 160deg tinted gradient, hue = hash(name)" }, "heroAmbientFilter": { "$type": "filter", "$value": "blur(60px) saturate(1.2) brightness(.55)", "inset": "-80px", "scale": 1.1, "source": "index.html:96", "usedBy": "poster-only hero/title art: blurred wash of the poster behind the one-sheet (Compose: RenderEffect blur 30 dp, saturation 1.2, brightness .55) [INFERENCE: Compose mapping]" }, "cardAmbientFilter": { "$type": "filter", "$value": "blur(26px) brightness(.5)", "inset": "-30px", "source": "index.html:129", "usedBy": "poster-only art in a landscape card: blurred wash behind the one-sheet" } }, "font": { "family": { "$type": "fontFamily", "$value": "system-ui,-apple-system,\"Segoe UI\",Roboto,\"Noto Sans\",Ubuntu,Cantarell,sans-serif", "source": "index.html:14", "usedBy": "all stage text", "note": "On the box this resolves to the platform sans (Roboto) [INFERENCE]; Compose uses the default FontFamily." }, "tabularNumbers": { "$value": "font-variant-numeric: tabular-nums", "source": "index.html:156", "usedBy": "every ratings row" } }, "type": { "displayDetail": { "$type": "typography", "px": 88.0, "sp": 44.0, "source": "index.html:213", "usedBy": "title page title (film, series, episode = series name); clamp 2 lines", "weight": 800, "weightSource": "index.html:103", "lineHeight": 1.02, "lineHeightSource": "index.html:103", "letterSpacingEm": -0.02, "$value": { "fontSize": "44sp", "fontWeight": 800 } }, "displayHero": { "$type": "typography", "px": 84.0, "sp": 42.0, "source": "index.html:103", "usedBy": "Home hero and Discover picks hero title; clamp 2 lines", "weight": 800, "weightSource": "index.html:103", "lineHeight": 1.02, "lineHeightSource": "index.html:103", "letterSpacingEm": -0.02, "$value": { "fontSize": "42sp", "fontWeight": 800 } }, "screenTitle": { "$type": "typography", "px": 64.0, "sp": 32.0, "source": "index.html:200", "usedBy": "screen headers: library (Movies/Shows/Anime), See all, service page, Settings, Discover-without-hero; also \"Who's watching?\" (.picker h1) and person name (.p-head h1)", "weight": 800, "weightSource": "index.html:200", "letterSpacingEm": -0.02, "$value": { "fontSize": "32sp", "fontWeight": 800 } }, "sectionTitle": { "$type": "typography", "px": 56.0, "sp": 28.0, "source": "index.html:181", "usedBy": "the \"Discover\" section header inside Home; player hand-off title (.player h2)", "weight": 800, "weightSource": "index.html:181", "letterSpacingEm": -0.02, "$value": { "fontSize": "28sp", "fontWeight": 800 } }, "sheetTitle": { "$type": "typography", "px": 44.0, "sp": 22.0, "source": "index.html:228", "usedBy": "side-sheet titles (Request, Version, Coming in, ...)", "weight": 800, "weightSource": "index.html:228", "letterSpacingEm": -0.01, "$value": { "fontSize": "22sp", "fontWeight": 800 } }, "wordmark": { "$type": "typography", "px": 36.0, "sp": 18.0, "source": "index.html:79", "usedBy": "the \"Wolfskin\" wordmark at the left of the nav and in the picker", "weight": 800, "weightSource": "index.html:79", "letterSpacingEm": -0.02, "$value": { "fontSize": "18sp", "fontWeight": 800 } }, "stateTitle": { "$type": "typography", "px": 36.0, "sp": 18.0, "source": "index.html:195", "usedBy": "state panel titles and receipt titles (.receipt h3)", "$value": { "fontSize": "18sp", "fontWeight": 400 } }, "rowHeading": { "$type": "typography", "px": 34.0, "sp": 17.0, "source": "index.html:119", "usedBy": "row headings above every shelf and grid section", "weight": 700, "weightSource": "index.html:119", "letterSpacingEm": -0.01, "$value": { "fontSize": "17sp", "fontWeight": 700 } }, "pickerName": { "$type": "typography", "px": 34.0, "sp": 17.0, "source": "index.html:260", "usedBy": "names under avatars in Who's watching", "weight": 650, "weightSource": "index.html:260", "$value": { "fontSize": "17sp", "fontWeight": 650 } }, "nav": { "$type": "typography", "px": 30.0, "sp": 15.0, "source": "index.html:81", "usedBy": "nav destination labels", "weight": 600, "weightSource": "index.html:81", "$value": { "fontSize": "15sp", "fontWeight": 600 } }, "listTitle": { "$type": "typography", "px": 30.0, "sp": 15.0, "source": "index.html:231", "usedBy": "sheet row names; Settings row titles (.srow .tx b); Coming-in row titles (.crow .tx b); See all label; service entry wordmark", "weight": 650, "weightSource": "index.html:231", "$value": { "fontSize": "15sp", "fontWeight": 650 } }, "body": { "$type": "typography", "px": 28.0, "sp": 14.0, "source": "index.html:104", "usedBy": "facts lines (h-meta), overviews (h-ov, d-ov), card caption title (.cap b), tile titles (.card .ov b), buttons (.btn), state text, receipts, toast, rating numbers and marks", "$value": { "fontSize": "14sp", "fontWeight": 400 } }, "button": { "$type": "typography", "px": 28.0, "sp": 14.0, "source": "index.html:107", "usedBy": "pill buttons (primary and secondary)", "weight": 700, "weightSource": "index.html:107", "$value": { "fontSize": "14sp", "fontWeight": 700 } }, "label": { "$type": "typography", "px": 26.0, "sp": 13.0, "source": "index.html:185", "usedBy": "chips; kicker (26/650); d-line; d-prog; icon-button caption (.bcap); cast name (.pnm b, 26/700); sheet section labels (.sheet .lab)", "weight": 650, "weightSource": "index.html:185", "$value": { "fontSize": "13sp", "fontWeight": 650 } }, "caption": { "$type": "typography", "px": 24.0, "sp": 12.0, "source": "index.html:154", "usedBy": "THE FLOOR. Card facts under a focused poster; tile second lines; pills; small print; character names; readiness stages; letter rail; rating-plate floor. Nothing on the stage is smaller.", "$value": { "fontSize": "12sp", "fontWeight": 400 } }, "ratingPlate": { "$type": "typography", "px": 28.0, "sp": 14.0, "floorPx": 24, "floorSp": 12.0, "stepPx": 0.5, "weight": 700, "lineHeight": 1.2, "source": "index.html:164", "floorSource": "index.html:802", "$value": { "fontSize": "14sp", "min": "12sp", "fontWeight": 700 }, "usedBy": "rating plate on the focused card: starts at 28 px (14 sp, the fork titleSmall), shrinks in 0.5 px steps to the 24 px floor, then drops trailing entries (fitRow, D9)" }, "avatarLetter": { "$type": "typography", "weight": 800, "source": "index.html:89", "sizes": { "picker": 100, "nav": 30, "sheetRow": 28, "shelfHead": 24, "chip": 24, "libHeader": 26, "sectionHeader": 28 }, "usedBy": "avatar letters (px per context)" }, "stageLineHeight": { "$type": "number", "$value": 1.3, "source": "index.html:61", "usedBy": "default line height for all stage text (1.3)" } }, "radius": { "card": { "$type": "dimension", "$value": "8dp", "px": 16.0, "dp": 8.0, "source": "index.html:125", "usedBy": "every card (poster, tile, wide, entry, See all, tail, skeleton)" }, "button": { "$type": "dimension", "$value": "19dp", "px": 38.0, "dp": 19.0, "source": "index.html:107", "usedBy": "pill buttons = height / 2" }, "chip": { "$type": "dimension", "$value": "17dp", "px": 34.0, "dp": 17.0, "source": "index.html:185", "usedBy": "chips = height / 2" }, "navItem": { "$type": "dimension", "$value": "16dp", "px": 32.0, "dp": 16.0, "source": "index.html:81", "usedBy": "nav item and blob = height / 2" }, "listRow": { "$type": "dimension", "$value": "12dp", "px": 24.0, "dp": 12.0, "source": "index.html:231", "usedBy": "sheet rows and Settings rows" }, "panel": { "$type": "dimension", "$value": "11dp", "px": 22.0, "dp": 11.0, "source": "index.html:194", "usedBy": "state panels, receipts, Coming-in rows, toast" }, "menu": { "$type": "dimension", "$value": "13dp", "px": 26.0, "dp": 13.0, "source": "index.html:264", "usedBy": "menu panel" }, "heroSheet": { "$type": "dimension", "$value": "9dp", "px": 18.0, "dp": 9.0, "source": "index.html:97", "usedBy": "upright poster (\"one-sheet\") on poster-only hero/title art" }, "cardSheet": { "$type": "dimension", "$value": "5dp", "px": 10.0, "dp": 5.0, "source": "index.html:128", "usedBy": "one-sheet inside a landscape card" }, "thumb": { "$type": "dimension", "$value": "6dp", "px": 12.0, "dp": 6.0, "source": "index.html:248", "usedBy": "Coming-in poster thumbnail 96x144" }, "pill": { "$type": "dimension", "$value": "11dp", "px": 22.0, "dp": 11.0, "source": "index.html:122", "usedBy": "sample/Saved/Wolfskin/upstream pills" }, "progress": { "$type": "dimension", "$value": "2dp", "px": 4.0, "dp": 2.0, "source": "index.html:138", "usedBy": "progress bars" }, "circle": { "$type": "dimension", "$value": "50%", "source": "index.html:89", "usedBy": "avatars, badges, person circles, gear" } }, "layout": { "safeSide": { "$type": "dimension", "$value": "48dp", "px": 96, "dp": 48.0, "source": "index.html:654", "usedBy": "left/right safe inset on every screen (5% of 1920); first card x, all copy blocks" }, "contentWidth": { "$type": "dimension", "$value": "864dp", "px": 1728, "dp": 864.0, "source": "index.html:1035", "usedBy": "content measure 1920 - 2 x 96; slotsOf() = floor((1728 + gap + 8) / (w + gap))" }, "safeTopPicker": { "$type": "dimension", "$value": "27dp", "px": 54.0, "dp": 27.0, "source": "index.html:258", "usedBy": "top inset of the wordmark in Who's watching (5% of 1080)" }, "navBand": { "$type": "dimension", "$value": "75dp", "px": 150.0, "dp": 75.0, "source": "index.html:71", "usedBy": "nav band height on every non-full-screen screen" }, "navPaddingTop": { "$type": "dimension", "$value": "22dp", "px": 44.0, "dp": 22.0, "source": "index.html:74", "usedBy": "nav row top padding; items are vertically centred in 44..150, so a 64 px item spans y 65..129 (measured)" }, "navItemHeight": { "$type": "dimension", "$value": "32dp", "px": 64.0, "dp": 32.0, "source": "index.html:81", "usedBy": "nav item / blob height; nav avatar is 64x64" }, "navItemPaddingX": { "$type": "dimension", "$value": "13dp", "px": 26.0, "dp": 13.0, "source": "index.html:81", "usedBy": "nav item horizontal padding" }, "navItemGap": { "$type": "dimension", "$value": "6dp", "px": 12.0, "dp": 6.0, "source": "index.html:74", "usedBy": "gap between nav items" }, "wordmarkGap": { "$type": "dimension", "$value": "20dp", "px": 40.0, "dp": 20.0, "source": "index.html:79", "usedBy": "space after the wordmark before Home" }, "navUnderline": { "$type": "dimension", "$value": "2dp", "px": 4.0, "dp": 2.0, "source": "index.html:83", "usedBy": "accent underline under the current destination, inset 26 px each side, 4 px above the item bottom; hidden while that item is focused" }, "navBlobAvatarPad": { "$type": "dimension", "$value": "4dp", "px": 8, "dp": 4.0, "source": "index.html:1681", "usedBy": "the blob grows 8 px each side around the avatar (80x80 circle)" }, "navSolidAfter": { "$type": "dimension", "$value": "20dp", "px": 40, "dp": 20.0, "source": "index.html:1668", "usedBy": "the nav band switches to the solid gradient once the frame scrollTop > 40" }, "pinnedLine": { "$type": "dimension", "$value": "84dp", "px": 168, "dp": 84.0, "source": "index.html:654", "usedBy": "focus-follow: a focused row (not hero) scrolls so its heading top sits 168 px from the top; rows whose bottom fits the first screen (y < 700 and y + h <= 1040) do not scroll" }, "windowAhead": { "$type": "dimension", "$value": "350dp", "px": 700, "dp": 350.0, "source": "index.html:654", "usedBy": "prototype windowing distance (rows within 700 px of the viewport exist); in Compose lazy layouts do this" }, "rowHeadingBand": { "$type": "dimension", "$value": "42dp", "px": 84, "dp": 42.0, "source": "index.html:1018", "usedBy": "shelf row: heading band (44 px heading at top 0, then 40 px) before the cards start at y + 84 (HEAD_OFF)" }, "rowGapAfter": { "$type": "dimension", "$value": "34dp", "px": 68, "dp": 34.0, "source": "index.html:1018", "usedBy": "shelf row: space after cards (+ caption band) before the next row; shelf row height = 84 + card h + caption + 68" }, "gridRowGap": { "$type": "dimension", "$value": "22dp", "px": 44, "dp": 22.0, "source": "index.html:1019", "usedBy": "grid row height = 384 + 96 + 44 = 524 (library, See all, person)" }, "row_hero": { "$type": "dimension", "$value": "410dp", "px": 820, "dp": 410.0, "source": "index.html:1016", "usedBy": "Home hero row height (the hero art itself is 1920x1080 behind it)" }, "row_phero": { "$type": "dimension", "$value": "370dp", "px": 740, "dp": 370.0, "source": "index.html:1017", "usedBy": "Discover picks-hero row height" }, "row_section": { "$type": "dimension", "$value": "75dp", "px": 150, "dp": 75.0, "source": "index.html:1020", "usedBy": "Home \"Discover\" section header row" }, "row_chips": { "$type": "dimension", "$value": "52dp", "px": 104, "dp": 52.0, "source": "index.html:1021", "usedBy": "chip row above shelves (Discover filters, service type chips, seasons); 104 px" }, "row_note": { "$type": "dimension", "$value": "42dp", "px": 84, "dp": 42.0, "source": "index.html:1022", "usedBy": "one-line note row (picks unavailable, no seeds, episode note)" }, "row_state": { "$type": "dimension", "$value": "150dp", "px": 300, "dp": 150.0, "source": "index.html:1023", "usedBy": "state panel row (calm or warning, with recovery buttons)" }, "row_lhead": { "$type": "dimension", "$value": "118dp", "px": 236, "dp": 118.0, "source": "index.html:1024", "usedBy": "screen header row (library, See all, service, Settings, Discover without hero)" }, "row_dhead": { "$type": "dimension", "$value": "450dp", "px": 900, "dp": 450.0, "source": "index.html:1025", "usedBy": "title page header row (copy + buttons over the backdrop)" }, "row_phead": { "$type": "dimension", "$value": "235dp", "px": 470, "dp": 235.0, "source": "index.html:1026", "usedBy": "person page header row" }, "row_gh": { "$type": "dimension", "$value": "42dp", "px": 84, "dp": 42.0, "source": "index.html:1027", "usedBy": "grid section heading row (person page)" }, "row_endcta": { "$type": "dimension", "$value": "100dp", "px": 200, "dp": 100.0, "source": "index.html:1028", "usedBy": "Home end action row (\"Explore more in Discover\")" }, "row_srow": { "$type": "dimension", "$value": "62dp", "px": 124, "dp": 62.0, "source": "index.html:1029", "usedBy": "Settings list row pitch (row 104 + 20)" }, "heroCopyLeft": { "$type": "dimension", "$value": "48dp", "px": 96.0, "dp": 48.0, "source": "index.html:100", "usedBy": "hero copy block x" }, "heroCopyBottom": { "$type": "dimension", "$value": "55dp", "px": 110.0, "dp": 55.0, "source": "index.html:100", "usedBy": "hero copy block anchored 110 px above the 1080 art bottom (bottom edge of buttons at y 710 on Home)" }, "heroCopyWidth": { "$type": "dimension", "$value": "500dp", "px": 1000.0, "dp": 500.0, "source": "index.html:100", "usedBy": "hero copy block width" }, "heroOverviewMax": { "$type": "dimension", "$value": "480dp", "px": 960.0, "dp": 480.0, "source": "index.html:105", "usedBy": "hero overview max width; 2-line clamp" }, "detailCopyTop": { "$type": "dimension", "$value": "100dp", "px": 200.0, "dp": 100.0, "source": "index.html:212", "usedBy": "title page copy block top" }, "detailCopyWidth": { "$type": "dimension", "$value": "530dp", "px": 1060.0, "dp": 530.0, "source": "index.html:212", "usedBy": "title page copy block width; overview 3-line clamp" }, "heroSheet": { "$type": "dimension", "px": [ 420, 630 ], "dp": [ 210, 315 ], "right": 180, "top": 150, "source": "index.html:97", "usedBy": "one-sheet poster on a poster-only Home hero/title page; Discover picks hero uses 360x540 at top 140 (.row-phero .art-sheet)" }, "buttonHeight": { "$type": "dimension", "$value": "38dp", "px": 76.0, "dp": 38.0, "source": "index.html:107", "usedBy": "pill button height (primary, secondary, sheet buttons)" }, "buttonPaddingX": { "$type": "dimension", "$value": "19dp", "px": 38.0, "dp": 19.0, "source": "index.html:107", "usedBy": "pill button horizontal padding" }, "buttonGap": { "$type": "dimension", "$value": "10dp", "px": 20.0, "dp": 10.0, "source": "index.html:106", "usedBy": "gap between buttons in a row" }, "buttonIcon": { "$type": "dimension", "$value": "15dp", "px": 30.0, "dp": 15.0, "source": "index.html:108", "usedBy": "glyph inside a pill button; icon gap 14" }, "iconButton": { "$type": "dimension", "$value": "38dp", "px": 76.0, "dp": 38.0, "source": "index.html:274", "usedBy": "round icon button (Restart, Version, Audio and subtitles, Mark watched, Trailer, More); glyph 32" }, "iconCaptionLine": { "$type": "dimension", "$value": "19dp", "px": 38.0, "dp": 19.0, "source": "index.html:281", "usedBy": "reserved caption line under a button row that has icon buttons (margin-top 16); shows \"label ยท note\" of the focused icon button" }, "sheetWidth": { "$type": "dimension", "$value": "400dp", "px": 800.0, "dp": 400.0, "source": "index.html:227", "usedBy": "right-side sheet width; padding 80 top/bottom, 64 sides; vertical gap 18" }, "sheetRow": { "$type": "dimension", "$value": "50dp", "px": 100.0, "dp": 50.0, "source": "index.html:231", "usedBy": "person row in request sheets (version rows auto height, padding 20/26; track rows 84)" }, "settingsRow": { "$type": "dimension", "px": [ 1300, 104 ], "dp": [ 650, 52 ], "source": "index.html:321", "usedBy": "Settings list row (icon 36, title 30/700, one-line description 24, pills, chevron)" }, "stateWidth": { "$type": "dimension", "$value": "550dp", "px": 1100.0, "dp": 550.0, "source": "index.html:194", "usedBy": "state panel width; padding 34/40; title 36; text 28" }, "captionOffset": { "$type": "dimension", "px": 38, "dp": 19, "source": "index.html:1242", "usedBy": "caption under a focused poster starts 38 px below the card bottom (capHtml top = headH + h + CAP_OFF; was 18, amended 2026-11-06); width 640" }, "plateInset": { "$type": "dimension", "px": { "top": 14, "side": 16, "bottomFade": 46 }, "available": "card width - 32", "source": "index.html:162, index.html:1234", "usedBy": "rating plate padding; the plate row must fit width - 32 px" }, "badge": { "$type": "dimension", "$value": "24dp", "px": 48.0, "dp": 24.0, "source": "index.html:141", "usedBy": "state badge circle, 14 px from right and bottom; glyph 28" }, "badgeInset": { "$type": "dimension", "$value": "7dp", "px": 14.0, "dp": 7.0, "source": "index.html:141", "usedBy": "badge inset from the card corner" }, "progressBar": { "$type": "dimension", "$value": "4dp", "px": 8.0, "dp": 4.0, "source": "index.html:138", "usedBy": "progress bar on wide cards (inset 18 left/right, 12 from bottom); title page bar is 320x8" }, "pickerAvatar": { "$type": "dimension", "$value": "110dp", "px": 220.0, "dp": 110.0, "source": "index.html:261", "usedBy": "Who's watching avatar; letter 100 px; avatars 90 px apart; name 26 px below" }, "pickerGap": { "$type": "dimension", "$value": "45dp", "px": 90.0, "dp": 45.0, "source": "index.html:259", "usedBy": "gap between people in the picker (also between gear slots)" }, "pickerStackGap": { "$type": "dimension", "$value": "35dp", "px": 70.0, "dp": 35.0, "source": "index.html:256", "usedBy": "vertical gap between \"Who's watching?\", the people and the gear row (gear row margin-top -34)" }, "gear": { "$type": "dimension", "$value": "28dp", "px": 56.0, "dp": 28.0, "source": "index.html:306", "usedBy": "Settings gear under the last person; glyph 30; caption 24 px, 72 px below the gear top, visible only on focus" }, "navAvatar": { "$type": "dimension", "$value": "32dp", "px": 64.0, "dp": 32.0, "source": "index.html:90", "usedBy": "avatar at the right end of the nav" }, "castCircle": { "$type": "dimension", "$value": "100dp", "px": 200, "dp": 100.0, "source": "index.html:285", "usedBy": "cast circle; name 26/700 at 216 px; character 24 muted; card 290 tall" }, "personPortrait": { "$type": "dimension", "$value": "120dp", "px": 240.0, "dp": 120.0, "source": "index.html:294", "usedBy": "person page portrait circle; header at x 96, y 170, gap 48" }, "thumb": { "$type": "dimension", "px": [ 96, 144 ], "dp": [ 48, 72 ], "source": "index.html:248", "usedBy": "Coming-in poster thumbnail" }, "toast": { "$type": "dimension", "px": { "right": 96, "top": 150, "maxWidth": 760 }, "source": "index.html:270", "usedBy": "toast position (top right under the nav)" }, "letterRail": { "$type": "dimension", "px": { "width": 52, "letterHeight": 30, "focusedLetterHeight": 44, "right": 96, "top": 150 }, "source": "index.html:204", "usedBy": "letter rail (only for destinations with >= 200 titles)" }, "row_chipsGrid": { "$type": "dimension", "$value": "68dp", "px": 136, "dp": 68.0, "source": "index.html:rowH", "usedBy": "chip row directly above a grid (library toolbar, See all type chips): 136 px, so 52 px from the chips to the first card row (was 104 / 20 px)" } }, "card": { "poster": { "$type": "card", "px": { "w": 256, "h": 384, "gap": 40, "pitch": 296, "captionBand": 96 }, "dp": { "w": 128.0, "h": 192.0, "gap": 20.0, "pitch": 148.0, "captionBand": 48.0 }, "ratio": "2:3", "fullSlotsPerRow": 6, "source": "index.html:656", "usedBy": "every title shelf, library grid, See all grid, More like this, person filmography; art only at rest" }, "tile": { "$type": "card", "px": { "w": 400, "h": 225, "gap": 40, "pitch": 440, "captionBand": 0 }, "dp": { "w": 200.0, "h": 112.5, "gap": 20.0, "pitch": 220.0, "captionBand": 0.0 }, "ratio": "16:9", "fullSlotsPerRow": 4, "source": "index.html:657", "usedBy": "Next up, New in your library, season episodes; title + second line on a bottom gradient" }, "wide": { "$type": "card", "px": { "w": 544, "h": 306, "gap": 40, "pitch": 584, "captionBand": 0 }, "dp": { "w": 272.0, "h": 153.0, "gap": 20.0, "pitch": 292.0, "captionBand": 0.0 }, "ratio": "16:9", "fullSlotsPerRow": 3, "source": "index.html:658", "usedBy": "Continue watching (only when a person has more than one resume item; the first resume is the hero)" }, "entry": { "$type": "card", "px": { "w": 256, "h": 144, "gap": 40, "pitch": 296, "captionBand": 0 }, "dp": { "w": 128.0, "h": 72.0, "gap": 20.0, "pitch": 148.0, "captionBand": 0.0 }, "ratio": "16:9", "fullSlotsPerRow": 6, "source": "index.html:659", "usedBy": "Discover Services row (text wordmark on a tinted gradient; no third-party logos)" }, "person": { "$type": "card", "px": { "w": 200, "h": 310, "gap": 44, "pitch": 244, "captionBand": 0 }, "dp": { "w": 100.0, "h": 155.0, "gap": 22.0, "pitch": 122.0, "captionBand": 0.0 }, "ratio": "1:1 circle", "fullSlotsPerRow": 7, "source": "index.html:660", "usedBy": "Cast row: 200 px circle + name + character, 310 tall (name starts 236 below the top)" } }, "focus": { "scaleCard": { "$type": "number", "$value": 1.08, "source": "index.html:134", "page": { ".card.f": 1.08, ".pp.f (picker person)": 1.08, ".pgear.f": 1.08, "person circle": 1.08 }, "usedBy": "cards of every kind, cast circles, picker people, the gear" }, "scaleControl": { "$type": "number", "$value": 1.05, "source": "index.html:189", "page": { ".btn.f": 1.06, ".chip.f": 1.05 }, "usedBy": "pill and icon buttons, chips", "note": "q1 = 3 scales, settled by Justin 2026-11-05 (corr=c248698d8546e1ab): buttons 1.05. The page button scale 1.06 folds into 1.05 (delta 0.01 = 2 px on a 200 px button). Spec 4.5, 12.4 #1." }, "scaleRow": { "$type": "number", "$value": 1.02, "source": "index.html:327", "page": { ".prow.f": 1.03, ".crow.f": 1.02, ".srow.f": 1.02 }, "usedBy": "full-width list rows: sheet rows, Coming-in rows, Settings rows", "note": "q1, settled by Justin 2026-11-05 (corr=c248698d8546e1ab): sheet rows 1.02. The page sheet-row scale 1.03 folds into 1.02. Spec 4.5, 12.4 #1." }, "ringCard": { "$type": "shadow", "$value": "0 0 0 2px var(--accent), inset 0 0 0 2px var(--accent) (drawn above the art), 0 18px 50px rgb(0 0 0 / .65)", "gapPx": 0, "ringPx": 4, "gapDp": 0, "ringDp": 2, "ringColor": "color.accent", "liftShadow": "0 18px 50px rgb(0 0 0 / .65)", "source": "index.html:134", "usedBy": "focused card (poster, tile, wide, entry) and cast circle: one 4 px lilac ring centred on the art edge, 2 px outside and 2 px over the art, no gap, plus a lift shadow (Justin 2026-11-06: \"2px inset, 4px ring\")", "outsidePx": 2, "insidePx": 2 }, "ringButton": { "$type": "shadow", "$value": "0 0 0 4px var(--bg),0 0 0 8px var(--accent),0 12px 40px rgb(0 0 0 / .5)", "gapPx": 4, "ringPx": 4, "ringColor": "color.accent", "liftShadow": "0 12px 40px rgb(0 0 0 / .5)", "source": "index.html:110", "usedBy": "focused pill and icon buttons (primary stays white, secondary lightens to buttonSecondaryFocused)" }, "ringAvatar": { "$type": "shadow", "$value": "0 0 0 4px var(--bg),0 0 0 8px var(--fg)", "gapPx": 4, "ringPx": 4, "ringColor": "color.fg", "source": "index.html:92", "usedBy": "focused nav avatar and focused gear (white ring, not lilac)" }, "ringPicker": { "$type": "shadow", "$value": "0 0 0 6px var(--bg),0 0 0 12px var(--fg)", "gapPx": 6, "ringPx": 6, "ringColor": "color.fg", "source": "index.html:263", "usedBy": "focused person in Who's watching (white 6 + 6 ring; name turns fg)" }, "fillInverse": { "$type": "rule", "$value": "background color.fg, text color.onFocus", "source": "index.html:189, index.html:237, index.html:327, index.html:253", "usedBy": "focused chip, sheet row, Settings row, Coming-in row and nav blob: white fill with dark text (no ring)" } }, "motion": { "fast": { "$type": "duration", "$value": "140ms", "easing": "ease = cubic-bezier(.25,.1,.25,1)", "source": "index.html:125", "page": { ".card/.btn/.chip/.pp/.prow/.crow/.srow/.pgear transform": "140ms", ".cap opacity (caption fade-in)": "120ms", ".dots svg opacity": "140ms" }, "usedBy": "focus scale + ring in and out on every focusable; caption fade under a focused poster (page 120 ms folds in)", "note": "q1, settled by Justin 2026-11-05 (corr=c248698d8546e1ab): the caption fade is 140 ms. Three durations in total (140, 160, 180) plus the blob spring." }, "base": { "$type": "duration", "$value": "160ms", "easing": "ease", "source": "index.html:162", "page": { ".rplate opacity": "160ms", "#nav background": "160ms", ".track transform (row slide)": "160ms", ".chips transform": "160ms", ".dbg .dim opacity": "160ms", ".toast opacity": "160ms" }, "usedBy": "rating plate fade, nav band solid/transparent, row track slide by one card, chip strip shift, title-page dim as it scrolls" }, "scroll": { "$type": "duration", "$value": "180ms", "easing": "ease-out cubic: 1 - (1 - t)^3 = cubic-bezier(.33,1,.68,1)", "source": "index.html:1076", "usedBy": "vertical frame scroll to the pinned line (Compose BringIntoViewSpec animation spec)" }, "navBlob": { "$type": "transition", "$value": { "type": "spring", "dampingRatio": 1.0, "stiffness": 650 }, "page": { "transition": "transform .2s ease-out,width .2s ease-out,height .2s ease-out,background-color .2s ease-out", "measured": "x 478 -> 688 px: 557.4 at 50 ms, 621.8 at 99 ms (68.5% of travel), 668.4 at 150 ms, at rest by 200 ms; width 198 -> 171 animates with it" }, "source": "index.html:76", "usedBy": "nav indicator: x, width, height and colour (white when the nav has focus, rest colour behind the current destination when not); label colour follows (page .ni color .2s ease-out)", "verified": { "script": "data/tv-spec/spring_check.py", "method": "mass 1, x'' = -k(x-1) - 2*zeta*sqrt(k)*x', integrated at 0.01 ms steps; k searched 400-1000 step 10 for least error against the page measurements", "pxAt50_99_150_200ms": [ 554.5, 628.7, 665.9, 680.2 ], "pageMeasuredPx": [ 557.4, 621.8, 668.4, 688.0 ], "reaches95pctMs": 186, "reaches99pctMs": 260, "within1pxMs": 294, "maxOvershoot": 0, "previousStiffness": "530 (revision 1, reached 66.4% at 99 ms against the page 68.5%)" }, "note": "q1 = spring, settled by Justin 2026-11-05 (corr=c248698d8546e1ab): a no-bounce spring matched to the measured 200 ms ease-out. Damping ratio 1.0 is critically damped, so it cannot overshoot. Stiffness 650 is the best fit to the measured curve (revision 1 had 530, an untested derivation). Visually settled (95%) by 186 ms; the last pixel arrives at about 300 ms. Snap with no animation when the system animator scale is 0." }, "ratingDwell": { "$type": "duration", "$value": "350ms", "source": "index.html:860", "usedBy": "a focused card requests ratings only after 350 ms of dwell; moving on cancels; cached ratings show at once (fork DEBOUNCE_MILLIS 350)" }, "toast": { "$type": "duration", "$value": "3200ms", "source": "index.html:1733", "usedBy": "toast visible time" }, "reducedMotion": { "$type": "rule", "$value": "all transitions instant", "source": "index.html:330", "usedBy": "when Settings.Global ANIMATOR_DURATION_SCALE == 0: no scale animation, no scroll animation, blob snaps" } }, "image": { "posterRequest": { "px": [ 256, 384 ], "decode": "RGB565", "source": "index.html:548 (memory table), answers p3", "usedBy": "poster cards; decode at min(source, slot); fixture sources are 300x450" }, "tileRequest": { "px": [ 416, 234 ], "decode": "ARGB_8888", "source": "index.html:549", "usedBy": "tile cards" }, "wideRequest": { "px": [ 560, 315 ], "decode": "ARGB_8888", "source": "index.html:401", "usedBy": "Continue watching cards" }, "heroRequest": { "px": [ 1280, 720 ], "decode": "ARGB_8888", "bytes": 3686400, "source": "index.html:549", "usedBy": "Home hero, Discover hero, title backdrop: the 1280x720 source, never upscaled; released when scrolled past" }, "castRequest": { "px": [ 200, 200 ], "source": "index.html:550", "usedBy": "cast portraits: decode min(source 185x278, slot)" }, "crossfade": { "$value": false, "source": "index.html:536; answers p3", "usedBy": "no crossfade on cards (Coil crossfade off)" }, "memoryCacheMB": { "$value": 64, "source": "index.html:552; answers p3", "usedBy": "Coil memory cache cap (not a heap percentage)" }, "diskCacheMB": { "$value": 128, "source": "index.html:553", "usedBy": "Coil disk cache" }, "concurrentReads": { "$value": { "catalogue": 2, "image": 4 }, "source": "index.html:489 (Rules row)", "usedBy": "at most 2 catalogue reads and 4 image reads at once" } }, "behaviour": { "rowLimit": { "$value": 20, "source": "index.html:666", "usedBy": "cards per shelf before See all (D3)" }, "letterRailMin": { "$value": 200, "source": "index.html:667", "usedBy": "the letter rail exists only for a destination of >= 200 titles, sort A-Z (n4)" }, "gridColumns": { "$value": 6, "source": "index.html:1425", "usedBy": "library, See all and person grids" }, "libraryChunk": { "$value": 200, "source": "index.html:469", "usedBy": "library read in 200-item chunks ahead of focus" }, "laneLoadRows": { "$value": 2, "source": "index.html:472", "usedBy": "catalogue lane is read when within 2 rows of focus" }, "nextPageCards": { "$value": 10, "source": "index.html:472", "usedBy": "catalogue page 2+ is read when focus is 10 cards from the row end" }, "heroSlides": { "$value": 5, "source": "index.html:883", "usedBy": "Discover picks hero slides" }, "cacheRatingsHours": { "$value": 6, "entries": 300, "source": "index.html:473", "usedBy": "ratings cache per (type, tmdb) and per (series, season)" }, "laneCacheMinutes": { "$value": 30, "source": "index.html:472", "usedBy": "last-known-good per catalogue lane" }, "libraryRefreshMinutes": { "$value": 10, "source": "index.html:469", "usedBy": "library snapshot refreshed on open if older than 10 min" }, "requestsPollSeconds": { "$value": 30, "source": "index.html:479", "usedBy": "Requests (Coming in) poll while a request is unsettled or the sheet is open" }, "comingInVisibleRows": { "$value": 5, "expanded": 4, "source": "index.html:1907", "usedBy": "Coming-in sheet shows 5 rows (4 while one is expanded), window follows focus" } } }