@putdotio/design 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md CHANGED
@@ -115,7 +115,8 @@ Default radius `6px`; `4px` for tight controls, `8px` or `10px` for panels, pill
115
115
  - **Primary:** `--button-primary-bg`, hover `--button-primary-bg-hover`, label `--button-primary-fg`. At most one per action group.
116
116
  - **Secondary:** `--button-default-bg` plus a `--border` border; hover steps both (`--button-default-bg-hover`, `--border-hover`).
117
117
  - **Ghost:** text only, `--button-ghost-fg`; hover adds `--button-ghost-bg-hover` and lifts the label to `--button-ghost-fg-hover`. Use it for anything quieter than secondary: header `Sign in`, toolbar actions, `Cancel` beside a confirm, icon-only buttons.
118
- - **Disabled:** colour drops to `--solid` at full opacity.
118
+ - **Disabled:** colour drops to `--solid` at full opacity: filled variants take a `--solid` fill with `--solid-foreground` labels; quiet ones take `--solid` text. A finished action stays filled with `data-state="success"` on `.btn-success`.
119
+ - **Focus:** `--shadow-focus`, a 2px `--bg` gap then a 2px `--solid-hover` ring, at least 3:1 against the page and panels in both modes.
119
120
  - A bordered button always fills (secondary). Border-only buttons read smaller beside solid fills and vanish on dark.
120
121
  - Four sizes share one box per size across tiers: default 36px, `md` 32px, `sm` 28px, `xs` 24px. Read `--button-height*`, `--button-padding-x*`, `--button-label-size*`, `--button-tracking*`, `--button-icon-size`, `--button-gap`.
121
122
  - A dark bar on a light page is a `.dark`-scoped region.
@@ -128,15 +129,26 @@ Default radius `6px`; `4px` for tight controls, `8px` or `10px` for panels, pill
128
129
  - Fields read `--field-bg`, `--field-border`, and `--field-ring` (focus). `aria-invalid="true"` turns border and text red; the fill stays.
129
130
  - Raised panels read `--panel-*`.
130
131
  - Feedback uses `.form-callout`: `--alert-*` colours, `data-state` `info`, `success`, or `error`; `.inline` drops surface and border. It holds a message only. `.form-group` (`6px` gap) spaces a control and its feedback.
132
+ - Every field and form error eases in through `.form-reveal`: `grid-template-rows` `0fr` to `1fr` plus opacity, over `--dur-reveal` (`180ms`) with `--ease-out`, so whatever sits below slides instead of jumping. A field error sits under its field inside `.form-group`, where the row absorbs the group gap; a form error sits above the submit button. `data-state="open"` shows it. Keep the `role="alert"` message mounted and rendered; empty it once a closed row has collapsed. An `aria-invalid` field or code fades its border red over the same `--dur-reveal`. Under `prefers-reduced-motion` nothing animates.
131
133
 
132
134
  ### Auth
133
135
 
134
- - A fluid column up to `340px`, the `36px` field recipe, a `30.33px` wordmark box (26px ink × 7/6), centred `19px`/500 headings, and a centred two-line prompt footer. auth.put.io's set-password screen ships the action alone: drift.
135
- - Copy: “Sign in”, “Sign up”, and “That username or password doesn't look right.” followed by a “Reset your password” link.
136
- - References: `web-s06-auth`, `web-p00c-auth-signin`, `web-p00d-auth-signup`, `web-p00e-auth-2fa`.
137
- - One-time codes are one `.otp-input` (`inputmode="numeric"`, `autocomplete="one-time-code"`, `maxlength="6"`, `aria-invalid`) shown through two `.otp-group`s, one `.otp-separator`, and six `.otp-slot`s; script syncs slot text and `filled`/`active`, and maps a clicked slot to the input selection.
138
- - Editing an invalid code clears the error; retry waits for six digits. `data-state="verifying"` disables the input, keeps the digits, and announces through a polite live region. `data-state="success"` keeps the digits read-only, announces continuation in a `role="status"` callout, and shows no further action; the consumer owns navigation. auth.put.io disables the button, relabels it “Verified”, and announces that through a visually hidden status region.
139
- - No recovery-code action until a consumer owns the route. Groups own the responsive width, so the control fits narrow cards without scrolling.
136
+ - Sign in, sign up, password reset, and two-factor pages have no card or panel: the form sits on `--bg`. Recipe: `.auth-page`, `.auth-header`, `.auth-main`, `.auth-column`, `.auth-title`, `.auth-links`, `.auth-footer`.
137
+ - The logo sits top-left in the header, in a `30.33px` box (26px ink × 7/6). The top right stays empty by default.
138
+ - One left-aligned column, up to `340px`, is centred horizontally and vertically.
139
+ - A quiet centred footer links Terms of Service, Privacy Policy, and Help in muted `--fs-sm`.
140
+ - One title per screen: `22px`, medium, `-0.01em` tracking. No subtitles, taglines, or marketing lines; copy stays short.
141
+ - Fields are filled: the page points `--field-bg` at `--bg-secondary` and `--field-border` at `--line`. Fields and buttons in the column are `44px`.
142
+ - Labels are small, muted, and above the field.
143
+ - The primary button is full-width yellow. Secondary actions sit under the form as muted text links: “Forgot password?”, “New to put.io? Sign up”, “Wrong account? Sign out”.
144
+ - Tab order runs fields, submit, then secondary links; DOM order follows visual order, with no positive `tabindex`. Two-factor runs the code, Verify, then its links. The reveal-password toggle takes `tabindex="-1"` and keeps its accessible name: keyboard-only users can't toggle visibility, screen-reader users still can in browse mode. No link leaves the tab order.
145
+ - Form errors: one message in a `.form-reveal` row right above the submit button, in red `--fs-base` text. Invalid fields turn red. Auth forms never use the boxed `.form-callout`.
146
+ - Copy: “Sign in”, “Sign up”, and “That username or password doesn't look right.”
147
+ - auth.put.io still ships the older card layout: drift.
148
+ - References: `web-s06-auth`, `web-p00c-auth-signin`, `web-p00d-auth-signup`, `web-p00e-auth-2fa`. Outside references: the TIDAL, Stripe Link, Cursor, Linear, and Vercel sign-in pages.
149
+ - One-time codes are one `.otp-input` (`inputmode="numeric"`, `autocomplete="one-time-code"`, `maxlength="6"`, `aria-invalid`) shown through two `.otp-group`s, one `.otp-separator`, and six `.otp-slot`s; script syncs slot text and `filled`/`active`, and maps a clicked slot to the input selection. The slots stretch the full column with mono digits. The label reads “Code from your authenticator app”, with no hint line.
150
+ - Editing an invalid code closes the error; retry waits for six digits. `data-state="verifying"` disables the input, keeps the digits, and announces through a polite live region. `data-state="success"` keeps the digits read-only, announces continuation in a `role="status"` region, and shows no further action; the consumer owns navigation. The submit turns a disabled `.btn-success` with `data-state="success"`, relabelled “Verified”, announced through a visually hidden status region.
151
+ - “Use a recovery code” appears only once a consumer owns the route. Groups own the responsive width, so the control fits narrow columns without scrolling.
140
152
 
