Files
accounted/components/onboarding/journey/journey.css
T
MattssonandClaude Fable 5.1 fc2d78a7c4 feat(onboarding): the orgnr step suggests companies as you type (SCB search, TIC on the pick) (#2452)
* feat(onboarding): the orgnr step suggests companies as you type, SCB search, TIC on the pick

Most people do not know their organisationsnummer. They left the
onboarding for allabolag, searched their company name there, copied the
number and pasted it back. #2421 let the field take a name, but only on
Enter and behind a screen that still said "organisationsnummer", so the
detour stayed. Now the field suggests companies while a name is typed
(name, orgnr or "Enskild firma", city; arrow keys or click to pick), the
pick fills the company like a typed orgnr, and the screen says "Vilket
företag är det?" with "Företagsnamn eller organisationsnummer" as the
placeholder.

Why the problem occurred: the one identifier the step asked for is the one
the user is least likely to remember, and the free-text path added in
#2421 was invisible (copy unchanged) and had to be guessed (Enter only),
because the only search index behind it was TIC, whose Lens budget cannot
take a call per keystroke.

What was removed or simplified: nothing is stored and no new state model:
a picked suggestion is an ORG_SUBMITTED with prefill, so the existing
LOOKUP_RESULT transitions (found, not found, disabled, error) decide the
step exactly as for a typed number. SCB's name search already existed for
the parties picker; it gained one option (sole traders) instead of a
second client. No rate limiting anywhere, per the founder.

Why this shape: SCB's företagsregister is free and already configured for
the parties picker, so search-as-you-type costs nothing while typing; TIC
runs once, on the pick, as it always did on Enter. TIC per keystroke was
rejected (3000/month). SCB alone was rejected for the pick because it
knows no F-skatt, VAT registration or fiscal year. The Enter path and the
chip row from #2421 stay as the fallback when no row is picked. Sole
traders are offered (they are half the users) but their row names the
form and never prints the personnummer, and the field shows the company
name after a pick for the same reason.

Changes:
- app/api/company/search: GET ?q= over the SCB client with sole traders
  included, top 6 rows plus a truncated flag; requireAuth() (no company
  yet), 400 for short or numeric q, 503 without SCB credentials, 502 when
  SCB does not answer.
- lib/parties/scb/client.ts: searchByName(query, { includeSoleTraders }),
  legalFormCode on every candidate; the parties picker is unchanged.
- lib/company-lookup: CompanySuggestion, COMPANY_SUGGEST_MAX,
  fetchCompanySuggestions (503 is disabled, everything else error, never
  throws), toCompanySuggestion (SCB legal form 49/10/61 into the TIC
  vocabulary mapSetupEntityType reads).
- lib/onboarding-journey/reducer.ts: SUGGESTION_PICKED (orgnr, name and
  form as prefill, lookupPending; lookupRan stays false until TIC answers).
- components/onboarding/journey: 300 ms debounced SCB search with abort of
  the superseded request, listbox under the field (combobox ARIA, arrow
  keys, Escape, Enter picks the highlighted row, otherwise the Enter path),
  copy switches with companySearchEnabled or ticEnabled; both journey
  pages pass isScbConfigured().
- messages sv+en: five strings.

Tests: route (401, 400 short, 400 missing, 400 numeric, 503, happy with a
sole trader, cap at 6, flood, 502); fetchCompanySuggestions (every
outcome); toCompanySuggestion; reducer (pick equals typed orgnr after TIC,
TIC overrides prefill, TIC off keeps the AB past form and name, unmapped
form falls to the picker, sole trader confirms the name, replaces a
previous orgnr, ignored off-step); SCB client sole-trader option.

Fixes #2448

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNDuYBHVu172tesKfJmcmi

* fix(onboarding): the suggestion list stays visible and stands alone (skeptic on e56eb242c)

Three independent refuters on the frozen commit; every refutation that
stood is fixed here.

- The listbox was position: absolute inside the field, but the step
  scrolls (.jny-qstep is overflow-y: auto), so the list was clipped to
  the first row and mouse picks were unreachable (measured in headless
  Chrome). It now renders in flow under the field, where the chip row
  from #2421 already lives.
- After Enter on a name (the #2421 path), SEARCH_RESULT flipped
  lookupPending back and the debounced effect refetched SCB, laying the
  listbox over the chip row or next to the nomatch note. The effect is
  now quiet while searchHits is non-empty and for text the user already
  confirmed (Enter or a pick), until the text changes.
- The "many matches, type more" hint only rendered inside the list, so
  the flood case (SCB counts over 100 rows and sends none) showed
  nothing. The hint now renders on its own for that case.
- app/companies/new-client (byrå adds a client) renders the same journey
  and now passes companySearchEnabled like the other two pages.
- A stale mouse highlight could commit a row from the previous text on
  Enter: typing resets the highlight.
- Any 503 switched the picker off for the session; only the route's own
  SCB_NOT_CONFIGURED does now.
- NOTFOUND_EDIT / CEASED_EDIT dropped only the number and kept the
  abandoned pick's name and form, which a later TIC error path would
  have written into the company. Both now drop name and form too, unless
  they came from BankID's CompanyRoles prefill, which is not about the
  number.

Not changed, recorded: a sole trader picked from SCB whom TIC does not
know lands on the "no company on that number" step with the name in the
field; the flow continues with the SCB name prefilled. The search JSON
carries the personnummer of sole-trader rows to the authenticated
browser (the row prints "Enskild firma"), same class as #2421's Enter
search; flagged to the founder.

Refs #2448

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNDuYBHVu172tesKfJmcmi

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 11:43:14 +02:00

320 lines
18 KiB
CSS

/* Journey onboarding styles, ported from the founder-approved concept
(artifact c82c9358). Namespaced under .jny; uses the app's tokens
(--font-display, --ease-out, --ease-spring, color tokens).
Motion here deliberately deviates from the dashboard motion rules:
the concept was signed off with springs, ink and the particle orb. */
/* ── app-shell composition: the page never scrolls ───────────────── */
.jny {
height: 100vh;
height: 100dvh;
min-height: 540px;
overflow: hidden;
display: flex;
flex-direction: column;
text-align: center;
}
/* The real flow escapes the onboarding layout's max-w-lg wrapper and sits
over the shared OnboardingBackdrop (which stays untouched, per plan). */
.jny.jny-fixed {
position: fixed;
inset: 0;
z-index: 10;
min-height: 0;
}
/* mode='add': quiet escape back to the app, top left */
.jny-escape { position: absolute; top: 18px; left: 22px; z-index: 11; text-decoration: none; }
.jny-center {
flex: 1;
min-height: 0;
width: 100%;
max-width: 640px;
margin: 0 auto;
padding: 1vh 24px 0;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
/* optical centering: mirrors the top block below the question and
collapses first when a tall step needs the room */
.jny-balance { flex: 0 20 180px; min-height: 0; }
.jny-backrow { flex: none; margin: 8px 0 12px; min-height: 20px; }
/* ── journey band ────────────────────────────────────────────────── */
.jny-journey { flex: none; position: relative; width: min(680px, 94vw); margin: 6px auto 0; height: 112px; }
.jny-pt { position: absolute; top: 23px; transform: translateX(-50%); text-align: center; width: 150px; }
.jny-dot { display: block; width: 9px; height: 9px; margin: 0 auto; border-radius: 99px; border: 1px solid hsl(var(--border)); background: hsl(var(--background)); transition: background-color 300ms var(--ease-out), border-color 300ms var(--ease-out); }
.jny-pt.is-done .jny-dot { background: hsl(var(--success)); border-color: hsl(var(--success)); animation: jnyDotPop 460ms var(--ease-spring); }
.jny-pt.is-active .jny-dot { opacity: 0; }
.jny-lbl { display: block; margin-top: 28px; font-size: 10.5px; color: hsl(var(--muted-foreground)); letter-spacing: 0.05em; text-transform: uppercase; transition: color 300ms var(--ease-out); }
.jny-pt.is-done .jny-lbl, .jny-pt.is-active .jny-lbl { color: hsl(var(--foreground)); }
.jny-pt.is-done { cursor: pointer; }
.jny-pt.is-done:hover .jny-lbl { text-decoration: underline dotted; text-underline-offset: 3px; }
.jny-pt:focus-visible { outline: 2px solid hsl(var(--primary)); outline-offset: 4px; border-radius: 8px; }
.jny-ans { display: block; margin-top: 3px; font-size: 11.5px; color: hsl(var(--muted-foreground)); line-height: 1.45; }
.jny-canvas { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
@keyframes jnyDotPop { 0% { transform: scale(1); } 45% { transform: scale(1.8); } 100% { transform: scale(1); } }
@media (max-width: 640px) { .jny-lbl, .jny-ans { display: none; } }
/* ── fact line (lookup results ink in under the band) ────────────── */
.jny-facts { flex: none; margin: 18px 0 0; min-height: 48px; max-width: 540px; transition: opacity 500ms var(--ease-out); }
.jny-facts.is-gone { opacity: 0; pointer-events: none; min-height: 0; height: 0; margin: 0; overflow: hidden; }
.jny-factname { font-family: var(--font-display); font-size: 18px; letter-spacing: -0.01em; min-height: 26px; }
.jny-factmeta { font-size: 12.5px; color: hsl(var(--muted-foreground)); margin-top: 5px; line-height: 1.7; }
.jny-factmeta .jny-f { opacity: 0; transition: opacity 600ms var(--ease-out); }
.jny-factmeta .jny-f.is-on { opacity: 1; }
.jny-factmeta .jny-f.is-warn { color: hsl(var(--destructive)); }
/* ── ink typography ──────────────────────────────────────────────── */
.jny-ink { display: inline-block; animation: jnyInkDry 620ms var(--ease-out) both; }
@keyframes jnyInkDry {
from { opacity: 0; filter: blur(7px); transform: translateY(5px); }
55% { opacity: 1; }
to { opacity: 1; filter: blur(0); transform: none; }
}
/* ── question area ───────────────────────────────────────────────── */
.jny-qarea {
flex: 1;
min-height: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding: 10px 0;
}
.jny-qstep { max-height: 100%; overflow-y: auto; overscroll-behavior: contain; animation: jnyQIn 380ms var(--ease-out); }
@keyframes jnyQIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.jny-qtitle {
font-family: var(--font-display);
font-weight: 400;
font-size: clamp(24px, 3.4vw, 32px);
line-height: 1.25;
letter-spacing: -0.01em;
margin: 0 0 8px;
text-wrap: balance;
}
.jny-qsub { font-size: 13px; color: hsl(var(--muted-foreground)); margin: 0 0 26px; }
.jny-attn { font-size: 12.5px; color: hsl(var(--warning)); margin: -14px 0 20px; }
.jny-qhelp {
display: inline-flex; align-items: center; justify-content: center;
width: 17px; height: 17px; border-radius: 99px;
border: 1px solid hsl(var(--border));
color: hsl(var(--muted-foreground));
font-size: 11px; line-height: 1;
margin-left: 7px; vertical-align: -3px;
background: none; cursor: pointer;
transition: background-color 150ms var(--ease-out), color 150ms var(--ease-out), border-color 150ms var(--ease-out);
}
.jny-qhelp:hover, .jny-qhelp.is-on { color: hsl(var(--foreground)); border-color: hsl(var(--foreground) / 0.35); background: hsl(var(--secondary) / 0.5); }
.jny-qinfo { font-size: 12.5px; color: hsl(var(--muted-foreground)); max-width: 52ch; margin: -14px auto 26px; line-height: 1.65; animation: jnyQIn 300ms var(--ease-out); }
/* ── inputs ──────────────────────────────────────────────────────── */
.jny-biginput { position: relative; border-bottom: 1px solid hsl(var(--border)); padding: 8px 0 12px; max-width: 360px; margin: 0 auto; transition: border-color 200ms var(--ease-out); }
.jny-biginput:focus-within { border-color: hsl(var(--foreground) / 0.55); }
.jny-biginput input {
width: 100%; border: 0; outline: 0; background: none;
font: inherit; color: inherit;
font-size: 26px; letter-spacing: 0.04em; font-variant-numeric: tabular-nums;
text-align: center;
}
.jny-biginput input::placeholder { color: hsl(var(--muted-foreground) / 0.45); }
.jny-biginput.is-err { animation: jnyShake 340ms var(--ease-out); }
@keyframes jnyShake { 0%, 100% { transform: none; } 20%, 60% { transform: translateX(-5px); } 40%, 80% { transform: translateX(5px); } }
/* Search-as-you-type picker under the orgnr field: an 8px surface on the
page, 4px rows inset inside it (the radius ladder), flat hover. In flow
(the step scrolls, so a floated list would be clipped to the field). */
.jny-suggest {
max-width: 360px; margin: 10px auto 0; padding: 4px; list-style: none;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 8px;
text-align: left;
animation: jnyQIn 200ms var(--ease-out);
}
.jny-suggest li {
display: flex; flex-direction: column; gap: 2px;
padding: 9px 12px; border-radius: 4px; cursor: pointer;
font-size: 14px; line-height: 1.3; letter-spacing: 0;
transition: background-color 150ms var(--ease-out);
}
.jny-suggest li:hover, .jny-suggest li.is-active { background: hsl(var(--secondary) / 0.6); }
.jny-suggest li.is-inactive .jny-sug-name { color: hsl(var(--muted-foreground)); }
.jny-sug-name { font-weight: 400; }
.jny-sug-sub { font-size: 11.5px; color: hsl(var(--muted-foreground)); font-variant-numeric: tabular-nums; }
.jny-enterhint { font-size: 11.5px; color: hsl(var(--muted-foreground)); margin-top: 10px; }
.jny-enterhint b { border: 1px solid hsl(var(--border)); border-radius: 5px; padding: 1px 6px; font-weight: 400; }
.jny-addr { max-width: 380px; margin: 26px auto 0; }
.jny-addr .jny-biginput { max-width: none; margin: 0; }
.jny-addr input { font-size: 19px; letter-spacing: 0.01em; text-align: center; }
.jny-addr-row { display: flex; gap: 26px; margin-top: 4px; }
.jny-addr-zip { flex: 0 0 130px; }
.jny-addr-city { flex: 1; }
/* ── chips ───────────────────────────────────────────────────────── */
.jny-chips { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; max-width: 520px; margin: 0 auto; }
.jny-pick {
border: 1px solid hsl(var(--border));
border-radius: 99px;
padding: 10px 18px;
font-size: 13.5px;
font-family: inherit;
color: inherit;
background: hsl(var(--background));
cursor: pointer;
transition: background-color 150ms var(--ease-out), transform 180ms var(--ease-out);
}
.jny-pick:hover { background: hsl(var(--secondary) / 0.6); transform: translateY(-1px); }
.jny-pick:active { transform: translateY(0) scale(0.96); }
.jny-pick.is-sel { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: hsl(var(--primary)); }
.jny-pick .jny-rec { font-size: 10.5px; color: hsl(var(--muted-foreground)); display: block; }
.jny-pick.is-sel .jny-rec { color: hsl(var(--primary-foreground) / 0.7); }
.jny-qactions { display: flex; gap: 18px; align-items: center; justify-content: center; margin-top: 30px; }
.jny-btn {
display: inline-flex; align-items: center; gap: 7px;
border-radius: 99px; padding: 8px 22px; border: 0;
font-size: 13px; font-weight: 500; font-family: inherit; cursor: pointer;
background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
transition: background-color 150ms var(--ease-out);
}
.jny-btn:hover { background: hsl(var(--primary) / 0.85); }
.jny-btn-quiet { border: 0; background: none; cursor: pointer; font-family: inherit; color: hsl(var(--muted-foreground)); font-size: 12.5px; }
.jny-btn-quiet:hover { color: hsl(var(--foreground)); }
/* ── fiscal-year band ────────────────────────────────────────────── */
.jny-yband { max-width: 500px; margin: 4px auto 0; }
.jny-yb-grid { position: relative; padding: 4px 0 0; }
.jny-yb-cells { position: relative; display: grid; z-index: 1; }
.jny-yb-cells span { font-size: 10px; color: hsl(var(--muted-foreground) / 0.65); line-height: 28px; font-variant-numeric: tabular-nums; transition: color 350ms var(--ease-out); }
.jny-yb-cells span.is-in { color: hsl(var(--foreground)); font-weight: 500; }
.jny-yb-span {
position: absolute; top: 5px; height: 26px; z-index: 0; opacity: 0;
border-radius: 99px;
background: hsl(var(--secondary) / 0.8);
border: 1px solid hsl(var(--border));
transition: left 480ms var(--ease-spring), width 480ms var(--ease-spring), opacity 250ms var(--ease-out);
}
.jny-yb-span.is-on { opacity: 1; }
.jny-yb-years { display: grid; margin-top: 3px; }
.jny-yb-years span { font-size: 10px; color: hsl(var(--muted-foreground) / 0.55); text-align: left; padding-left: 3px; }
.jny-yb-label { font-family: var(--font-display); font-size: 17px; margin-top: 14px; min-height: 25px; }
.jny-yb-note { font-size: 12px; color: hsl(var(--muted-foreground)); margin-top: 5px; min-height: 18px; }
.jny-mchips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; max-width: 460px; margin: 18px auto 0; }
.jny-mchip {
border: 1px solid hsl(var(--border)); border-radius: 99px;
padding: 5px 12px; font-size: 12px;
font-family: inherit; color: inherit; cursor: pointer;
background: hsl(var(--background));
transition: background-color 150ms var(--ease-out), transform 150ms var(--ease-out);
}
.jny-mchip:hover { background: hsl(var(--secondary) / 0.6); transform: translateY(-1px); }
.jny-mchip:active { transform: translateY(0) scale(0.96); }
.jny-mchip.is-sel { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: hsl(var(--primary)); }
.jny-dp-days { max-width: 420px; gap: 6px; }
.jny-dp-days .jny-mchip { min-width: 32px; padding: 4px 0; font-variant-numeric: tabular-nums; }
.jny-dp-label { font-family: var(--font-display); font-size: 17px; margin-top: 16px; min-height: 25px; }
/* ── company picker rows (BankID roster) ─────────────────────────── */
.jny-rowlist {
max-width: 470px; margin: 18px auto 0; text-align: left;
max-height: 42vh; overflow-y: auto; overscroll-behavior: contain; padding: 4px;
-webkit-mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
}
.jny-rowsec { font-size: 10.5px; letter-spacing: 0.05em; text-transform: uppercase; color: hsl(var(--muted-foreground)); margin: 22px 14px 4px; }
.jny-rowpick { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; width: 100%; padding: 11px 14px; border: 0; border-radius: 12px; text-align: left; font-family: inherit; color: inherit; background: none; cursor: pointer; transition: background-color 150ms var(--ease-out); }
.jny-rowpick:hover { background: hsl(var(--secondary) / 0.55); }
.jny-rowpick:active { transform: scale(0.99); }
.jny-rowpick .jny-rleft { display: flex; flex-direction: column; min-width: 0; }
.jny-rowpick .jny-rname { font-size: 14px; }
.jny-rowpick .jny-rmeta { font-size: 11.5px; color: hsl(var(--muted-foreground)); margin-top: 1px; }
.jny-rowpick .jny-rorg { font-size: 11.5px; color: hsl(var(--muted-foreground) / 0.75); font-variant-numeric: tabular-nums; white-space: nowrap; }
.jny-rowpick.is-existing .jny-rname { color: hsl(var(--muted-foreground)); }
.jny-rownote { font-size: 12px; color: hsl(var(--muted-foreground)); padding: 14px; }
.jny-filter { max-width: 340px; }
.jny-filter input { font-size: 17px; letter-spacing: 0.01em; }
/* ── done scene: profile card + notes ────────────────────────────── */
.jny-card {
margin: 22px auto 0; max-width: 380px; text-align: left;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 14px;
padding: 16px 20px;
box-shadow: 0 1px 2px hsl(0 0% 0% / 0.05), 0 16px 40px -24px hsl(0 0% 0% / 0.25);
}
.jny-card-eyebrow { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: hsl(var(--muted-foreground)); }
.jny-card-name { font-family: var(--font-display); font-size: 17px; margin-top: 2px; }
.jny-card dl { margin: 12px 0 0; }
.jny-card-row {
display: flex; justify-content: space-between; gap: 18px;
padding: 7px 0; border-top: 1px dashed hsl(var(--border) / 0.8);
font-size: 12.5px;
opacity: 0; transform: translateY(4px);
transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
}
.jny-card-row.is-on { opacity: 1; transform: none; }
.jny-card-row dt { color: hsl(var(--muted-foreground)); margin: 0; }
.jny-card-row dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.jny-notes { margin: 20px auto 0; max-width: 430px; font-size: 12.5px; color: hsl(var(--muted-foreground)); line-height: 2; }
.jny-notes div { opacity: 0; transform: translateY(4px); transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out); }
.jny-notes div.is-on { opacity: 1; transform: none; }
/* ── dawn: progress as atmosphere (composes with OnboardingBackdrop) ─ */
.jny-dawn {
position: fixed; left: 0; right: 0; bottom: 0; height: 55vh;
pointer-events: none; z-index: 0;
background: radial-gradient(120% 95% at 50% 100%, hsl(36 60% 62% / 0.26), transparent 66%);
opacity: var(--jny-dawn, 0);
transition: opacity 1100ms var(--ease-out);
}
.dark .jny-dawn { background: radial-gradient(120% 95% at 50% 100%, hsl(30 45% 42% / 0.26), transparent 66%); }
/* ── compression for short windows: shrink, never scroll ─────────── */
@media (max-height: 780px) {
.jny-facts { margin-top: 10px; min-height: 36px; }
.jny-qtitle { font-size: clamp(20px, 3vw, 26px); }
.jny-qsub { margin-bottom: 16px; }
.jny-qactions { margin-top: 20px; }
.jny-card { margin-top: 14px; padding: 12px 18px; }
.jny-card-row { padding: 5px 0; }
.jny-notes { margin-top: 12px; line-height: 1.8; }
.jny-rowlist { max-height: 36vh; margin-top: 12px; }
.jny-mchips { margin-top: 12px; }
.jny-balance { flex-basis: 110px; }
}
@media (prefers-reduced-motion: reduce) {
.jny *, .jny *::before, .jny *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
/* ── source step: where were the books before ────────────────────── */
/* One grid, equal-weight tiles. Providers carry a real logo on a small
white mark (the LogoMark grammar); SIE and new-business are text tiles
in the same shape. Hover is a flat background shift, no lift. */
.jny-srcgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; max-width: 440px; margin: 0 auto; }
.jny-srcpick {
display: flex; align-items: center; gap: 12px;
min-height: 52px; padding: 10px 16px;
border: 1px solid hsl(var(--border)); border-radius: 8px;
background: hsl(var(--background));
font-family: inherit; font-size: 13.5px; color: inherit; text-align: left;
cursor: pointer;
transition: background-color 150ms var(--ease-out);
}
.jny-srcpick:hover { background: hsl(var(--secondary) / 0.6); }
.jny-srcpick.is-text { justify-content: center; text-align: center; }
.jny-srcpick.is-span { grid-column: 1 / -1; }
.jny-srcmark {
flex: none; display: flex; align-items: center; justify-content: center;
width: 28px; height: 28px; overflow: hidden;
border: 1px solid hsl(var(--border)); border-radius: 4px; background: #fff;
}
.jny-srcmark img { width: 18px; height: 18px; object-fit: contain; }
@media (max-height: 780px) {
.jny-srcpick { min-height: 46px; padding: 8px 14px; }
}