Files
at-container-registry/pkg/appview/src/css/main.css
2026-04-22 20:13:40 -05:00

1014 lines
35 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ========================================
TAILWIND + DAISYUI
======================================== */
@import "tailwindcss";
/* Content sources for class detection */
@source "../../templates/**/*.html";
@source "../../public/js/**/*.js";
@plugin "@tailwindcss/typography";
@plugin "daisyui" {
themes:
light --default,
dark --prefersdark;
}
/* ========================================
WEB FONTS — Onest (display), Figtree (body), Commit Mono (mono)
All self-hosted under /fonts/. Variable bodies for display/body,
static weights for mono.
======================================== */
/* Onest — variable (400..800), display face */
@font-face {
font-family: "Onest";
font-style: normal;
font-weight: 400 800;
font-display: swap;
src: url("/fonts/onest-latin.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Onest";
font-style: normal;
font-weight: 400 800;
font-display: swap;
src: url("/fonts/onest-latin-ext.woff2") format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Figtree — variable (300..900), body face */
@font-face {
font-family: "Figtree";
font-style: normal;
font-weight: 300 900;
font-display: swap;
src: url("/fonts/figtree-latin.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Figtree";
font-style: normal;
font-weight: 300 900;
font-display: swap;
src: url("/fonts/figtree-latin-ext.woff2") format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Commit Mono — static 400/400-italic/700, mono face */
@font-face {
font-family: "Commit Mono";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/fonts/commit-mono-400.woff2") format("woff2");
}
@font-face {
font-family: "Commit Mono";
font-style: italic;
font-weight: 400;
font-display: swap;
src: url("/fonts/commit-mono-400-italic.woff2") format("woff2");
}
@font-face {
font-family: "Commit Mono";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/fonts/commit-mono-700.woff2") format("woff2");
}
/* ========================================
TYPE SYSTEM — semantic tokens
Override Tailwind 4's default font stacks so `font-sans` / `font-mono`
utilities (and DaisyUI components) pick up our vendored fonts.
======================================== */
@theme inline {
--font-sans: "Figtree", ui-sans-serif, system-ui, sans-serif;
--font-mono: "Commit Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
--font-display: "Onest", ui-sans-serif, system-ui, sans-serif;
}
/* ============================================
DARK - "Deep Ocean"
============================================ */
@plugin "daisyui/theme" {
name: "dark";
default: false;
prefersdark: true;
color-scheme: "dark";
--color-base-100: oklch(20% 0.05 250);
--color-base-200: oklch(25% 0.05 250);
--color-base-300: oklch(30% 0.05 250);
--color-base-content: oklch(95.4% 0.022 211);
--color-primary: oklch(60% 0.126 221.723);
--color-primary-content: oklch(10% 0.126 221.723);
--color-secondary: oklch(76.43% 0.135 57.94);
--color-secondary-content: oklch(26% 0.079 36.259);
--color-accent: oklch(77.32% 0.1 187.98);
--color-accent-content: oklch(27% 0.046 192.524);
--color-neutral: oklch(32.3% 0.032 259.7);
--color-neutral-content: oklch(93.3% 0.026 208.7);
--color-info: oklch(74% 0.16 232.661);
--color-info-content: oklch(29% 0.066 243.157);
--color-success: oklch(76% 0.177 163.223);
--color-success-content: oklch(37% 0.077 168.94);
--color-warning: oklch(82% 0.189 84.429);
--color-warning-content: oklch(41% 0.112 45.904);
--color-error: oklch(71% 0.194 13.428);
--color-error-content: oklch(27% 0.105 12.094);
--radius-selector: 0.5rem;
--radius-field: 0.25rem;
--radius-box: 0.5rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}
/* ============================================
LIGHT - "Surface / Shallow Water"
============================================ */
@plugin "daisyui/theme" {
name: "light";
default: true;
prefersdark: false;
color-scheme: "light";
--color-base-100: oklch(98% 0.01 225);
--color-base-200: oklch(95% 0.02 225);
--color-base-300: oklch(92% 0.03 225);
--color-base-content: oklch(21.1% 0.037 254.4);
--color-primary: oklch(60% 0.126 221.723);
--color-primary-content: oklch(10% 0.126 221.723);
--color-secondary: oklch(76.43% 0.135 57.94);
--color-secondary-content: oklch(26% 0.079 36.259);
--color-accent: oklch(77.32% 0.1 187.98);
--color-accent-content: oklch(27% 0.046 192.524);
--color-neutral: oklch(32.3% 0.032 259.7);
--color-neutral-content: oklch(93.3% 0.026 208.7);
--color-info: oklch(74% 0.16 232.661);
--color-info-content: oklch(29% 0.066 243.157);
--color-success: oklch(76% 0.177 163.223);
--color-success-content: oklch(37% 0.077 168.94);
--color-warning: oklch(82% 0.189 84.429);
--color-warning-content: oklch(41% 0.112 45.904);
--color-error: oklch(71% 0.194 13.428);
--color-error-content: oklch(27% 0.105 12.094);
--radius-selector: 0.5rem;
--radius-field: 0.25rem;
--radius-box: 0.5rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}
/* ========================================
ADDITIONAL CSS VARIABLES
======================================== */
:root {
/* Dedicated star/favorite color — semantically distinct from `warning`
even if values currently coincide. Used by .text-star/.fill-star/etc. */
--color-star: oklch(82% 0.189 84.429);
/* Helm brand color (official Helm blue #0F1689). Two variants so the
light-mode value stays legible on a near-white surface and the
dark-mode value stays legible on Deep Ocean. */
--color-helm-light: oklch(31% 0.181 267.5);
--color-helm-dark: oklch(64.6% 0.19 273.2);
/* Vulnerability severity scale. Held constant across themes on purpose:
CVE severity is a product-semantic signal that needs to read the same
way regardless of surface. Content-pair colors come from the same hue
family (lighter for dark surfaces, darker for light surfaces) to avoid
gray-on-color contrast problems. */
--color-severity-critical: oklch(45% 0.19 25);
--color-severity-critical-content: oklch(97% 0.01 25);
--color-severity-high: oklch(56% 0.19 50);
--color-severity-high-content: oklch(97% 0.01 50);
--color-severity-medium: oklch(72% 0.15 70);
--color-severity-medium-content: oklch(25% 0.05 70);
--color-severity-low: oklch(80% 0.1 85);
--color-severity-low-content: oklch(25% 0.05 85);
}
/* ========================================
STAR / FAVORITE UTILITIES
Tailwind 4 @utility so the `!` important modifier and
variants (hover:, group-hover:) still work on these classes.
======================================== */
@utility text-star {
color: var(--color-star);
}
@utility stroke-star {
stroke: var(--color-star);
}
@utility fill-star {
fill: var(--color-star);
}
@utility border-star {
border-color: var(--color-star);
}
/* ========================================
TYPE UTILITIES — display font + tabular numerals
`font-display` applies Onest; use it on brand lockups and hero
headings. `tabular-nums` aligns numeric columns in dense tables.
======================================== */
@utility font-display {
font-family: var(--font-display);
letter-spacing: 0.005em;
}
@utility tabular-nums {
font-variant-numeric: tabular-nums;
}
/* Card elevation. In dark mode, shadows disappear against a dark surface —
depth is communicated primarily by the base-100 → base-200 surface ramp
plus a white-alpha edge ring (see `.card-interactive` rule below). On
hover we add a neutral-black amplified shadow (~45× the light-mode
alpha). Tinted or glow shadows read as highlights, not elevation. */
[data-theme="dark"] {
--shadow-card-hover:
0 2px 4px -1px oklch(0% 0 0 / 0.55),
0 12px 24px -6px oklch(0% 0 0 / 0.45);
}
[data-theme="light"] {
--shadow-card-hover:
0 1px 2px oklch(0% 0 0 / 0.06),
0 8px 24px -6px oklch(0% 0 0 / 0.12);
}
/* ========================================
NAVBAR GHOST BUTTON HOVER
Override DaisyUI's neutral hover for nav icons
======================================== */
.navbar .btn-ghost:hover {
--btn-bg: oklch(from var(--color-secondary) l c h / 0.15);
--btn-border: transparent;
}
/* ========================================
STICKY FOOTER LAYOUT + BASE TYPE
======================================== */
@layer base {
body {
@apply min-h-screen flex flex-col;
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
main {
@apply flex-1;
}
/* Cap long prose at a comfortable reading width. Pages that need to
break out of this (dashboards, tables) override with max-w-* utilities. */
.prose {
max-width: 68ch;
}
/* Every table gets tabular-nums by default. Registry UIs are tables of
numbers (pull counts, sizes, CVE counts, timestamps) — digits should
align vertically so the eye can compare rows. */
table {
font-variant-numeric: tabular-nums;
}
}
/* ========================================
TYPOGRAPHY (PROSE) THEME INTEGRATION
======================================== */
@layer base {
/* Make prose inherit DaisyUI theme colors */
.prose {
--tw-prose-body: var(--color-base-content);
--tw-prose-headings: var(--color-base-content);
--tw-prose-lead: var(--color-base-content);
--tw-prose-links: var(--color-primary);
--tw-prose-bold: var(--color-base-content);
--tw-prose-counters: var(--color-base-content);
--tw-prose-bullets: var(--color-base-content);
--tw-prose-hr: var(--color-base-300);
--tw-prose-quotes: var(--color-base-content);
--tw-prose-quote-borders: var(--color-base-300);
--tw-prose-captions: var(--color-base-content);
--tw-prose-code: var(--color-base-content);
--tw-prose-pre-code: var(--color-base-content);
--tw-prose-pre-bg: var(--color-base-200);
--tw-prose-th-borders: var(--color-base-300);
--tw-prose-td-borders: var(--color-base-300);
}
}
/* ========================================
SVG SPRITE ICONS
======================================== */
.icon {
display: inline-block;
width: 1em;
height: 1em;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
vertical-align: -0.125em;
}
/* Icon sizes (matching Tailwind size-* utilities) */
.icon.size-3 { width: 0.75rem; height: 0.75rem; }
.icon.size-4 { width: 1rem; height: 1rem; }
.icon.size-5 { width: 1.25rem; height: 1.25rem; }
.icon.size-6 { width: 1.5rem; height: 1.5rem; }
.icon.size-8 { width: 2rem; height: 2rem; }
.icon.size-10 { width: 2.5rem; height: 2.5rem; }
.icon.size-12 { width: 3rem; height: 3rem; }
.icon.size-16 { width: 4rem; height: 4rem; }
.icon.size-20 { width: 5rem; height: 5rem; }
/* Special size for slightly larger than 1rem (used in star/pull count) */
.icon.size-\[1\.1rem\] { width: 1.1rem; height: 1.1rem; }
/* Animate spin for loader icons */
.icon.animate-spin {
animation: spin 1s linear infinite;
}
/* ========================================
ACCESSIBILITY UTILITIES
======================================== */
@layer utilities {
/* Screen reader only - visually hidden but accessible */
.sr-only {
@apply absolute w-px h-px p-0 -m-px overflow-hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Skip-to-content link. Hidden until keyboard-focused, then anchors at
the top-left so the user can press Enter to jump past the nav. */
.skip-link {
@apply absolute left-2 z-50 px-3 py-2 rounded-md;
@apply bg-primary text-primary-content font-medium text-sm;
top: -10rem;
transition: top 0.15s ease-out;
}
.skip-link:focus,
.skip-link:focus-visible {
top: 0.5rem;
}
}
/* ========================================
KEYBOARD FOCUS RING
Applied only on :focus-visible so mouse users don't see it.
Uses the primary hue so it reads as part of the Deep Ocean palette.
======================================== */
:where(a, button, [role="button"], [role="tab"], input, select, textarea, summary, [tabindex]):focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* ========================================
TOUCH TARGET SIZING
Small buttons and compact form controls meet the keyboard minimum on
desktop but fall below the 44×44 recommended touch target on touch
devices (WCAG 2.5.5). Grow them on any device that can't reliably
produce hover — covers pure touch as well as hybrid touchscreen
laptops where `pointer: coarse` alone misses.
======================================== */
@media (pointer: coarse), (hover: none) {
/* Icon-only buttons grow both axes — daisyUI's circle/square variants
are the marker for these. */
:is(.btn-circle, .btn-square):is(.btn-xs, .btn-sm) {
min-width: 2.75rem;
min-height: 2.75rem;
}
/* Text buttons only need vertical clearance — padding handles width. */
.btn-xs, .btn-sm {
min-height: 2.75rem;
}
/* Small checkbox/radio: expand the clickable region without distorting
the control itself — negative margin on the parent <label> would be
cleaner but requires markup changes; here we just grow the box. */
:is(.checkbox, .radio):is(.checkbox-xs, .radio-xs, .checkbox-sm, .radio-sm) {
min-width: 1.5rem;
min-height: 1.5rem;
}
/* daisyUI menu items (used in dropdowns, settings sidebar) are small
link-like elements. Ensure they meet the tap threshold. */
.menu li > a,
.menu li > button {
min-height: 2.75rem;
}
/* Custom tablist buttons (repo-tab, editor-tab) should match. Padding
already puts them near the threshold on desktop; lock in the floor. */
.repo-tab,
.editor-tab {
min-height: 2.75rem;
}
.sailor-typeahead-selected .sailor-typeahead-clear {
width: 2.75rem;
height: 2.75rem;
}
}
/* ========================================
COMMAND BLOCK ON NARROW VIEWPORTS
On desktop .cmd uses `truncate` for a tidy inline look. On mobile,
truncation hides the very thing the user wants to copy ("atcr.io/…"),
so switch to horizontal scrolling — the copy button still copies the
full text, but users can also see what they're about to paste.
======================================== */
@media (max-width: 40rem) {
.cmd code {
overflow-x: auto;
overflow-y: hidden;
text-overflow: clip;
/* Touch-friendly scroll momentum */
-webkit-overflow-scrolling: touch;
}
}
/* ========================================
REDUCED MOTION
Honor users who opt out of animation. Collapse all durations to a
near-instant value rather than removing transitions entirely, so
state changes still fire (transitionend listeners, etc.).
======================================== */
@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;
}
}
/* ========================================
FORCED COLORS (Windows High Contrast)
The OS strips box-shadow entirely, so any UI that communicates a
"separate surface" purely through shadow becomes invisible. Paint
explicit borders using system colors so edges survive. Hover/active
states use the Highlight system color which the OS also guarantees
is perceptually distinct.
======================================== */
@media (forced-colors: active) {
.card-interactive {
border: 1px solid CanvasText;
}
.card-interactive:hover {
outline: 2px solid Highlight;
outline-offset: 2px;
}
/* Severity badges lose their distinguishing fill in forced-colors;
fall back to a visible border so the severity strip still reads
as a series of distinct cells. */
.vuln-strip > span {
border: 1px solid CanvasText;
}
}
/* ========================================
CUSTOM COMPONENTS (Not in DaisyUI)
======================================== */
@layer components {
/* ----------------------------------------
COMMAND / CODE DISPLAY
---------------------------------------- */
.cmd {
@apply flex items-center gap-2 relative w-fit max-w-full;
@apply bg-base-300 rounded-md;
@apply px-3 py-2;
}
/* `min-w-0` + `flex-1` let the code shrink below its intrinsic width so
`truncate` can actually produce an ellipsis inside a flex container.
Without them, long commands overflow silently. `pr-10` reserves room
for the absolutely-positioned copy button so the ellipsis doesn't
sit under it. */
.cmd code {
@apply font-mono text-sm truncate min-w-0 flex-1 pr-10;
}
/* Copy button visibility:
- Touch / coarse-pointer devices (tap can't produce :hover and
rarely produces :focus): always visible at sm+ widths so users
can find the control.
- Hover-capable devices (desktop): hidden until the .cmd group is
hovered or the button itself focused, keeping the command line
visually tidy while power users still get the affordance.
Mobile (<sm) always shows the button regardless. */
.cmd .cmd-copy {
@apply opacity-100;
}
@media (hover: hover) and (pointer: fine) {
.cmd .cmd-copy {
@apply sm:opacity-0 transition-opacity;
}
.cmd:hover .cmd-copy,
.cmd .cmd-copy:focus,
.cmd .cmd-copy:focus-visible {
@apply opacity-100;
}
}
/* ----------------------------------------
EXPANDABLE SEARCH (nav-specific)
Uses transform instead of width for GPU-accelerated animation
---------------------------------------- */
.nav-search-wrapper {
@apply relative flex items-center;
}
.nav-search-form {
@apply absolute right-full mr-2;
@apply w-62 opacity-0;
@apply transition-[transform,opacity] duration-300;
transform: scaleX(0);
transform-origin: right;
}
.nav-search-wrapper.expanded .nav-search-form {
@apply opacity-100;
transform: scaleX(1);
}
/* ----------------------------------------
HELM BRAND COLOR (official Helm blue #0F1689)
Tokens live on :root (--color-helm-{light,dark}) so the value is
declared once and any future brand shift updates every consumer.
---------------------------------------- */
.text-helm {
color: var(--color-helm-light);
}
[data-theme="dark"] .text-helm {
color: var(--color-helm-dark);
}
.badge-helm {
--badge-color: var(--color-helm-light);
}
[data-theme="dark"] .badge-helm {
--badge-color: var(--color-helm-dark);
}
/* ----------------------------------------
TIER BADGE COLORS
---------------------------------------- */
.supporter-badge {
@apply badge-accent;
}
.supporter-badge-owner {
@apply badge-primary;
}
/* ----------------------------------------
CARD EXTENSIONS
---------------------------------------- */
/* Resting cards rely on surface lightness for elevation. In dark mode
we also paint a 1px inner white-alpha ring so the edge reads against
the slightly-lighter card surface — borders do more perceptual work
than shadows in dark. Hover then adds a real (neutral-black) shadow. */
.card-interactive {
position: relative;
transition: transform 250ms cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 250ms cubic-bezier(0.16, 1, 0.3, 1);
}
[data-theme="dark"] .card-interactive {
box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.06);
}
.card-interactive:hover {
box-shadow: var(--shadow-card-hover);
transform: translateY(-2px);
}
[data-theme="dark"] .card-interactive:hover {
box-shadow:
inset 0 0 0 1px oklch(100% 0 0 / 0.1),
var(--shadow-card-hover);
}
/* Stretched-link pattern: the whole card is one accessible link.
The anchor fills the card via absolute positioning; real interactive
descendants (owner/repo links, copy buttons, inline commands) raise
themselves above it with z-index so they remain clickable. */
.card-stretched-link {
position: absolute;
inset: 0;
z-index: 1;
border-radius: inherit;
text-decoration: none;
}
.card-stretched-link:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
.card-interactive :is(a:not(.card-stretched-link), button, .cmd, [role="button"]) {
position: relative;
z-index: 2;
}
/* ----------------------------------------
SAILOR TYPEAHEAD
---------------------------------------- */
.sailor-typeahead {
position: relative;
}
.sailor-typeahead-dropdown {
@apply absolute top-full left-0 right-0;
@apply bg-base-100 border border-base-300;
@apply rounded-lg shadow-lg;
@apply max-h-80 overflow-y-auto z-50;
margin-top: 0.25rem;
/* Don't escape the viewport on narrow screens — the dropdown is
anchored to its input, but if the input sits near a viewport
edge the right: 0 anchor can still overflow. */
max-width: calc(100vw - 1rem);
}
.sailor-typeahead-header {
@apply px-3 py-2 text-xs font-semibold uppercase;
@apply text-base-content/60 border-b border-base-300;
}
.sailor-typeahead-item {
@apply flex items-center gap-3 px-3 py-2.5;
@apply cursor-pointer transition-colors duration-150;
@apply text-base-content;
}
.sailor-typeahead-item:hover,
.sailor-typeahead-item.focused {
@apply bg-base-300;
}
.sailor-typeahead-item-compact {
@apply py-2;
}
.sailor-typeahead-avatar {
@apply shrink-0 w-9 h-9 rounded-full overflow-hidden;
@apply bg-base-300;
}
.sailor-typeahead-avatar img {
@apply w-full h-full object-cover;
}
.sailor-typeahead-text {
@apply flex-1 min-w-0;
}
.sailor-typeahead-name {
@apply text-sm font-medium truncate;
}
.sailor-typeahead-handle {
@apply text-xs text-base-content/60 truncate;
}
.sailor-typeahead-selected {
@apply flex items-center gap-3 w-full;
@apply border border-base-300 rounded-lg bg-base-100;
@apply px-4;
height: 3rem; /* matches input-lg */
cursor: default;
}
.sailor-typeahead-selected .sailor-typeahead-avatar {
@apply w-9 h-9;
}
.sailor-typeahead-selected .sailor-typeahead-clear {
@apply shrink-0 w-8 h-8 rounded-full;
@apply flex items-center justify-center;
@apply text-xl leading-none text-base-content/60;
@apply hover:bg-base-300 hover:text-base-content;
@apply transition-colors cursor-pointer;
}
/* ----------------------------------------
SAILOR INFO DISCLOSURE
---------------------------------------- */
.sailor-info summary {
@apply cursor-pointer text-base font-medium text-base-content/80;
@apply py-2 select-none;
list-style: none;
}
.sailor-info summary::-webkit-details-marker {
display: none;
}
.sailor-info summary::before {
content: '\25B8';
@apply inline-block mr-2 transition-transform;
}
.sailor-info[open] summary::before {
transform: rotate(90deg);
}
.sailor-info-body {
@apply text-base text-base-content/75 leading-relaxed space-y-3 pl-5 pt-1 pb-2;
}
/* ----------------------------------------
MENU FORM ITEM (styled like menu <a>)
For forms inside DaisyUI menu dropdowns
Uses label as visible element, button is sr-only
---------------------------------------- */
.menu li > form {
@apply w-full;
}
.menu li > form > label {
@apply block w-full;
}
/* ----------------------------------------
RESPONSIVE TABLE — STACK ON MOBILE
Below md, each row becomes a labeled block.
Add data-label="..." to each <td>.
---------------------------------------- */
@media (max-width: 47.999rem) {
.table-stack-mobile thead {
@apply sr-only;
}
.table-stack-mobile,
.table-stack-mobile tbody,
.table-stack-mobile tr,
.table-stack-mobile td {
display: block;
width: 100%;
}
.table-stack-mobile tr {
@apply border border-base-300 rounded-md mb-3 p-3 bg-base-100;
}
.table-stack-mobile tr:nth-child(even) {
@apply bg-base-200;
}
.table-stack-mobile td {
@apply py-1;
border: 0;
}
.table-stack-mobile td::before {
content: attr(data-label);
@apply block text-xs font-semibold uppercase tracking-wide text-base-content/60 mb-0.5;
}
.table-stack-mobile td[data-label=""]::before,
.table-stack-mobile td:not([data-label])::before {
content: none;
}
}
/* ----------------------------------------
MODAL — MOBILE GUTTER
DaisyUI's .modal-box is ~512px wide by default with no mobile clamp;
on viewports under that it presses against the screen edges. Force
a 1rem gutter on each side so the dialog reads as a card. Only
applies under the sm breakpoint so desktop max-w-* utilities win.
---------------------------------------- */
@media (max-width: 39.999rem) {
.modal .modal-box {
max-width: calc(100vw - 2rem);
}
}
/* ----------------------------------------
VULNERABILITY SEVERITY BOX STRIP
Docker Hub-style connected severity boxes
---------------------------------------- */
.vuln-strip {
@apply inline-flex items-stretch text-xs font-semibold leading-none;
/* Never spill out of a narrow column. The strip's natural width
(~115px on mobile) almost always fits; this is a safety net for
ultra-cramped table cells and side panels. */
max-width: 100%;
}
.vuln-strip > span {
@apply px-1 py-1 min-w-5 text-center cursor-pointer sm:px-2 sm:min-w-7;
}
.vuln-strip > span:first-child { @apply rounded-l-sm; }
.vuln-strip > span:last-child { @apply rounded-r-sm; }
.vuln-box-critical { background-color: var(--color-severity-critical); color: var(--color-severity-critical-content); }
.vuln-box-high { background-color: var(--color-severity-high); color: var(--color-severity-high-content); }
.vuln-box-medium { background-color: var(--color-severity-medium); color: var(--color-severity-medium-content); }
.vuln-box-low { background-color: var(--color-severity-low); color: var(--color-severity-low-content); }
/* ----------------------------------------
SIGNUP PROVIDER LIST
A single bordered container where each row is a "mooring" the user
can pick between. No per-row card, no nested borders, no shadows.
The single maritime gesture on this surface is the row divider:
a repeating short-dash pattern that reads as a depth-sounding plot
mark. It replaces a plain 1px rule without becoming decoration
(the dividers still do the work of separating rows).
Typography intent: the domain is rendered in the mono face because
the domain IS the identity — same thing users will see in the URL
bar on the provider's signup page. Mono treats it as an instrument
label, not prose.
---------------------------------------- */
.provider-row + .provider-row {
/* Background-image is a row of 4px dashes with 4px gaps —
the plotted-depth look without resorting to a border-image. */
background-image: linear-gradient(
to right,
var(--color-base-300) 0 4px,
transparent 4px 8px
);
background-repeat: repeat-x;
background-size: 8px 1px;
background-position: left top;
}
.provider-row-inner {
@apply flex items-center gap-4 p-4 sm:px-5;
@apply transition-colors duration-150;
background: transparent;
}
.provider-row:hover .provider-row-inner {
background: color-mix(in oklch, var(--color-base-200) 55%, transparent);
}
.provider-mark {
@apply shrink-0 w-10 h-10 rounded-full overflow-hidden bg-base-200;
@apply flex items-center justify-center;
/* Subtle inner ring so the avatar has a defined edge even when the
art fills corner-to-corner. */
box-shadow: inset 0 0 0 1px oklch(from var(--color-base-content) l c h / 0.08);
}
.provider-mark img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
/* Mono mark variant: the SVG is used as a CSS mask and the element's
background-color carries the theme-aware fill. One file per provider,
theme colors driven by CSS, no shipping of light/dark pairs. */
.provider-mark-mono {
display: block;
width: 60%;
height: 60%;
background-color: var(--color-base-content);
mask-image: var(--mark-url);
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-image: var(--mark-url);
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
}
.provider-body {
@apply flex-1 min-w-0 flex flex-col;
gap: 0.125rem;
}
.provider-title-row {
@apply flex items-baseline gap-2 flex-wrap;
}
.provider-domain {
font-family: var(--font-mono);
font-weight: 500;
font-size: 0.975rem;
letter-spacing: -0.01em;
color: var(--color-base-content);
}
/* Region chip. Tight, instrumenty. Tabular-like letterforms via the
mono face; flag emoji sits first and uses the system emoji font stack
to keep rendering consistent across platforms (Windows, Linux, macOS). */
.provider-chip {
@apply inline-flex items-center gap-1.5 px-2 py-[0.15rem] rounded-sm;
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 500;
letter-spacing: 0.04em;
color: color-mix(in oklch, var(--color-base-content) 70%, transparent);
background: color-mix(in oklch, var(--color-base-300) 55%, transparent);
}
.provider-chip-flag {
font-family:
"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
"Twemoji Mozilla", "EmojiOne Color", sans-serif;
font-size: 0.9em;
line-height: 1;
letter-spacing: 0;
}
.provider-meta {
@apply flex items-center gap-2 text-xs;
color: color-mix(in oklch, var(--color-base-content) 55%, transparent);
}
.provider-meta a {
@apply underline-offset-2;
}
.provider-meta a:hover {
@apply underline text-base-content;
}
.provider-meta span {
color: color-mix(in oklch, var(--color-base-content) 30%, transparent);
}
.provider-cta {
@apply shrink-0;
}
/* Mobile: avatar + domain on row 1; region/links + CTA on row 2 so the
CTA gets full width and the row doesn't wrap into 3 lines. */
@media (max-width: 32rem) {
.provider-row-inner {
display: grid;
grid-template-columns: 2.5rem 1fr;
grid-template-areas:
"mark body"
"cta cta";
row-gap: 0.85rem;
}
.provider-mark { grid-area: mark; }
.provider-body { grid-area: body; }
.provider-cta { grid-area: cta; width: 100%; justify-content: center; }
}
/* ----------------------------------------
SIGNUP CONTINUE — handoff mark
A slightly larger avatar ringed in primary, so the "you are leaving"
page reads as explicitly about the destination provider.
---------------------------------------- */
.signup-handoff-mark {
@apply w-16 h-16 rounded-full overflow-hidden bg-base-200;
@apply flex items-center justify-center;
box-shadow:
0 0 0 1px var(--color-base-300),
0 0 0 5px color-mix(in oklch, var(--color-primary) 12%, transparent);
}
.signup-handoff-mark img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.signup-handoff-mark .provider-mark-mono {
width: 60%;
height: 60%;
}
}
/* ========================================
HTMX REQUEST INDICATOR
Opt-in: elements with class `htmx-indicator` are hidden except during
an in-flight request. Keeps request UX declarative — no JS required.
======================================== */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { display: inline-block; }
/* ========================================
TOAST CONTAINER
======================================== */
#toast-container {
pointer-events: none;
}
#toast-container > * {
pointer-events: auto;
}
/* ========================================
SUPPORTER BADGE TEXT COLOR OVERRIDES
Unlayered — wins over DaisyUI's layered
.badge base class (utilities layer)
======================================== */
.supporter-badge { color: var(--color-accent-content); }
.supporter-badge-owner { color: var(--color-primary-content); }