@arcforgelabs/flow 2026.10.25 → 2026.10.27
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/README.md +6 -0
- package/npm-shrinkwrap.json +627 -2
- package/package.json +3 -1
- package/server/api/invoicing.js +586 -0
- package/server/app.js +38 -1
- package/server/auth/people.js +26 -0
- package/server/auth/routes.js +13 -0
- package/server/core/automations.js +140 -0
- package/server/core/invoice-rules.js +224 -0
- package/server/core/invoices.js +11 -1
- package/server/core/invoicing-rules.js +255 -0
- package/server/core/ledger.js +50 -14
- package/server/core/links.js +3 -3
- package/server/core/monitoring.js +34 -0
- package/server/core/pipeline.js +495 -0
- package/server/core/sending.js +282 -0
- package/server/db/migrations/0008_people_appearance.sql +1 -0
- package/server/db/migrations/0009_pipeline_matcher.sql +46 -0
- package/server/db/migrations/0010_invoicing_rules.sql +51 -0
- package/server/db/migrations/0011_invoice_rules.sql +21 -0
- package/server/db/migrations/0012_invoice_sending.sql +9 -0
- package/server/db/migrations/meta/0008_snapshot.json +2479 -0
- package/server/db/migrations/meta/0009_snapshot.json +2809 -0
- package/server/db/migrations/meta/0010_snapshot.json +3158 -0
- package/server/db/migrations/meta/0011_snapshot.json +3314 -0
- package/server/db/migrations/meta/0012_snapshot.json +3380 -0
- package/server/db/migrations/meta/_journal.json +35 -0
- package/server/db/schema.js +186 -2
- package/server/index.js +20 -3
- package/server/integrations/adapters.js +2 -1
- package/server/integrations/port.js +8 -0
- package/server/integrations/seazona/feed.js +11 -0
- package/server/integrations/seazona/index.js +7 -1
- package/server/integrations/seazona/ledger.js +4 -0
- package/server/integrations/sentry/index.js +105 -0
- package/server/integrations/xero/client.js +5 -0
- package/server/integrations/xero/drafts.js +123 -16
- package/server/integrations/xero/import.js +22 -0
- package/server/integrations/xero/index.js +21 -3
- package/server/integrations/xero/lookup.js +94 -0
- package/server/integrations/xero/migrations/0005_xero_send_modes.sql +10 -0
- package/server/integrations/xero/migrations/meta/_journal.json +7 -0
- package/server/theme/color-contrast.js +39 -2
- package/server/theme/contrast-audit.js +63 -0
- package/server/theme/themes.js +10 -2
- package/server/ui/app-page.js +6 -10
- package/server/ui/app.js +364 -105
- package/server/ui/appearance.js +3 -2
- package/server/ui/automations.js +96 -0
- package/server/ui/clerk-appearance.js +18 -2
- package/server/ui/flow.css +334 -22
- package/server/ui/invoice-rules.js +237 -0
- package/server/ui/invoicing-settings.js +108 -0
- package/server/ui/invoicing.js +597 -0
- package/server/ui/jobs.js +172 -0
- package/server/ui/logos/seazona.svg +1 -0
- package/server/ui/logos/sentry.svg +1 -0
- package/server/ui/logos/xero.svg +1 -0
- package/server/ui/monitoring.js +53 -0
- package/server/ui/routes.js +5 -1
package/server/ui/appearance.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Appearance in the browser: which design system (theme family), light or
|
|
2
2
|
// dark, and an optional accent colour. The families and their tokens live in
|
|
3
|
-
// server/theme/themes.js. The choice
|
|
4
|
-
//
|
|
3
|
+
// server/theme/themes.js. The choice belongs to the person's account
|
|
4
|
+
// (server/ui/app.js saves it); localStorage keeps this browser's copy so a
|
|
5
|
+
// page paints in it before sign-in is known. server/ui/page.js runs the same family and mode
|
|
5
6
|
// resolution inline before first paint so there is no flash; keep the two in
|
|
6
7
|
// step. Ported from Arc Forge Labs' Field (client/src/lib/color-mode.ts).
|
|
7
8
|
import { accentOverrides, normalizeAccent, normalizeFamily, tokensFor } from '/assets/theme/themes.js';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Automation rows (server/core/automations.js, /api/invoicing/automations).
|
|
2
|
+
// Each automation shows where its trigger lives: the work system's ready rule
|
|
3
|
+
// on that integration's page, the draft rule on Settings, Invoicing. One row
|
|
4
|
+
// for each: its name, one short line, and its switch, plus a row for each of
|
|
5
|
+
// its settings. A change saves at once. Everyone can read them; an owner or
|
|
6
|
+
// admin changes them. The caller passes in app.js's shared pieces as `ui`.
|
|
7
|
+
|
|
8
|
+
const DOCS = 'https://github.com/arcforgelabs/flow/blob/main/docs/AUTOMATIONS.md';
|
|
9
|
+
|
|
10
|
+
// A section of the automations kept at `place`, filled when they load.
|
|
11
|
+
export function automationSection(ui, place, title) {
|
|
12
|
+
const { h, flow, toast, notice, me } = ui;
|
|
13
|
+
const section = h('section', { class: 'fx-set' }, h('h2', { class: 'fx-set__title' }, title), h('div', { class: 'fx-skeleton' }));
|
|
14
|
+
const disabled = !['owner', 'admin'].includes(me?.person.role);
|
|
15
|
+
|
|
16
|
+
async function save(key, changes, control) {
|
|
17
|
+
control.disabled = true;
|
|
18
|
+
try {
|
|
19
|
+
const { automation } = await flow(`/api/invoicing/automations/${key}`, { method: 'PUT', body: JSON.stringify(changes) });
|
|
20
|
+
toast('Saved.', 'success');
|
|
21
|
+
return automation;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
toast(error.message, 'danger');
|
|
24
|
+
return null;
|
|
25
|
+
} finally {
|
|
26
|
+
control.disabled = disabled;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const row = (name, hint, control) => h('div', { class: 'fx-set__row' },
|
|
31
|
+
h('div', { class: 'fx-set__text' }, h('span', { class: 'fx-set__name' }, name), h('span', { class: 'fx-set__hint' }, hint)),
|
|
32
|
+
h('div', { class: 'fx-set__control' }, control));
|
|
33
|
+
|
|
34
|
+
// A menu of the statuses the work system reported, each with its job count.
|
|
35
|
+
// A status saved earlier stays in the menu even when no job has it now.
|
|
36
|
+
const statusPicker = (automation) => {
|
|
37
|
+
const summary = h('summary', { class: 'fx-select fx-multi__summary' });
|
|
38
|
+
const list = h('div', { class: 'fx-multi__list', role: 'group', 'aria-label': 'Ready statuses' }, h('div', { class: 'fx-skeleton' }));
|
|
39
|
+
const picker = h('details', { class: 'fx-multi' }, summary, list);
|
|
40
|
+
let options = [];
|
|
41
|
+
const show = () => {
|
|
42
|
+
const chosen = new Set(automation.config.statuses);
|
|
43
|
+
const names = options.filter((o) => chosen.has(o.value)).map((o) => o.label);
|
|
44
|
+
summary.textContent = names.length ? names.join(', ') : 'None';
|
|
45
|
+
list.replaceChildren(...(options.length ? options.map((o) => {
|
|
46
|
+
const box = h('input', { type: 'checkbox', checked: chosen.has(o.value), disabled });
|
|
47
|
+
box.addEventListener('change', async () => {
|
|
48
|
+
const next = options.map((x) => x.value).filter((v) => (v === o.value ? box.checked : chosen.has(v)));
|
|
49
|
+
if (!next.length) {
|
|
50
|
+
box.checked = true;
|
|
51
|
+
toast('Pick at least one status. To stop it, turn the automation off.', 'danger');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const saved = await save(automation.key, { config: { statuses: next } }, box);
|
|
55
|
+
if (saved) automation.config = saved.config;
|
|
56
|
+
show();
|
|
57
|
+
});
|
|
58
|
+
return h('label', { class: 'fx-multi__option' }, box, h('span', {}, o.label), h('span', { class: 'fx-dim' }, String(o.jobs)));
|
|
59
|
+
}) : [h('p', { class: 'fx-dim', style: { margin: 0 } }, 'Seazona has reported no statuses yet.')]));
|
|
60
|
+
};
|
|
61
|
+
(async () => {
|
|
62
|
+
try {
|
|
63
|
+
({ statuses: options } = await flow('/api/invoicing/work-statuses'));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
options = [];
|
|
66
|
+
toast(error.message, 'danger');
|
|
67
|
+
}
|
|
68
|
+
const known = new Set(options.map((o) => o.value));
|
|
69
|
+
for (const value of automation.config.statuses) if (!known.has(value)) options.push({ value, label: value, jobs: 0 });
|
|
70
|
+
show();
|
|
71
|
+
})();
|
|
72
|
+
return picker;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const rowsOf = (automation) => {
|
|
76
|
+
const button = h('button', { class: 'fx-switch', type: 'button', role: 'switch', 'aria-label': automation.name, 'aria-checked': String(automation.enabled), disabled });
|
|
77
|
+
button.addEventListener('click', async () => {
|
|
78
|
+
const saved = await save(automation.key, { enabled: button.getAttribute('aria-checked') !== 'true' }, button);
|
|
79
|
+
if (saved) button.setAttribute('aria-checked', String(saved.enabled));
|
|
80
|
+
});
|
|
81
|
+
const out = [row(automation.name, automation.hint, button)];
|
|
82
|
+
if (automation.config.statuses) out.push(row('Ready statuses', 'Picked from the statuses Seazona reports.', statusPicker(automation)));
|
|
83
|
+
return out;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
(async () => {
|
|
87
|
+
try {
|
|
88
|
+
const { automations } = await flow(`/api/invoicing/automations?place=${encodeURIComponent(place)}`);
|
|
89
|
+
section.replaceChildren(h('h2', { class: 'fx-set__title' }, title), ...automations.flatMap(rowsOf),
|
|
90
|
+
h('p', { class: 'fx-dim', style: { margin: '8px 0 0' } }, h('a', { href: DOCS, target: '_blank', rel: 'noopener' }, 'How automations work')));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
section.replaceChildren(h('h2', { class: 'fx-set__title' }, title), notice(error.message, 'danger'));
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
return section;
|
|
96
|
+
}
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
// --t-* tokens at render time, so Clerk changes with the theme and mode.
|
|
4
4
|
// Ported from Arc Forge Labs' Field (client/src/lib/clerk-appearance.ts).
|
|
5
5
|
|
|
6
|
+
// Clerk's "Manage account" cog has no hub, so at 16px it reads as a blob.
|
|
7
|
+
// This gear has the same 1.5 stroke as Clerk's sign-out icon beside it.
|
|
8
|
+
const GEAR = `url("data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linejoin="round"><path d="M7.06 3.45L7.20 2.05L8.80 2.05L8.94 3.45L10.55 4.11L11.64 3.23L12.77 4.36L11.89 5.45L12.55 7.06L13.95 7.20L13.95 8.80L12.55 8.94L11.89 10.55L12.77 11.64L11.64 12.77L10.55 11.89L8.94 12.55L8.80 13.95L7.20 13.95L7.06 12.55L5.45 11.89L4.36 12.77L3.23 11.64L4.11 10.55L3.45 8.94L2.05 8.80L2.05 7.20L3.45 7.06L4.11 5.45L3.23 4.36L4.36 3.23L5.45 4.11Z"/><circle cx="8" cy="8" r="2"/></svg>')}")`;
|
|
9
|
+
const gearIcon = {
|
|
10
|
+
'&&': { background: 'currentColor', mask: `${GEAR} center / contain no-repeat`, WebkitMask: `${GEAR} center / contain no-repeat` },
|
|
11
|
+
'&& > *': { visibility: 'hidden' },
|
|
12
|
+
};
|
|
13
|
+
|
|
6
14
|
function token(name, fallback) {
|
|
7
15
|
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
8
16
|
return value || fallback;
|
|
@@ -33,13 +41,14 @@ export function flowClerkAppearance({ embedded = false, publicSignUp = false } =
|
|
|
33
41
|
const danger = token('--t-danger', '#b8442a');
|
|
34
42
|
const inputEdge = { ...controlEdge, '&&[data-feedback="error"]': { boxShadow: edge(danger) } };
|
|
35
43
|
const display = token('--t-display', 'ui-serif, Georgia, serif');
|
|
44
|
+
const dim = token('--t-dim', '#5b6773');
|
|
36
45
|
return {
|
|
37
46
|
variables: {
|
|
38
47
|
colorPrimary: token('--t-primary', '#1f6f8f'),
|
|
39
48
|
colorPrimaryForeground: token('--t-on-primary', '#ffffff'),
|
|
40
49
|
colorBackground: token('--t-paper', '#ffffff'),
|
|
41
50
|
colorForeground: text,
|
|
42
|
-
colorMutedForeground:
|
|
51
|
+
colorMutedForeground: dim,
|
|
43
52
|
colorMuted: token('--t-s1', '#f7f9fb'),
|
|
44
53
|
colorInput: token('--t-paper', '#ffffff'),
|
|
45
54
|
colorInputForeground: text,
|
|
@@ -47,7 +56,10 @@ export function flowClerkAppearance({ embedded = false, publicSignUp = false } =
|
|
|
47
56
|
colorSuccess: token('--t-success', '#3f6b2a'),
|
|
48
57
|
colorWarning: token('--t-warn', '#8a5a00'),
|
|
49
58
|
colorNeutral: text,
|
|
50
|
-
|
|
59
|
+
// Clerk builds its border shades (dividers, hover edges, the badge text)
|
|
60
|
+
// as fractions of colorBorder, so it takes the opaque text colour: a
|
|
61
|
+
// colour already faded to 12% left the "Primary" badge near 1:1.
|
|
62
|
+
colorBorder: text,
|
|
51
63
|
colorRing: token('--t-ring', '#1f6f8f'),
|
|
52
64
|
fontFamily: token('--t-body', 'ui-sans-serif, system-ui, sans-serif'),
|
|
53
65
|
fontSize: '14px',
|
|
@@ -67,9 +79,13 @@ export function flowClerkAppearance({ embedded = false, publicSignUp = false } =
|
|
|
67
79
|
scrollBox: embedded ? { borderRadius: 0 } : undefined,
|
|
68
80
|
formFieldInput: inputEdge,
|
|
69
81
|
otpCodeFieldInput: inputEdge,
|
|
82
|
+
// Clerk's default badge ("Primary" by an email address) draws its text
|
|
83
|
+
// in a 62% border shade, under 4.5:1 on light papers; --t-dim reads.
|
|
84
|
+
badge: { '&&': { color: dim, background: `color-mix(in srgb, ${text} 5%, transparent)`, borderColor: line } },
|
|
70
85
|
formButtonPrimary: { boxShadow: 'none', textTransform: 'none', fontWeight: 650 },
|
|
71
86
|
socialButtonsBlockButton: controlEdge,
|
|
72
87
|
userButtonPopoverCard: { border: `1px solid ${line}`, boxShadow: shadow },
|
|
88
|
+
userButtonPopoverActionButtonIcon__manageAccount: gearIcon,
|
|
73
89
|
footer: embedded ? { display: 'none' } : undefined,
|
|
74
90
|
},
|
|
75
91
|
};
|
package/server/ui/flow.css
CHANGED
|
@@ -106,25 +106,32 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
|
|
|
106
106
|
}
|
|
107
107
|
.fx-panel { overflow: hidden; }
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
.fx-
|
|
109
|
+
/* The signed-in app: a standard left sidebar, then the view. */
|
|
110
|
+
.fx-shell { min-height: 100vh; }
|
|
111
|
+
.fx-side {
|
|
111
112
|
position: sticky; top: 0; z-index: 10;
|
|
112
|
-
display: flex;
|
|
113
|
-
height:
|
|
114
|
-
background:
|
|
115
|
-
border-bottom: 1px solid var(--fx-line);
|
|
116
|
-
backdrop-filter: saturate(1.2) blur(8px);
|
|
113
|
+
display: flex; flex-direction: column; gap: 4px; flex: none;
|
|
114
|
+
width: 232px; height: 100vh; padding: 16px 12px 12px;
|
|
115
|
+
background: var(--t-s1); border-right: 1px solid var(--fx-line);
|
|
117
116
|
}
|
|
118
|
-
.fx-
|
|
117
|
+
.fx-side > .fx-brand { padding: 4px 8px 18px; font-size: 19px; }
|
|
118
|
+
.fx-side > .fx-brand .fx-mark { width: 26px; height: 26px; }
|
|
119
|
+
.fx-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; min-height: 0; overflow-y: auto; }
|
|
120
|
+
.fx-nav__group { padding: 16px 10px 6px; color: var(--t-dim); font: var(--t-label-weight) var(--t-label-size)/1.3 var(--t-body); letter-spacing: var(--t-label-spacing); text-transform: var(--t-label-case); }
|
|
121
|
+
.fx-nav__group:first-child { padding-top: 4px; }
|
|
119
122
|
.fx-nav a {
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
display: flex; align-items: center; gap: 10px;
|
|
124
|
+
padding: 8px 10px; border-radius: var(--t-radius);
|
|
125
|
+
color: var(--t-text); font-weight: 500; font-size: 14px; text-decoration: none; white-space: nowrap;
|
|
122
126
|
}
|
|
123
127
|
.fx-nav a:hover { color: var(--t-strong); background: var(--fx-hover); }
|
|
124
|
-
.fx-nav a[aria-current="page"] { color: var(--t-
|
|
125
|
-
.fx-
|
|
126
|
-
.fx-
|
|
127
|
-
.fx-
|
|
128
|
+
.fx-nav a[aria-current="page"] { color: var(--t-accent); background: var(--t-accent-bg); font-weight: 650; box-shadow: inset 2px 0 0 var(--t-brand); }
|
|
129
|
+
.fx-nav__count { margin-left: auto; color: var(--t-dim); font-size: 12px; font-weight: 700; }
|
|
130
|
+
.fx-ico { display: inline-flex; flex: none; }
|
|
131
|
+
.fx-ico svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
|
132
|
+
.fx-side__end { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 6px 0; border-top: 1px solid var(--fx-line); }
|
|
133
|
+
.fx-side__user { display: flex; min-width: 32px; min-height: 32px; align-items: center; }
|
|
134
|
+
.fx-main { flex: 1; min-width: 0; max-width: 1180px; margin: 0 auto; padding: 32px clamp(16px, 4vw, 48px) 64px; display: grid; gap: 20px; align-content: start; }
|
|
128
135
|
.fx-main:focus { outline: none; box-shadow: none; }
|
|
129
136
|
|
|
130
137
|
/* Page header: title and one line, then the actions, primary last. */
|
|
@@ -156,6 +163,7 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
|
|
|
156
163
|
.fx-status[data-tone="success"] { color: var(--t-success); background: var(--t-success-bg); }
|
|
157
164
|
.fx-status[data-tone="warn"] { color: var(--t-warn); background: var(--t-warn-bg); }
|
|
158
165
|
.fx-status[data-tone="mute"] { color: var(--t-dim); background: var(--fx-hover); }
|
|
166
|
+
.fx-status[data-tone="danger"] { color: var(--t-danger); background: var(--t-danger-bg); }
|
|
159
167
|
.fx-tag {
|
|
160
168
|
margin-left: 8px; padding: 1px 7px; border: 1px solid var(--fx-line); border-radius: 999px;
|
|
161
169
|
color: var(--t-dim); font-size: 11.5px; font-weight: 600;
|
|
@@ -195,6 +203,207 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
|
|
|
195
203
|
.fx-roles dt { color: var(--t-strong); font-weight: 600; }
|
|
196
204
|
.fx-roles dd { margin: 0; }
|
|
197
205
|
|
|
206
|
+
/* ── Integrations ───────────────────────────────────────────────────────── */
|
|
207
|
+
.fx-chip { border: 1px solid var(--fx-line); border-radius: 999px; background: var(--t-paper); color: var(--t-text); }
|
|
208
|
+
.fx-int-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
|
|
209
|
+
.fx-int-bar .fx-search { margin: 0; }
|
|
210
|
+
.fx-int-sections { display: grid; gap: 32px; }
|
|
211
|
+
.fx-int-section-list { display: grid; gap: 16px; }
|
|
212
|
+
.fx-int-heading { color: var(--t-strong); font: 600 19px/1.3 var(--t-body); }
|
|
213
|
+
.fx-int-heading-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
214
|
+
.fx-int-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
|
|
215
|
+
.fx-int-card {
|
|
216
|
+
display: grid; grid-template-rows: auto 1fr auto; gap: 14px; padding: 18px;
|
|
217
|
+
border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); background: var(--t-paper);
|
|
218
|
+
color: var(--t-text); text-decoration: none;
|
|
219
|
+
transition: transform 140ms var(--fx-ease), box-shadow 140ms var(--fx-ease), border-color 140ms var(--fx-ease);
|
|
220
|
+
}
|
|
221
|
+
.fx-int-card:hover, .fx-int-card:focus-visible { transform: translateY(-2px); border-color: var(--t-brd-hi, var(--t-control)); box-shadow: 0 8px 24px rgba(0, 0, 0, .1); }
|
|
222
|
+
.fx-int-card__head { display: flex; align-items: center; gap: 12px; }
|
|
223
|
+
.fx-int-card__head h3 { flex: 1; color: var(--t-strong); font: 650 16px/1.25 var(--t-body); }
|
|
224
|
+
.fx-int-card__blurb { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 2.9em; color: var(--t-text); font-size: 14px; line-height: 1.45; }
|
|
225
|
+
.fx-int-card__btn {
|
|
226
|
+
display: flex; align-items: center; justify-content: center; min-height: 34px;
|
|
227
|
+
border: 1px solid var(--t-control); border-radius: var(--t-radius); color: var(--t-strong); font-weight: 600; font-size: 14px;
|
|
228
|
+
transition: background-color 140ms var(--fx-ease), color 140ms var(--fx-ease), border-color 140ms var(--fx-ease);
|
|
229
|
+
}
|
|
230
|
+
.fx-int-card:hover .fx-int-card__btn, .fx-int-card:focus-visible .fx-int-card__btn { background: var(--t-primary); border-color: var(--t-primary); color: var(--t-on-primary); }
|
|
231
|
+
.fx-logo {
|
|
232
|
+
display: grid; place-items: center; flex: none; width: 44px; height: 44px; border-radius: 10px;
|
|
233
|
+
background: #fff; box-shadow: inset 0 0 0 1px var(--fx-line);
|
|
234
|
+
}
|
|
235
|
+
.fx-logo img { width: 28px; height: 28px; object-fit: contain; }
|
|
236
|
+
.fx-logo--lg { width: 52px; height: 52px; }
|
|
237
|
+
.fx-logo--lg img { width: 34px; height: 34px; }
|
|
238
|
+
|
|
239
|
+
.fx-logo--xl { width: 112px; height: 112px; border-radius: 22px; }
|
|
240
|
+
.fx-logo--xl img { width: 68px; height: 68px; }
|
|
241
|
+
.fx-chip--static { display: inline-flex; align-items: center; min-height: 26px; padding: 0 10px; font-size: 12.5px; cursor: default; }
|
|
242
|
+
|
|
243
|
+
/* One integration's page: hero, then the setup beside its facts. */
|
|
244
|
+
.fx-int-page { display: grid; gap: 24px; }
|
|
245
|
+
.fx-int-hero { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 24px; }
|
|
246
|
+
.fx-int-hero__text { display: grid; gap: 8px; flex: 1; min-width: 260px; }
|
|
247
|
+
.fx-int-hero__text .fx-title { font-size: 34px; }
|
|
248
|
+
.fx-int-hero__maker { display: flex; gap: 10px; align-items: baseline; color: var(--t-strong); font-weight: 650; }
|
|
249
|
+
.fx-int-hero__blurb { max-width: 64ch; color: var(--t-text); font-size: 15.5px; }
|
|
250
|
+
.fx-int-hero__actions { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 6px; }
|
|
251
|
+
.fx-int-page__grid { display: grid; grid-template-columns: minmax(0, 1fr) 240px; gap: 32px; align-items: start; }
|
|
252
|
+
.fx-int-page__main { display: grid; gap: 28px; }
|
|
253
|
+
.fx-int-section { display: grid; gap: 14px; padding-top: 20px; border-top: 1px solid var(--fx-line); }
|
|
254
|
+
.fx-int-section__title { color: var(--t-strong); font: 650 17px/1.3 var(--t-body); }
|
|
255
|
+
.fx-int-section .fx-btn { justify-self: start; }
|
|
256
|
+
.fx-steps { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; counter-reset: step; }
|
|
257
|
+
.fx-steps li { position: relative; padding-left: 38px; min-height: 26px; counter-increment: step; font-size: 14.5px; line-height: 1.55; }
|
|
258
|
+
.fx-steps li::before {
|
|
259
|
+
content: counter(step); position: absolute; left: 0; top: 0; display: grid; place-items: center; width: 26px; height: 26px;
|
|
260
|
+
border-radius: 50%; background: var(--t-accent-bg); color: var(--t-accent); box-shadow: inset 0 0 0 1px var(--t-accent-brd); font-size: 12.5px; font-weight: 700;
|
|
261
|
+
}
|
|
262
|
+
.fx-int-page__meta { position: sticky; top: 24px; }
|
|
263
|
+
.fx-meta { display: grid; gap: 0; margin: 0; }
|
|
264
|
+
.fx-meta > div { display: grid; gap: 6px; padding: 14px 0; border-bottom: 1px solid var(--fx-line-soft); }
|
|
265
|
+
.fx-meta > div:first-child { padding-top: 0; }
|
|
266
|
+
.fx-meta dt { color: var(--t-dim); font-size: 12.5px; font-weight: 600; }
|
|
267
|
+
.fx-meta dd { margin: 0; color: var(--t-strong); font-size: 14px; justify-self: start; }
|
|
268
|
+
.fx-progress {
|
|
269
|
+
position: absolute; z-index: 15; top: calc(100% + 8px); left: 0; display: grid; gap: 10px; width: min(360px, 90vw); padding: 14px 16px;
|
|
270
|
+
border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); background: var(--t-paper); box-shadow: 0 16px 40px rgba(0, 0, 0, .18);
|
|
271
|
+
animation: fx-rise 140ms var(--fx-ease);
|
|
272
|
+
}
|
|
273
|
+
.fx-progress__head { display: flex; justify-content: space-between; }
|
|
274
|
+
.fx-progress ol { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; font-size: 14px; }
|
|
275
|
+
.fx-progress li { display: flex; align-items: center; gap: 10px; color: var(--t-strong); }
|
|
276
|
+
.fx-progress li[data-state="waiting"] { color: var(--t-dim); }
|
|
277
|
+
.fx-progress li[data-state="failed"] { color: var(--t-danger); }
|
|
278
|
+
.fx-progress__mark { display: grid; place-items: center; flex: none; width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--fx-line); color: #fff; font-size: 12px; font-weight: 800; }
|
|
279
|
+
.fx-progress li[data-state="done"] .fx-progress__mark { background: var(--t-success); border-color: var(--t-success); }
|
|
280
|
+
.fx-progress li[data-state="failed"] .fx-progress__mark { background: var(--t-danger); border-color: var(--t-danger); }
|
|
281
|
+
.fx-progress li[data-state="running"] .fx-progress__mark { border-color: var(--fx-line); border-top-color: var(--t-brand); animation: fx-spin 800ms linear infinite; }
|
|
282
|
+
.fx-spinner--sm { width: 14px; height: 14px; border-width: 2px; border-color: color-mix(in srgb, currentColor 35%, transparent); border-top-color: currentColor; }
|
|
283
|
+
@media (max-width: 900px) {
|
|
284
|
+
.fx-int-page__grid { grid-template-columns: minmax(0, 1fr); }
|
|
285
|
+
.fx-int-page__meta { position: static; }
|
|
286
|
+
.fx-logo--xl { width: 72px; height: 72px; border-radius: 16px; }
|
|
287
|
+
.fx-logo--xl img { width: 44px; height: 44px; }
|
|
288
|
+
}
|
|
289
|
+
.fx-drawer .fx-select { width: 100%; }
|
|
290
|
+
|
|
291
|
+
/* ── Invoices ───────────────────────────────────────────────────────────── */
|
|
292
|
+
.fx-num { text-align: right; font-family: var(--t-data, var(--t-body)); font-variant-numeric: tabular-nums; }
|
|
293
|
+
.fx-strong { color: var(--t-strong); font-weight: 600; }
|
|
294
|
+
a.fx-strong { text-decoration: none; }
|
|
295
|
+
a.fx-strong:hover { text-decoration: underline; }
|
|
296
|
+
.fx-toolbar { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--fx-line); }
|
|
297
|
+
.fx-tabs { display: flex; gap: 2px; }
|
|
298
|
+
.fx-tabs button { flex: none; }
|
|
299
|
+
@media (max-width: 640px) { .fx-tabs { max-width: 100%; overflow-x: auto; scrollbar-width: none; } }
|
|
300
|
+
.fx-tabs button {
|
|
301
|
+
display: inline-flex; align-items: center; gap: 8px; padding: 10px 14px; margin-bottom: -1px;
|
|
302
|
+
border: 0; border-bottom: 2px solid transparent; background: transparent; cursor: pointer;
|
|
303
|
+
color: var(--t-dim); font: 600 14px var(--t-body);
|
|
304
|
+
}
|
|
305
|
+
.fx-tabs button:hover { color: var(--t-strong); }
|
|
306
|
+
.fx-tabs button[aria-selected="true"] { color: var(--t-strong); border-bottom-color: var(--t-brand); }
|
|
307
|
+
.fx-tabs__count { padding: 0 7px; border-radius: 999px; background: var(--fx-hover); color: var(--t-dim); font-size: 12px; line-height: 20px; }
|
|
308
|
+
.fx-tabs button[data-tone="danger"] .fx-tabs__count { background: var(--t-danger-bg); color: var(--t-danger); }
|
|
309
|
+
/* Invoices: the Send all ready bar above the Ready list. */
|
|
310
|
+
.fx-sendbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; padding: 12px 16px; border: 1px solid var(--fx-line); border-radius: 8px; background: var(--t-paper); margin-top: 12px; }
|
|
311
|
+
.fx-sendbar[hidden] { display: none; }
|
|
312
|
+
.fx-sendbar p { margin: 0; }
|
|
313
|
+
.fx-problem { color: var(--t-danger); }
|
|
314
|
+
.fx-sendbar__actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
315
|
+
.fx-tick-cell { width: 40px; }
|
|
316
|
+
.fx-tick { width: 16px; height: 16px; accent-color: var(--t-brand); cursor: pointer; }
|
|
317
|
+
.fx-search { width: min(320px, 100%); margin-bottom: 8px; }
|
|
318
|
+
.fx-table--rows tbody tr { cursor: pointer; }
|
|
319
|
+
.fx-table th.fx-num { text-align: right; }
|
|
320
|
+
|
|
321
|
+
.fx-crumbs { display: flex; gap: 8px; color: var(--t-dim); font-size: 13.5px; }
|
|
322
|
+
.fx-crumbs a { color: var(--t-dim); text-decoration: none; }
|
|
323
|
+
.fx-crumbs a:hover { color: var(--t-strong); text-decoration: underline; }
|
|
324
|
+
.fx-invoice { display: grid; gap: 20px; }
|
|
325
|
+
.fx-invoice__grid { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 20px; align-items: start; }
|
|
326
|
+
.fx-invoice__main, .fx-invoice__aside { display: grid; gap: 16px; }
|
|
327
|
+
.fx-invoice__aside:empty { display: none; }
|
|
328
|
+
.fx-invoice__grid:has(.fx-invoice__aside:empty) { grid-template-columns: minmax(0, 1fr); }
|
|
329
|
+
.fx-invoice__head { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 16px; padding: 18px 20px; }
|
|
330
|
+
.fx-side-card { display: grid; gap: 10px; padding: 16px; }
|
|
331
|
+
.fx-facts { display: grid; grid-template-columns: max-content 1fr; gap: 6px 12px; margin: 0; font-size: 13.5px; }
|
|
332
|
+
.fx-facts dt { color: var(--t-dim); }
|
|
333
|
+
.fx-facts dd { margin: 0; color: var(--t-strong); overflow-wrap: anywhere; }
|
|
334
|
+
.fx-timeline { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; font-size: 13.5px; }
|
|
335
|
+
.fx-timeline li { display: grid; gap: 1px; padding-left: 14px; border-left: 2px solid var(--t-brand); }
|
|
336
|
+
|
|
337
|
+
.fx-suggest-host { position: relative; }
|
|
338
|
+
.fx-suggest {
|
|
339
|
+
position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; right: 0; display: grid; padding: 4px; max-height: 280px; overflow-y: auto;
|
|
340
|
+
border: 1px solid var(--fx-line); border-radius: var(--t-radius); background: var(--t-paper); box-shadow: 0 12px 32px rgba(0, 0, 0, .16);
|
|
341
|
+
}
|
|
342
|
+
.fx-suggest button { display: grid; gap: 1px; padding: 8px 10px; border: 0; border-radius: var(--t-radius); background: transparent; color: var(--t-strong); text-align: left; font: 500 14px var(--t-body); cursor: pointer; }
|
|
343
|
+
.fx-suggest button:hover { background: var(--fx-hover); }
|
|
344
|
+
.fx-suggest__none { padding: 8px 10px; }
|
|
345
|
+
|
|
346
|
+
.fx-lines { overflow: visible; }
|
|
347
|
+
.fx-lines__table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
348
|
+
.fx-lines__table th {
|
|
349
|
+
padding: 10px 8px; text-align: left; border-bottom: 1px solid var(--fx-line); background: var(--t-s1);
|
|
350
|
+
color: var(--t-dim); font: var(--t-label-weight) var(--t-label-size)/1.3 var(--t-body); letter-spacing: var(--t-label-spacing); text-transform: var(--t-label-case);
|
|
351
|
+
}
|
|
352
|
+
.fx-lines__table th:first-child, .fx-lines__table td:first-child { padding-left: 16px; }
|
|
353
|
+
.fx-lines__table th:last-child, .fx-lines__table td:last-child { padding-right: 16px; }
|
|
354
|
+
.fx-lines__table th.fx-num { text-align: right; }
|
|
355
|
+
.fx-lines__table td { padding: 8px; border-bottom: 1px solid var(--fx-line-soft); vertical-align: middle; }
|
|
356
|
+
.fx-lines__table .fx-input, .fx-lines__table .fx-select { min-height: 34px; }
|
|
357
|
+
.fx-lines__table .fx-select { width: 100%; min-width: 0; }
|
|
358
|
+
.fx-lines__item { width: 130px; }
|
|
359
|
+
.fx-lines__qty { width: 76px; }
|
|
360
|
+
.fx-lines__price { width: 112px; }
|
|
361
|
+
.fx-lines__gst { width: 116px; }
|
|
362
|
+
.fx-lines__table--read td { padding-top: 12px; padding-bottom: 12px; }
|
|
363
|
+
.fx-lines__foot { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: start; gap: 16px; padding: 12px 16px 18px; }
|
|
364
|
+
.fx-totals { display: grid; grid-template-columns: auto minmax(120px, auto); gap: 6px 24px; margin: 0; font-size: 14px; }
|
|
365
|
+
.fx-totals dt { color: var(--t-dim); }
|
|
366
|
+
.fx-totals dd { margin: 0; text-align: right; font-family: var(--t-data, var(--t-body)); font-variant-numeric: tabular-nums; color: var(--t-strong); }
|
|
367
|
+
.fx-totals__grand { padding-top: 8px; border-top: 1px solid var(--fx-line); font-size: 17px; font-weight: 700; color: var(--t-strong) !important; }
|
|
368
|
+
.fx-doc__meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin: 0; padding: 20px; border-bottom: 1px solid var(--fx-line-soft); }
|
|
369
|
+
.fx-doc__meta dd { display: grid; margin: 4px 0 0; color: var(--t-strong); font-weight: 600; }
|
|
370
|
+
@media (max-width: 1320px) {
|
|
371
|
+
.fx-invoice__grid { grid-template-columns: minmax(0, 1fr); }
|
|
372
|
+
.fx-invoice__aside { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
|
|
373
|
+
}
|
|
374
|
+
@media (max-width: 960px) {
|
|
375
|
+
.fx-invoice__head, .fx-doc__meta { grid-template-columns: 1fr 1fr; }
|
|
376
|
+
.fx-lines { overflow-x: auto; }
|
|
377
|
+
.fx-lines__table { min-width: 680px; }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/* ── Jobs board ─────────────────────────────────────────────────────────── */
|
|
381
|
+
.fx-main--wide { max-width: none; }
|
|
382
|
+
.fx-main > * { min-width: 0; }
|
|
383
|
+
.fx-toolbar--plain { border-bottom: 0; align-items: center; }
|
|
384
|
+
.fx-toolbar--plain .fx-search { margin-bottom: 0; }
|
|
385
|
+
.fx-empty .fx-btn { margin-top: 12px; }
|
|
386
|
+
.fx-panel.fx-empty { display: grid; justify-items: center; }
|
|
387
|
+
.fx-board { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(240px, 1fr); gap: 12px; overflow-x: auto; padding-bottom: 8px; align-items: start; }
|
|
388
|
+
.fx-board__col { display: grid; gap: 8px; align-content: start; min-height: 240px; padding: 10px; border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); background: var(--t-s1); }
|
|
389
|
+
.fx-board__head { display: flex; align-items: center; gap: 8px; padding: 2px 4px 4px; font-size: 14px; }
|
|
390
|
+
.fx-board__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--t-accent); flex: none; }
|
|
391
|
+
.fx-board__dot[data-tone="warn"] { background: var(--t-warn); }
|
|
392
|
+
.fx-board__dot[data-tone="success"] { background: var(--t-success); }
|
|
393
|
+
.fx-board__dot[data-tone="mute"] { background: var(--t-dim); }
|
|
394
|
+
.fx-board__count { padding: 0 7px; border-radius: 999px; background: var(--fx-hover); color: var(--t-dim); font-size: 12px; font-weight: 700; line-height: 20px; }
|
|
395
|
+
.fx-board__sum { margin-left: auto; color: var(--t-dim); font-size: 12.5px; font-variant-numeric: tabular-nums; }
|
|
396
|
+
.fx-board__cards { display: grid; gap: 8px; }
|
|
397
|
+
.fx-board__none { padding: 12px 4px; color: var(--t-dim); font-size: 13px; }
|
|
398
|
+
.fx-job { display: grid; gap: 4px; padding: 10px 12px; border: 1px solid var(--fx-line); border-radius: var(--t-radius); background: var(--t-paper); box-shadow: var(--t-shadow); font-size: 13.5px; }
|
|
399
|
+
.fx-job--flag { border-left: 3px solid var(--t-warn); }
|
|
400
|
+
.fx-job__top { display: flex; align-items: baseline; gap: 8px; }
|
|
401
|
+
.fx-job__amount { margin-left: auto; color: var(--t-strong); font-weight: 600; font-family: var(--t-data, var(--t-body)); font-variant-numeric: tabular-nums; }
|
|
402
|
+
.fx-job__client { color: var(--t-text); }
|
|
403
|
+
.fx-job__meta { display: flex; flex-wrap: wrap; gap: 4px 10px; color: var(--t-dim); font-size: 12.5px; }
|
|
404
|
+
.fx-job__why { color: var(--t-warn); font-size: 12.5px; }
|
|
405
|
+
.fx-job .fx-btn { justify-self: start; margin-top: 4px; }
|
|
406
|
+
|
|
198
407
|
/* ── Dialog ─────────────────────────────────────────────────────────────── */
|
|
199
408
|
.fx-dialog {
|
|
200
409
|
width: min(440px, calc(100vw - 32px)); padding: 0;
|
|
@@ -222,16 +431,12 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
|
|
|
222
431
|
[data-mode="light"] .fx-auth__brand .fx-mark rect { fill: var(--t-on-primary); }
|
|
223
432
|
[data-mode="light"] .fx-auth__brand .fx-mark path { stroke: var(--t-primary); }
|
|
224
433
|
.fx-auth__title { max-width: 16ch; font: var(--t-display-weight) clamp(30px, 3.6vw, 46px)/1.08 var(--t-display); letter-spacing: -0.02em; }
|
|
225
|
-
.fx-auth__points { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; font-size: 16px; opacity: .88; }
|
|
226
|
-
.fx-auth__points li { display: flex; gap: 12px; align-items: baseline; }
|
|
227
|
-
.fx-auth__points li::before { content: ""; flex: none; width: 18px; height: 2px; border-radius: 2px; background: currentColor; transform: translateY(-4px); opacity: .7; }
|
|
228
434
|
.fx-auth__form { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 40px 16px; }
|
|
229
435
|
.fx-auth__note { color: var(--t-dim); text-align: center; }
|
|
230
436
|
.fx-auth__widget { display: flex; justify-content: center; min-height: 360px; width: 100%; }
|
|
231
437
|
@media (max-width: 820px) {
|
|
232
438
|
.fx-auth { grid-template-columns: 1fr; }
|
|
233
439
|
.fx-auth__brand { gap: 16px; padding: 28px 16px; }
|
|
234
|
-
.fx-auth__points { display: none; }
|
|
235
440
|
.fx-auth__title { font-size: 26px; max-width: none; }
|
|
236
441
|
}
|
|
237
442
|
|
|
@@ -254,7 +459,27 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
254
459
|
|
|
255
460
|
/* ── Appearance ─────────────────────────────────────────────────────────── */
|
|
256
461
|
.fx-appearance { display: grid; gap: 28px; }
|
|
257
|
-
|
|
462
|
+
|
|
463
|
+
/* ── Settings rows: name, one line under it, the control on the right ──── */
|
|
464
|
+
.fx-settings { display: grid; }
|
|
465
|
+
.fx-setting { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 32px; align-items: center; padding: 14px 0; }
|
|
466
|
+
.fx-setting + .fx-setting { border-top: 1px solid var(--fx-line-soft); }
|
|
467
|
+
.fx-setting__text { display: grid; gap: 3px; min-width: 0; }
|
|
468
|
+
.fx-setting__name { color: var(--t-strong); font: 500 14.5px/1.35 var(--t-body); }
|
|
469
|
+
.fx-setting__hint { margin: 0; color: var(--t-dim); font-size: 13px; line-height: 1.45; }
|
|
470
|
+
.fx-setting__control { display: flex; justify-content: flex-end; min-width: 0; }
|
|
471
|
+
.fx-setting__control > .fx-input, .fx-setting__control > .fx-select, .fx-setting__stack { width: 320px; max-width: 100%; }
|
|
472
|
+
.fx-setting__control .fx-swatches { flex-wrap: nowrap; }
|
|
473
|
+
.fx-setting__stack { display: grid; gap: 6px; justify-items: end; }
|
|
474
|
+
.fx-setting__value { font-size: 12.5px; color: var(--t-strong); overflow-wrap: anywhere; text-align: right; }
|
|
475
|
+
.fx-setting--wide { grid-template-columns: minmax(0, 1fr); }
|
|
476
|
+
.fx-setting--wide .fx-setting__control { display: block; }
|
|
477
|
+
@media (max-width: 640px) {
|
|
478
|
+
.fx-setting { grid-template-columns: minmax(0, 1fr); }
|
|
479
|
+
.fx-setting__control { justify-content: flex-start; }
|
|
480
|
+
.fx-setting__stack { justify-items: start; }
|
|
481
|
+
.fx-setting__control .fx-swatches { flex-wrap: wrap; }
|
|
482
|
+
}
|
|
258
483
|
.fx-seg { display: inline-flex; justify-self: start; padding: 3px; gap: 2px; border-radius: calc(var(--t-radius) + 3px); background: var(--t-s2); }
|
|
259
484
|
.fx-seg__item {
|
|
260
485
|
min-height: 32px; padding: 0 16px; border: 0; border-radius: var(--t-radius);
|
|
@@ -288,11 +513,21 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
288
513
|
.fx-swatch-custom { display: inline-flex; align-items: center; gap: 8px; color: var(--t-dim); font-size: 13px; font-weight: 600; }
|
|
289
514
|
.fx-swatch-custom input { width: 34px; height: 30px; padding: 0; border: 1px solid var(--t-control); border-radius: var(--t-radius); background: var(--t-paper); cursor: pointer; }
|
|
290
515
|
|
|
516
|
+
/* ── Narrow screens: the sidebar becomes a bar across the top. ──────────── */
|
|
517
|
+
@media (max-width: 820px) {
|
|
518
|
+
.fx-shell { flex-direction: column; }
|
|
519
|
+
.fx-side { flex-direction: row; align-items: center; width: auto; height: 60px; padding: 0 12px; border-right: 0; border-bottom: 1px solid var(--fx-line); }
|
|
520
|
+
.fx-side > .fx-brand { padding: 0; }
|
|
521
|
+
.fx-side > .fx-brand span { display: none; }
|
|
522
|
+
.fx-nav { flex-direction: row; overflow-x: auto; overflow-y: hidden; }
|
|
523
|
+
.fx-nav__group { display: none; }
|
|
524
|
+
.fx-nav a { padding: 7px 9px; }
|
|
525
|
+
.fx-nav a .fx-ico { display: none; }
|
|
526
|
+
.fx-side__end { padding: 0; border-top: 0; }
|
|
527
|
+
}
|
|
528
|
+
|
|
291
529
|
/* ── Phones ─────────────────────────────────────────────────────────────── */
|
|
292
530
|
@media (max-width: 640px) {
|
|
293
|
-
.fx-top { gap: 12px; }
|
|
294
|
-
.fx-top .fx-brand span { display: none; }
|
|
295
|
-
.fx-nav a { padding: 7px 9px; }
|
|
296
531
|
.fx-main { padding-top: 20px; }
|
|
297
532
|
.fx-title { font-size: 24px; }
|
|
298
533
|
.fx-table thead { display: none; }
|
|
@@ -312,3 +547,80 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
312
547
|
@media (prefers-reduced-motion: reduce) {
|
|
313
548
|
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
314
549
|
}
|
|
550
|
+
|
|
551
|
+
/* ── Settings rows ───────────────────────────────────────────────────────
|
|
552
|
+
One row for each setting: its name, one short line under it, and the
|
|
553
|
+
control on the right. */
|
|
554
|
+
.fx-set { display: grid; gap: 4px; }
|
|
555
|
+
.fx-set__title { margin: 8px 0 4px; color: var(--t-strong); font: 600 15px/1.3 var(--t-body); }
|
|
556
|
+
.fx-set__row { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 12px 0; }
|
|
557
|
+
.fx-set__row + .fx-set__row { border-top: 1px solid var(--fx-line); }
|
|
558
|
+
.fx-set__text { display: grid; gap: 2px; min-width: 0; }
|
|
559
|
+
.fx-set__name { color: var(--t-text); font: 500 14px/1.35 var(--t-body); }
|
|
560
|
+
.fx-set__hint { color: var(--t-dim); font-size: 13px; line-height: 1.35; }
|
|
561
|
+
.fx-set__control { flex: none; display: flex; justify-content: flex-end; }
|
|
562
|
+
.fx-set__control .fx-input { width: 220px; }
|
|
563
|
+
.fx-set__control .fx-input[type="number"] { width: 96px; }
|
|
564
|
+
.fx-set__control .fx-select { min-width: 220px; }
|
|
565
|
+
.fx-set + .fx-set { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--fx-line); }
|
|
566
|
+
/* Invoice rules (invoice-rules.js): a row's Edit button and switch, and the
|
|
567
|
+
rule dialog's condition lines. */
|
|
568
|
+
.fx-rule__controls { align-items: center; gap: 10px; }
|
|
569
|
+
.fx-rule__footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 8px; }
|
|
570
|
+
.fx-dialog:has(.fx-rule__dialog) { width: min(680px, calc(100vw - 32px)); }
|
|
571
|
+
.fx-rule__dialog .fx-btn--sm { justify-self: start; }
|
|
572
|
+
.fx-rule__conditions { display: grid; gap: 8px; }
|
|
573
|
+
.fx-rule__condition { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1.4fr) auto; gap: 8px; align-items: start; }
|
|
574
|
+
.fx-rule__condition + .fx-rule__condition { padding-top: 8px; border-top: 1px solid var(--fx-line); }
|
|
575
|
+
.fx-rule__condition .fx-select, .fx-rule__condition .fx-input { width: 100%; min-width: 0; }
|
|
576
|
+
.fx-rule__value { display: flex; gap: 6px; align-items: center; min-width: 0; }
|
|
577
|
+
.fx-rule__many { display: grid; gap: 2px; max-height: 160px; overflow: auto; padding: 4px 8px; border: 1px solid var(--fx-line); border-radius: 8px; width: 100%; }
|
|
578
|
+
.fx-rule__delete { justify-self: start; color: var(--t-danger); }
|
|
579
|
+
@media (max-width: 640px) {
|
|
580
|
+
.fx-rule__condition { grid-template-columns: minmax(0, 1fr); }
|
|
581
|
+
}
|
|
582
|
+
@media (max-width: 560px) {
|
|
583
|
+
.fx-set__row { gap: 12px; }
|
|
584
|
+
.fx-set__control .fx-input { width: 140px; }
|
|
585
|
+
.fx-set__control .fx-input[type="number"] { width: 72px; }
|
|
586
|
+
.fx-set__control .fx-select { min-width: 140px; }
|
|
587
|
+
}
|
|
588
|
+
.fx-multi { position: relative; width: 220px; max-width: 100%; }
|
|
589
|
+
.fx-multi__summary { display: flex; align-items: center; list-style: none; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
590
|
+
@media (max-width: 600px) { .fx-multi { width: 150px; } }
|
|
591
|
+
.fx-multi__summary::-webkit-details-marker { display: none; }
|
|
592
|
+
.fx-multi__list { position: absolute; right: 0; top: calc(100% + 4px); z-index: 30; display: grid; gap: 2px; min-width: 100%; max-height: 280px; overflow-y: auto; padding: 6px; background: var(--t-paper); border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); box-shadow: var(--t-shadow); }
|
|
593
|
+
.fx-multi__option { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 6px 8px; cursor: pointer; white-space: nowrap; }
|
|
594
|
+
.fx-multi__option:hover { background: var(--fx-line-soft); }
|
|
595
|
+
.fx-switch {
|
|
596
|
+
position: relative; width: 40px; height: 22px; padding: 0; flex: none; cursor: pointer;
|
|
597
|
+
border: 0; border-radius: 999px; background: var(--t-control);
|
|
598
|
+
}
|
|
599
|
+
.fx-switch::after {
|
|
600
|
+
content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
|
|
601
|
+
border-radius: 50%; background: var(--t-paper); transition: transform 0.15s;
|
|
602
|
+
}
|
|
603
|
+
.fx-switch[aria-checked="true"] { background: var(--t-primary); }
|
|
604
|
+
.fx-switch[aria-checked="true"]::after { transform: translateX(18px); }
|
|
605
|
+
.fx-switch:focus-visible { outline: none; box-shadow: var(--fx-focus); }
|
|
606
|
+
.fx-switch:disabled { cursor: default; opacity: 0.6; }
|
|
607
|
+
|
|
608
|
+
/* ── Sentry's report form (monitoring.js) ──────────────────────────────────
|
|
609
|
+
The form lives in a shadow root and reads these variables from its host.
|
|
610
|
+
Rules here win over the form's own defaults, so it takes each person's
|
|
611
|
+
theme and mode, and follows a change at once. */
|
|
612
|
+
#sentry-feedback {
|
|
613
|
+
--font-family: var(--t-body);
|
|
614
|
+
--foreground: var(--t-text);
|
|
615
|
+
--background: var(--t-paper);
|
|
616
|
+
--accent-foreground: var(--t-on-primary);
|
|
617
|
+
--accent-background: var(--t-primary);
|
|
618
|
+
--success-color: var(--t-success);
|
|
619
|
+
--error-color: var(--t-danger);
|
|
620
|
+
--border: 1px solid var(--t-control);
|
|
621
|
+
--box-shadow: var(--t-shadow);
|
|
622
|
+
--outline: 2px solid var(--t-primary);
|
|
623
|
+
--interactive-filter: brightness(95%);
|
|
624
|
+
color-scheme: light;
|
|
625
|
+
}
|
|
626
|
+
[data-mode="dark"] #sentry-feedback { --interactive-filter: brightness(150%); color-scheme: dark; }
|