141
153
  ### TV
142
154
 
@@ -172,6 +172,7 @@
172
172
  --yellow-text-secondary: hsl(46, 100%, 27%);
173
173
  --yellow-text: hsl(43, 32%, 21%);
174
174
  --lime-3: hsl(85, 76.0%, 92.3%);
175
+ --shadow-focus: 0 0 0 2px hsl(0, 0%, 99.0%), 0 0 0 4px hsl(0, 0%, 52.3%);
175
176
  --html-bg: hsl(0, 0%, 97.3%);
176
177
  --app-bg: hsl(0, 0%, 100%);
177
178
  --nav-bg: hsl(0, 0%, 100%);
@@ -256,7 +257,6 @@
256
257
  --shadow-md: 0 4px 12px hsla(0, 0%, 0%, 0.08);
257
258
  --shadow-lg: 0 16px 40px hsla(0, 0%, 0%, 0.12);
258
259
  --shadow-focus-color: hsla(47, 100%, 65%, 0.35);
259
- --shadow-focus: 0 0 0 3px hsla(47, 100%, 65%, 0.35);
260
260
  --font-sans: "GT America", sans-serif;
261
261
  --font-display: "GT America", sans-serif;
262
262
  --font-mono: "Berkeley Mono", monospace;
@@ -301,6 +301,7 @@
301
301
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
302
302
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
303
303
  --dur-fast: 120ms;
304
+ --dur-reveal: 180ms;
304
305
  --dur-base: 200ms;
305
306
  --dur-slow: 320ms;
306
307
  --z-dropdown: 1000;
@@ -479,6 +480,7 @@
479
480
  --yellow-text-secondary: hsl(45, 100%, 64%);
480
481
  --yellow-text: hsl(46, 89%, 86%);
481
482
  --lime-3: hsl(85, 45%, 12%);
483
+ --shadow-focus: 0 0 0 2px hsl(0, 0%, 8.5%), 0 0 0 4px hsl(0, 0%, 49.4%);
482
484
  --html-bg: hsl(0, 0%, 0%);
483
485
  --app-bg: hsl(0, 0%, 8.5%);
484
486
  --nav-bg: hsl(0, 0%, 8.5%);
@@ -1070,6 +1070,10 @@
1070
1070
  "$type": "duration",
1071
1071
  "$value": "120ms"
1072
1072
  },
