wolfskin/wolfskin-design/tokens.json

1592 lines
No EOL
44 KiB
JSON

{
"$description": "Wolfskin TV design tokens, extracted by script from the locked prototype (data/tv-v2/opus-final-2026-11-05/index.html, sha256 ee8844fc...). 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).",
"meta": {
"density": 2.0,
"stagePx": [
1920,
1080
],
"stageDp": [
960,
540
],
"source": "data/tv-v2/opus-final-2026-11-05/index.html",
"sourceSha256": "ee8844fc6d0237a3727de0d9454db8e5cf3bb14083fb7877d7bb0f4233c6e23d",
"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": "30dp",
"px": 60,
"dp": 30.0,
"source": "index.html:1018",
"usedBy": "shelf row: heading band (44 px heading at top 0) before the cards start at y + 60"
},
"rowGapAfter": {
"$type": "dimension",
"$value": "22dp",
"px": 44,
"dp": 22.0,
"source": "index.html:1018",
"usedBy": "shelf row: space after cards (+ caption band) before the next row; shelf row height = 60 + card h + caption + 44"
},
"gridRowGap": {
"$type": "dimension",
"$value": "14dp",
"px": 28,
"dp": 14.0,
"source": "index.html:1019",
"usedBy": "grid row height = 384 + 76 + 28 = 488 (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 (Discover filters, library toolbar, See all/service type chips, seasons)"
},
"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 \"<b>label</b> · 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": 18,
"dp": 9,
"source": "index.html:1242",
"usedBy": "caption under a focused poster starts 18 px below the card bottom (capHtml top = headH + h + 18); 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)"
}
},
"card": {
"poster": {
"$type": "card",
"px": {
"w": 256,
"h": 384,
"gap": 24,
"pitch": 280,
"captionBand": 76
},
"dp": {
"w": 128.0,
"h": 192.0,
"gap": 12.0,
"pitch": 140.0,
"captionBand": 38.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": 416,
"h": 234,
"gap": 24,
"pitch": 440,
"captionBand": 0
},
"dp": {
"w": 208.0,
"h": 117.0,
"gap": 12.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": 560,
"h": 315,
"gap": 24,
"pitch": 584,
"captionBand": 0
},
"dp": {
"w": 280.0,
"h": 157.5,
"gap": 12.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": 24,
"pitch": 280,
"captionBand": 0
},
"dp": {
"w": 128.0,
"h": 72.0,
"gap": 12.0,
"pitch": 140.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": 290,
"gap": 28,
"pitch": 228,
"captionBand": 0
},
"dp": {
"w": 100.0,
"h": 145.0,
"gap": 14.0,
"pitch": 114.0,
"captionBand": 0.0
},
"ratio": "1:1 circle",
"fullSlotsPerRow": 7,
"source": "index.html:660",
"usedBy": "Cast row: 200 px circle + name + character, 290 px tall"
}
},
"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 4px var(--bg),0 0 0 8px var(--accent),0 18px 50px rgb(0 0 0 / .65)",
"gapPx": 4,
"ringPx": 4,
"gapDp": 2,
"ringDp": 2,
"gapColor": "color.bg",
"ringColor": "color.accent",
"liftShadow": "0 18px 50px rgb(0 0 0 / .65)",
"source": "index.html:134",
"usedBy": "focused card: a 4 px bg-coloured gap then a 4 px lilac ring OUTSIDE the art (never covers it), plus a lift shadow; drawn on the circle for cast cards"
},
"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"
}
}
}