@volksverpetzer/ui-web 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/InputButton.css +25 -0
- package/dist/InputButton.d.ts +22 -0
- package/dist/InputButton.js +37 -0
- package/dist/ToolPage.css +113 -0
- package/dist/ToolPage.d.ts +39 -0
- package/dist/ToolPage.js +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/styles.css +140 -0
- package/package.json +2 -2
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.vvp-ui-input-button {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: block;
|
|
4
|
+
width: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Compound selectors (rather than relying on file load order) so these
|
|
9
|
+
* reliably win over Input's and Button's own same-specificity base rules
|
|
10
|
+
* regardless of how the package's CSS files get concatenated.
|
|
11
|
+
*/
|
|
12
|
+
.vvp-ui-input-button__field.vvp-ui-input {
|
|
13
|
+
padding-right: 7rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.vvp-ui-input-button__action.vvp-ui-btn {
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: 2px;
|
|
19
|
+
right: 2px;
|
|
20
|
+
bottom: 2px;
|
|
21
|
+
border-top-left-radius: 0;
|
|
22
|
+
border-bottom-left-radius: 0;
|
|
23
|
+
border-top-right-radius: var(--vvp-radius-md, 12px);
|
|
24
|
+
border-bottom-right-radius: var(--vvp-radius-md, 12px);
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import "./InputButton.css";
|
|
3
|
+
export interface InputButtonProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "children"> {
|
|
4
|
+
/** Rendered inside the embedded action button, e.g. "Shorten". */
|
|
5
|
+
buttonLabel: ReactNode;
|
|
6
|
+
/** Disables just the action button — the input stays interactive (e.g. while a submission triggered by it is in flight). */
|
|
7
|
+
buttonDisabled?: boolean;
|
|
8
|
+
/** aria-label for the action button, for icon-only labels. */
|
|
9
|
+
buttonAriaLabel?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A text input with a primary action button embedded in its right edge —
|
|
13
|
+
* the "paste a value and go" pattern (a URL shortener, an email signup
|
|
14
|
+
* field). Composes `Input` and `Button` (`variant="primary"`, `size="sm"`,
|
|
15
|
+
* `type="submit"`) rather than inventing new styling.
|
|
16
|
+
*
|
|
17
|
+
* The button's own rendered width is measured and applied as the input's
|
|
18
|
+
* right padding, so a longer label (e.g. a "Shortening…" loading state)
|
|
19
|
+
* never overlaps typed text. A fixed fallback covers the brief window
|
|
20
|
+
* before that measurement lands (first paint, or SSR).
|
|
21
|
+
*/
|
|
22
|
+
export declare const InputButton: import("react").ForwardRefExoticComponent<InputButtonProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useLayoutEffect, useRef, useState, } from "react";
|
|
3
|
+
import "./InputButton.css";
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
import { Input } from "./Input";
|
|
6
|
+
// useLayoutEffect warns when it runs during SSR (no DOM to measure yet);
|
|
7
|
+
// useEffect is a no-op there and picks up the real measurement on mount.
|
|
8
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
9
|
+
/**
|
|
10
|
+
* A text input with a primary action button embedded in its right edge —
|
|
11
|
+
* the "paste a value and go" pattern (a URL shortener, an email signup
|
|
12
|
+
* field). Composes `Input` and `Button` (`variant="primary"`, `size="sm"`,
|
|
13
|
+
* `type="submit"`) rather than inventing new styling.
|
|
14
|
+
*
|
|
15
|
+
* The button's own rendered width is measured and applied as the input's
|
|
16
|
+
* right padding, so a longer label (e.g. a "Shortening…" loading state)
|
|
17
|
+
* never overlaps typed text. A fixed fallback covers the brief window
|
|
18
|
+
* before that measurement lands (first paint, or SSR).
|
|
19
|
+
*/
|
|
20
|
+
export const InputButton = forwardRef(function InputButton({ buttonLabel, buttonDisabled, buttonAriaLabel, style, ...rest }, ref) {
|
|
21
|
+
const buttonRef = useRef(null);
|
|
22
|
+
const [buttonWidth, setButtonWidth] = useState(0);
|
|
23
|
+
useIsomorphicLayoutEffect(() => {
|
|
24
|
+
const button = buttonRef.current;
|
|
25
|
+
if (!button)
|
|
26
|
+
return;
|
|
27
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
28
|
+
setButtonWidth(entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width);
|
|
29
|
+
});
|
|
30
|
+
observer.observe(button);
|
|
31
|
+
return () => observer.disconnect();
|
|
32
|
+
}, []);
|
|
33
|
+
return (_jsxs("div", { className: "vvp-ui-input-button", children: [_jsx(Input, { ref: ref, className: "vvp-ui-input-button__field", style: {
|
|
34
|
+
...style,
|
|
35
|
+
paddingRight: buttonWidth ? buttonWidth + 8 : undefined,
|
|
36
|
+
}, ...rest }), _jsx(Button, { ref: buttonRef, type: "submit", size: "sm", disabled: buttonDisabled, "aria-label": buttonAriaLabel, className: "vvp-ui-input-button__action", children: buttonLabel })] }));
|
|
37
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
.vvp-ui-tool-page {
|
|
2
|
+
position: relative;
|
|
3
|
+
min-height: 100vh;
|
|
4
|
+
background: var(--vvp-background, #fff);
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.vvp-ui-tool-page__background {
|
|
9
|
+
position: absolute;
|
|
10
|
+
top: 0;
|
|
11
|
+
left: 0;
|
|
12
|
+
right: 0;
|
|
13
|
+
height: 220px;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
opacity: 0.1;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
z-index: 0;
|
|
18
|
+
mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
|
|
19
|
+
-webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.vvp-ui-tool-page__background svg {
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
display: block;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.vvp-ui-tool-page__topbar {
|
|
29
|
+
position: fixed;
|
|
30
|
+
top: var(--vvp-spacing-lg, 16px);
|
|
31
|
+
right: var(--vvp-spacing-lg, 16px);
|
|
32
|
+
z-index: 50;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ThemeToggle self-positions fixed top-right; inside our own topbar it
|
|
39
|
+
should lay out with the GitHub link instead, so it's un-fixed here. */
|
|
40
|
+
.vvp-ui-tool-page__topbar .vvp-ui-theme-toggle {
|
|
41
|
+
position: static;
|
|
42
|
+
top: auto;
|
|
43
|
+
right: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.vvp-ui-tool-page__icon-link {
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.vvp-ui-tool-page__inner {
|
|
53
|
+
position: relative;
|
|
54
|
+
z-index: 1;
|
|
55
|
+
max-width: 960px;
|
|
56
|
+
margin: 0 auto;
|
|
57
|
+
padding: var(--vvp-spacing-huge, 40px) var(--vvp-spacing-lg, 16px);
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
gap: var(--vvp-spacing-xl, 20px);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Matches ai.volksverpetzer.de's pre-results state: the header/content/
|
|
64
|
+
tags/help block sits in the vertical middle of the viewport rather than
|
|
65
|
+
pinned to the top. */
|
|
66
|
+
.vvp-ui-tool-page__inner--centered {
|
|
67
|
+
min-height: 70vh;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.vvp-ui-tool-page__header {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
76
|
+
text-align: center;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.vvp-ui-tool-page__title {
|
|
80
|
+
margin: 0;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
85
|
+
font-size: var(--vvp-font-size-xxxl, 30px);
|
|
86
|
+
font-weight: 700;
|
|
87
|
+
line-height: 1.2;
|
|
88
|
+
color: var(--vvp-text, #111);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.vvp-ui-tool-page__icon {
|
|
92
|
+
display: inline-flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
color: var(--vvp-primary, #1b7194);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.vvp-ui-tool-page__tags {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
gap: var(--vvp-spacing-xs, 4px);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.vvp-ui-tool-page__content {
|
|
105
|
+
width: 100%;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.vvp-ui-tool-page__help {
|
|
109
|
+
margin: 0;
|
|
110
|
+
text-align: center;
|
|
111
|
+
font-size: var(--vvp-font-size-sm, 14px);
|
|
112
|
+
color: var(--vvp-text-muted, #666);
|
|
113
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import type { BadgeVariant } from "./Badge";
|
|
3
|
+
import "./ToolPage.css";
|
|
4
|
+
export interface ToolPageTag {
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
variant?: BadgeVariant;
|
|
7
|
+
}
|
|
8
|
+
export interface ToolPageProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
title: ReactNode;
|
|
11
|
+
/** Small badges rendered below the title, e.g. feature/category tags. */
|
|
12
|
+
tags?: ToolPageTag[];
|
|
13
|
+
/** The page's actual content — a search input, an audio list, a form. */
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
/** Muted note rendered below `children` — usage hints, empty-state help. */
|
|
16
|
+
helpText?: ReactNode;
|
|
17
|
+
/** Renders a GitHub icon link in the top-right corner, next to the theme toggle. */
|
|
18
|
+
githubUrl?: string;
|
|
19
|
+
/** @default true */
|
|
20
|
+
background?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Vertically centers the icon/title/tags/content/helpText block in the
|
|
23
|
+
* middle of the viewport — the pre-results look on ai.volksverpetzer.de.
|
|
24
|
+
* Turn off once there's enough content that top-aligning it reads better
|
|
25
|
+
* (e.g. a long list) — this is a static flag, not a scroll-based one, so
|
|
26
|
+
* the caller decides when that switch happens.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
centered?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Full-page shell for a single-purpose tool page — the pattern shared by
|
|
33
|
+
* ai.volksverpetzer.de (icon + centered title, a search input, tag pills)
|
|
34
|
+
* and this app's audio converter list. Centered icon/title/tags header,
|
|
35
|
+
* full-width content slot, optional help text below it, a fixed top-right
|
|
36
|
+
* corner with `ThemeToggle` and an optional GitHub link, and (by default)
|
|
37
|
+
* a decorative jagged brand-blue shape along the top edge.
|
|
38
|
+
*/
|
|
39
|
+
export declare function ToolPage({ icon, title, tags, children, helpText, githubUrl, background, centered, className, ...rest }: ToolPageProps): import("react").JSX.Element;
|
package/dist/ToolPage.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "./Badge";
|
|
3
|
+
import { ThemeToggle } from "./ThemeToggle";
|
|
4
|
+
import "./ToolPage.css";
|
|
5
|
+
const GithubIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.57.1.79-.25.79-.55 0-.27-.01-1.16-.02-2.11-3.2.7-3.87-1.36-3.87-1.36-.53-1.33-1.29-1.69-1.29-1.69-1.05-.72.08-.7.08-.7 1.16.08 1.78 1.19 1.78 1.19 1.03 1.77 2.71 1.26 3.37.96.1-.75.4-1.26.73-1.55-2.55-.29-5.23-1.28-5.23-5.69 0-1.26.45-2.29 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.79 0c2.2-1.49 3.17-1.18 3.17-1.18.64 1.59.24 2.76.12 3.05.74.8 1.18 1.83 1.18 3.09 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.07.78 2.15 0 1.55-.01 2.81-.01 3.19 0 .3.21.66.79.55A10.52 10.52 0 0 0 23.5 12c0-6.35-5.15-11.5-11.5-11.5Z" }) }));
|
|
6
|
+
/**
|
|
7
|
+
* Full-page shell for a single-purpose tool page — the pattern shared by
|
|
8
|
+
* ai.volksverpetzer.de (icon + centered title, a search input, tag pills)
|
|
9
|
+
* and this app's audio converter list. Centered icon/title/tags header,
|
|
10
|
+
* full-width content slot, optional help text below it, a fixed top-right
|
|
11
|
+
* corner with `ThemeToggle` and an optional GitHub link, and (by default)
|
|
12
|
+
* a decorative jagged brand-blue shape along the top edge.
|
|
13
|
+
*/
|
|
14
|
+
export function ToolPage({ icon, title, tags, children, helpText, githubUrl, background = true, centered = false, className, ...rest }) {
|
|
15
|
+
const classes = ["vvp-ui-tool-page", className].filter(Boolean).join(" ");
|
|
16
|
+
const innerClasses = [
|
|
17
|
+
"vvp-ui-tool-page__inner",
|
|
18
|
+
centered && "vvp-ui-tool-page__inner--centered",
|
|
19
|
+
]
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.join(" ");
|
|
22
|
+
return (_jsxs("div", { className: classes, ...rest, children: [background ? (_jsx("div", { className: "vvp-ui-tool-page__background", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 1440 320", preserveAspectRatio: "none", fill: "var(--vvp-primary, #1b7194)", children: _jsx("path", { d: "M0,0 L0,140 L75,70 L155,160 L235,50 L310,170 L390,80 L465,175 L545,60 L620,150 L700,40 L775,165 L855,75 L935,175 L1010,55 L1090,145 L1165,65 L1245,165 L1320,85 L1440,130 L1440,0 Z" }) }) })) : null, _jsxs("div", { className: "vvp-ui-tool-page__topbar", children: [githubUrl ? (_jsx("a", { href: githubUrl, target: "_blank", rel: "noopener noreferrer", "aria-label": "GitHub", className: "vvp-ui-btn vvp-ui-btn--ghost vvp-ui-btn--sm vvp-ui-tool-page__icon-link", children: _jsx(GithubIcon, {}) })) : null, _jsx(ThemeToggle, {})] }), _jsxs("div", { className: innerClasses, children: [_jsxs("div", { className: "vvp-ui-tool-page__header", children: [_jsxs("h1", { className: "vvp-ui-tool-page__title", children: [icon ? (_jsx("span", { className: "vvp-ui-tool-page__icon", "aria-hidden": "true", children: icon })) : null, title] }), tags && tags.length > 0 ? (_jsx("div", { className: "vvp-ui-tool-page__tags", children: tags.map((tag, index) => (_jsx(Badge, { variant: tag.variant ?? "neutral", size: "sm", children: tag.label }, index))) })) : null] }), _jsx("div", { className: "vvp-ui-tool-page__content", children: children }), helpText ? _jsx("p", { className: "vvp-ui-tool-page__help", children: helpText }) : null] })] }));
|
|
23
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,3 +7,5 @@ export { Input, type InputProps } from "./Input";
|
|
|
7
7
|
export { Alert, type AlertProps, type AlertVariant } from "./Alert";
|
|
8
8
|
export { Slider, type SliderProps } from "./Slider";
|
|
9
9
|
export { ThemeToggle } from "./ThemeToggle";
|
|
10
|
+
export { InputButton, type InputButtonProps } from "./InputButton";
|
|
11
|
+
export { ToolPage, type ToolPageProps, type ToolPageTag } from "./ToolPage";
|
package/dist/index.js
CHANGED
package/dist/styles.css
CHANGED
|
@@ -217,6 +217,32 @@
|
|
|
217
217
|
outline-color: var(--vvp-error, #c62828);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
.vvp-ui-input-button {
|
|
221
|
+
position: relative;
|
|
222
|
+
display: block;
|
|
223
|
+
width: 100%;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* Compound selectors (rather than relying on file load order) so these
|
|
228
|
+
* reliably win over Input's and Button's own same-specificity base rules
|
|
229
|
+
* regardless of how the package's CSS files get concatenated.
|
|
230
|
+
*/
|
|
231
|
+
.vvp-ui-input-button__field.vvp-ui-input {
|
|
232
|
+
padding-right: 7rem;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.vvp-ui-input-button__action.vvp-ui-btn {
|
|
236
|
+
position: absolute;
|
|
237
|
+
top: 2px;
|
|
238
|
+
right: 2px;
|
|
239
|
+
bottom: 2px;
|
|
240
|
+
border-top-left-radius: 0;
|
|
241
|
+
border-bottom-left-radius: 0;
|
|
242
|
+
border-top-right-radius: var(--vvp-radius-md, 12px);
|
|
243
|
+
border-bottom-right-radius: var(--vvp-radius-md, 12px);
|
|
244
|
+
}
|
|
245
|
+
|
|
220
246
|
/*
|
|
221
247
|
* LinkButton's fixed look: a subtle bordered "pill card" with a soft
|
|
222
248
|
* drop shadow, distinct from Button's ghost variant (see LinkButton.tsx
|
|
@@ -406,3 +432,117 @@
|
|
|
406
432
|
z-index: 50;
|
|
407
433
|
}
|
|
408
434
|
|
|
435
|
+
.vvp-ui-tool-page {
|
|
436
|
+
position: relative;
|
|
437
|
+
min-height: 100vh;
|
|
438
|
+
background: var(--vvp-background, #fff);
|
|
439
|
+
overflow: hidden;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.vvp-ui-tool-page__background {
|
|
443
|
+
position: absolute;
|
|
444
|
+
top: 0;
|
|
445
|
+
left: 0;
|
|
446
|
+
right: 0;
|
|
447
|
+
height: 220px;
|
|
448
|
+
overflow: hidden;
|
|
449
|
+
opacity: 0.1;
|
|
450
|
+
pointer-events: none;
|
|
451
|
+
z-index: 0;
|
|
452
|
+
mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
|
|
453
|
+
-webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.vvp-ui-tool-page__background svg {
|
|
457
|
+
width: 100%;
|
|
458
|
+
height: 100%;
|
|
459
|
+
display: block;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.vvp-ui-tool-page__topbar {
|
|
463
|
+
position: fixed;
|
|
464
|
+
top: var(--vvp-spacing-lg, 16px);
|
|
465
|
+
right: var(--vvp-spacing-lg, 16px);
|
|
466
|
+
z-index: 50;
|
|
467
|
+
display: flex;
|
|
468
|
+
align-items: center;
|
|
469
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/* ThemeToggle self-positions fixed top-right; inside our own topbar it
|
|
473
|
+
should lay out with the GitHub link instead, so it's un-fixed here. */
|
|
474
|
+
.vvp-ui-tool-page__topbar .vvp-ui-theme-toggle {
|
|
475
|
+
position: static;
|
|
476
|
+
top: auto;
|
|
477
|
+
right: auto;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.vvp-ui-tool-page__icon-link {
|
|
481
|
+
display: inline-flex;
|
|
482
|
+
align-items: center;
|
|
483
|
+
justify-content: center;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.vvp-ui-tool-page__inner {
|
|
487
|
+
position: relative;
|
|
488
|
+
z-index: 1;
|
|
489
|
+
max-width: 960px;
|
|
490
|
+
margin: 0 auto;
|
|
491
|
+
padding: var(--vvp-spacing-huge, 40px) var(--vvp-spacing-lg, 16px);
|
|
492
|
+
display: flex;
|
|
493
|
+
flex-direction: column;
|
|
494
|
+
gap: var(--vvp-spacing-xl, 20px);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Matches ai.volksverpetzer.de's pre-results state: the header/content/
|
|
498
|
+
tags/help block sits in the vertical middle of the viewport rather than
|
|
499
|
+
pinned to the top. */
|
|
500
|
+
.vvp-ui-tool-page__inner--centered {
|
|
501
|
+
min-height: 70vh;
|
|
502
|
+
justify-content: center;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.vvp-ui-tool-page__header {
|
|
506
|
+
display: flex;
|
|
507
|
+
flex-direction: column;
|
|
508
|
+
align-items: center;
|
|
509
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
510
|
+
text-align: center;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.vvp-ui-tool-page__title {
|
|
514
|
+
margin: 0;
|
|
515
|
+
display: flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
justify-content: center;
|
|
518
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
519
|
+
font-size: var(--vvp-font-size-xxxl, 30px);
|
|
520
|
+
font-weight: 700;
|
|
521
|
+
line-height: 1.2;
|
|
522
|
+
color: var(--vvp-text, #111);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.vvp-ui-tool-page__icon {
|
|
526
|
+
display: inline-flex;
|
|
527
|
+
align-items: center;
|
|
528
|
+
color: var(--vvp-primary, #1b7194);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.vvp-ui-tool-page__tags {
|
|
532
|
+
display: flex;
|
|
533
|
+
flex-wrap: wrap;
|
|
534
|
+
justify-content: center;
|
|
535
|
+
gap: var(--vvp-spacing-xs, 4px);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.vvp-ui-tool-page__content {
|
|
539
|
+
width: 100%;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.vvp-ui-tool-page__help {
|
|
543
|
+
margin: 0;
|
|
544
|
+
text-align: center;
|
|
545
|
+
font-size: var(--vvp-font-size-sm, 14px);
|
|
546
|
+
color: var(--vvp-text-muted, #666);
|
|
547
|
+
}
|
|
548
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volksverpetzer/ui-web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared brand-visible React components (Button, Badge, Card, ProgressBar, Input, Alert, Slider, ThemeToggle) for vvp_link_shortener, vvp_divi5_extensions, crowdfunding, and vectorcrawl. Plain, hand-scoped CSS (vvp-ui-* class prefix) consuming the --vvp-* custom properties emitted by @volksverpetzer/design-tokens — no Tailwind classes, so it drops into a Tailwind app and Divi's WordPress-embedded CSS alike.",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Shared brand-visible React components (Button, Badge, Card, ProgressBar, Input, Alert, Slider, ThemeToggle, InputButton) for vvp_link_shortener, vvp_divi5_extensions, crowdfunding, and vectorcrawl. Plain, hand-scoped CSS (vvp-ui-* class prefix) consuming the --vvp-* custom properties emitted by @volksverpetzer/design-tokens — no Tailwind classes, so it drops into a Tailwind app and Divi's WordPress-embedded CSS alike.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|