From 0a45646c69cb762eb96c674f11153bf672b70c63 Mon Sep 17 00:00:00 2001 From: Altay Date: Tue, 7 Jul 2026 15:03:39 +0300 Subject: [PATCH 1/8] =?UTF-8?q?feat(design):=20port=202026-07=20handoff=20?= =?UTF-8?q?=E2=80=94=20z/bp=20tokens,=20type=20rebase,=207=20new=20cards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the July 2026 round from the putio-design-system Claude Design project into the repo's own pipeline: - tokens: z scale (--z-dropdown..--z-tooltip, 1000-1500), breakpoints (--bp-sm/md/lg = 640/768/1280px), --shadow aliases --shadow-color in both modes, root font-size 14px stepping to 15px at >= --bp-lg, type scale rebased to production (--fs-base 1rem, fixed 48/64/96px display tiers, --fs-4xl removed), button default/info promoted from foundation.css aliases to mode-scoped DTCG tokens, reduced-motion block; 313 -> 330 tokens - previews: seven new cards (menu, table, web player, task progress, folder picker, segmented control, skeleton) registered in the guide; tv-shell.css fully scoped under .tv; web-shell layout fixes; APCA contrast-contract copy and data-contrast-contract tags; href="#" sweeps; a11y hardening on import (menu labelling, data-state selection, role=group player frames, indeterminate select-all) - docs: DESIGN.md front-matter + prose follow the new scale; system/README.md gains shadcn/Base UI interop, z/breakpoint, and theme-system guidance Project-side regressions were kept out (mode-aware solid-foreground, role=img/switch attributes, #F3C435 hover caption) and relayed as feedback to the design project. Co-Authored-By: Claude Fable 5 --- DESIGN.md | 10 +- dist/css/tokens.css | 83 ++++-- dist/figma/putio.tokens.json | 58 +++- dist/tokens.d.ts | 2 +- dist/tokens.dtcg.json | 256 ++++++++++++++++-- dist/tokens.flat.json | 190 +++++++++++-- dist/tokens.js | 190 +++++++++++-- system/README.md | 101 ++++++- system/design-system.html | 25 +- system/preview/_components.css | 11 +- system/preview/accessibility.html | 36 +-- system/preview/brand-empty-state.html | 14 +- system/preview/components-breadcrumbs.html | 10 +- system/preview/components-buttons.html | 2 +- system/preview/components-folder-picker.html | 170 ++++++++++++ system/preview/components-form-fields.html | 6 +- system/preview/components-form-layouts.html | 26 +- system/preview/components-menu.html | 189 +++++++++++++ system/preview/components-notification.html | 4 +- system/preview/components-player-web.html | 215 +++++++++++++++ .../preview/components-segmented-control.html | 124 +++++++++ system/preview/components-skeleton.html | 122 +++++++++ system/preview/components-table.html | 193 +++++++++++++ system/preview/components-task-progress.html | 178 ++++++++++++ system/preview/tv-shell.css | 150 +++++----- system/preview/web-shell.html | 19 +- system/tokens.css | 83 ++++-- tokens/components.tokens.json | 112 ++++++++ tokens/foundation.css | 48 ++-- tokens/primitives.tokens.json | 141 ++++++++-- tokens/semantic.tokens.json | 3 +- 31 files changed, 2429 insertions(+), 342 deletions(-) create mode 100644 system/preview/components-folder-picker.html create mode 100644 system/preview/components-menu.html create mode 100644 system/preview/components-player-web.html create mode 100644 system/preview/components-segmented-control.html create mode 100644 system/preview/components-skeleton.html create mode 100644 system/preview/components-table.html create mode 100644 system/preview/components-task-progress.html diff --git a/DESIGN.md b/DESIGN.md index 8f3483d..400c438 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -24,17 +24,17 @@ typography: ui: family: "\"GT America\", sans-serif" weight: 400 - size: "0.875rem" + size: "1rem" lineHeight: 1.45 display: family: "\"GT America\", sans-serif" weight: 900 - size: "6rem" + size: "96px" lineHeight: 1.1 mono: family: "\"Berkeley Mono\", \"GT America Mono\", monospace" weight: 400 - size: "0.8125rem" + size: "0.875rem" lineHeight: 1.45 rounded: sm: "4px" @@ -78,12 +78,16 @@ Use GT America for product UI and display. Use GT America Mono for compact produ Body UI text should stay readable and dense. Use medium weight for labels and controls, bold weight only where the hierarchy earns it, and black weight for deliberate display moments. +The root font-size is responsive like the product: 14px, stepping to 15px at the `--bp-lg` breakpoint. Body, labels, and inputs sit at `--fs-base` (1rem, equal to the root); captions and button labels sit at `--fs-sm` (0.875rem). The display tiers (`--fs-2xl`, `--fs-3xl`, `--fs-display`) are fixed pixel sizes and do not scale with the root. + ## Layout & Spacing put.io layouts are file-browser layouts first: lists, rows, columns, sidebars, toolbars, and sheets. Prefer whitespace and subtle dividers before card-heavy composition. The spacing scale is intentionally steep: `4, 8, 16, 32, 64, 128, 256, 512`. Use it consistently rather than tuning one-off gaps. +Stacking uses the token scale — `--z-dropdown` (1000), `--z-sticky` (1100), `--z-modal` (1300), `--z-notification` (1400), `--z-tooltip` (1500) — gapped for platform chrome; never invent z-index numbers. Breakpoints (`--bp-sm`/`--bp-md`/`--bp-lg` = 640/768/1280px) mirror the product's responsive tiers; CSS media queries cannot read custom properties, so write the literal pixels in `@media` and keep them in sync with the tokens. + ## Elevation & Depth Elevation should clarify interaction, not decorate. Use borders and soft shadows for popovers, modals, menus, TV focus, and draggable/drop targets. Avoid decorative glow except where it is part of a focused TV state. diff --git a/dist/css/tokens.css b/dist/css/tokens.css index ccd3d70..b934236 100644 --- a/dist/css/tokens.css +++ b/dist/css/tokens.css @@ -15,7 +15,12 @@ --button-success-bg-hover: hsl(151, 58%, 37%); --button-danger-bg: hsl(358, 75%, 59%); --button-danger-bg-hover: hsl(357, 66%, 53%); + --button-info-bg: hsl(0, 0%, 56.1%); --button-info-bg-hover: hsl(0, 0%, 52.3%); + --button-info-fg: hsl(0, 0%, 0%); + --button-default-bg: hsl(0, 0%, 95.1%); + --button-default-bg-hover: hsl(0, 0%, 93.0%); + --button-default-fg: hsl(0, 0%, 9.0%); --input-bg: hsl(0, 0%, 100%); --input-border: hsl(0, 0%, 85.8%); --field-bg: hsl(0, 0%, 100%); @@ -144,15 +149,14 @@ --font-ui-mono: "GT America Mono", monospace; --font-mono: "Berkeley Mono", "GT America Mono", monospace; --fs-xs: 0.75rem; - --fs-sm: 0.8125rem; - --fs-base: 0.875rem; - --fs-md: 1rem; - --fs-lg: 1.125rem; - --fs-xl: 1.5rem; - --fs-2xl: 2rem; - --fs-3xl: 3rem; - --fs-4xl: 4rem; - --fs-display: 6rem; + --fs-sm: 0.875rem; + --fs-base: 1rem; + --fs-md: 1.125rem; + --fs-lg: 1.5rem; + --fs-xl: 2rem; + --fs-2xl: 48px; + --fs-3xl: 64px; + --fs-display: 96px; --lh-tight: 1.1; --lh-snug: 1.25; --lh-normal: 1.45; @@ -185,6 +189,14 @@ --dur-fast: 120ms; --dur-base: 200ms; --dur-slow: 320ms; + --z-dropdown: 1000; + --z-sticky: 1100; + --z-modal: 1300; + --z-notification: 1400; + --z-tooltip: 1500; + --bp-sm: 640px; + --bp-md: 768px; + --bp-lg: 1280px; } /* -- Dark (.dark) --------------------------------------------------- */ @@ -196,7 +208,12 @@ --button-success-bg-hover: hsl(151, 68%, 35%); --button-danger-bg: hsl(358, 75%, 59%); --button-danger-bg-hover: hsl(357, 67%, 53%); + --button-info-bg: hsl(0, 0%, 43.9%); --button-info-bg-hover: hsl(0, 0%, 49.4%); + --button-info-fg: hsl(0, 0%, 100%); + --button-default-bg: hsl(0, 0%, 13.6%); + --button-default-bg-hover: hsl(0, 0%, 15.8%); + --button-default-fg: hsl(0, 0%, 93.0%); --input-bg: hsl(0, 0%, 8.5%); --input-border: hsl(0, 0%, 24.3%); --field-bg: hsl(0, 0%, 8.5%); @@ -310,7 +327,7 @@ html { background: var(--html-bg); color: var(--text); font-family: var(--font-sans); - font-size: 15px; /* 14–15 responsive base, per app convention */ + font-size: 14px; /* app.put.io root: fontSize ["14px","14px","15px"] — 15px at ≥ --bp-lg (media query below) */ line-height: var(--lh-normal); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -322,6 +339,13 @@ body { margin: 0; } +/* Root steps up on desktop, matching app.put.io's responsive base. + 14px/15px literals must stay in sync with --bp-lg (media queries + can't read custom properties). */ +@media (min-width: 1280px) { + html { font-size: 15px; } +} + /* -- Semantic type ------------------------------------------------- */ .h-display, h1.display { @@ -343,7 +367,7 @@ body { h1, .h1 { font-family: var(--font-sans); - font-size: var(--fs-3xl); + font-size: var(--fs-xl); font-weight: var(--fw-bold); line-height: var(--lh-tight); letter-spacing: var(--tracking-tight); @@ -352,7 +376,7 @@ h1, .h1 { h2, .h2 { font-family: var(--font-sans); - font-size: var(--fs-2xl); + font-size: var(--fs-lg); font-weight: var(--fw-bold); line-height: var(--lh-tight); margin: 0 0 var(--space-3); @@ -360,7 +384,7 @@ h2, .h2 { h3, .h3 { font-family: var(--font-sans); - font-size: var(--fs-xl); + font-size: var(--fs-md); font-weight: var(--fw-medium); line-height: var(--lh-snug); margin: 0 0 var(--space-2); @@ -368,7 +392,7 @@ h3, .h3 { h4, .h4 { font-family: var(--font-sans); - font-size: var(--fs-lg); + font-size: var(--fs-base); font-weight: var(--fw-medium); line-height: var(--lh-snug); margin: 0 0 var(--space-2); @@ -389,26 +413,26 @@ p, .body { } .lead { - font-size: var(--fs-lg); + font-size: var(--fs-md); font-weight: var(--fw-regular); line-height: var(--lh-normal); color: var(--text-secondary); } small, .caption { - font-size: var(--fs-xs); + font-size: var(--fs-sm); /* production caption = fontSize 0 */ color: var(--text-secondary); } .label { - font-size: var(--fs-sm); + font-size: var(--fs-base); /* production label = fontSize 1, medium */ font-weight: var(--fw-medium); color: var(--text); } .button-text { font-weight: var(--fw-medium); - font-size: var(--fs-base); + font-size: var(--fs-sm); /* production button = fontSize 0 */ letter-spacing: 0; } @@ -427,16 +451,6 @@ a { } a:hover { text-decoration-color: currentColor; } -/* -- Component-role aliases --------------------------------------- */ -/* Button default + info variants — live aliases; the DTCG layer doesn't define these two. */ -:root { - --button-default-bg: var(--component-bg); - --button-default-bg-hover: var(--component-bg-hover); - --button-default-fg: var(--text); - --button-info-bg: var(--solid); - --button-info-fg: var(--solid-foreground); -} - /* -- Utility classes ---------------------------------------------- */ .fg-1 { color: var(--text); } .fg-2 { color: var(--text-secondary); } @@ -448,3 +462,16 @@ a:hover { text-decoration-color: currentColor; } .bg-2 { background: var(--bg-secondary); } .bg-component { background: var(--component-bg); } .bg-yellow { background: var(--yellow-solid); color: var(--primary-foreground); } + +/* -- Reduced motion ------------------------------------------------ */ +/* Honor the OS "reduce motion" setting: collapse transitions/animations + to near-instant and stop looping/auto-scrolling effects. Components + should not rely on motion to convey state. */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/dist/figma/putio.tokens.json b/dist/figma/putio.tokens.json index 79148e4..d8fc256 100644 --- a/dist/figma/putio.tokens.json +++ b/dist/figma/putio.tokens.json @@ -67,10 +67,37 @@ } }, "info": { + "background": { + "$type": "color", + "$value": "hsl(0, 0%, 56.1%)", + "$description": "Neutral solid command background in light mode." + }, "backgroundHover": { "$type": "color", "$value": "hsl(0, 0%, 52.3%)", "$description": "Neutral solid command hover background in light mode." + }, + "foreground": { + "$type": "color", + "$value": "hsl(0, 0%, 0%)", + "$description": "Label on the neutral solid — mode-aware for AA contrast on the gray fill." + } + }, + "default": { + "background": { + "$type": "color", + "$value": "hsl(0, 0%, 95.1%)", + "$description": "Quiet default button fill in light mode." + }, + "backgroundHover": { + "$type": "color", + "$value": "hsl(0, 0%, 93.0%)", + "$description": "Default button hover fill in light mode." + }, + "foreground": { + "$type": "color", + "$value": "hsl(0, 0%, 9.0%)", + "$description": "Default button label in light mode." } } }, @@ -342,7 +369,8 @@ }, "color": { "$type": "color", - "$value": "hsl(0, 0%, 78.0%)" + "$value": "hsl(0, 0%, 78.0%)", + "$description": "Compat name kept for existing web consumers — alias of elevation.shadowColor so the two can never drift." }, "focusColor": { "$type": "color", @@ -375,39 +403,43 @@ }, "sm": { "$type": "dimension", - "$value": "0.8125rem" + "$value": "0.875rem", + "$description": "Caption and button labels — production fontSize 0." }, "base": { "$type": "dimension", - "$value": "0.875rem" + "$value": "1rem", + "$description": "Body, labels, inputs — production fontSize 1; equals the responsive root (14px, 15px at >= breakpoint.lg)." }, "md": { "$type": "dimension", - "$value": "1rem" + "$value": "1.125rem", + "$description": "Subheading." }, "lg": { "$type": "dimension", - "$value": "1.125rem" + "$value": "1.5rem", + "$description": "Heading." }, "xl": { "$type": "dimension", - "$value": "1.5rem" + "$value": "2rem", + "$description": "Page title." }, "2xl": { "$type": "dimension", - "$value": "2rem" + "$value": "48px", + "$description": "Fixed display tier — does not scale with the responsive root." }, "3xl": { "$type": "dimension", - "$value": "3rem" - }, - "4xl": { - "$type": "dimension", - "$value": "4rem" + "$value": "64px", + "$description": "Fixed display tier — does not scale with the responsive root." }, "display": { "$type": "dimension", - "$value": "6rem" + "$value": "96px", + "$description": "Fixed display tier for brand display moments." } }, "lineHeight": { diff --git a/dist/tokens.d.ts b/dist/tokens.d.ts index 7c3aba9..7575e00 100644 --- a/dist/tokens.d.ts +++ b/dist/tokens.d.ts @@ -1,6 +1,6 @@ // Do not edit directly. Generated from tokens/**/*.tokens.json. -export type PutioTokenName = "component.button.primary.background" | "component.button.primary.backgroundHover" | "component.button.primary.backgroundHoverDark" | "component.button.primary.foreground" | "component.button.primary.radius" | "component.button.secondary.background" | "component.button.secondary.backgroundDark" | "component.button.secondary.foreground" | "component.button.secondary.foregroundDark" | "component.button.success.background" | "component.button.success.backgroundDark" | "component.button.success.backgroundHover" | "component.button.success.backgroundHoverDark" | "component.button.success.foreground" | "component.button.danger.background" | "component.button.danger.backgroundDark" | "component.button.danger.backgroundHover" | "component.button.danger.backgroundHoverDark" | "component.button.danger.foreground" | "component.button.info.backgroundHover" | "component.button.info.backgroundHoverDark" | "component.input.background" | "component.input.backgroundDark" | "component.input.border" | "component.input.borderDark" | "component.input.radius" | "component.field.background" | "component.field.backgroundDark" | "component.field.backgroundDisabled" | "component.field.backgroundDisabledDark" | "component.field.border" | "component.field.borderDark" | "component.field.borderHover" | "component.field.borderHoverDark" | "component.field.borderFocus" | "component.field.borderFocusDark" | "component.field.text" | "component.field.textDark" | "component.field.placeholder" | "component.field.placeholderDark" | "component.field.ring" | "component.field.ringDark" | "component.panel.background" | "component.panel.backgroundDark" | "component.panel.border" | "component.panel.borderDark" | "component.panel.radius" | "component.panel.shadow" | "component.panel.shadowDark" | "component.alias.background" | "component.alias.backgroundDark" | "component.alias.foreground" | "component.alias.foregroundDark" | "component.alias.foregroundMuted" | "component.alias.foregroundMutedDark" | "component.alias.primary" | "component.alias.primaryForeground" | "component.alias.success" | "component.alias.successDark" | "component.alias.successForeground" | "component.alias.destructive" | "component.alias.destructiveDark" | "component.alias.destructiveForeground" | "component.alias.invertForeground" | "component.alias.invertForegroundDark" | "component.alias.solidForeground" | "component.alias.solidForegroundDark" | "component.alias.muted" | "component.alias.mutedDark" | "component.alias.mutedForeground" | "component.alias.mutedForegroundDark" | "component.alias.accent" | "component.alias.accentDark" | "component.alias.accentForeground" | "component.alias.accentForegroundDark" | "component.alias.card" | "component.alias.cardDark" | "component.alias.cardForeground" | "component.alias.cardForegroundDark" | "component.alias.popover" | "component.alias.popoverDark" | "component.alias.popoverForeground" | "component.alias.popoverForegroundDark" | "component.alias.input" | "component.alias.inputDark" | "component.alias.ring" | "component.fileRow.background" | "component.fileRow.backgroundDark" | "component.fileRow.hoverBackground" | "component.fileRow.hoverBackgroundDark" | "component.fileRow.icon" | "component.notification.infoBackground" | "component.notification.infoBackgroundDark" | "component.notification.dangerBackground" | "component.notification.dangerBackgroundDark" | "context.tv.surface.0" | "context.tv.surface.1" | "context.tv.surface.2" | "context.tv.surface.3" | "context.tv.text.primary" | "context.tv.text.secondary" | "context.tv.text.tertiary" | "context.tv.glass.1" | "context.tv.glass.2" | "context.tv.glass.3" | "context.tv.glass.border" | "context.tv.glass.borderStrong" | "context.tv.focus.ring" | "context.tv.focus.liftShadow" | "context.tv.channelArt.production.posterBackground" | "context.tv.channelArt.production.splashBackground" | "context.tv.channelArt.development.background" | "context.tv.channelArt.development.accent" | "context.tv.channelArt.development.grid" | "context.tv.channelArt.lab.background" | "context.tv.channelArt.lab.foreground" | "context.tv.channelArt.lab.accent" | "color.brand.yellow" | "color.neutral.light.bg" | "color.neutral.light.bgSecondary" | "color.neutral.light.componentBg" | "color.neutral.light.componentBgHover" | "color.neutral.light.componentBgActive" | "color.neutral.light.line" | "color.neutral.light.border" | "color.neutral.light.borderHover" | "color.neutral.light.solid" | "color.neutral.light.solidHover" | "color.neutral.light.textSecondary" | "color.neutral.light.text" | "color.neutral.dark.bg" | "color.neutral.dark.bgSecondary" | "color.neutral.dark.componentBg" | "color.neutral.dark.componentBgHover" | "color.neutral.dark.componentBgActive" | "color.neutral.dark.line" | "color.neutral.dark.border" | "color.neutral.dark.borderHover" | "color.neutral.dark.solid" | "color.neutral.dark.solidHover" | "color.neutral.dark.textSecondary" | "color.neutral.dark.text" | "color.green.light.bg" | "color.green.light.bgSecondary" | "color.green.light.componentBg" | "color.green.light.componentBgHover" | "color.green.light.componentBgActive" | "color.green.light.line" | "color.green.light.border" | "color.green.light.borderHover" | "color.green.light.solid" | "color.green.light.solidHover" | "color.green.light.textSecondary" | "color.green.light.text" | "color.green.dark.bg" | "color.green.dark.bgSecondary" | "color.green.dark.componentBg" | "color.green.dark.componentBgHover" | "color.green.dark.componentBgActive" | "color.green.dark.line" | "color.green.dark.border" | "color.green.dark.borderHover" | "color.green.dark.solid" | "color.green.dark.solidHover" | "color.green.dark.textSecondary" | "color.green.dark.text" | "color.red.light.bg" | "color.red.light.bgSecondary" | "color.red.light.componentBg" | "color.red.light.componentBgHover" | "color.red.light.componentBgActive" | "color.red.light.line" | "color.red.light.border" | "color.red.light.borderHover" | "color.red.light.solid" | "color.red.light.solidHover" | "color.red.light.textSecondary" | "color.red.light.text" | "color.red.dark.bg" | "color.red.dark.bgSecondary" | "color.red.dark.componentBg" | "color.red.dark.componentBgHover" | "color.red.dark.componentBgActive" | "color.red.dark.line" | "color.red.dark.border" | "color.red.dark.borderHover" | "color.red.dark.solid" | "color.red.dark.solidHover" | "color.red.dark.textSecondary" | "color.red.dark.text" | "color.yellow.light.bg" | "color.yellow.light.bgSecondary" | "color.yellow.light.componentBg" | "color.yellow.light.componentBgHover" | "color.yellow.light.componentBgActive" | "color.yellow.light.line" | "color.yellow.light.border" | "color.yellow.light.borderHover" | "color.yellow.light.solidHover" | "color.yellow.light.textSecondary" | "color.yellow.light.text" | "color.yellow.dark.bg" | "color.yellow.dark.bgSecondary" | "color.yellow.dark.componentBg" | "color.yellow.dark.componentBgHover" | "color.yellow.dark.componentBgActive" | "color.yellow.dark.line" | "color.yellow.dark.border" | "color.yellow.dark.borderHover" | "color.yellow.dark.solidHover" | "color.yellow.dark.textSecondary" | "color.yellow.dark.text" | "color.transfer.light.lime3" | "color.transfer.dark.lime3" | "elevation.shadowColor" | "elevation.color" | "elevation.sm" | "elevation.md" | "elevation.lg" | "elevation.focusColor" | "elevation.focus" | "typography.fontFamily.sans" | "typography.fontFamily.display" | "typography.fontFamily.uiMono" | "typography.fontFamily.mono" | "typography.fontSize.xs" | "typography.fontSize.sm" | "typography.fontSize.base" | "typography.fontSize.md" | "typography.fontSize.lg" | "typography.fontSize.xl" | "typography.fontSize.2xl" | "typography.fontSize.3xl" | "typography.fontSize.4xl" | "typography.fontSize.display" | "typography.lineHeight.tight" | "typography.lineHeight.snug" | "typography.lineHeight.normal" | "typography.lineHeight.loose" | "typography.fontWeight.regular" | "typography.fontWeight.medium" | "typography.fontWeight.bold" | "typography.fontWeight.black" | "typography.letterSpacing.tight" | "typography.letterSpacing.normal" | "typography.letterSpacing.wide" | "typography.letterSpacing.uppercase" | "spacing.0" | "spacing.1" | "spacing.2" | "spacing.3" | "spacing.4" | "spacing.5" | "spacing.6" | "spacing.7" | "spacing.8" | "radius.sm" | "radius.default" | "radius.md" | "radius.lg" | "radius.pill" | "border.width" | "motion.easing.out" | "motion.easing.inOut" | "motion.duration.fast" | "motion.duration.base" | "motion.duration.slow" | "surface.light.htmlBg" | "surface.light.appBg" | "surface.light.navBg" | "surface.light.navItemBg" | "surface.light.navItemBgHover" | "surface.light.navItemBgActive" | "surface.light.listItemBg" | "surface.light.listItemBgHover" | "surface.light.listItemBgActive" | "surface.light.listItemBorder" | "surface.light.segmentedControlBg" | "surface.light.segmentedControlBgActive" | "surface.light.transferListItemCompletingBg" | "surface.light.transferListItemDownloadingBg" | "surface.light.transferListItemFinishedBg" | "surface.light.transferListItemPassiveBg" | "surface.light.hiContrast" | "surface.light.loContrast" | "surface.light.transparent" | "surface.light.overlayInline" | "surface.light.overlayFull" | "surface.dark.htmlBg" | "surface.dark.appBg" | "surface.dark.navBg" | "surface.dark.navItemBg" | "surface.dark.navItemBgHover" | "surface.dark.navItemBgActive" | "surface.dark.listItemBg" | "surface.dark.listItemBgHover" | "surface.dark.listItemBgActive" | "surface.dark.listItemBorder" | "surface.dark.segmentedControlBg" | "surface.dark.segmentedControlBgActive" | "surface.dark.transferListItemCompletingBg" | "surface.dark.transferListItemDownloadingBg" | "surface.dark.transferListItemFinishedBg" | "surface.dark.transferListItemPassiveBg" | "surface.dark.hiContrast" | "surface.dark.loContrast" | "surface.dark.overlayInline" | "surface.dark.overlayFull" | "surface.dark.shadowColor" | "surface.dark.shadow" | "surface.dark.shadowSm" | "surface.dark.shadowMd" | "surface.dark.shadowLg"; +export type PutioTokenName = "component.button.primary.background" | "component.button.primary.backgroundHover" | "component.button.primary.backgroundHoverDark" | "component.button.primary.foreground" | "component.button.primary.radius" | "component.button.secondary.background" | "component.button.secondary.backgroundDark" | "component.button.secondary.foreground" | "component.button.secondary.foregroundDark" | "component.button.success.background" | "component.button.success.backgroundDark" | "component.button.success.backgroundHover" | "component.button.success.backgroundHoverDark" | "component.button.success.foreground" | "component.button.danger.background" | "component.button.danger.backgroundDark" | "component.button.danger.backgroundHover" | "component.button.danger.backgroundHoverDark" | "component.button.danger.foreground" | "component.button.info.background" | "component.button.info.backgroundDark" | "component.button.info.backgroundHover" | "component.button.info.backgroundHoverDark" | "component.button.info.foreground" | "component.button.info.foregroundDark" | "component.button.default.background" | "component.button.default.backgroundDark" | "component.button.default.backgroundHover" | "component.button.default.backgroundHoverDark" | "component.button.default.foreground" | "component.button.default.foregroundDark" | "component.input.background" | "component.input.backgroundDark" | "component.input.border" | "component.input.borderDark" | "component.input.radius" | "component.field.background" | "component.field.backgroundDark" | "component.field.backgroundDisabled" | "component.field.backgroundDisabledDark" | "component.field.border" | "component.field.borderDark" | "component.field.borderHover" | "component.field.borderHoverDark" | "component.field.borderFocus" | "component.field.borderFocusDark" | "component.field.text" | "component.field.textDark" | "component.field.placeholder" | "component.field.placeholderDark" | "component.field.ring" | "component.field.ringDark" | "component.panel.background" | "component.panel.backgroundDark" | "component.panel.border" | "component.panel.borderDark" | "component.panel.radius" | "component.panel.shadow" | "component.panel.shadowDark" | "component.alias.background" | "component.alias.backgroundDark" | "component.alias.foreground" | "component.alias.foregroundDark" | "component.alias.foregroundMuted" | "component.alias.foregroundMutedDark" | "component.alias.primary" | "component.alias.primaryForeground" | "component.alias.success" | "component.alias.successDark" | "component.alias.successForeground" | "component.alias.destructive" | "component.alias.destructiveDark" | "component.alias.destructiveForeground" | "component.alias.invertForeground" | "component.alias.invertForegroundDark" | "component.alias.solidForeground" | "component.alias.solidForegroundDark" | "component.alias.muted" | "component.alias.mutedDark" | "component.alias.mutedForeground" | "component.alias.mutedForegroundDark" | "component.alias.accent" | "component.alias.accentDark" | "component.alias.accentForeground" | "component.alias.accentForegroundDark" | "component.alias.card" | "component.alias.cardDark" | "component.alias.cardForeground" | "component.alias.cardForegroundDark" | "component.alias.popover" | "component.alias.popoverDark" | "component.alias.popoverForeground" | "component.alias.popoverForegroundDark" | "component.alias.input" | "component.alias.inputDark" | "component.alias.ring" | "component.fileRow.background" | "component.fileRow.backgroundDark" | "component.fileRow.hoverBackground" | "component.fileRow.hoverBackgroundDark" | "component.fileRow.icon" | "component.notification.infoBackground" | "component.notification.infoBackgroundDark" | "component.notification.dangerBackground" | "component.notification.dangerBackgroundDark" | "context.tv.surface.0" | "context.tv.surface.1" | "context.tv.surface.2" | "context.tv.surface.3" | "context.tv.text.primary" | "context.tv.text.secondary" | "context.tv.text.tertiary" | "context.tv.glass.1" | "context.tv.glass.2" | "context.tv.glass.3" | "context.tv.glass.border" | "context.tv.glass.borderStrong" | "context.tv.focus.ring" | "context.tv.focus.liftShadow" | "context.tv.channelArt.production.posterBackground" | "context.tv.channelArt.production.splashBackground" | "context.tv.channelArt.development.background" | "context.tv.channelArt.development.accent" | "context.tv.channelArt.development.grid" | "context.tv.channelArt.lab.background" | "context.tv.channelArt.lab.foreground" | "context.tv.channelArt.lab.accent" | "color.brand.yellow" | "color.neutral.light.bg" | "color.neutral.light.bgSecondary" | "color.neutral.light.componentBg" | "color.neutral.light.componentBgHover" | "color.neutral.light.componentBgActive" | "color.neutral.light.line" | "color.neutral.light.border" | "color.neutral.light.borderHover" | "color.neutral.light.solid" | "color.neutral.light.solidHover" | "color.neutral.light.textSecondary" | "color.neutral.light.text" | "color.neutral.dark.bg" | "color.neutral.dark.bgSecondary" | "color.neutral.dark.componentBg" | "color.neutral.dark.componentBgHover" | "color.neutral.dark.componentBgActive" | "color.neutral.dark.line" | "color.neutral.dark.border" | "color.neutral.dark.borderHover" | "color.neutral.dark.solid" | "color.neutral.dark.solidHover" | "color.neutral.dark.textSecondary" | "color.neutral.dark.text" | "color.green.light.bg" | "color.green.light.bgSecondary" | "color.green.light.componentBg" | "color.green.light.componentBgHover" | "color.green.light.componentBgActive" | "color.green.light.line" | "color.green.light.border" | "color.green.light.borderHover" | "color.green.light.solid" | "color.green.light.solidHover" | "color.green.light.textSecondary" | "color.green.light.text" | "color.green.dark.bg" | "color.green.dark.bgSecondary" | "color.green.dark.componentBg" | "color.green.dark.componentBgHover" | "color.green.dark.componentBgActive" | "color.green.dark.line" | "color.green.dark.border" | "color.green.dark.borderHover" | "color.green.dark.solid" | "color.green.dark.solidHover" | "color.green.dark.textSecondary" | "color.green.dark.text" | "color.red.light.bg" | "color.red.light.bgSecondary" | "color.red.light.componentBg" | "color.red.light.componentBgHover" | "color.red.light.componentBgActive" | "color.red.light.line" | "color.red.light.border" | "color.red.light.borderHover" | "color.red.light.solid" | "color.red.light.solidHover" | "color.red.light.textSecondary" | "color.red.light.text" | "color.red.dark.bg" | "color.red.dark.bgSecondary" | "color.red.dark.componentBg" | "color.red.dark.componentBgHover" | "color.red.dark.componentBgActive" | "color.red.dark.line" | "color.red.dark.border" | "color.red.dark.borderHover" | "color.red.dark.solid" | "color.red.dark.solidHover" | "color.red.dark.textSecondary" | "color.red.dark.text" | "color.yellow.light.bg" | "color.yellow.light.bgSecondary" | "color.yellow.light.componentBg" | "color.yellow.light.componentBgHover" | "color.yellow.light.componentBgActive" | "color.yellow.light.line" | "color.yellow.light.border" | "color.yellow.light.borderHover" | "color.yellow.light.solidHover" | "color.yellow.light.textSecondary" | "color.yellow.light.text" | "color.yellow.dark.bg" | "color.yellow.dark.bgSecondary" | "color.yellow.dark.componentBg" | "color.yellow.dark.componentBgHover" | "color.yellow.dark.componentBgActive" | "color.yellow.dark.line" | "color.yellow.dark.border" | "color.yellow.dark.borderHover" | "color.yellow.dark.solidHover" | "color.yellow.dark.textSecondary" | "color.yellow.dark.text" | "color.transfer.light.lime3" | "color.transfer.dark.lime3" | "elevation.shadowColor" | "elevation.color" | "elevation.sm" | "elevation.md" | "elevation.lg" | "elevation.focusColor" | "elevation.focus" | "typography.fontFamily.sans" | "typography.fontFamily.display" | "typography.fontFamily.uiMono" | "typography.fontFamily.mono" | "typography.fontSize.xs" | "typography.fontSize.sm" | "typography.fontSize.base" | "typography.fontSize.md" | "typography.fontSize.lg" | "typography.fontSize.xl" | "typography.fontSize.2xl" | "typography.fontSize.3xl" | "typography.fontSize.display" | "typography.lineHeight.tight" | "typography.lineHeight.snug" | "typography.lineHeight.normal" | "typography.lineHeight.loose" | "typography.fontWeight.regular" | "typography.fontWeight.medium" | "typography.fontWeight.bold" | "typography.fontWeight.black" | "typography.letterSpacing.tight" | "typography.letterSpacing.normal" | "typography.letterSpacing.wide" | "typography.letterSpacing.uppercase" | "spacing.0" | "spacing.1" | "spacing.2" | "spacing.3" | "spacing.4" | "spacing.5" | "spacing.6" | "spacing.7" | "spacing.8" | "radius.sm" | "radius.default" | "radius.md" | "radius.lg" | "radius.pill" | "border.width" | "motion.easing.out" | "motion.easing.inOut" | "motion.duration.fast" | "motion.duration.base" | "motion.duration.slow" | "z.dropdown" | "z.sticky" | "z.modal" | "z.notification" | "z.tooltip" | "breakpoint.sm" | "breakpoint.md" | "breakpoint.lg" | "surface.light.htmlBg" | "surface.light.appBg" | "surface.light.navBg" | "surface.light.navItemBg" | "surface.light.navItemBgHover" | "surface.light.navItemBgActive" | "surface.light.listItemBg" | "surface.light.listItemBgHover" | "surface.light.listItemBgActive" | "surface.light.listItemBorder" | "surface.light.segmentedControlBg" | "surface.light.segmentedControlBgActive" | "surface.light.transferListItemCompletingBg" | "surface.light.transferListItemDownloadingBg" | "surface.light.transferListItemFinishedBg" | "surface.light.transferListItemPassiveBg" | "surface.light.hiContrast" | "surface.light.loContrast" | "surface.light.transparent" | "surface.light.overlayInline" | "surface.light.overlayFull" | "surface.dark.htmlBg" | "surface.dark.appBg" | "surface.dark.navBg" | "surface.dark.navItemBg" | "surface.dark.navItemBgHover" | "surface.dark.navItemBgActive" | "surface.dark.listItemBg" | "surface.dark.listItemBgHover" | "surface.dark.listItemBgActive" | "surface.dark.listItemBorder" | "surface.dark.segmentedControlBg" | "surface.dark.segmentedControlBgActive" | "surface.dark.transferListItemCompletingBg" | "surface.dark.transferListItemDownloadingBg" | "surface.dark.transferListItemFinishedBg" | "surface.dark.transferListItemPassiveBg" | "surface.dark.hiContrast" | "surface.dark.loContrast" | "surface.dark.overlayInline" | "surface.dark.overlayFull" | "surface.dark.shadowColor" | "surface.dark.shadow" | "surface.dark.shadowSm" | "surface.dark.shadowMd" | "surface.dark.shadowLg"; export type PutioToken = { readonly cssName: string; diff --git a/dist/tokens.dtcg.json b/dist/tokens.dtcg.json index 6f9cca0..161dc67 100644 --- a/dist/tokens.dtcg.json +++ b/dist/tokens.dtcg.json @@ -215,6 +215,28 @@ } }, "info": { + "background": { + "$type": "color", + "$value": "{color.neutral.light.solid}", + "$description": "Neutral solid command background in light mode.", + "$extensions": { + "putio": { + "cssName": "button-info-bg", + "mode": "light" + } + } + }, + "backgroundDark": { + "$type": "color", + "$value": "{color.neutral.dark.solid}", + "$description": "Neutral solid command background in dark mode.", + "$extensions": { + "putio": { + "cssName": "button-info-bg", + "mode": "dark" + } + } + }, "backgroundHover": { "$type": "color", "$value": "{color.neutral.light.solidHover}", @@ -236,6 +258,96 @@ "mode": "dark" } } + }, + "foreground": { + "$type": "color", + "$value": "{component.alias.solidForeground}", + "$description": "Label on the neutral solid — mode-aware for AA contrast on the gray fill.", + "$extensions": { + "putio": { + "cssName": "button-info-fg", + "mode": "light" + } + } + }, + "foregroundDark": { + "$type": "color", + "$value": "{component.alias.solidForegroundDark}", + "$description": "Label on the neutral solid in dark mode.", + "$extensions": { + "putio": { + "cssName": "button-info-fg", + "mode": "dark" + } + } + } + }, + "default": { + "background": { + "$type": "color", + "$value": "{color.neutral.light.componentBg}", + "$description": "Quiet default button fill in light mode.", + "$extensions": { + "putio": { + "cssName": "button-default-bg", + "mode": "light" + } + } + }, + "backgroundDark": { + "$type": "color", + "$value": "{color.neutral.dark.componentBg}", + "$description": "Quiet default button fill in dark mode.", + "$extensions": { + "putio": { + "cssName": "button-default-bg", + "mode": "dark" + } + } + }, + "backgroundHover": { + "$type": "color", + "$value": "{color.neutral.light.componentBgHover}", + "$description": "Default button hover fill in light mode.", + "$extensions": { + "putio": { + "cssName": "button-default-bg-hover", + "mode": "light" + } + } + }, + "backgroundHoverDark": { + "$type": "color", + "$value": "{color.neutral.dark.componentBgHover}", + "$description": "Default button hover fill in dark mode.", + "$extensions": { + "putio": { + "cssName": "button-default-bg-hover", + "mode": "dark" + } + } + }, + "foreground": { + "$type": "color", + "$value": "{color.neutral.light.text}", + "$description": "Default button label in light mode.", + "$extensions": { + "putio": { + "cssName": "button-default-fg", + "mode": "light" + } + } + }, + "foregroundDark": { + "$type": "color", + "$value": "{color.neutral.dark.text}", + "$description": "Default button label in dark mode.", + "$extensions": { + "putio": { + "cssName": "button-default-fg", + "mode": "dark" + } + } } } }, @@ -2339,7 +2451,8 @@ }, "color": { "$type": "color", - "$value": "hsl(0, 0%, 78.0%)", + "$value": "{elevation.shadowColor}", + "$description": "Compat name kept for existing web consumers — alias of elevation.shadowColor so the two can never drift.", "$extensions": { "putio": { "cssName": "shadow", @@ -2465,7 +2578,8 @@ }, "sm": { "$type": "dimension", - "$value": "0.8125rem", + "$value": "0.875rem", + "$description": "Caption and button labels — production fontSize 0.", "$extensions": { "putio": { "cssName": "fs-sm", @@ -2476,7 +2590,8 @@ }, "base": { "$type": "dimension", - "$value": "0.875rem", + "$value": "1rem", + "$description": "Body, labels, inputs — production fontSize 1; equals the responsive root (14px, 15px at >= breakpoint.lg).", "$extensions": { "putio": { "cssName": "fs-base", @@ -2487,7 +2602,8 @@ }, "md": { "$type": "dimension", - "$value": "1rem", + "$value": "1.125rem", + "$description": "Subheading.", "$extensions": { "putio": { "cssName": "fs-md", @@ -2498,7 +2614,8 @@ }, "lg": { "$type": "dimension", - "$value": "1.125rem", + "$value": "1.5rem", + "$description": "Heading.", "$extensions": { "putio": { "cssName": "fs-lg", @@ -2509,7 +2626,8 @@ }, "xl": { "$type": "dimension", - "$value": "1.5rem", + "$value": "2rem", + "$description": "Page title.", "$extensions": { "putio": { "cssName": "fs-xl", @@ -2520,7 +2638,8 @@ }, "2xl": { "$type": "dimension", - "$value": "2rem", + "$value": "48px", + "$description": "Fixed display tier — does not scale with the responsive root.", "$extensions": { "putio": { "cssName": "fs-2xl", @@ -2531,7 +2650,8 @@ }, "3xl": { "$type": "dimension", - "$value": "3rem", + "$value": "64px", + "$description": "Fixed display tier — does not scale with the responsive root.", "$extensions": { "putio": { "cssName": "fs-3xl", @@ -2540,20 +2660,10 @@ } } }, - "4xl": { - "$type": "dimension", - "$value": "4rem", - "$extensions": { - "putio": { - "cssName": "fs-4xl", - "mode": "global", - "figma": true - } - } - }, "display": { "$type": "dimension", - "$value": "6rem", + "$value": "96px", + "$description": "Fixed display tier for brand display moments.", "$extensions": { "putio": { "cssName": "fs-display", @@ -2934,6 +3044,109 @@ } } }, + "z": { + "$description": "Stacking scale. Gapped for platform chrome — never invent z-index numbers in apps.", + "dropdown": { + "$type": "number", + "$value": "1000", + "$extensions": { + "putio": { + "cssName": "z-dropdown", + "mode": "global", + "category": "other", + "figma": false + } + } + }, + "sticky": { + "$type": "number", + "$value": "1100", + "$extensions": { + "putio": { + "cssName": "z-sticky", + "mode": "global", + "category": "other", + "figma": false + } + } + }, + "modal": { + "$type": "number", + "$value": "1300", + "$extensions": { + "putio": { + "cssName": "z-modal", + "mode": "global", + "category": "other", + "figma": false + } + } + }, + "notification": { + "$type": "number", + "$value": "1400", + "$extensions": { + "putio": { + "cssName": "z-notification", + "mode": "global", + "category": "other", + "figma": false + } + } + }, + "tooltip": { + "$type": "number", + "$value": "1500", + "$extensions": { + "putio": { + "cssName": "z-tooltip", + "mode": "global", + "category": "other", + "figma": false + } + } + } + }, + "breakpoint": { + "$description": "Responsive breakpoints, lifted from app.put.io's BREAKPOINTS. Generator-facing: CSS media queries cannot read custom properties, so emitters bind these as Tailwind screens, StyleX consts, or documented literals.", + "sm": { + "$type": "dimension", + "$value": "640px", + "$extensions": { + "putio": { + "cssName": "bp-sm", + "mode": "global", + "category": "other", + "figma": false + } + } + }, + "md": { + "$type": "dimension", + "$value": "768px", + "$extensions": { + "putio": { + "cssName": "bp-md", + "mode": "global", + "category": "other", + "figma": false + } + } + }, + "lg": { + "$type": "dimension", + "$value": "1280px", + "$description": "The root font-size steps from 14px to 15px at this width.", + "$extensions": { + "putio": { + "cssName": "bp-lg", + "mode": "global", + "category": "other", + "figma": false + } + } + } + }, "surface": { "light": { "htmlBg": { @@ -3360,7 +3573,8 @@ }, "shadow": { "$type": "color", - "$value": "hsl(0, 0%, 0%)", + "$value": "{surface.dark.shadowColor}", + "$description": "Compat name kept for existing web consumers — alias of surface.dark.shadowColor so the two can never drift.", "$extensions": { "putio": { "cssName": "shadow", diff --git a/dist/tokens.flat.json b/dist/tokens.flat.json index e1066b5..6e1efae 100644 --- a/dist/tokens.flat.json +++ b/dist/tokens.flat.json @@ -146,6 +146,22 @@ "originalValue": "{component.alias.destructiveForeground}", "description": "Text or icon color on destructive command backgrounds." }, + "component.button.info.background": { + "cssName": "--button-info-bg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 56.1%)", + "originalValue": "{color.neutral.light.solid}", + "description": "Neutral solid command background in light mode." + }, + "component.button.info.backgroundDark": { + "cssName": "--button-info-bg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 43.9%)", + "originalValue": "{color.neutral.dark.solid}", + "description": "Neutral solid command background in dark mode." + }, "component.button.info.backgroundHover": { "cssName": "--button-info-bg-hover", "type": "color", @@ -162,6 +178,70 @@ "originalValue": "{color.neutral.dark.solidHover}", "description": "Neutral solid command hover background in dark mode." }, + "component.button.info.foreground": { + "cssName": "--button-info-fg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 0%)", + "originalValue": "{component.alias.solidForeground}", + "description": "Label on the neutral solid — mode-aware for AA contrast on the gray fill." + }, + "component.button.info.foregroundDark": { + "cssName": "--button-info-fg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 100%)", + "originalValue": "{component.alias.solidForegroundDark}", + "description": "Label on the neutral solid in dark mode." + }, + "component.button.default.background": { + "cssName": "--button-default-bg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 95.1%)", + "originalValue": "{color.neutral.light.componentBg}", + "description": "Quiet default button fill in light mode." + }, + "component.button.default.backgroundDark": { + "cssName": "--button-default-bg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 13.6%)", + "originalValue": "{color.neutral.dark.componentBg}", + "description": "Quiet default button fill in dark mode." + }, + "component.button.default.backgroundHover": { + "cssName": "--button-default-bg-hover", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 93.0%)", + "originalValue": "{color.neutral.light.componentBgHover}", + "description": "Default button hover fill in light mode." + }, + "component.button.default.backgroundHoverDark": { + "cssName": "--button-default-bg-hover", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 15.8%)", + "originalValue": "{color.neutral.dark.componentBgHover}", + "description": "Default button hover fill in dark mode." + }, + "component.button.default.foreground": { + "cssName": "--button-default-fg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 9.0%)", + "originalValue": "{color.neutral.light.text}", + "description": "Default button label in light mode." + }, + "component.button.default.foregroundDark": { + "cssName": "--button-default-fg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 93.0%)", + "originalValue": "{color.neutral.dark.text}", + "description": "Default button label in dark mode." + }, "component.input.background": { "cssName": "--input-bg", "type": "color", @@ -1553,7 +1633,8 @@ "type": "color", "mode": "global", "value": "hsl(0, 0%, 78.0%)", - "originalValue": "hsl(0, 0%, 78.0%)" + "originalValue": "{elevation.shadowColor}", + "description": "Compat name kept for existing web consumers — alias of elevation.shadowColor so the two can never drift." }, "elevation.sm": { "cssName": "--shadow-sm", @@ -1629,64 +1710,65 @@ "cssName": "--fs-sm", "type": "dimension", "mode": "global", - "value": "0.8125rem", - "originalValue": "0.8125rem" + "value": "0.875rem", + "originalValue": "0.875rem", + "description": "Caption and button labels — production fontSize 0." }, "typography.fontSize.base": { "cssName": "--fs-base", "type": "dimension", "mode": "global", - "value": "0.875rem", - "originalValue": "0.875rem" + "value": "1rem", + "originalValue": "1rem", + "description": "Body, labels, inputs — production fontSize 1; equals the responsive root (14px, 15px at >= breakpoint.lg)." }, "typography.fontSize.md": { "cssName": "--fs-md", "type": "dimension", "mode": "global", - "value": "1rem", - "originalValue": "1rem" + "value": "1.125rem", + "originalValue": "1.125rem", + "description": "Subheading." }, "typography.fontSize.lg": { "cssName": "--fs-lg", "type": "dimension", "mode": "global", - "value": "1.125rem", - "originalValue": "1.125rem" + "value": "1.5rem", + "originalValue": "1.5rem", + "description": "Heading." }, "typography.fontSize.xl": { "cssName": "--fs-xl", "type": "dimension", "mode": "global", - "value": "1.5rem", - "originalValue": "1.5rem" + "value": "2rem", + "originalValue": "2rem", + "description": "Page title." }, "typography.fontSize.2xl": { "cssName": "--fs-2xl", "type": "dimension", "mode": "global", - "value": "2rem", - "originalValue": "2rem" + "value": "48px", + "originalValue": "48px", + "description": "Fixed display tier — does not scale with the responsive root." }, "typography.fontSize.3xl": { "cssName": "--fs-3xl", "type": "dimension", "mode": "global", - "value": "3rem", - "originalValue": "3rem" - }, - "typography.fontSize.4xl": { - "cssName": "--fs-4xl", - "type": "dimension", - "mode": "global", - "value": "4rem", - "originalValue": "4rem" + "value": "64px", + "originalValue": "64px", + "description": "Fixed display tier — does not scale with the responsive root." }, "typography.fontSize.display": { "cssName": "--fs-display", "type": "dimension", "mode": "global", - "value": "6rem", - "originalValue": "6rem" + "value": "96px", + "originalValue": "96px", + "description": "Fixed display tier for brand display moments." }, "typography.lineHeight.tight": { "cssName": "--lh-tight", @@ -1912,6 +1994,63 @@ "value": "320ms", "originalValue": "320ms" }, + "z.dropdown": { + "cssName": "--z-dropdown", + "type": "number", + "mode": "global", + "value": "1000", + "originalValue": "1000" + }, + "z.sticky": { + "cssName": "--z-sticky", + "type": "number", + "mode": "global", + "value": "1100", + "originalValue": "1100" + }, + "z.modal": { + "cssName": "--z-modal", + "type": "number", + "mode": "global", + "value": "1300", + "originalValue": "1300" + }, + "z.notification": { + "cssName": "--z-notification", + "type": "number", + "mode": "global", + "value": "1400", + "originalValue": "1400" + }, + "z.tooltip": { + "cssName": "--z-tooltip", + "type": "number", + "mode": "global", + "value": "1500", + "originalValue": "1500" + }, + "breakpoint.sm": { + "cssName": "--bp-sm", + "type": "dimension", + "mode": "global", + "value": "640px", + "originalValue": "640px" + }, + "breakpoint.md": { + "cssName": "--bp-md", + "type": "dimension", + "mode": "global", + "value": "768px", + "originalValue": "768px" + }, + "breakpoint.lg": { + "cssName": "--bp-lg", + "type": "dimension", + "mode": "global", + "value": "1280px", + "originalValue": "1280px", + "description": "The root font-size steps from 14px to 15px at this width." + }, "surface.light.htmlBg": { "cssName": "--html-bg", "type": "color", @@ -2211,7 +2350,8 @@ "type": "color", "mode": "dark", "value": "hsl(0, 0%, 0%)", - "originalValue": "hsl(0, 0%, 0%)" + "originalValue": "{surface.dark.shadowColor}", + "description": "Compat name kept for existing web consumers — alias of surface.dark.shadowColor so the two can never drift." }, "surface.dark.shadowSm": { "cssName": "--shadow-sm", diff --git a/dist/tokens.js b/dist/tokens.js index 9177cca..8f407fd 100644 --- a/dist/tokens.js +++ b/dist/tokens.js @@ -148,6 +148,22 @@ export const tokens = { "originalValue": "{component.alias.destructiveForeground}", "description": "Text or icon color on destructive command backgrounds." }, + "component.button.info.background": { + "cssName": "--button-info-bg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 56.1%)", + "originalValue": "{color.neutral.light.solid}", + "description": "Neutral solid command background in light mode." + }, + "component.button.info.backgroundDark": { + "cssName": "--button-info-bg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 43.9%)", + "originalValue": "{color.neutral.dark.solid}", + "description": "Neutral solid command background in dark mode." + }, "component.button.info.backgroundHover": { "cssName": "--button-info-bg-hover", "type": "color", @@ -164,6 +180,70 @@ export const tokens = { "originalValue": "{color.neutral.dark.solidHover}", "description": "Neutral solid command hover background in dark mode." }, + "component.button.info.foreground": { + "cssName": "--button-info-fg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 0%)", + "originalValue": "{component.alias.solidForeground}", + "description": "Label on the neutral solid — mode-aware for AA contrast on the gray fill." + }, + "component.button.info.foregroundDark": { + "cssName": "--button-info-fg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 100%)", + "originalValue": "{component.alias.solidForegroundDark}", + "description": "Label on the neutral solid in dark mode." + }, + "component.button.default.background": { + "cssName": "--button-default-bg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 95.1%)", + "originalValue": "{color.neutral.light.componentBg}", + "description": "Quiet default button fill in light mode." + }, + "component.button.default.backgroundDark": { + "cssName": "--button-default-bg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 13.6%)", + "originalValue": "{color.neutral.dark.componentBg}", + "description": "Quiet default button fill in dark mode." + }, + "component.button.default.backgroundHover": { + "cssName": "--button-default-bg-hover", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 93.0%)", + "originalValue": "{color.neutral.light.componentBgHover}", + "description": "Default button hover fill in light mode." + }, + "component.button.default.backgroundHoverDark": { + "cssName": "--button-default-bg-hover", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 15.8%)", + "originalValue": "{color.neutral.dark.componentBgHover}", + "description": "Default button hover fill in dark mode." + }, + "component.button.default.foreground": { + "cssName": "--button-default-fg", + "type": "color", + "mode": "light", + "value": "hsl(0, 0%, 9.0%)", + "originalValue": "{color.neutral.light.text}", + "description": "Default button label in light mode." + }, + "component.button.default.foregroundDark": { + "cssName": "--button-default-fg", + "type": "color", + "mode": "dark", + "value": "hsl(0, 0%, 93.0%)", + "originalValue": "{color.neutral.dark.text}", + "description": "Default button label in dark mode." + }, "component.input.background": { "cssName": "--input-bg", "type": "color", @@ -1555,7 +1635,8 @@ export const tokens = { "type": "color", "mode": "global", "value": "hsl(0, 0%, 78.0%)", - "originalValue": "hsl(0, 0%, 78.0%)" + "originalValue": "{elevation.shadowColor}", + "description": "Compat name kept for existing web consumers — alias of elevation.shadowColor so the two can never drift." }, "elevation.sm": { "cssName": "--shadow-sm", @@ -1631,64 +1712,65 @@ export const tokens = { "cssName": "--fs-sm", "type": "dimension", "mode": "global", - "value": "0.8125rem", - "originalValue": "0.8125rem" + "value": "0.875rem", + "originalValue": "0.875rem", + "description": "Caption and button labels — production fontSize 0." }, "typography.fontSize.base": { "cssName": "--fs-base", "type": "dimension", "mode": "global", - "value": "0.875rem", - "originalValue": "0.875rem" + "value": "1rem", + "originalValue": "1rem", + "description": "Body, labels, inputs — production fontSize 1; equals the responsive root (14px, 15px at >= breakpoint.lg)." }, "typography.fontSize.md": { "cssName": "--fs-md", "type": "dimension", "mode": "global", - "value": "1rem", - "originalValue": "1rem" + "value": "1.125rem", + "originalValue": "1.125rem", + "description": "Subheading." }, "typography.fontSize.lg": { "cssName": "--fs-lg", "type": "dimension", "mode": "global", - "value": "1.125rem", - "originalValue": "1.125rem" + "value": "1.5rem", + "originalValue": "1.5rem", + "description": "Heading." }, "typography.fontSize.xl": { "cssName": "--fs-xl", "type": "dimension", "mode": "global", - "value": "1.5rem", - "originalValue": "1.5rem" + "value": "2rem", + "originalValue": "2rem", + "description": "Page title." }, "typography.fontSize.2xl": { "cssName": "--fs-2xl", "type": "dimension", "mode": "global", - "value": "2rem", - "originalValue": "2rem" + "value": "48px", + "originalValue": "48px", + "description": "Fixed display tier — does not scale with the responsive root." }, "typography.fontSize.3xl": { "cssName": "--fs-3xl", "type": "dimension", "mode": "global", - "value": "3rem", - "originalValue": "3rem" - }, - "typography.fontSize.4xl": { - "cssName": "--fs-4xl", - "type": "dimension", - "mode": "global", - "value": "4rem", - "originalValue": "4rem" + "value": "64px", + "originalValue": "64px", + "description": "Fixed display tier — does not scale with the responsive root." }, "typography.fontSize.display": { "cssName": "--fs-display", "type": "dimension", "mode": "global", - "value": "6rem", - "originalValue": "6rem" + "value": "96px", + "originalValue": "96px", + "description": "Fixed display tier for brand display moments." }, "typography.lineHeight.tight": { "cssName": "--lh-tight", @@ -1914,6 +1996,63 @@ export const tokens = { "value": "320ms", "originalValue": "320ms" }, + "z.dropdown": { + "cssName": "--z-dropdown", + "type": "number", + "mode": "global", + "value": "1000", + "originalValue": "1000" + }, + "z.sticky": { + "cssName": "--z-sticky", + "type": "number", + "mode": "global", + "value": "1100", + "originalValue": "1100" + }, + "z.modal": { + "cssName": "--z-modal", + "type": "number", + "mode": "global", + "value": "1300", + "originalValue": "1300" + }, + "z.notification": { + "cssName": "--z-notification", + "type": "number", + "mode": "global", + "value": "1400", + "originalValue": "1400" + }, + "z.tooltip": { + "cssName": "--z-tooltip", + "type": "number", + "mode": "global", + "value": "1500", + "originalValue": "1500" + }, + "breakpoint.sm": { + "cssName": "--bp-sm", + "type": "dimension", + "mode": "global", + "value": "640px", + "originalValue": "640px" + }, + "breakpoint.md": { + "cssName": "--bp-md", + "type": "dimension", + "mode": "global", + "value": "768px", + "originalValue": "768px" + }, + "breakpoint.lg": { + "cssName": "--bp-lg", + "type": "dimension", + "mode": "global", + "value": "1280px", + "originalValue": "1280px", + "description": "The root font-size steps from 14px to 15px at this width." + }, "surface.light.htmlBg": { "cssName": "--html-bg", "type": "color", @@ -2213,7 +2352,8 @@ export const tokens = { "type": "color", "mode": "dark", "value": "hsl(0, 0%, 0%)", - "originalValue": "hsl(0, 0%, 0%)" + "originalValue": "{surface.dark.shadowColor}", + "description": "Compat name kept for existing web consumers — alias of surface.dark.shadowColor so the two can never drift." }, "surface.dark.shadowSm": { "cssName": "--shadow-sm", diff --git a/system/README.md b/system/README.md index 0cf4df2..f63ce44 100644 --- a/system/README.md +++ b/system/README.md @@ -18,8 +18,18 @@ project root/ ## Fonts -Preview pages load the public put.io font CSS from `static.put.io`: GT America, -GT America Mono, and Berkeley Mono. Do not commit font files here. +Preview pages load the public put.io font CSS from `static.put.io` (the same +source as the product), so the design system always matches production. Do not +commit font files here. Each HTML file links three stylesheets in ``: + +```html + + + +``` + +Canonical weight mapping (matches the brand font host): +`100 ultra-light · 200 thin · 300 light · 400 regular · 500 medium · 700 bold · 900 black` ## Using tokens @@ -38,13 +48,21 @@ than hard-coded palette values. Brand yellow is emitted as hsl form of canonical `#FDCE45`. Primary hover feedback uses `--yellow-solid-hover`, exposed to consumers as `--button-primary-bg-hover`. +Stacking uses the token scale — `--z-dropdown` (1000) < `--z-sticky` (1100) < +`--z-modal` (1300) < `--z-notification` (1400) < `--z-tooltip` (1500). Never +invent z-index numbers. Breakpoints (`--bp-sm`/`--bp-md`/`--bp-lg` = +640/768/1280px) mirror the product's responsive tiers; they are generator-facing +values — CSS media queries can't read custom properties, so write the literal +px in `@media` and keep it in sync with the token. The root font-size is +responsive like production: 14px, stepping to 15px at ≥ `--bp-lg`. + Web package consumers import the package CSS export: ```css @import "@putdotio/design/css"; ``` -For **TV preview cards**, also load `preview/tv-shell.css` — it adds the `.tv`, `.topnav`, `.scr`, glass tiers, and player chrome on top of the tokens. This is preview-only support CSS; platform repos implement TV components in their native UI stacks: +For **TV preview cards**, also load `preview/tv-shell.css` — it adds the `.tv`, `.topnav`, `.scr`, glass tiers, and player chrome on top of the tokens. Every component selector is scoped under `.tv`, so the file is safe to bundle globally without bleeding into web components. This is preview-only support CSS; platform repos implement TV components in their native UI stacks: ```html @@ -53,24 +71,81 @@ For **TV preview cards**, also load `preview/tv-shell.css` — it adds the `.tv` ## Core rules -- **Yellow `#FDCE45` is the brand fill in both modes.** Use it for primary CTAs, folder icons, button/nav/TV focus rings (at 35% alpha), and progress bars. Use `--yellow-solid-hover` / `--button-primary-bg-hover` for CTA hover feedback, `--primary-foreground` for labels on yellow, and `--yellow-text-secondary` for yellow text on light backgrounds. +- **Yellow `#FDCE45` is the brand constant at rest.** Never gradient, tint, or replace `--yellow-solid`. The rule applies to the resting state — `--yellow-solid-hover` (`#F3C435`) is a peer brand color with its own canonical hex, used for hover in both modes. Primary CTAs, folder icons, button/nav/TV focus rings (at 35% alpha), progress bars. +- **Yellow as text on light needs `--yellow-text-secondary`** (≈5:1, AA) — the brand `--yellow-solid` is fill-only on light backgrounds. +- **Labels on yellow** use `--primary-foreground` (`hsl(38, 65%, 10%)` — warm dark, never pure black). Passes AA on the yellow fill without feeling "hard." - **Icons: Phosphor-style inline SVG.** No emoji, ever. -- **One design, two modes.** Light and dark are the same markup and components with different token values. Toggle `.dark` on ``; do not fork the page or rebuild a light-specific copy. +- **One design, two modes.** Light and dark are the same markup and components with different token values. Toggle `.dark` on ``; do not fork the page or rebuild a light-specific copy. Components must consume semantic tokens (`--bg`, `--text`, `--border`, `--accent`, …), not raw `#hex` or `hsl()`. - **Fields stay quiet.** Use `--field-bg`, `--field-border`, and `--field-ring` for text entry. Invalid fields use `aria-invalid="true"` with red border/text only, never a red fill. - **Panels are shared.** Use `--panel-bg`, `--panel-border`, `--panel-radius`, and `--panel-shadow` for raised cards and auth-style shells instead of screen-specific variables. - **Content-agnostic.** put.io doesn't know what the user's files are. Never parse `The.Wire.S03E04.1080p.mkv` into `The Wire`. Raw filenames only. See the root [`DESIGN.md`](../DESIGN.md) for the full principle. ## Theme System -The guide owns the theme toggle. It stores `putio-ds-theme` in `localStorage`, -updates `html.dark`, and broadcasts the current mode to embedded preview iframes. -Standalone preview pages read the same storage key before paint. A URL query -(`?theme=light` or `?theme=dark`) can override the preview mode for screenshots. +The guide (`design-system.html`) owns the theme toggle. It writes +`putio-ds-theme` to `localStorage`, updates `html.dark`, and broadcasts +`{type:'__theme', theme}` to every embedded preview iframe via `postMessage`, +so embeds flip live without reloading. Each preview's inline bootstrap reads +the same storage key on standalone open, and `preview/_resize.js` listens for +the parent broadcast when embedded. A URL query (`?theme=light` or +`?theme=dark`) overrides both — useful for screenshots. Fixed-mode previews opt out with ``. This is only -for product mockups whose visual language is intrinsically one mode, such as TV -and mobile shell specimens. Component previews should stay token-driven and work -in both modes. +for product mockups whose visual language is intrinsically one mode — TV's +translucent glass material and on-video player chrome can't render on a light +surface, so `tv-*.html`, `mobile-shell.html`, and `components-player-web.html` +are locked. Locked previews ignore both `localStorage` and parent broadcasts. +If you write a new component preview, you almost certainly don't want the +lock — let the tokens do the work and your specimen will render correctly in +both modes. + +## shadcn / Base UI interop + +The token layer ships a name-alias bridge so external component libraries that +read canonical shadcn token names theme correctly without renaming. Drop in a +shadcn/ui block, install a Base UI component, paste a Tailwind preset — they +resolve against our values via the aliases. Always alias, never duplicate, so +the two name systems can't drift. + +| shadcn name | put.io target | +| --- | --- | +| `--primary` / `--primary-foreground` | `--yellow-solid` / `var(--primary-foreground)` (warm dark) | +| `--destructive` / `--destructive-foreground` | `--red-solid` / `var(--destructive-foreground)` (= `#fff`) | +| `--success` / `--success-foreground` | `--green-solid` / `var(--success-foreground)` (= `#fff`) | +| `--muted` / `--muted-foreground` | `--bg-secondary` / `--text-secondary` | +| `--accent` / `--accent-foreground` | `--component-bg` / `--text` | +| `--card` / `--card-foreground` | `--component-bg` / `--text` | +| `--popover` / `--popover-foreground` | `--component-bg` / `--text` | +| `--input` | `--border` | +| `--ring` | `--shadow-focus-color` | +| `--background` / `--foreground` | `--bg` / `--text` | + +The foreground tokens (`--primary-foreground`, `--destructive-foreground`, +`--success-foreground`) encode the mode-safe inverse pair — always read these +instead of writing literal `#000` / `#fff` on a solid surface. + +Use whichever name reads cleaner in context. Our own components prefer the long +descriptive names (`--component-bg-hover`, `--yellow-text-secondary`) because +they describe the role exactly. shadcn-imported code prefers the short names. + +### State via `data-state`, not class flags + +Base UI / headless primitives set +`data-state="open|closed|checked|unchecked|active|completed|pending|loading"` +on the DOM. Our components target those same attribute values in CSS — see the +wizard step bar in `components-form-layouts.html` +(`.step[data-state="completed"]`) and the menu/table cards. Prefer `data-state` +over inventing class flags (`.is-active`, `.on`, `.current`) so the same DOM +contract works whether the controlling code is React, Vue, vanilla JS, or +hand-written HTML, and so devtools can read the component's state at a glance. + +### Compound parts via `data-slot` + +shadcn marks parts of compound components with `data-slot="card-header"`, +`data-slot="card-title"`, etc. We do the same on the modal and folder picker +(`data-slot="modal" / "modal-header" / "modal-body" / "modal-footer"`) and the +auth card. This lets external CSS tweak one part without inventing nested class +chains. ## Design variants @@ -85,4 +160,4 @@ Four documented type stacks — pick one per surface: ## Preview cards -Open `system/design-system.html` for the full index. Each card renders a single concept against the actual tokens; treat it as the visual spec. +Open `system/design-system.html` for the full index. Each card renders a single concept against the actual tokens — read it like a spec. diff --git a/system/design-system.html b/system/design-system.html index 98fe8d6..785fadb 100644 --- a/system/design-system.html +++ b/system/design-system.html @@ -1144,15 +1144,15 @@

