Files
dt-design-system/packages/web/src/components/bevels.css

662 lines
22 KiB
CSS

/* dt-web-theme: Bevel Utilities */
/* Angular/beveled clip-path patterns from the DT design system */
/* Source: dt-shopify-storefront clip-path patterns + react-native-dt-theme bevelPaths.ts */
/* */
/* clip-path clips CSS borders and box-shadow. This file uses the storefront's */
/* dual-element technique: the element's own background serves as the "border" color, */
/* and a ::before pseudo-element provides the inner surface fill. The gap between */
/* them follows the clip-path shape perfectly. */
/* */
/* For glow effects on clipped elements, use filter: drop-shadow() (not box-shadow). */
/* ============================================================================
Card Bevels — dual bottom bevels (bottom-right large + bottom-left small)
Source: storefront .card-container + .clip-corners-inner pattern
Outer bg = border color, ::before = surface fill
Border width: ~3px uniform (storefront --card-border-width: 0.2em)
============================================================================ */
[data-brand="dt"] .dt-bevel-card,
[data-brand="dt"] .card {
--dt-card-progress: 0;
--_card-pad: var(--space-6);
display: flex;
flex-direction: column;
position: relative;
isolation: isolate;
background: var(--dt-card-color, var(--color-primary));
border: none;
border-radius: 0;
padding: var(--_card-pad);
padding-left: calc(var(--bevel-sm) + var(--_card-pad));
overflow: clip;
clip-path: polygon(0% 0%,
100% 0%,
100% calc(100% - var(--bevel-md)),
calc(100% - var(--bevel-md)) 100%,
var(--bevel-sm) 100%,
0% calc(100% - var(--bevel-sm)));
}
[data-brand="dt"] .dt-bevel-card::before,
[data-brand="dt"] .card::before {
content: '';
position: absolute;
inset: 0;
background: var(--color-surface);
z-index: -1;
/* Inner clip-path: left edge at bevel-sm to create progress bar zone.
The progress bar occupies x=0 to x=bevel-sm with its own borders.
Inner surface starts at bevel-sm, so the left frame is wider than 3px.
Diagonal vertices use bevel-md + 3px (not bevel-md alone) so the
perpendicular border width on the 45-degree diagonal visually matches
the 3px straight edges despite sub-pixel antialiasing. */
clip-path: polygon(var(--bevel-sm) 3px,
calc(100% - 3px) 3px,
calc(100% - 3px) calc(100% - var(--bevel-md) - 3px),
calc(100% - var(--bevel-md) - 3px) calc(100% - 3px),
var(--bevel-sm) calc(100% - 3px));
}
/* Card Progress Bar — structural fill area on the left edge
Sits between the 3px frame borders, follows bottom-left bevel diagonal.
Gradient fills top-to-bottom based on --dt-card-progress so the card
visually "opens" as the bar descends.
Filled portion: fully opaque accent color. Unfilled portion: semi-transparent surface.
At 0%: all semi-transparent surface. At 100%: all opaque accent.
Override --dt-progress-empty-opacity to control unfilled portion transparency. */
[data-brand="dt"] .dt-bevel-card::after,
[data-brand="dt"] .card::after {
content: '';
position: absolute;
inset: 0;
z-index: 1;
clip-path: polygon(3px 3px,
calc(var(--bevel-sm) - 3px) 3px,
calc(var(--bevel-sm) - 3px) calc(100% - 6px),
3px calc(100% - var(--bevel-sm)));
background: linear-gradient(to bottom,
rgba(var(--dt-card-color-rgb, var(--color-primary-rgb)), 1) calc(var(--dt-card-progress, 0) * 1%),
rgba(var(--color-surface-rgb),
var(--dt-progress-empty-opacity, 0.6)) calc(var(--dt-card-progress, 0) * 1%));
pointer-events: none;
}
/* Card children — ensure body content renders above the progress bar (::after z-index:1) */
[data-brand="dt"] .dt-bevel-card > *:not(.dt-badge-bottom-right):not(.dt-card-badge),
[data-brand="dt"] .card > *:not(.dt-badge-bottom-right):not(.dt-card-badge) {
position: relative;
z-index: 2;
}
/* Card Grow — stretch to fill available space in flex/grid containers.
By default the body (content area) grows. Use .dt-card-grow-title to
grow the header instead. */
.dt-card-grow {
flex: 1 1 0%;
height: 100%;
}
.dt-card-grow > .card-title {
flex: 0 0 auto;
}
.dt-card-grow:not(.dt-card-grow-title) > :last-child {
flex: 1 1 auto;
}
.dt-card-grow.dt-card-grow-title > .card-title {
flex: 1 1 auto;
}
/* Card Header — storefront .card-header pattern
Title starts at bevel-sm (past the progress bar zone) and extends to the right edge.
Below it, the ::before dark surface shows through for the body area. */
[data-brand="dt"] .card>.card-title,
[data-brand="dt"] .dt-bevel-card>.card-title {
/* Pull title toward card edges to sit flush inside the 3px frame border.
Uses 2px inset (1px overlap into the frame border) on top/right/left
to prevent sub-pixel anti-aliasing gaps. The overlap is invisible
because the header and frame border share the same accent color.
Left edge lands at bevel-sm - 1px, keeping the progress bar column
visible alongside the header (the progress bar spans full card height). */
margin: calc(2px - var(--_card-pad)) calc(2px - var(--_card-pad)) var(--space-4) calc(-1px - var(--_card-pad));
padding: 1rem calc(var(--_card-pad) + 1px) 1rem calc(var(--_card-pad) + 1px);
/* Sits above ::before so its own bg covers the dark surface */
background: var(--dt-card-color, var(--color-primary));
color: var(--color-bg);
font-weight: 900;
letter-spacing: 0.05em;
font-size: 1rem;
position: relative;
z-index: 2;
}
/* Force icon spans to inherit header color (overrides inline styles) */
[data-brand="dt"] .card>.card-title>* {
color: inherit !important;
}
/* Full-bleed card body — pulls content flush against the inner frame border.
Use for images/media that should fill the card body edge-to-edge.
Margins align with the card's inner surface edges so the card's structural
3px border is the only visible border (no double-border). */
[data-brand="dt"] .card>.card-body-flush,
[data-brand="dt"] .dt-bevel-card>.card-body-flush {
margin: 0 calc(3px - var(--_card-pad)) calc(3px - var(--_card-pad)) calc(-1 * var(--_card-pad));
overflow: hidden;
background: var(--dt-card-color, var(--color-primary));
}
/* Bottom-right bevel on card body content. Clip-path matches
the card's inner surface bevel so the 3px structural border is visible
at the diagonal corner. Badge overlays are excluded. */
[data-brand="dt"] .card-body-flush > *:not(.dt-badge-bottom-right):not(.dt-card-badge) {
display: block;
clip-path: polygon(
0% 0%,
100% 0%,
100% calc(100% - var(--bevel-md)),
calc(100% - var(--bevel-md)) 100%,
0% 100%);
}
/* Remove gap between card title and full-bleed body */
[data-brand="dt"] .card>.card-title + .card-body-flush,
[data-brand="dt"] .dt-bevel-card>.card-title + .card-body-flush {
margin-top: calc(-1 * var(--space-4));
}
/* ============================================================================
Button Bevels — top-right corner cut
Source: storefront .label-clipped pattern (solid fill + clip-path)
Filled buttons (primary/danger): direct clip-path, solid bg
Outline button (secondary): dual-element technique via ::before
============================================================================ */
[data-brand="dt"] .btn-primary,
[data-brand="dt"] .btn-danger {
border: none;
clip-path: polygon(0% 0%,
calc(100% - var(--bevel-sm)) 0%,
100% var(--bevel-sm),
100% 100%,
0% 100%);
}
[data-brand="dt"] .btn-secondary {
position: relative;
isolation: isolate;
background: var(--color-primary);
border: none;
clip-path: polygon(0% 0%,
calc(100% - var(--bevel-sm)) 0%,
100% var(--bevel-sm),
100% 100%,
0% 100%);
}
[data-brand="dt"] .btn-secondary::before {
content: '';
position: absolute;
inset: 0;
background: var(--color-bg);
z-index: -1;
clip-path: polygon(2px 2px,
calc(100% - var(--bevel-sm)) 2px,
calc(100% - 2px) var(--bevel-sm),
calc(100% - 2px) calc(100% - 2px),
2px calc(100% - 2px));
}
[data-brand="dt"] .btn-secondary:hover {
background: var(--color-accent);
}
[data-brand="dt"] .btn-secondary:hover::before {
background: var(--color-surface-hover);
}
/* ============================================================================
Badge/Label Bevels — top-right bevel
Source: storefront .label-clipped pattern
Dual-element technique: badge bg = border color, ::before = inner fill
============================================================================ */
[data-brand="dt"] .badge {
--badge-border-color: var(--color-border);
--badge-fill: var(--color-surface);
position: relative;
isolation: isolate;
background: var(--badge-border-color);
color: var(--color-text-primary);
clip-path: polygon(0% 0%,
calc(100% - var(--bevel-sm)) 0%,
100% var(--bevel-sm),
100% 100%,
0% 100%);
border: none;
border-radius: 0;
}
[data-brand="dt"] .badge::before {
content: '';
position: absolute;
inset: 0;
background: var(--badge-fill);
z-index: -1;
clip-path: polygon(2px 2px,
calc(100% - var(--bevel-sm)) 2px,
calc(100% - 2px) var(--bevel-sm),
calc(100% - 2px) calc(100% - 2px),
2px calc(100% - 2px));
}
/* Colored badge variants — solid color fill with contrasting text.
Dark mode: black text on bright neon fill.
Light mode: white text on bright fill (via --color-bg which flips per theme). */
[data-brand="dt"] .badge-success {
--badge-border-color: var(--color-success);
--badge-fill: var(--color-success);
color: var(--color-bg);
}
[data-brand="dt"] .badge-error {
--badge-border-color: var(--color-error);
--badge-fill: var(--color-error);
color: var(--color-bg);
}
[data-brand="dt"] .badge-warning {
--badge-border-color: var(--color-warning);
--badge-fill: var(--color-warning);
color: var(--color-bg);
}
[data-brand="dt"] .badge-info {
--badge-border-color: var(--color-info);
--badge-fill: var(--color-info);
color: var(--color-bg);
}
/* Classic brand colored badges — same solid fill approach */
[data-brand="classic"] .badge-success {
color: var(--color-bg);
}
[data-brand="classic"] .badge-error {
color: var(--color-bg);
}
[data-brand="classic"] .badge-warning {
color: var(--color-bg);
}
[data-brand="classic"] .badge-info {
color: var(--color-bg);
}
/* Mode-variant badges — bridge .mode-* vars to badge color vars */
[data-brand="dt"] .badge.mode-normal {
--badge-border-color: var(--mode-normal);
--badge-fill: var(--mode-normal);
color: var(--color-bg);
}
[data-brand="dt"] .badge.mode-emphasis {
--badge-border-color: var(--mode-emphasis);
--badge-fill: var(--mode-emphasis);
color: var(--color-bg);
}
[data-brand="dt"] .badge.mode-warning {
--badge-border-color: var(--mode-warning);
--badge-fill: var(--mode-warning);
color: var(--color-bg);
}
[data-brand="dt"] .badge.mode-success {
--badge-border-color: var(--mode-success);
--badge-fill: var(--mode-success);
color: var(--color-bg);
}
[data-brand="dt"] .badge.mode-other {
--badge-border-color: var(--mode-other);
--badge-fill: var(--mode-other);
color: var(--color-bg);
}
/* ============================================================================
Opt-in Bevel Utility Classes
Use these for custom elements that need bevel shapes
============================================================================ */
[data-brand="dt"] .dt-bevel-btn {
clip-path: polygon(0% 0%,
calc(100% - var(--bevel-sm)) 0%,
100% var(--bevel-sm),
100% 100%,
0% 100%);
}
[data-brand="dt"] .dt-bevel-label {
clip-path: polygon(0% 0%,
calc(100% - var(--bevel-sm)) 0%,
100% var(--bevel-sm),
100% 100%,
0% 100%);
border-radius: 0;
}
/* ============================================================================
Media Frame Bevels — diagonal opposite corners (top-left + bottom-right)
Source: storefront .media-frame-clipped pattern
============================================================================ */
.dt-bevel-media {
position: relative;
isolation: isolate;
background: var(--dt-card-color, var(--color-primary));
clip-path: polygon(var(--bevel-md) 0%,
100% 0%,
100% calc(100% - var(--bevel-md)),
calc(100% - var(--bevel-md)) 100%,
0% 100%,
0% var(--bevel-md));
}
/* Inner surface fill — shows as background when no image is loaded */
.dt-bevel-media::before {
content: '';
position: absolute;
inset: 0;
background: var(--color-surface);
z-index: -1;
clip-path: polygon(
calc(var(--bevel-md) + 1px) 3px,
calc(100% - 3px) 3px,
calc(100% - 3px) calc(100% - var(--bevel-md) - 1px),
calc(100% - var(--bevel-md) - 1px) calc(100% - 3px),
3px calc(100% - 3px),
3px calc(var(--bevel-md) + 1px));
}
.dt-bevel-media > img,
.dt-bevel-media > video,
.dt-bevel-media > model-viewer,
.dt-bevel-media > iframe {
display: block;
clip-path: polygon(
calc(var(--bevel-md) + 1px) 3px,
calc(100% - 3px) 3px,
calc(100% - 3px) calc(100% - var(--bevel-md) - 1px),
calc(100% - var(--bevel-md) - 1px) calc(100% - 3px),
3px calc(100% - 3px),
3px calc(var(--bevel-md) + 1px));
}
/* Video letterboxing — respect natural aspect ratio with black bars */
.dt-bevel-media > video {
width: 100%;
height: 100%;
object-fit: contain;
background: #000;
}
/* Media frame placeholder — shows when no image/video is present.
Surface-colored background is already provided by ::before.
This adds centered "MISSING MEDIA" text via ::after. */
.dt-bevel-media::after {
content: 'MISSING MEDIA';
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: -1;
color: var(--color-text-muted, #888);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
pointer-events: none;
}
/* Card body placeholder — shows when image container is empty or image fails.
Uses surface background and centered placeholder text. */
.dt-badge-parent:empty::after,
.card-body-flush:empty::after {
content: 'MISSING MEDIA';
display: flex;
align-items: center;
justify-content: center;
min-height: 120px;
color: var(--color-text-muted, #888);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
background: var(--color-surface);
}
/* ============================================================================
Modal/Drawer Bevels — dual bottom bevels (bottom-right + bottom-left)
============================================================================ */
.dt-bevel-modal {
clip-path: polygon(0% 0%,
100% 0%,
100% calc(100% - var(--bevel-lg)),
calc(100% - var(--bevel-lg)) 100%,
var(--bevel-lg) 100%,
0% calc(100% - var(--bevel-lg)));
}
/* ============================================================================
Drawer Bevels — exposed-edge bevels
Source: storefront aside clip-path pattern
============================================================================ */
.dt-bevel-drawer-right {
clip-path: polygon(0% var(--bevel-lg),
var(--bevel-lg) 0%,
100% 0%,
100% 100%,
var(--bevel-lg) 100%,
0% calc(100% - var(--bevel-lg)));
}
/* Inner surface clip — inset from outer to create colored border frame */
.dt-bevel-drawer-right-inner {
clip-path: polygon(3px calc(var(--bevel-lg) + 1px),
calc(var(--bevel-lg) + 1px) 3px,
100% 3px,
100% calc(100% - 3px),
calc(var(--bevel-lg) + 1px) calc(100% - 3px),
3px calc(100% - var(--bevel-lg) - 1px));
}
.dt-bevel-drawer-left {
clip-path: polygon(0% 0%,
calc(100% - var(--bevel-lg)) 0%,
100% var(--bevel-lg),
100% calc(100% - var(--bevel-lg)),
calc(100% - var(--bevel-lg)) 100%,
0% 100%);
}
/* Inner surface clip — inset from outer to create colored border frame */
.dt-bevel-drawer-left-inner {
clip-path: polygon(0% 3px,
calc(100% - var(--bevel-lg) - 1px) 3px,
calc(100% - 3px) calc(var(--bevel-lg) + 1px),
calc(100% - 3px) calc(100% - var(--bevel-lg) - 1px),
calc(100% - var(--bevel-lg) - 1px) calc(100% - 3px),
0% calc(100% - 3px));
}
/* ============================================================================
Small Bevels — for compact elements (arrows, stepper buttons)
============================================================================ */
.dt-bevel-sm {
clip-path: polygon(var(--bevel-sm) 0%,
100% 0%,
100% calc(100% - var(--bevel-sm)),
calc(100% - var(--bevel-sm)) 100%,
0% 100%,
0% var(--bevel-sm));
}
/* ============================================================================
Accordion/Menu — thick top border accent (storefront menu-item-clipped)
============================================================================ */
[data-brand="dt"] .dt-accent-top,
[data-brand="dt"] .terminal {
border-top: 3px solid var(--color-primary);
}
/* ============================================================================
Card Color Modes — per-instance color overrides
Sets --dt-card-color, --dt-card-color-selected, --dt-card-color-rgb,
and --accent-mode for use by all card sub-components.
Source: dt-shopify-storefront .mode-* classes
============================================================================ */
[data-brand="dt"] .mode-normal {
--dt-card-color: var(--mode-normal);
--dt-card-color-rgb: var(--mode-normal-rgb);
--dt-card-color-selected: var(--mode-normal-selected);
--accent-mode: var(--mode-emphasis);
}
[data-brand="dt"] .mode-emphasis {
--dt-card-color: var(--mode-emphasis);
--dt-card-color-rgb: var(--mode-emphasis-rgb);
--dt-card-color-selected: var(--mode-emphasis-selected);
--accent-mode: var(--mode-normal);
}
[data-brand="dt"] .mode-warning {
--dt-card-color: var(--mode-warning);
--dt-card-color-rgb: var(--mode-warning-rgb);
--dt-card-color-selected: var(--mode-warning-selected);
--accent-mode: var(--mode-emphasis);
}
[data-brand="dt"] .mode-success {
--dt-card-color: var(--mode-success);
--dt-card-color-rgb: var(--mode-success-rgb);
--dt-card-color-selected: var(--mode-success-selected);
--accent-mode: var(--mode-emphasis);
}
[data-brand="dt"] .mode-other {
--dt-card-color: var(--mode-other);
--dt-card-color-rgb: var(--mode-other-rgb);
--dt-card-color-selected: var(--mode-other-selected);
--accent-mode: var(--mode-normal);
}
/* ============================================================================
Card Badge — bottom-right product type chip (LAB, BUNDLE, etc.)
Source: dt-shopify-storefront CyberProductCard LabChip / BundleChip
Positioned at card level so the card's clip-path bevels the badge corner.
Consumer provides badge content/styling; card handles placement.
============================================================================ */
/* Legacy: standalone badge parent for non-card contexts */
.dt-badge-parent {
position: relative;
}
/* Legacy: standalone badge positioning for non-card contexts */
.dt-badge-bottom-right {
position: absolute;
bottom: 8px;
right: 8px;
z-index: 4;
padding: 2px calc(8px + 0.2rem) 2px 8px;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
background-color: var(--dt-card-color, var(--color-primary));
color: var(--color-bg);
}
/* Card-level badge slot — positioned so the card's bevel clip-path
clips the badge's bottom-right corner at 45 degrees.
bottom/right: 3px aligns with the inner frame border edge.
The badge extends into the bevel zone and gets clipped. */
.card > .dt-card-badge,
.dt-bevel-card > .dt-card-badge {
position: absolute;
bottom: 3px;
right: 3px;
z-index: 4;
}
/* ============================================================================
Interactive Bevel Button — storefront .menu-item-clipped pattern
Default: outlined rectangle, no clip-path, 5px top border
Hover: bottom-right bevel appears, fills with mode color, pulse animation
Selected: bevel persists, fills with 70% opacity mode color
============================================================================ */
[data-brand="dt"] .dt-btn {
color: var(--accent-mode, var(--color-primary));
background-color: var(--color-bg);
border: 1px solid var(--dt-card-color, var(--color-primary));
border-top: 5px solid var(--dt-card-color, var(--color-primary));
text-decoration: none;
padding: 0.5rem 0.75rem;
position: relative;
display: inline-block;
overflow: visible;
font-weight: 600;
font-size: 0.875rem;
letter-spacing: 0.05em;
cursor: pointer;
transition: background-color var(--transition-fast),
color var(--transition-fast),
clip-path var(--transition-fast);
}
[data-brand="dt"] .dt-btn:hover {
clip-path: polygon(0% 0%,
100% 0%,
100% calc(100% - var(--menu-item-corner)),
calc(100% - var(--menu-item-corner)) 100%,
0% 100%);
color: var(--color-bg);
background-color: var(--dt-card-color, var(--color-primary));
animation: dt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
[data-brand="dt"] .dt-btn.selected {
clip-path: polygon(0% 0%,
100% 0%,
100% calc(100% - var(--menu-item-corner)),
calc(100% - var(--menu-item-corner)) 100%,
0% 100%);
background-color: var(--dt-card-color-selected, rgba(var(--color-primary-rgb), 0.7));
color: var(--color-bg);
animation: none;
}
[data-brand="dt"] .dt-btn.selected:hover {
animation: dt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Classic brand: no clip-path, uses border-radius instead */
[data-brand="classic"] .dt-btn {
border-radius: var(--radius);
border-top-width: 2px;
}
[data-brand="classic"] .dt-btn:hover {
clip-path: none;
background-color: var(--dt-card-color, var(--color-primary));
color: var(--color-bg);
border-radius: var(--radius);
}
[data-brand="classic"] .dt-btn.selected {
clip-path: none;
background-color: var(--dt-card-color-selected, rgba(var(--color-primary-rgb), 0.7));
color: var(--color-bg);
border-radius: var(--radius);
}