1073
+ "reveal": {
1074
+ "$type": "duration",
1075
+ "$value": "180ms"
1076
+ },
1073
1077
  "base": {
1074
1078
  "$type": "duration",
1075
1079
  "$value": "200ms"
package/dist/tokens.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Do not edit directly. Generated from tokens/**/*.tokens.json.
2
2
 
3
- 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.secondary.backgroundHover" | "component.button.secondary.backgroundHoverDark" | "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.button.ghost.background" | "component.button.ghost.foreground" | "component.button.ghost.foregroundDark" | "component.button.ghost.foregroundHover" | "component.button.ghost.foregroundHoverDark" | "component.button.ghost.backgroundHover" | "component.button.ghost.backgroundHoverDark" | "component.button.height" | "component.button.heightMd" | "component.button.heightSm" | "component.button.heightXs" | "component.button.paddingX" | "component.button.paddingXXs" | "component.button.labelSize" | "component.button.labelSizeXs" | "component.button.tracking" | "component.button.trackingMd" | "component.button.trackingSm" | "component.button.trackingXs" | "component.button.iconSize" | "component.button.gap" | "component.nav.height" | "component.nav.heightCompact" | "component.nav.ctaHeight" | "component.input.background" | "component.input.backgroundDark" | "component.input.border" | "component.input.borderDark" | "component.input.radius" | "component.input.borderHover" | "component.input.borderHoverDark" | "component.input.borderFocus" | "component.input.borderFocusDark" | "component.input.ring" | "component.input.ringDark" | "component.input.text" | "component.input.textDark" | "component.input.placeholder" | "component.input.placeholderDark" | "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.radius" | "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.activeBackground" | "component.fileRow.activeBackgroundDark" | "component.fileRow.border" | "component.fileRow.borderDark" | "component.fileRow.icon" | "component.notification.infoBackground" | "component.notification.infoBackgroundDark" | "component.notification.dangerBackground" | "component.notification.dangerBackgroundDark" | "component.notification.background" | "component.notification.backgroundDark" | "component.notification.border" | "component.notification.borderDark" | "component.notification.foreground" | "component.notification.foregroundDark" | "component.notification.icon" | "component.notification.iconDark" | "component.notification.successBackground" | "component.notification.successBackgroundDark" | "component.notification.successBorder" | "component.notification.successBorderDark" | "component.notification.successForeground" | "component.notification.successForegroundDark" | "component.notification.dangerBorder" | "component.notification.dangerBorderDark" | "component.notification.dangerForeground" | "component.notification.dangerForegroundDark" | "component.notification.badgeBackground" | "component.notification.badgeForeground" | "component.menu.background" | "component.menu.backgroundDark" | "component.menu.border" | "component.menu.borderDark" | "component.menu.radius" | "component.menu.shadow" | "component.menu.shadowDark" | "component.menu.itemBackgroundHover" | "component.menu.itemBackgroundHoverDark" | "component.menu.itemText" | "component.menu.itemTextDark" | "component.menu.itemIcon" | "component.menu.itemIconDark" | "component.menu.itemShortcut" | "component.menu.itemShortcutDark" | "component.menu.itemDangerText" | "component.menu.itemDangerTextDark" | "component.menu.itemDangerBackgroundHover" | "component.menu.itemDangerBackgroundHoverDark" | "component.menu.itemHeight" | "component.menu.separator" | "component.menu.separatorDark" | "component.menu.label" | "component.menu.labelDark" | "component.palette.background" | "component.palette.backgroundDark" | "component.palette.border" | "component.palette.borderDark" | "component.palette.shadow" | "component.palette.shadowDark" | "component.palette.scrim" | "component.palette.scrimDark" | "component.palette.width" | "component.palette.inputHeight" | "component.palette.itemHeight" | "component.palette.itemBackgroundActive" | "component.palette.itemBackgroundActiveDark" | "component.palette.groupLabel" | "component.palette.groupLabelDark" | "component.palette.itemMeta" | "component.palette.itemMetaDark" | "component.sheet.background" | "component.sheet.backgroundDark" | "component.sheet.border" | "component.sheet.borderDark" | "component.sheet.shadow" | "component.sheet.shadowDark" | "component.sheet.scrim" | "component.sheet.scrimDark" | "component.sheet.width" | "component.avatar.yellow" | "component.avatar.green" | "component.avatar.red" | "component.avatar.blue" | "component.avatar.purple" | "component.avatar.teal" | "component.avatar.orange" | "component.avatar.foreground" | "component.avatar.foregroundWarm" | "component.alert.radius" | "component.alert.infoBackground" | "component.alert.infoBackgroundDark" | "component.alert.infoBorder" | "component.alert.infoBorderDark" | "component.alert.infoText" | "component.alert.infoTextDark" | "component.alert.infoBody" | "component.alert.infoBodyDark" | "component.alert.infoIcon" | "component.alert.infoIconDark" | "component.alert.brandBackground" | "component.alert.brandBackgroundDark" | "component.alert.brandBorder" | "component.alert.brandBorderDark" | "component.alert.brandText" | "component.alert.brandTextDark" | "component.alert.brandBody" | "component.alert.brandBodyDark" | "component.alert.brandIcon" | "component.alert.brandIconDark" | "component.alert.successBackground" | "component.alert.successBackgroundDark" | "component.alert.successBorder" | "component.alert.successBorderDark" | "component.alert.successText" | "component.alert.successTextDark" | "component.alert.successBody" | "component.alert.successBodyDark" | "component.alert.successIcon" | "component.alert.successIconDark" | "component.alert.dangerBackground" | "component.alert.dangerBackgroundDark" | "component.alert.dangerBorder" | "component.alert.dangerBorderDark" | "component.alert.dangerText" | "component.alert.dangerTextDark" | "component.alert.dangerBody" | "component.alert.dangerBodyDark" | "component.alert.dangerIcon" | "component.alert.dangerIconDark" | "component.slider.track" | "component.slider.trackDark" | "component.slider.buffer" | "component.slider.bufferDark" | "component.slider.fill" | "component.slider.knob" | "component.slider.fillDisabled" | "component.slider.fillDisabledDark" | "component.slider.trackHeight" | "component.slider.knobSize" | "component.slider.hitHeight" | "component.pagination.itemSize" | "component.pagination.itemText" | "component.pagination.itemTextDark" | "component.pagination.itemTextActive" | "component.pagination.itemTextActiveDark" | "component.pagination.itemBackgroundHover" | "component.pagination.itemBackgroundHoverDark" | "component.pagination.itemBackgroundActive" | "component.pagination.itemBackgroundActiveDark" | "component.pagination.itemBorderActive" | "component.pagination.itemBorderActiveDark" | "context.tv.text.primary" | "context.tv.text.secondary" | "context.tv.text.tertiary" | "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.nightly.background" | "context.tv.channelArt.nightly.accent" | "context.tv.channelArt.nightly.star" | "context.tv.channelArt.nightly.moon" | "context.tv.channelArt.nightly.moonCrater" | "context.tv.channelArt.lab.background" | "context.tv.channelArt.lab.foreground" | "context.tv.channelArt.lab.accent" | "tv.video.background" | "tv.video.heroScrimTop" | "tv.video.heroScrimBottom" | "tv.video.textOnVideo" | "tv.text.heading" | "tv.text.label" | "tv.text.body" | "tv.text.caption" | "tv.text.smol" | "tv.fontWeight.regular" | "tv.fontWeight.medium" | "tv.space.xxs" | "tv.space.xs" | "tv.space.sm" | "tv.space.md" | "tv.space.lg" | "tv.space.xl" | "tv.space.xxl" | "tv.overscan.x" | "tv.overscan.y" | "tv.radius" | "tv.focus.borderWidth" | "tv.focus.row.background" | "tv.focus.row.backgroundFocused" | "tv.focus.control.background" | "tv.focus.control.backgroundFocused" | "tv.focus.control.border" | "tv.focus.control.borderFocused" | "tv.z.modal" | "tv.z.toast" | "tv.z.overlay" | "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.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" | "radius.tv" | "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";
3
+ 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.secondary.backgroundHover" | "component.button.secondary.backgroundHoverDark" | "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.button.ghost.background" | "component.button.ghost.foreground" | "component.button.ghost.foregroundDark" | "component.button.ghost.foregroundHover" | "component.button.ghost.foregroundHoverDark" | "component.button.ghost.backgroundHover" | "component.button.ghost.backgroundHoverDark" | "component.button.height" | "component.button.heightMd" | "component.button.heightSm" | "component.button.heightXs" | "component.button.paddingX" | "component.button.paddingXXs" | "component.button.labelSize" | "component.button.labelSizeXs" | "component.button.tracking" | "component.button.trackingMd" | "component.button.trackingSm" | "component.button.trackingXs" | "component.button.iconSize" | "component.button.gap" | "component.nav.height" | "component.nav.heightCompact" | "component.nav.ctaHeight" | "component.input.background" | "component.input.backgroundDark" | "component.input.border" | "component.input.borderDark" | "component.input.radius" | "component.input.borderHover" | "component.input.borderHoverDark" | "component.input.borderFocus" | "component.input.borderFocusDark" | "component.input.ring" | "component.input.ringDark" | "component.input.text" | "component.input.textDark" | "component.input.placeholder" | "component.input.placeholderDark" | "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.radius" | "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.activeBackground" | "component.fileRow.activeBackgroundDark" | "component.fileRow.border" | "component.fileRow.borderDark" | "component.fileRow.icon" | "component.notification.infoBackground" | "component.notification.infoBackgroundDark" | "component.notification.dangerBackground" | "component.notification.dangerBackgroundDark" | "component.notification.background" | "component.notification.backgroundDark" | "component.notification.border" | "component.notification.borderDark" | "component.notification.foreground" | "component.notification.foregroundDark" | "component.notification.icon" | "component.notification.iconDark" | "component.notification.successBackground" | "component.notification.successBackgroundDark" | "component.notification.successBorder" | "component.notification.successBorderDark" | "component.notification.successForeground" | "component.notification.successForegroundDark" | "component.notification.dangerBorder" | "component.notification.dangerBorderDark" | "component.notification.dangerForeground" | "component.notification.dangerForegroundDark" | "component.notification.badgeBackground" | "component.notification.badgeForeground" | "component.menu.background" | "component.menu.backgroundDark" | "component.menu.border" | "component.menu.borderDark" | "component.menu.radius" | "component.menu.shadow" | "component.menu.shadowDark" | "component.menu.itemBackgroundHover" | "component.menu.itemBackgroundHoverDark" | "component.menu.itemText" | "component.menu.itemTextDark" | "component.menu.itemIcon" | "component.menu.itemIconDark" | "component.menu.itemShortcut" | "component.menu.itemShortcutDark" | "component.menu.itemDangerText" | "component.menu.itemDangerTextDark" | "component.menu.itemDangerBackgroundHover" | "component.menu.itemDangerBackgroundHoverDark" | "component.menu.itemHeight" | "component.menu.separator" | "component.menu.separatorDark" | "component.menu.label" | "component.menu.labelDark" | "component.palette.background" | "component.palette.backgroundDark" | "component.palette.border" | "component.palette.borderDark" | "component.palette.shadow" | "component.palette.shadowDark" | "component.palette.scrim" | "component.palette.scrimDark" | "component.palette.width" | "component.palette.inputHeight" | "component.palette.itemHeight" | "component.palette.itemBackgroundActive" | "component.palette.itemBackgroundActiveDark" | "component.palette.groupLabel" | "component.palette.groupLabelDark" | "component.palette.itemMeta" | "component.palette.itemMetaDark" | "component.sheet.background" | "component.sheet.backgroundDark" | "component.sheet.border" | "component.sheet.borderDark" | "component.sheet.shadow" | "component.sheet.shadowDark" | "component.sheet.scrim" | "component.sheet.scrimDark" | "component.sheet.width" | "component.avatar.yellow" | "component.avatar.green" | "component.avatar.red" | "component.avatar.blue" | "component.avatar.purple" | "component.avatar.teal" | "component.avatar.orange" | "component.avatar.foreground" | "component.avatar.foregroundWarm" | "component.alert.radius" | "component.alert.infoBackground" | "component.alert.infoBackgroundDark" | "component.alert.infoBorder" | "component.alert.infoBorderDark" | "component.alert.infoText" | "component.alert.infoTextDark" | "component.alert.infoBody" | "component.alert.infoBodyDark" | "component.alert.infoIcon" | "component.alert.infoIconDark" | "component.alert.brandBackground" | "component.alert.brandBackgroundDark" | "component.alert.brandBorder" | "component.alert.brandBorderDark" | "component.alert.brandText" | "component.alert.brandTextDark" | "component.alert.brandBody" | "component.alert.brandBodyDark" | "component.alert.brandIcon" | "component.alert.brandIconDark" | "component.alert.successBackground" | "component.alert.successBackgroundDark" | "component.alert.successBorder" | "component.alert.successBorderDark" | "component.alert.successText" | "component.alert.successTextDark" | "component.alert.successBody" | "component.alert.successBodyDark" | "component.alert.successIcon" | "component.alert.successIconDark" | "component.alert.dangerBackground" | "component.alert.dangerBackgroundDark" | "component.alert.dangerBorder" | "component.alert.dangerBorderDark" | "component.alert.dangerText" | "component.alert.dangerTextDark" | "component.alert.dangerBody" | "component.alert.dangerBodyDark" | "component.alert.dangerIcon" | "component.alert.dangerIconDark" | "component.slider.track" | "component.slider.trackDark" | "component.slider.buffer" | "component.slider.bufferDark" | "component.slider.fill" | "component.slider.knob" | "component.slider.fillDisabled" | "component.slider.fillDisabledDark" | "component.slider.trackHeight" | "component.slider.knobSize" | "component.slider.hitHeight" | "component.pagination.itemSize" | "component.pagination.itemText" | "component.pagination.itemTextDark" | "component.pagination.itemTextActive" | "component.pagination.itemTextActiveDark" | "component.pagination.itemBackgroundHover" | "component.pagination.itemBackgroundHoverDark" | "component.pagination.itemBackgroundActive" | "component.pagination.itemBackgroundActiveDark" | "component.pagination.itemBorderActive" | "component.pagination.itemBorderActiveDark" | "context.tv.text.primary" | "context.tv.text.secondary" | "context.tv.text.tertiary" | "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.nightly.background" | "context.tv.channelArt.nightly.accent" | "context.tv.channelArt.nightly.star" | "context.tv.channelArt.nightly.moon" | "context.tv.channelArt.nightly.moonCrater" | "context.tv.channelArt.lab.background" | "context.tv.channelArt.lab.foreground" | "context.tv.channelArt.lab.accent" | "tv.video.background" | "tv.video.heroScrimTop" | "tv.video.heroScrimBottom" | "tv.video.textOnVideo" | "tv.text.heading" | "tv.text.label" | "tv.text.body" | "tv.text.caption" | "tv.text.smol" | "tv.fontWeight.regular" | "tv.fontWeight.medium" | "tv.space.xxs" | "tv.space.xs" | "tv.space.sm" | "tv.space.md" | "tv.space.lg" | "tv.space.xl" | "tv.space.xxl" | "tv.overscan.x" | "tv.overscan.y" | "tv.radius" | "tv.focus.borderWidth" | "tv.focus.row.background" | "tv.focus.row.backgroundFocused" | "tv.focus.control.background" | "tv.focus.control.backgroundFocused" | "tv.focus.control.border" | "tv.focus.control.borderFocused" | "tv.z.modal" | "tv.z.toast" | "tv.z.overlay" | "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" | "elevation.focusDark" | "typography.fontFamily.sans" | "typography.fontFamily.display" | "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" | "radius.tv" | "border.width" | "motion.easing.out" | "motion.easing.inOut" | "motion.duration.fast" | "motion.duration.reveal" | "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";
4
4
 
5
5
  export type PutioToken = {
6
6
  readonly cssName: string;
@@ -4746,11 +4746,24 @@
4746
4746
  },
4747
4747
  "focus": {
4748
4748
  "$type": "string",
4749
- "$value": "0 0 0 3px {elevation.focusColor}",
4749
+ "$value": "0 0 0 2px {color.neutral.light.bg}, 0 0 0 4px {color.neutral.light.solidHover}",
4750
4750
  "$extensions": {
4751
4751
  "putio": {
4752
4752
  "cssName": "shadow-focus",
4753
- "mode": "global",
4753
+ "mode": "light",
4754
+ "figma": false
4755
+ }
4756
+ },
4757
+ "$description": "Button, link, and nav focus: a 2px page-coloured gap, then a 2px opaque ring at least 3:1 against the page and panels."
4758
+ },
4759
+ "focusDark": {
4760
+ "$type": "string",
4761
+ "$value": "0 0 0 2px {color.neutral.dark.bg}, 0 0 0 4px {color.neutral.dark.solidHover}",
4762
+ "$description": "Dark-mode button, link, and nav focus ring.",
4763
+ "$extensions": {
4764
+ "putio": {
4765
+ "cssName": "shadow-focus",
4766
+ "mode": "dark",
4754
4767
  "figma": false
4755
4768
  }
4756
4769
  }
@@ -5273,6 +5286,17 @@
5273
5286
  }
5274
5287
  }
