- .summer-partial .summer-callout:not(:last-child) gets margin-bottom: 12px, so a feature-off callout no longer touches the stats strip under it - scoped to rendered partials: FormField.vue's field notice and the hint partials, whose callout is the last child, are unchanged
508 lines
12 KiB
CSS
508 lines
12 KiB
CSS
@import "tailwindcss";
|
|
|
|
/* The generated API document and types carry prose, not classes: keep them
|
|
out of the class scan so an admin API change does not churn the built CSS
|
|
in boardwalk/dist (D-04 drift check). Tests are not shipped either: words
|
|
in test code (a "blur" event, say) must never add utilities to the bundle. */
|
|
@source not "../api/schema.d.ts";
|
|
@source not "../../openapi";
|
|
@source not "../../tests";
|
|
|
|
/* Self-hosted fonts (D-07). Whole-weight files carry latin and latin-ext with
|
|
unicode-range, so Polish diacritics and basic Latin both resolve. */
|
|
@import "@fontsource/dm-sans/400.css";
|
|
@import "@fontsource/dm-sans/500.css";
|
|
@import "@fontsource/dm-sans/600.css";
|
|
@import "@fontsource/dm-sans/700.css";
|
|
@import "@fontsource/dm-mono/400.css";
|
|
@import "@fontsource/dm-mono/500.css";
|
|
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
|
|
/* Design tokens from design/README.md (Direction C v2). Utilities read CSS
|
|
variables so light and dark mode swap values without new classes. */
|
|
@theme {
|
|
--font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif;
|
|
--font-mono: "DM Mono", ui-monospace, monospace;
|
|
|
|
--color-bg: var(--c-bg);
|
|
--color-surface: var(--c-surface);
|
|
--color-subtle: var(--c-subtle);
|
|
--color-border: var(--c-border);
|
|
--color-border-strong: var(--c-border-strong);
|
|
--color-text: var(--c-text);
|
|
--color-muted: var(--c-muted);
|
|
--color-placeholder: var(--c-placeholder);
|
|
--color-primary: var(--c-primary);
|
|
--color-on-primary: var(--c-on-primary);
|
|
--color-danger: var(--c-danger);
|
|
--color-danger-soft: var(--c-danger-soft);
|
|
--color-ok-bg: var(--c-ok-bg);
|
|
--color-ok-text: var(--c-ok-text);
|
|
--color-ring: var(--c-ring);
|
|
--color-hover: var(--c-hover);
|
|
--color-sel: var(--c-sel);
|
|
--color-skel: var(--c-skel);
|
|
--color-overlay: var(--c-overlay);
|
|
--color-side: var(--c-side);
|
|
--color-side-border: var(--c-side-border);
|
|
--color-accent: #fcd34d;
|
|
--color-on-accent: #1b2540;
|
|
--color-accent-soft: rgba(252, 211, 77, 0.14);
|
|
--color-side-text: #c3cbda;
|
|
--color-side-label: #9aa6bd;
|
|
--color-side-hover: rgba(255, 255, 255, 0.08);
|
|
|
|
--radius-control: 10px;
|
|
--radius-card: 16px;
|
|
--radius-modal: 20px;
|
|
--radius-inner: 12px;
|
|
--radius-tab: 9px;
|
|
--radius-checkbox: 5px;
|
|
--radius-pager: 8px;
|
|
--radius-pill: 999px;
|
|
|
|
--spacing-button: 42px;
|
|
--spacing-input: 44px;
|
|
--spacing-header: 64px;
|
|
--spacing-nav: 40px;
|
|
--spacing-row: 54px;
|
|
--spacing-row-head: 44px;
|
|
--spacing-pager: 34px;
|
|
--spacing-rail: 96px;
|
|
--spacing-panel: 224px;
|
|
|
|
--shadow-card: var(--c-shadow-card);
|
|
--shadow-login: 0 24px 60px rgba(0, 0, 0, 0.35);
|
|
--shadow-pop: 0 16px 40px rgba(20, 27, 45, 0.18);
|
|
--shadow-menu: 0 16px 40px rgba(20, 27, 45, 0.16);
|
|
--shadow-toast: 0 16px 40px rgba(0, 0, 0, 0.25);
|
|
--shadow-tab: 0 1px 3px rgba(20, 27, 45, 0.12);
|
|
}
|
|
|
|
:root {
|
|
--c-bg: #f4f6f9;
|
|
--c-surface: #ffffff;
|
|
--c-subtle: #f3f5f8;
|
|
--c-border: #e6e9ef;
|
|
--c-border-strong: #d2d8e2;
|
|
--c-text: #141b2d;
|
|
--c-muted: #566175;
|
|
--c-placeholder: #6b7588;
|
|
--c-primary: #22304d;
|
|
--c-on-primary: #ffffff;
|
|
--c-danger: #c62828;
|
|
--c-danger-soft: #fdf0f0;
|
|
--c-ok-bg: #e3f4e8;
|
|
--c-ok-text: #1c6b35;
|
|
--c-ring: rgba(252, 196, 40, 0.55);
|
|
--c-hover: #f1f3f7;
|
|
--c-sel: #fdf3cf;
|
|
--c-skel: #eceff4;
|
|
--c-overlay: rgba(20, 27, 45, 0.5);
|
|
--c-side: #1d2740;
|
|
--c-side-border: transparent;
|
|
--c-shadow-card: 0 1px 2px rgba(20, 27, 45, 0.04), 0 4px 16px rgba(20, 27, 45, 0.05);
|
|
}
|
|
|
|
.dark {
|
|
color-scheme: dark;
|
|
--c-bg: #111726;
|
|
--c-surface: #182033;
|
|
--c-subtle: #1f283d;
|
|
--c-border: #29334b;
|
|
--c-border-strong: #3a4661;
|
|
--c-text: #eef1f6;
|
|
--c-muted: #a9b3c6;
|
|
--c-placeholder: #8a95ab;
|
|
--c-primary: #fcd34d;
|
|
--c-on-primary: #1b2540;
|
|
--c-danger: #f58a8a;
|
|
--c-danger-soft: #3a1d24;
|
|
--c-ok-bg: #173826;
|
|
--c-ok-text: #8fdfa8;
|
|
--c-ring: rgba(252, 211, 77, 0.45);
|
|
--c-hover: #212b42;
|
|
--c-sel: #3a3622;
|
|
--c-skel: #263049;
|
|
--c-overlay: rgba(5, 8, 16, 0.7);
|
|
--c-side: #0d1320;
|
|
--c-side-border: #222b40;
|
|
--c-shadow-card: none;
|
|
}
|
|
|
|
@layer base {
|
|
html {
|
|
font-family: var(--font-sans);
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
color: var(--c-text);
|
|
background: var(--c-bg);
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* Every interactive element shows the 3px ring; never remove it. */
|
|
a:focus-visible,
|
|
button:focus-visible,
|
|
[role="button"]:focus-visible,
|
|
[tabindex]:focus-visible {
|
|
outline: 3px solid var(--c-ring);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
input:focus-visible,
|
|
select:focus-visible,
|
|
textarea:focus-visible {
|
|
outline: none;
|
|
border-color: var(--c-primary);
|
|
box-shadow: 0 0 0 3px var(--c-ring);
|
|
}
|
|
|
|
input::placeholder,
|
|
textarea::placeholder {
|
|
color: var(--c-placeholder);
|
|
}
|
|
}
|
|
|
|
/* Motion (design: Interactions, Transitions): subtle only. Hover and
|
|
background changes use the 150 ms ease-out utilities on each control;
|
|
dialogs and their backdrop fade in and scale from 0.98 over 200 ms; toasts
|
|
fade in the same way. Closing is immediate, so Reka's presence never waits
|
|
on an exit animation. No decorative animation. */
|
|
@keyframes summer-fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes summer-dialog-in {
|
|
from {
|
|
opacity: 0;
|
|
scale: 0.98;
|
|
}
|
|
}
|
|
|
|
@layer components {
|
|
/* Backdrops of the picker (data-dialog-overlay) and of ConfirmDialog. */
|
|
.bg-overlay[data-state="open"] {
|
|
animation: summer-fade-in 200ms ease-out;
|
|
}
|
|
|
|
[data-dialog-content][data-state="open"],
|
|
[role="alertdialog"][data-state="open"] {
|
|
animation: summer-dialog-in 200ms ease-out;
|
|
}
|
|
|
|
.toast-enter-active {
|
|
transition:
|
|
opacity 200ms ease-out,
|
|
scale 200ms ease-out;
|
|
}
|
|
|
|
.toast-enter-from {
|
|
opacity: 0;
|
|
scale: 0.98;
|
|
}
|
|
|
|
/* Partial style kit (Phase 10.1, UI-SPEC): stable class names a plugin's
|
|
partial templates may use, so server-rendered content looks native
|
|
without plugin CSS. They read only the public --c-* variables. */
|
|
.summer-partial {
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
color: var(--c-text);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.summer-partial :where(p, ul, ol) {
|
|
margin: 0 0 8px;
|
|
}
|
|
|
|
.summer-partial :where(p, ul, ol):last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.summer-partial a {
|
|
color: var(--c-text);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.summer-partial a:focus-visible {
|
|
outline: 3px solid var(--c-ring);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Markdown preview: the server-rendered HTML of a markdown field. Preflight
|
|
strips heading, list and margin styles, so the pane restores them. */
|
|
.summer-markdown {
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
color: var(--c-text);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.summer-markdown :where(h1, h2, h3, h4) {
|
|
margin: 0 0 8px;
|
|
line-height: 1.3;
|
|
font-weight: 600;
|
|
color: var(--c-text);
|
|
}
|
|
|
|
.summer-markdown :where(h1) {
|
|
font-size: 22px;
|
|
}
|
|
|
|
.summer-markdown :where(h2) {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.summer-markdown :where(h3) {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.summer-markdown :where(h4) {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.summer-markdown :where(p, ul, ol, blockquote, pre, table) {
|
|
margin: 0 0 10px;
|
|
}
|
|
|
|
.summer-markdown :where(h1, h2, h3, h4, p, ul, ol, blockquote, pre, table):last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.summer-markdown :where(ul) {
|
|
list-style: disc;
|
|
padding-left: 22px;
|
|
}
|
|
|
|
.summer-markdown :where(ol) {
|
|
list-style: decimal;
|
|
padding-left: 22px;
|
|
}
|
|
|
|
.summer-markdown :where(code) {
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
background: var(--c-subtle);
|
|
border-radius: 4px;
|
|
padding: 1px 4px;
|
|
}
|
|
|
|
.summer-markdown :where(pre) {
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
background: var(--c-subtle);
|
|
border-radius: 8px;
|
|
padding: 10px 12px;
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
}
|
|
|
|
.summer-markdown :where(pre code) {
|
|
background: transparent;
|
|
padding: 0;
|
|
}
|
|
|
|
.summer-markdown :where(blockquote) {
|
|
border-left: 3px solid var(--c-border-strong);
|
|
padding-left: 12px;
|
|
color: var(--c-muted);
|
|
}
|
|
|
|
.summer-markdown :where(a) {
|
|
color: var(--c-text);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.summer-markdown :where(a):focus-visible {
|
|
outline: 3px solid var(--c-ring);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.summer-markdown :where(hr) {
|
|
border: 0;
|
|
border-top: 1px solid var(--c-border);
|
|
margin: 12px 0;
|
|
}
|
|
|
|
.summer-markdown :where(table) {
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.summer-markdown :where(th, td) {
|
|
border: 1px solid var(--c-border);
|
|
padding: 4px 8px;
|
|
text-align: left;
|
|
}
|
|
|
|
.summer-markdown :where(img) {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.summer-stats {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
column-gap: 32px;
|
|
row-gap: 8px;
|
|
margin: 0;
|
|
padding: 16px 20px;
|
|
background: var(--c-surface);
|
|
border: 1px solid var(--c-border);
|
|
border-radius: 16px;
|
|
box-shadow: var(--c-shadow-card);
|
|
}
|
|
|
|
.summer-stat {
|
|
display: flex;
|
|
flex-direction: column-reverse;
|
|
gap: 4px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.summer-stat__label {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
font-weight: 400;
|
|
color: var(--c-muted);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.summer-stat__value {
|
|
margin: 0;
|
|
font-size: 20px;
|
|
line-height: 1.2;
|
|
font-weight: 600;
|
|
color: var(--c-text);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* A note under a stat value (Phase 18, UI-SPEC F7): placed inside
|
|
.summer-stat__value after the value text, because .summer-stat is
|
|
column-reverse. */
|
|
.summer-stat__note {
|
|
display: block;
|
|
margin-top: 4px;
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
line-height: 1.5;
|
|
color: var(--c-muted);
|
|
}
|
|
|
|
/* Table kit (Phase 18, UI-SPEC F7): a native-looking table inside a
|
|
partial card. The wrapper scrolls horizontally inside the card. */
|
|
.summer-table-wrap {
|
|
overflow-x: auto;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.summer-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
color: var(--c-text);
|
|
}
|
|
|
|
.summer-table th {
|
|
height: var(--spacing-row-head);
|
|
padding: 0 14px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--c-muted);
|
|
text-align: start;
|
|
border-bottom: 1px solid var(--c-border);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.summer-table td {
|
|
padding: 12px 14px;
|
|
vertical-align: top;
|
|
border-bottom: 1px solid var(--c-border);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.summer-table :where(th, td):first-child {
|
|
padding-inline-start: 0;
|
|
}
|
|
|
|
.summer-table :where(th, td):last-child {
|
|
padding-inline-end: 0;
|
|
}
|
|
|
|
.summer-table__num {
|
|
text-align: end;
|
|
font-variant-numeric: tabular-nums;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.summer-table__sub {
|
|
display: block;
|
|
font-size: 13px;
|
|
color: var(--c-muted);
|
|
}
|
|
|
|
.summer-table tfoot td {
|
|
padding-top: 16px;
|
|
border-bottom: 0;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Callout (Phase 12.1, UI-SPEC): a status hint above a preview screen. */
|
|
.summer-callout {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
margin: 0;
|
|
padding: 14px 18px;
|
|
border-radius: 12px;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
/* Spaces a callout from the stats strip (or any sibling) that follows it inside a rendered partial. */
|
|
.summer-partial .summer-callout:not(:last-child) {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.summer-callout--warning {
|
|
background: var(--c-sel);
|
|
color: var(--c-text);
|
|
}
|
|
|
|
.summer-callout--danger {
|
|
background: var(--c-danger-soft);
|
|
color: var(--c-danger);
|
|
}
|
|
|
|
.summer-callout__title {
|
|
margin: 0;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.summer-callout__text {
|
|
margin: 0;
|
|
font-weight: 400;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.bg-overlay,
|
|
[data-dialog-content],
|
|
[role="alertdialog"] {
|
|
animation: none !important;
|
|
}
|
|
|
|
.toast-enter-active {
|
|
transition: none;
|
|
}
|
|
}
|