mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-05-01 13:35:46 +00:00
1014 lines
35 KiB
CSS
1014 lines
35 KiB
CSS
/* ========================================
|
||
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 (~4–5× 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); }
|