5275
5288
  },
5289
+ "reveal": {
5290
+ "$type": "duration",
5291
+ "$value": "180ms",
5292
+ "$extensions": {
5293
+ "putio": {
5294
+ "cssName": "dur-reveal",
5295
+ "mode": "global",
5296
+ "figma": true
5297
+ }
5298
+ }
5299
+ },
5276
5300
  "base": {
5277
5301
  "$type": "duration",
5278
5302
  "$value": "200ms",
@@ -3234,9 +3234,18 @@
3234
3234
  "elevation.focus": {
3235
3235
  "cssName": "--shadow-focus",
3236
3236
  "type": "string",
3237
- "mode": "global",
3238
- "value": "0 0 0 3px hsla(47, 100%, 65%, 0.35)",
3239
- "originalValue": "0 0 0 3px {elevation.focusColor}"
3237
+ "mode": "light",
3238
+ "value": "0 0 0 2px hsl(0, 0%, 99.0%), 0 0 0 4px hsl(0, 0%, 52.3%)",
3239
+ "originalValue": "0 0 0 2px {color.neutral.light.bg}, 0 0 0 4px {color.neutral.light.solidHover}",
3240
+ "description": "Button, link, and nav focus: a 2px page-coloured gap, then a 2px opaque ring at least 3:1 against the page and panels."
3241
+ },
3242
+ "elevation.focusDark": {
3243
+ "cssName": "--shadow-focus",
3244
+ "type": "string",
3245
+ "mode": "dark",
3246
+ "value": "0 0 0 2px hsl(0, 0%, 8.5%), 0 0 0 4px hsl(0, 0%, 49.4%)",
3247
+ "originalValue": "0 0 0 2px {color.neutral.dark.bg}, 0 0 0 4px {color.neutral.dark.solidHover}",
3248
+ "description": "Dark-mode button, link, and nav focus ring."
3240
3249
  },
3241
3250
  "typography.fontFamily.sans": {
3242
3251
  "cssName": "--font-sans",
@@ -3557,6 +3566,13 @@
3557
3566
  "value": "120ms",
3558
3567
  "originalValue": "120ms"
3559
3568
  },
3569
+ "motion.duration.reveal": {
3570
+ "cssName": "--dur-reveal",
3571
+ "type": "duration",
3572
+ "mode": "global",
3573
+ "value": "180ms",
3574
+ "originalValue": "180ms"
3575
+ },
3560
3576
  "motion.duration.base": {
3561
3577
  "cssName": "--dur-base",
3562
3578
  "type": "duration",
package/dist/tokens.js CHANGED
@@ -3236,9 +3236,18 @@ export const tokens = {
3236
3236
  "elevation.focus": {
3237
3237
  "cssName": "--shadow-focus",
3238
3238
  "type": "string",
3239
- "mode": "global",
3240
- "value": "0 0 0 3px hsla(47, 100%, 65%, 0.35)",
3241
- "originalValue": "0 0 0 3px {elevation.focusColor}"
3239
+ "mode": "light",
3240
+ "value": "0 0 0 2px hsl(0, 0%, 99.0%), 0 0 0 4px hsl(0, 0%, 52.3%)",
3241
+ "originalValue": "0 0 0 2px {color.neutral.light.bg}, 0 0 0 4px {color.neutral.light.solidHover}",
3242
+ "description": "Button, link, and nav focus: a 2px page-coloured gap, then a 2px opaque ring at least 3:1 against the page and panels."
3243
+ },
3244
+ "elevation.focusDark": {
3245
+ "cssName": "--shadow-focus",
3246
+ "type": "string",
3247
+ "mode": "dark",
3248
+ "value": "0 0 0 2px hsl(0, 0%, 8.5%), 0 0 0 4px hsl(0, 0%, 49.4%)",
3249
+ "originalValue": "0 0 0 2px {color.neutral.dark.bg}, 0 0 0 4px {color.neutral.dark.solidHover}",
3250
+ "description": "Dark-mode button, link, and nav focus ring."
3242
3251
  },
3243
3252
  "typography.fontFamily.sans": {
3244
3253
  "cssName": "--font-sans",
@@ -3559,6 +3568,13 @@ export const tokens = {
3559
3568
  "value": "120ms",
3560
3569
  "originalValue": "120ms"
3561
3570
  },
3571
+ "motion.duration.reveal": {
3572
+ "cssName": "--dur-reveal",
3573
+ "type": "duration",
3574
+ "mode": "global",
3575
+ "value": "180ms",
3576
+ "originalValue": "180ms"
3577
+ },
3562
3578
  "motion.duration.base": {
3563
3579
  "cssName": "--dur-base",
3564
3580
  "type": "duration",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putdotio/design",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "Public put.io design tokens, brand assets, and design-system guidance.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/system/README.md CHANGED
@@ -83,7 +83,7 @@ its cards use itself.
83
83
 
84
84
  ## Core rules
85
85
 
86
- - **Yellow** follows [Colors](../DESIGN.md#colors). Guide specifics: `--yellow-solid-hover` is the hover peer with one value in both modes, and button focus-visible rings use `--shadow-focus` (the yellow at 35% alpha, `--shadow-focus-color`). Form fields focus with the neutral `--field-ring` instead.
86
+ - **Yellow** follows [Colors](../DESIGN.md#colors). Guide specifics: `--yellow-solid-hover` is the hover peer with one value in both modes, and button focus-visible rings use `--shadow-focus`: a 2px `--bg` gap, then a 2px `--solid-hover` ring, at least 3:1 against the page and panels. `--shadow-focus-color` (the yellow at 35% alpha) stays only as shadcn's `--ring`. Form fields focus with `--field-ring` instead.
87
87
  - **Yellow as text on light** uses `--yellow-text-secondary` (≈5:1, AA); `--yellow-solid` is fill-only on light backgrounds.
88
88
  - **Labels on yellow** use `--primary-foreground`, a warm dark that passes AA on the yellow fill.
89
89
  - **One design, two modes.** Light and dark are the same markup and components with different token values. Toggle `.dark` on `<html>`; one page serves both modes. Components consume semantic tokens (`--bg`, `--text`, `--border`, `--accent`, …) instead of raw `#hex` or `hsl()`.
@@ -172,10 +172,8 @@ directly.
172
172
 
173
173
  shadcn marks parts of compound components with `data-slot="card-header"`,
174
174
  `data-slot="card-title"`, and so on. put.io does the same on the folder picker
175
- (`data-slot="modal" / "modal-header" / "modal-body" / "modal-footer"`) and the
176
- auth cards in `web-p00c-auth-signin.html` and `web-p00d-auth-signup.html`
177
- (`data-slot="auth" / "auth-header" / "auth-body"`), so external CSS can target
178
- one part without nested class chains.
175
+ (`data-slot="modal" / "modal-header" / "modal-body" / "modal-footer"`), so
176
+ external CSS can target one part without nested class chains.
179
177
 
180
178
  ## Design variants
181
179
 
@@ -147,11 +147,24 @@ button i { font-size: var(--button-icon-size); line-height: 1; display: inline-f
147
147
  .btn-ghost:disabled { opacity: 1; color: var(--solid); background: transparent; }
148
148
 
149
149
  /* States */
150
- /* Disabled buttons freeze their resting fill at reduced opacity and ignore
151
- hover entirely — hover styles above are gated on :not(:disabled), so there
152
- is no need to (and we must not) reset background to `inherit` here, which
153
- would strip a disabled primary/danger button of its color on hover. */
150
+ /* Bare disabled buttons fade; hover styles above are gated on :not(:disabled).
151
+ Filled variants instead drop their fill to --solid at full opacity, so a
152
+ disabled "Verifying…" stays legible. A finished action keeps its success
153
+ fill with data-state="success" (2FA "Verified"). */
154
154
  button:disabled { opacity: 0.3; cursor: not-allowed; }
155
+ :is(.btn-primary, .btn-success, .btn-danger, .btn-info, .btn-invert):not(.btn-quiet):disabled {
156
+ opacity: 1;
157
+ background: var(--solid);
158
+ border-color: var(--solid);
159
+ color: var(--solid-foreground);
160
+ }
161
+ .btn-default:not(.btn-quiet):disabled { opacity: 1; color: var(--solid); }
162
+ .btn-success[data-state="success"]:disabled {
163
+ background: var(--button-success-bg);
164
+ border-color: var(--button-success-bg);
165
+ color: var(--button-success-fg);
166
+ }
167
+ /* An opaque double ring: a --bg gap, then --solid-hover, ≥3:1 on page and panels. */
155
168
  button:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
156
169
 
157
170
  /* Loading state — hides label, paints a CSS spinner over the button */
@@ -211,8 +224,9 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
211
224
 
212
225
  .card = focal, elevated component surface. Sits ON --bg,
213
226
  uses --component-bg so it reads as raised in dark
214
- and contained in light. Auth card, modal, settings
215
- card, anything the user's attention should go TO.
227
+ and contained in light. Modal, settings card,
228
+ anything the user's attention should go TO. Auth
229
+ pages take neither: see .auth-page.
216
230
 
217
231
  .panel = quiet inline reference container. Sits ON --bg,
218
232
  uses --bg-secondary so it stays recessive but
@@ -298,17 +312,20 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
298
312
  }
299
313
  /* Invalid — border + text only, no surface fill.
300
314
  Step convention: rest = step 7 (--red-border),
301
- focus/hover = step 8 (--red-border-hover). */
315
+ hover = step 8 (--red-border-hover), focus = step 9 (--red-solid),
316
+ which holds 3:1 against the field fill.
317
+ The border fades in over --dur-reveal, in step with .form-reveal. */
302
318
  .field[aria-invalid="true"] {
303
319
  border-color: var(--red-border);
320
+ transition-duration: var(--dur-reveal);
304
321
  }
305
322
  .field[aria-invalid="true"]:hover {
306
323
  border-color: var(--red-border-hover);
307
324
  }
308
325
  .field[aria-invalid="true"]:focus,
309
326
  .field[aria-invalid="true"]:focus-visible {
310
- border-color: var(--red-border-hover);
311
- box-shadow: 0 0 0 1px var(--red-border-hover);
327
+ border-color: var(--red-solid);
328
+ box-shadow: 0 0 0 1px var(--red-solid);
312
329
  }
313
330
  /* Loading / async-validating — suppress focus ring so the spinner
314
331
  reads as the dominant signal. */
@@ -407,6 +424,38 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
407
424
  font-size: var(--fs-sm);
408
425
  }
409
426
 
427
+ /* ===========================================================
428
+ Form reveal — eases a field or form error in without a layout jump.
429
+
430
+ Every field error uses it: under its field inside .form-group, or
431
+ as a form's one message above the submit button. The row animates
432
+ its height through grid-template-rows (0fr → 1fr) with opacity, so
433
+ whatever sits below slides instead of jumping. Markup:
434
+ .form-reveal > div > .form-callout.inline[data-state="error"]; the
435
+ inner div clips, so spacing on the message collapses with it. Keep
436
+ the message (role="alert") mounted and rendered, so screen readers
437
+ hear new text: set its text and data-state="open" together; on
438
+ close, set data-state="closed" and empty the message once the row
439
+ has collapsed (transitionend), so it does not snap and a closed row
440
+ says nothing. The token base collapses the transition under
441
+ prefers-reduced-motion.
442
+ =========================================================== */
443
+ .form-reveal {
444
+ display: grid;
445
+ grid-template-rows: 0fr;
446
+ opacity: 0;
447
+ transition:
448
+ grid-template-rows var(--dur-reveal) var(--ease-out),
449
+ opacity var(--dur-reveal) var(--ease-out);
450
+ }
451
+ .form-reveal > * { min-height: 0; overflow: hidden; }
452
+ .form-reveal .form-callout { margin: 0; }
453
+ .form-reveal[data-state="open"] { grid-template-rows: 1fr; opacity: 1; }
454
+ /* Inside a .form-group the row absorbs the group gap, so a closed row
455
+ adds no space and an open one sits the usual 6px under its field. */
456
+ .form-group > .form-reveal { margin-top: -6px; }
457
+ .form-group > .form-reveal .form-callout { padding-top: 6px; }
458
+
410
459
  /* ===========================================================
411
460
  One-time password — one real input, six visual slots.
412
461
 
@@ -483,10 +532,11 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
483
532
  .otp:has(.otp-input[aria-invalid="true"]) .otp-slot {
484
533
  border-color: var(--red-border);
485
534
  color: var(--red-text-secondary);
535
+ transition-duration: var(--dur-reveal);
486
536
  }
487
537
  .otp:has(.otp-input[aria-invalid="true"]):focus-within .otp-slot[data-state="active"] {
488
- border-color: var(--red-border-hover);
489
- box-shadow: 0 0 0 1px var(--red-border-hover);
538
+ border-color: var(--red-solid);
539
+ box-shadow: 0 0 0 1px var(--red-solid);
490
540
  }
491
541
  .otp[data-state="success"] .otp-slot {
492
542
  border-color: var(--green-border);
@@ -512,6 +562,100 @@ input, textarea, select { text-transform: none; letter-spacing: normal; }
512
562
  cursor: not-allowed;
513
563
  }
514
564
 
565
+ /* ===========================================================
566
+ Auth page — sign in, sign up, password reset, two-factor.
567
+
568
+ Card-less: the form sits on the page background. Logo top-left
569
+ in .auth-header, one left-aligned 340px column centred in
570
+ .auth-main, a quiet link row in .auth-footer. The page re-points
571
+ --field-bg / --field-border, so .field and .otp-slot render filled
572
+ without a variant. Controls are 44px; the title is the only
573
+ heading. Under 640px the gutters drop to 24px so the logo lines
574
+ up with the column.
575
+ =========================================================== */
576
+ .auth-page {
577
+ --field-bg: var(--bg-secondary);
578
+ --field-border: var(--line);
579
+ /* Focus a step past hover, so it holds 3:1 against the filled field. */
580
+ --field-border-focus: var(--solid-hover);
581
+ --field-ring: 0 0 0 1px var(--solid-hover);
582
+ box-sizing: border-box;
583
+ min-height: 100dvh;
584
+ display: flex;
585
+ flex-direction: column;
586
+ background: var(--bg);
587
+ color: var(--text);
588
+ }
589
+ .auth-header {
590
+ display: flex;
591
+ align-items: center;
592
+ justify-content: space-between;
593
+ gap: 16px;
594
+ padding: 28px 32px;
595
+ }
596
+ .auth-logo { display: block; height: 30.33px; width: auto; }
597
+ .auth-main {
598
+ flex: 1 0 auto;
599
+ display: flex;
600
+ align-items: center;
601
+ justify-content: center;
602
+ padding: 24px 24px 48px;
603
+ }
604
+ .auth-column {
605
+ width: 100%;
606
+ max-width: 340px;
607
+ display: flex;
608
+ flex-direction: column;
609
+ }
610
+ .auth-title {
611
+ margin: 0 0 28px;
612
+ font-size: 22px;
613
+ line-height: 30px;
614
+ font-weight: var(--fw-medium);
615
+ letter-spacing: -0.01em;
616
+ color: var(--text);
617
+ }
618
+ .auth-column .form-stack { gap: 16px; }
619
+ .auth-column .label {
620
+ font-size: var(--fs-sm);
621
+ font-weight: var(--fw-regular);
622
+ color: var(--text-secondary);
623
+ }
624
+ .auth-column .field { height: 44px; }
625
+ .auth-column form > .form-reveal .form-callout { padding-top: 12px; font-size: var(--fs-base); }
626
+ .auth-column .btn-block { height: 44px; }
627
+ .auth-column form > .form-reveal + .btn-block { margin-top: 16px; }
628
+ .auth-links {
629
+ margin: 24px 0 0;
630
+ display: flex;
631
+ flex-wrap: wrap;
632
+ align-items: baseline;
633
+ justify-content: space-between;
634
+ gap: 8px 16px;
635
+ color: var(--text-secondary);
636
+ font-size: var(--fs-base);
637
+ }
638
+ /* A prompt's link reads as text ("New to put.io? Sign up"); a bare
639
+ action link ("Use a recovery code") stays muted. */
640
+ .auth-links p { margin: 0; }
641
+ .auth-links a { color: var(--text); text-underline-offset: 3px; }
642
+ .auth-links > a { color: var(--text-secondary); text-decoration: none; }
643
+ .auth-links > a:hover { color: var(--text); }
644
+ .auth-footer {
645
+ display: flex;
646
+ flex-wrap: wrap;
647
+ justify-content: center;
648
+ gap: 8px 16px;
649
+ padding: 24px 32px 28px;
650
+ font-size: var(--fs-sm);
651
+ }
652
+ .auth-footer a { color: var(--text-secondary); text-decoration: none; }
653
+ .auth-footer a:hover { color: var(--text); }
654
+ @media (max-width: 640px) {
655
+ .auth-header { padding: 24px; }
656
+ .auth-footer { padding: 24px; }
657
+ }
658
+
515
659
  /* Floating-menu surface — one recipe for dropdowns, context menus,
516
660
  submenus, and on-player popovers. Cards keep their item styles local.
517
661
  Consumed by web-c09-menu.html and web-p06-player.html — it looks unused
@@ -86,11 +86,24 @@
86
86
  },
87
87
  "focus": {
88
88
  "$type": "string",
89
- "$value": "0 0 0 3px {elevation.focusColor}",
89
+ "$value": "0 0 0 2px {color.neutral.light.bg}, 0 0 0 4px {color.neutral.light.solidHover}",
90
90
  "$extensions": {
91
91
  "putio": {
92
92
  "cssName": "shadow-focus",
93
- "mode": "global",
93
+ "mode": "light",
94
+ "figma": false
95
+ }
96
+ },
97
+ "$description": "Button, link, and nav focus: a 2px page-coloured gap, then a 2px opaque ring at least 3:1 against the page and panels."
98
+ },
99
+ "focusDark": {
100
+ "$type": "string",
101
+ "$value": "0 0 0 2px {color.neutral.dark.bg}, 0 0 0 4px {color.neutral.dark.solidHover}",
102
+ "$description": "Dark-mode button, link, and nav focus ring.",
103
+ "$extensions": {
104
+ "putio": {
105
+ "cssName": "shadow-focus",
106
+ "mode": "dark",
94
107
  "figma": false
95
108
  }
96
109
  }
@@ -613,6 +626,17 @@
613
626
  }
614
627
  }
615
628
  },
629
+ "reveal": {
630
+ "$type": "duration",
631
+ "$value": "180ms",
632
+ "$extensions": {
633
+ "putio": {
634
+ "cssName": "dur-reveal",
635
+ "mode": "global",
636
+ "figma": true
637
+ }
638
+ }
639
+ },
616
640
  "base": {
617
641
  "$type": "duration",
618
642
  "$value": "200ms",