Type

-
--fs-display
96 · 900
put.io
-
--fs-3xl
48 · 700
Display heading
-
--fs-2xl
32 · 700
Section heading
-
--fs-xl
24 · 500
Subheading · card title
-
--fs-lg
18 · 400
Lead copy for long-form
-
--fs-md
16 · 500
Subheading · dense UI
-
--fs-base
14 · 400
Body — app default
-
--fs-sm
13 · 400
Secondary body
-
--fs-xs
12 · 500 · UP
Label / caption
+
--fs-display
96px · 900
put.io
+
--fs-3xl
64px · 700
Display heading
+
--fs-2xl
48px · 700
Display heading · small
+
--fs-xl
2rem · 28–30 · 700
Page title
+
--fs-lg
1.5rem · 21–22.5 · 500
Heading
+
--fs-md
1.125rem · ~16 · 500
Subheading · card title
+
--fs-base
1rem · 14–15 · 400
Body — app default (= root, matches production fontSize 1)
+
--fs-sm
0.875rem · 12–13 · 400
Caption · button label (production fontSize 0)
+
--fs-xs
0.75rem · ~11 · 500 · UP
Fine print / overline
--font-ui-mono
GT Mono · tabular
11.20 TB · 12.4 MB/s · 00:03:42
--font-mono
Berkeley · filenames
ubuntu-22.04.iso · sha1: e3b0c44…
@@ -1737,6 +1737,12 @@

Other primitives

Notificationsopen ↗
Tooltipsopen ↗
+
Segmented controlopen ↗
+ +
Data tableopen ↗
+ +
Folder pickeropen ↗
+
Skeleton & loadingopen ↗
@@ -1822,6 +1828,7 @@

App shell

+
diff --git a/system/preview/_components.css b/system/preview/_components.css index dfb94dc..0b26dc3 100644 --- a/system/preview/_components.css +++ b/system/preview/_components.css @@ -26,7 +26,7 @@ button { font-family: var(--font-sans); font-weight: var(--fw-medium); - font-size: calc(var(--fs-base) * var(--tw-fs, 1)); + font-size: calc(var(--fs-sm) * var(--tw-fs, 1)); /* production button = fontSize 0 (0.875rem) */ /* Real starting bg so transition has something to interpolate from — otherwise UA `buttonface` paints silver until hover. */ background-color: transparent; @@ -71,9 +71,12 @@ button i { font-size: 14px; line-height: 1; display: inline-flex; } /* Variants \u2014 token-driven. Foregrounds read from --*-foreground tokens in tokens.css so "what color on top" lives in one place. - CONTRAST: solid success/danger/info fills are tuned for APCA Lc 60+ - on white text (WCAG 2.x AA-large, not AA-normal). Keep them at md/lg - sizes — at xs/sm the label drops below the large-text threshold. */ + CONTRAST CONTRACT: solid success/danger/info fills are tuned for + APCA Lc 60+ on white text (WCAG 2.x AA-large, not AA-normal). Mark + specimens that use them with data-contrast-contract="apca" so an + automated WCAG audit can tell an intentional APCA target from an + accidental low-contrast bug. Never use these variants at xs/sm — the + label drops below the large-text threshold. */ .btn-primary { background: var(--button-primary-bg); border-color: var(--button-primary-bg); color: var(--button-primary-fg); } .btn-primary:hover:not(:disabled) { background: var(--button-primary-bg-hover); border-color: var(--button-primary-bg-hover); } .btn-success { background: var(--button-success-bg); border-color: var(--button-success-bg); color: var(--button-success-fg); } diff --git a/system/preview/accessibility.html b/system/preview/accessibility.html index 8205609..c260d1b 100644 --- a/system/preview/accessibility.html +++ b/system/preview/accessibility.html @@ -450,18 +450,18 @@

Accessibility & type audit

Success · green -
+
#fff on --green-solid
- WCAG 3.16:1 · APCA Lc 63.7 · green-9 · AA-large + WCAG 3.16:1 · APCA Lc 63.7 · green-9 (APCA contract)
Danger · red -
+
#fff on --red-solid
- WCAG 3.91:1 · APCA Lc 70.9 · red-9 · AA-large + WCAG 3.91:1 · APCA Lc 70.9 · red-9 (APCA contract)
@@ -525,27 +525,27 @@

Accessibility & type audit

--fs-sm - Secondary body · 13px regular - 13px · 400 - Metadata, file dates, sizes. AA at --text-secondary. + Caption · 0.875rem (12–13px) regular + 0.875rem · 400 + Metadata, file dates, sizes, button labels. AA at --text-secondary.
--fs-base - Body copy · 14px regular — app default - 14px · 400 - Default body weight. All body text uses this. + Body copy · 1rem (14–15px) regular — app default + 1rem · 400 + Default body weight. Equals the root size, matching the product's fontSize 1.
--fs-md - Subheading · 16px medium - 16px · 500 + Subheading · 1.125rem (~16px) medium + 1.125rem · 500 Subheadings, dense UI emphasis. Mobile body minimum is 16px (prevents iOS auto-zoom on inputs).
--fs-lg - Lead copy · 18px regular - 18px · 400 - Long-form lead. At this size, AA threshold drops from 4.5 → 3 (large-text rule). + Heading · 1.5rem (21–22.5px) medium + 1.5rem · 500 + Section headings. At this size, AA threshold drops from 4.5 → 3 (large-text rule).
TV minimum @@ -640,15 +640,15 @@

Accessibility & type audit

- box-shadow: var(--shadow-focus)
- 2px brand yellow + --text edge · clears 3:1 both modes + var(--shadow-focus)
+ soft 3px brand-yellow halo · 35% alpha
Link · focus 2px outline + 3px offset
diff --git a/system/preview/brand-empty-state.html b/system/preview/brand-empty-state.html index d3f1bd8..1f8232f 100644 --- a/system/preview/brand-empty-state.html +++ b/system/preview/brand-empty-state.html @@ -183,7 +183,7 @@
__o-o__

No watched RSS feeds whatsoever.

-

Paste an RSS feed URL and we'll watch it for new files. See how it works.

+

Paste an RSS feed URL and we'll watch it for new files. See how it works.

@@ -196,7 +196,7 @@

No watched RSS feeds whatsoever.

( ⌐■_■)

Nothing in the queue.

-

Drop a magnet link, paste a URL, or install the browser extension — put.io handles the rest.

+

Drop a magnet link, paste a URL, or install the browser extension — put.io handles the rest.

@@ -226,7 +226,7 @@

Empty folder.

¯\_(ツ)_/¯

Nothing matches.

-

No files, folders, or transfers named cassini ring scans. Try fewer words, or search the whole account.

+

No files, folders, or transfers named cassini ring scans. Try fewer words, or search the whole account.

@@ -265,7 +265,7 @@

Trash is empty.

>_

No activity in the last 30 days.

-

Completed transfers, shares, and renames show up here for 90 days. Adjust retention.

+

Completed transfers, shares, and renames show up here for 90 days. Adjust retention.

@@ -278,7 +278,7 @@

No activity in the last 30 days.

┌( ಠ_ಠ)┘

No TVs paired.

-

Install put.io on Apple TV, Android TV, or Fire TV, then enter the code at put.io/pair.

+

Install put.io on Apple TV, Android TV, or Fire TV, then enter the code at put.io/pair.

put.io/pair · code: — — — —
@@ -292,7 +292,7 @@

No TVs paired.

[— — — —]

No OAuth apps yet.

-

Register an app to get a client ID and secret. Full docs at api.put.io.

+

Register an app to get a client ID and secret. Full docs at api.put.io.

@@ -321,7 +321,7 @@

All caught up.

!!

Storage is full.

-

You've hit your 1 TB plan limit. Delete something, or upgrade to 10 TB — new transfers resume automatically.

+

You've hit your 1 TB plan limit. Delete something, or upgrade to 10 TB — new transfers resume automatically.

diff --git a/system/preview/components-breadcrumbs.html b/system/preview/components-breadcrumbs.html index 1356001..2467687 100644 --- a/system/preview/components-breadcrumbs.html +++ b/system/preview/components-breadcrumbs.html @@ -141,11 +141,11 @@
- Files + Files - Films + Films - 2024 + 2024 Q1
@@ -153,11 +153,11 @@
- Files + Files - 2024 + 2024 Q1
diff --git a/system/preview/components-buttons.html b/system/preview/components-buttons.html index bd15c5a..cd693d7 100644 --- a/system/preview/components-buttons.html +++ b/system/preview/components-buttons.html @@ -169,7 +169,7 @@
- Accessibility floor. White-on-green and white-on-red sit at 3.2–3.9 contrast — they only pass WCAG as large text (≥14px medium uppercase). Never use btn-sm or btn-xs for btn-success, btn-danger, or btn-info. For tight rows use btn-primary (yellow on black = 14:1 AAA at any size) or btn-default. See the Accessibility audit preview. + Accessibility floor. White-on-green and white-on-red sit at 3.2–3.9 contrast — an accepted APCA Lc 60+ contract (medium-weight uppercase labels, tagged data-contrast-contract="apca"), not a WCAG 4.5:1 pass. Never use btn-sm or btn-xs for btn-success, btn-danger, or btn-info. For tight rows use btn-primary (yellow on black = 14:1 AAA at any size) or btn-default. See the Accessibility audit preview.
diff --git a/system/preview/components-folder-picker.html b/system/preview/components-folder-picker.html new file mode 100644 index 0000000..61b295e --- /dev/null +++ b/system/preview/components-folder-picker.html @@ -0,0 +1,170 @@ + + + +put.io · components · folder picker + + + + + + + + + +
+ +
+ 01 + Folder picker — move destination + FolderPicker +
+
+ The move/copy destination dialog. Standard modal recipe with data-slot parts; the tree body drops to --field-bg-style --app-bg so rows read as content, not chrome. Rows are 32px, indent is 20px per level, and folder icons are always --yellow-solid. The selected destination holds --list-item-bg-active plus a trailing check; the items’ current location is visible but disabled (aria-disabled, “current” tag) — hiding it would make the tree look wrong. Keyboard: ↑↓ move, expand, collapse, confirms. +
+ + + +
+ diff --git a/system/preview/components-form-fields.html b/system/preview/components-form-fields.html index cd68781..3a17fcc 100644 --- a/system/preview/components-form-fields.html +++ b/system/preview/components-form-fields.html @@ -637,9 +637,9 @@
Couldn't save your changes — 3 fields need attention.
diff --git a/system/preview/components-form-layouts.html b/system/preview/components-form-layouts.html index 012bdf1..e8e9834 100644 --- a/system/preview/components-form-layouts.html +++ b/system/preview/components-form-layouts.html @@ -569,7 +569,7 @@
Pushover key
-
Get a push notification when a transfer completes. Find your key at pushover.net.
+
Get a push notification when a transfer completes. Find your key at pushover.net.
@@ -903,7 +903,7 @@

Login

@@ -914,7 +914,7 @@

Login

- Don't have an account? Sign up + Don't have an account? Sign up
@@ -953,7 +953,7 @@

Two-factor authentication

- Lost access to your app? Use a recovery code + Lost access to your app? Use a recovery code
@@ -982,21 +982,21 @@

Welcome back

- Don't have an account? Sign up + Don't have an account? Sign up
@@ -1037,7 +1037,7 @@

Two-factor authentication

- Lost access to your app? Use a recovery code + Lost access to your app? Use a recovery code
@@ -1068,7 +1068,7 @@

Check your inbox

- Wrong address? Go back + Wrong address? Go back
@@ -1103,11 +1103,11 @@

Create your account

- By signing up you agree to our Terms and Privacy Policy. + By signing up you agree to our Terms and Privacy Policy.
- Already have an account? Log in + Already have an account? Log in
@@ -1138,7 +1138,7 @@

Reset your password

- Remembered it? Back to login + Remembered it? Back to login
@@ -1221,7 +1221,7 @@

Two-factor authentication

- Wrong account? Sign out + Wrong account? Sign out
diff --git a/system/preview/components-menu.html b/system/preview/components-menu.html new file mode 100644 index 0000000..dc89c6f --- /dev/null +++ b/system/preview/components-menu.html @@ -0,0 +1,189 @@ + + + +put.io · components · menu + + + + + + + + + +
+ +
+ 01 + Dropdown menu + Menu +
+
+ One surface recipe for every floating menu: --component-bg on --line, --shadow-md, --radius, 4px inner padding, stacked at --z-dropdown. Items are 32px with a 16px leading icon; shortcuts sit right in --font-ui-mono. Hover uses --component-bg-hover — never yellow. The controlling primitive sets data-state="open|closed". +
+ +
+
+ + +
+ +
+
Disabled items stay visible (aria-disabled="true", step-11 text, no hover) — hiding them makes menus feel unstable between files. The destructive item is red text at rest and gains a --red-component-bg surface on hover; it never renders as a solid red fill inside a menu.
+
+
+ +
+ 02 + Context menu — file row +
+
+ Right-click / long-press on a file row opens the same menu at the pointer. The originating row holds --list-item-bg-active while the menu is open so the target never gets lost. Same items, same order as the row’s dropdown — the two must not drift. +
+ +
+
+ + Sunset.Timelapse.4096x2160.ProRes.mov +
+ +
+ +
+ 03 + Submenu +
+
+ One level deep, maximum. The parent item shows a trailing caret and holds its hover surface (data-state="active") while the submenu is open. Submenus inherit the exact same surface recipe — no extra elevation step. +
+ + + +
+ diff --git a/system/preview/components-notification.html b/system/preview/components-notification.html index 91120a2..b69328f 100644 --- a/system/preview/components-notification.html +++ b/system/preview/components-notification.html @@ -291,11 +291,11 @@ diff --git a/system/preview/components-player-web.html b/system/preview/components-player-web.html new file mode 100644 index 0000000..0df2087 --- /dev/null +++ b/system/preview/components-player-web.html @@ -0,0 +1,215 @@ + + + +put.io · web · player + + + + + + + + + +
+ +
+ 01 + Player chrome + WebPlayer +
+
+ Chrome sits on a bottom scrim over the video, so it uses fixed on-video whites (--pl-*, the web sibling of TV’s glass tiers) — played progress and the knob are always --yellow-solid; buffered is a brighter white ahead of it. Time is --font-ui-mono tabular. The title shows the raw filename — the player never invents a poster or a parsed title. Chrome auto-hides after 3s of pointer idle; any keypress brings it back. +
+ +
+
Concert.Rehearsal.Cam2.1080p.mp4
+
+
+
+
+
+ + + + 1:16:23 / 2:46:05 + +
+ +
+
+ + + + +
+
+
+ +
+ 02 + States — subtitle menu · buffering +
+
+ The settings/subtitle popover is the standard menu recipe (see Menu) anchored above the control bar — theme surface, not on-video glass. The active track carries a yellow check. Buffering replaces the center of the frame with a single spinner (yellow arc on 20% white); the chrome stays put. Never block the scrubber while buffering. +
+ +
+
+
Concert.Rehearsal.Cam2.1080p.mp4
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ diff --git a/system/preview/components-segmented-control.html b/system/preview/components-segmented-control.html new file mode 100644 index 0000000..6292383 --- /dev/null +++ b/system/preview/components-segmented-control.html @@ -0,0 +1,124 @@ + + + +put.io · components · segmented control + + + + + + + + + +
+ +
+ 01 + Segmented control + SegmentedControl +
+
+ Switches a parameter of the current view — list vs grid, sort direction, plan interval. If it swaps the content region itself, use Tabs instead. Looks like Tabs but consumes its own surface tokens (--segmented-control-bg / --segmented-control-bg-active) so the web app token mapping stays 1:1, and speaks role="radiogroup" + aria-checked, not tablist. +
+ +
+
+ + +
+
+ + +
+
+ + + +
+
+ +
+ 02 + Anatomy +
+
+ --segmented-control-bgtray fill — inset one step below the surface it sits on + --segmented-control-bg-activeselected thumb — raises back to the surface, plus --shadow-sm + --linetray outline (non-interactive chrome, step 6) + --radius tray corner; thumbs use --radius-sm + 30px / 26pxsegment heights, default and sm; icon-only segments need an aria-label +
+
+ Selection is surface + shadow, never yellow — yellow marks brand and primary actions, not which option happens to be on. 2–4 segments; past that, use a Menu or Select. +
+ +
+ diff --git a/system/preview/components-skeleton.html b/system/preview/components-skeleton.html new file mode 100644 index 0000000..325f004 --- /dev/null +++ b/system/preview/components-skeleton.html @@ -0,0 +1,122 @@ + + + +put.io · components · skeleton + + + + + + + + + +
+ +
+ 01 + File-list skeleton + Skeleton +
+
+ Skeletons stand in for data with a known layout — the file list, the transfers table. The skeleton mirrors the real row grid (44px rows, same columns) so content doesn’t jump when it lands. Base is --component-bg; the shimmer sweep uses --component-bg-hover and only runs under prefers-reduced-motion: no-preference — the static two-tone is the reduced-motion fallback, not an afterthought. Vary name-bar widths so the list doesn’t look stamped. +
+ +
+
+
+
+
+
+
+ +
+ 02 + Which loading state, when +
+ +
+ skeleton + Layout known, data pending. Lists, tables, the storage card. Show immediately — no delay-then-skeleton double flash. + spinner + Layout unknown or action-scoped. Button loading (.is-loading), modal submit, player buffering. Sizes 16 / 20 / 28 — see Tasks. + progress bar + Measurable work. Transfers, zips, uploads. Yellow fill; never a spinner when you know the percent. + empty state + Loaded, and there is nothing. Never render an empty state while loading — that reads as data loss. See Brand · Empty state. +
+ +
+ Skeleton no more than one screen’s worth of rows; below the fold just loads. If data usually arrives <150ms, skip the skeleton entirely — a flash of placeholder is worse than nothing. +
+ +
+ diff --git a/system/preview/components-table.html b/system/preview/components-table.html new file mode 100644 index 0000000..2ccbf6e --- /dev/null +++ b/system/preview/components-table.html @@ -0,0 +1,193 @@ + + + +put.io · components · data table + + + + + + + + + +
+ +
+ 01 + Files table + DataTable +
+
+ Rows divide with --list-item-borderno zebra stripes, no per-row cards. Hover is --list-item-bg-hover, selection --list-item-bg-active. Headers are 11px uppercase --font-ui-mono; the sorted column carries aria-sort and a caret. Size and date columns right-align in --font-ui-mono with tabular numerals. Filenames render raw, in --font-mono, ellipsized — never parsed. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Actions
incoming18.4 GB2026-07-01 23:41
Glacier.Hike.2024.2160p.HDR.x265-GROUP.mkv12.1 GB2026-06-29 08:12
field-recordings-batch-07.flac840 MB2026-06-27 19:03
backup-photos-2025.zip64.2 GB2026-06-25 11:30
notes.txt2 KB2026-06-20 14:55
+ +
+ 02 + Selection toolbar +
+
+ When ≥1 row is selected, the toolbar appears above the table (sticky, --z-sticky). Actions are quiet btn-default — the destructive one keeps its red only in the confirm step, not in the bar. Esc or the × clears selection. +
+ + + +
+ + diff --git a/system/preview/components-task-progress.html b/system/preview/components-task-progress.html new file mode 100644 index 0000000..b475f5d --- /dev/null +++ b/system/preview/components-task-progress.html @@ -0,0 +1,178 @@ + + + +put.io · components · tasks + + + + + + + + + +
+ +
+ 01 + Task list + Tasks +
+
+ Server-side jobs the user waits on — zip creation, remote upload, bulk move. States ride data-state="pending | loading | active | completed | failed". Determinate progress is a 4px --yellow-solid fill on --component-bg-active; unknown duration uses the sliding indeterminate bar (paused under reduced motion). Completed flips the icon tile green; failed goes red with an inline reason and a quiet Retry — never a red-filled row. +
+ +
+
+
+
Creating zip · backup-photos-2025.zip
+
+
64% · 41.1 of 64.2 GB
+
+
+
+
Uploading to remote storage
+
+
preparing…
+
+
+
+
Moving 214 items to /archive
+
+
queued
+
+
+
+
Zip ready · concert-recordings.zip
+
+
+
+
+
Remote upload failed
+
Destination out of space (needs 12.1 GB, 3.4 GB free)
+
+
+
+ +
+ 02 + Progress primitives +
+
+ Three bar heights: 2px hairline (inside rows, under toolbars), 4px default (tasks, transfers), 8px emphasis (storage bar). Fill is --yellow-solid on --component-bg-active — green/red only as terminal states, never as in-flight colors. Spinners are a yellow arc on --component-bg-active at 16 / 20 / 28px. +
+ +
+ 2px hairline +
+ 4px default +
+ 8px emphasis +
+ spinners 16 · 20 · 28 + + + + + +
+ +
+ diff --git a/system/preview/tv-shell.css b/system/preview/tv-shell.css index f2c329f..d1a12db 100644 --- a/system/preview/tv-shell.css +++ b/system/preview/tv-shell.css @@ -1,7 +1,7 @@ /* ============================================================ put.io — tv-shell.css TV / 10ft surfaces (tvOS Liquid Glass shell). - Globally-scoped classes for .tv artboards (1280×720). + All component classes are scoped under .tv — the file is safe to load alongside web component CSS without bleeding into it. Load AFTER tokens.css in tv-*.html preview files: @@ -54,7 +54,7 @@ } /* TV pages run dark by default and on a dark canvas. */ -html, body { margin: 0; padding: 0; background: #1a1a1a; } +html:has(.tv), body:has(.tv) { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* Artboard host — every TV screen lives in one of these */ @@ -94,7 +94,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* Top-nav specular sheen */ -.topnav-tabs::after { +.tv .topnav-tabs::after { content: ""; position: absolute; inset: 0; @@ -109,7 +109,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } } /* Action pills: stronger material when not focused (focus state unchanged) */ -.btn:not(.focused) { +.tv .btn:not(.focused) { background: rgba(255,255,255,0.12); backdrop-filter: blur(30px) saturate(180%); -webkit-backdrop-filter: blur(30px) saturate(180%); @@ -130,7 +130,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* Specular highlight overlay for tvOS look — apply to .glass via .with-spec */ -.with-spec { +.tv .with-spec { position: relative; overflow: hidden; } @@ -138,7 +138,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* Edge fade on scrollable content — apple-style soft fade at top/bottom when content overflows. Apply .with-fade to any vertically scrollable container; mask-image fades 24px at each edge. */ -.with-fade { +.tv .with-fade { -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 24px, @@ -150,7 +150,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } #000 calc(100% - 24px), transparent 100%); } -.with-spec::after { +.tv .with-spec::after { content: ""; position: absolute; inset: 0; @@ -166,7 +166,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* Focused row — default "Subtle" variant: soft glass pill, yellow halo. Scoped to .row so it doesn't bleed into .tab / .btn which override loudly. */ -.row.focused { +.tv .row.focused { background: rgba(255,255,255,0.10); border-color: rgba(255,255,255,0.10); box-shadow: @@ -180,7 +180,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* TOP NAV — Apple TV-style floating glass tab bar */ /* ---------------------------------------------------------------- */ -.topnav { +.tv .topnav { position: relative; z-index: 5; margin: 22px 22px 0; @@ -189,14 +189,14 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } align-items: center; flex: 0 0 auto; } -.topnav-logo { +.tv .topnav-logo { display: flex; align-items: center; padding: 0 16px; justify-self: start; opacity: 0.95; } -.topnav-tabs { +.tv .topnav-tabs { justify-self: center; display: flex; align-items: center; @@ -214,7 +214,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } position: relative; overflow: hidden; } -.tab { +.tv .tab { display: flex; align-items: center; gap: 9px; @@ -227,16 +227,16 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } white-space: nowrap; position: relative; } -.tab .ico { color: currentColor; } -.tab.active { +.tv .tab .ico { color: currentColor; } +.tv .tab.active { color: var(--text-1); background: rgba(255,255,255,0.10); } -.tab.active .ico { color: var(--putio-yellow); } +.tv .tab.active .ico { color: var(--putio-yellow); } /* Focused tab — same loud tvOS inversion as action pills. Small chrome can SHOUT; this beats the active-tab styling. */ -.tab.focused { +.tv .tab.focused { background: rgba(255,255,255,0.96); color: #0a0a0a; box-shadow: @@ -244,9 +244,9 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } 0 -1px 0 rgba(0,0,0,0.05) inset, 0 18px 38px rgba(0,0,0,0.55); } -.tab.focused .ico { color: #0a0a0a; } -.tab.focused.active::after { display: none; } -.topnav-right { +.tv .tab.focused .ico { color: #0a0a0a; } +.tv .tab.focused.active::after { display: none; } +.tv .topnav-right { justify-self: end; display: flex; gap: 6px; @@ -261,14 +261,14 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* CONTENT REGION */ /* ---------------------------------------------------------------- */ -.scr { +.tv .scr { padding: 52px 60px 40px; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; } -.scr-head { +.tv .scr-head { display: flex; align-items: center; gap: 16px; @@ -276,15 +276,15 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } border-bottom: 1px solid rgba(255,255,255,0.06); margin-bottom: 18px; } -.scr-title { +.tv .scr-title { font-size: 38px; font-weight: 700; letter-spacing: -0.02em; line-height: 1; margin: 0; } -.scr-spacer { flex: 1; } -.scr-actions { +.tv .scr-spacer { flex: 1; } +.tv .scr-actions { display: flex; gap: 8px; } @@ -293,7 +293,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* PILL BUTTONS */ /* ---------------------------------------------------------------- */ /* ---------------------------------------------------------------- */ -.btn { +.tv .btn { display: inline-flex; align-items: center; gap: 8px; @@ -343,14 +343,14 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } 0 -1px 0 rgba(0,0,0,0.2) inset, 0 18px 38px rgba(220,60,60,0.45); } -.btn.btn-danger.focused svg { color: #fff; } -.btn.btn-danger:not(.focused) { color: var(--red-solid); } -.btn.focused svg { color: #0a0a0a; } -.btn-lg { +.tv .btn.btn-danger.focused svg { color: #fff; } +.tv .btn.btn-danger:not(.focused) { color: var(--red-solid); } +.tv .btn.focused svg { color: #0a0a0a; } +.tv .btn-lg { padding: 14px 26px; font-size: 16px; } -.btn-ghost { +.tv .btn-ghost { background: transparent; border-color: transparent; box-shadow: none; @@ -359,7 +359,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* LIST ROWS — files, settings, history */ /* ---------------------------------------------------------------- */ -.row { +.tv .row { display: flex; align-items: center; gap: 18px; @@ -370,8 +370,8 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } transition: transform 220ms cubic-bezier(.22,1,.36,1), background 200ms; position: relative; } -.row + .row { margin-top: 2px; } -.row .ico { +.tv .row + .row { margin-top: 2px; } +.tv .row .ico { flex: 0 0 auto; width: 30px; height: 30px; @@ -380,21 +380,21 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } align-items: center; justify-content: center; } -.row .body { flex: 1 1 auto; min-width: 0; } -.row .title { +.tv .row .body { flex: 1 1 auto; min-width: 0; } +.tv .row .title { font-size: 22px; font-weight: 500; letter-spacing: -0.005em; line-height: 1.2; } -.row .sub { +.tv .row .sub { margin-top: 2px; font-size: 16px; color: var(--text-2); font-family: var(--font-mono); font-variant-numeric: tabular-nums; } -.row .trailing { +.tv .row .trailing { margin-left: auto; color: var(--text-3); display: flex; @@ -405,7 +405,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* SECTION HEADER */ /* ---------------------------------------------------------------- */ -.section-h { +.tv .section-h { font-size: 14px; font-weight: 500; letter-spacing: 0.08em; @@ -418,7 +418,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } /* ---------------------------------------------------------------- */ /* TOGGLE */ /* ---------------------------------------------------------------- */ -.toggle { +.tv .toggle { width: 52px; height: 30px; border-radius: 999px; @@ -427,7 +427,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } transition: background 200ms; flex: 0 0 auto; } -.toggle::after { +.tv .toggle::after { content: ""; position: absolute; top: 3px; left: 3px; @@ -437,27 +437,27 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } transition: transform 200ms; box-shadow: 0 2px 4px rgba(0,0,0,0.4); } -.toggle.on { background: var(--green-solid); } -.toggle.on::after { transform: translateX(22px); } +.tv .toggle.on { background: var(--green-solid); } +.tv .toggle.on::after { transform: translateX(22px); } /* ---------------------------------------------------------------- */ /* MISC */ /* ---------------------------------------------------------------- */ -.divider { +.tv .divider { height: 1px; background: rgba(255,255,255,0.06); margin: 12px 0; } -.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; } -.chev { +.tv .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; } +.tv .chev { width: 18px; height: 18px; color: var(--text-3); } -.eye { color: var(--text-2); } +.tv .eye { color: var(--text-2); } /* progress bar (storage / playback) */ -.bar { +.tv .bar { width: 100%; height: 4px; border-radius: 999px; @@ -465,7 +465,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } overflow: hidden; position: relative; } -.bar > i { +.tv .bar > i { position: absolute; inset: 0 auto 0 0; background: var(--putio-yellow); @@ -473,7 +473,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } } /* put.io retro logo block */ -.brand { +.tv .brand { display: inline-flex; align-items: center; gap: 6px; @@ -484,14 +484,14 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } line-height: 1; color: #fff; } -.brand .dotmark { +.tv .brand .dotmark { width: 20px; height: 20px; border-radius: 50%; background: var(--putio-yellow); position: relative; display: inline-block; } -.brand .dotmark::before { +.tv .brand .dotmark::before { content: ""; position: absolute; inset: 4px; @@ -500,14 +500,14 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } } /* For the auth ASCII figure */ -.ascii { +.tv .ascii { font-family: var(--font-mono); white-space: pre; letter-spacing: -0.02em; } /* Activation code tiles */ -.code-tile { +.tv .code-tile { width: 64px; height: 78px; background: #f3f3f0; @@ -525,7 +525,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } } /* Video player chrome */ -.player-chrome { +.tv .player-chrome { position: absolute; inset: auto 0 0 0; padding: 0 40px 28px; @@ -536,19 +536,19 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } rgba(0,0,0,0.7) 0%, rgba(0,0,0,0) 100%); } -.player-scrub { +.tv .player-scrub { height: 6px; background: rgba(255,255,255,0.2); border-radius: 999px; position: relative; } -.player-scrub > i { +.tv .player-scrub > i { position: absolute; inset: 0 auto 0 0; background: rgba(255,255,255,0.95); border-radius: 999px; } -.player-scrub > b { +.tv .player-scrub > b { position: absolute; width: 14px; height: 14px; border-radius: 50%; @@ -558,7 +558,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } } /* Glass icon button (circular) */ -.glass-icon { +.tv .glass-icon { width: 56px; height: 56px; border-radius: 50%; @@ -573,14 +573,14 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } 0 1px 0 rgba(255,255,255,0.14) inset; color: #fff; } -.glass-icon.solid { +.tv .glass-icon.solid { background: rgba(255,255,255,0.95); color: #0a0a0a; border-color: rgba(255,255,255,0.95); } /* Modal sheet — proxy picker, subtitles, info */ -.sheet { +.tv .sheet { position: absolute; inset: 0; display: flex; @@ -588,7 +588,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } justify-content: center; z-index: 3; } -.sheet::before { +.tv .sheet::before { content: ""; position: absolute; inset: 0; @@ -596,7 +596,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } backdrop-filter: blur(20px) saturate(140%); -webkit-backdrop-filter: blur(20px) saturate(140%); } -.sheet-body { +.tv .sheet-body { position: relative; background: rgba(40,40,44,0.66); backdrop-filter: blur(60px) saturate(200%); @@ -615,7 +615,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } flex-direction: column; padding: 22px; } -.sheet-h { +.tv .sheet-h { padding: 4px 16px 16px; display: flex; align-items: baseline; @@ -623,31 +623,31 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } border-bottom: 1px solid rgba(255,255,255,0.07); margin-bottom: 8px; } -.sheet-h .sheet-title { +.tv .sheet-h .sheet-title { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; color: var(--text-1); } -.sheet-h .sheet-cap { +.tv .sheet-h .sheet-cap { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-3); } -.sheet-list { +.tv .sheet-list { overflow: auto; padding: 4px 6px; } -.sheet-list .row { +.tv .sheet-list .row { padding: 12px 16px; gap: 14px; border-radius: 14px; } -.sheet-list .row .title { font-size: 19px; } +.tv .sheet-list .row .title { font-size: 19px; } /* Radio circle */ -.radio { +.tv .radio { width: 22px; height: 22px; border-radius: 50%; @@ -658,11 +658,11 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } justify-content: center; background: rgba(0,0,0,0.18); } -.radio.on { +.tv .radio.on { border-color: var(--putio-yellow); background: rgba(253,206,69,0.12); } -.radio.on::after { +.tv .radio.on::after { content: ""; width: 10px; height: 10px; background: var(--putio-yellow); @@ -670,7 +670,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } } /* Keyboard (search) */ -.kbd { +.tv .kbd { display: grid; grid-template-columns: repeat(13, 1fr); gap: 6px; @@ -681,7 +681,7 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } -webkit-backdrop-filter: blur(28px) saturate(160%); border: 1px solid var(--glass-border); } -.key { +.tv .key { aspect-ratio: 1; display: flex; align-items: center; @@ -691,10 +691,10 @@ html, body { margin: 0; padding: 0; background: #1a1a1a; } background: rgba(255,255,255,0.05); color: var(--text-1); } -.key.focused { +.tv .key.focused { background: rgba(255,255,255,0.92); color: #0a0a0a; } /* Visible-only-for-tweak helper */ -.tv .hide { display: none; } +.tv .hide.hide { display: none; } diff --git a/system/preview/web-shell.html b/system/preview/web-shell.html index 68f7f21..1382249 100644 --- a/system/preview/web-shell.html +++ b/system/preview/web-shell.html @@ -1,3 +1,4 @@ +