Обновляем Caterium
+Удаляем старый экран входа и подключаем свежую версию…
+diff --git a/deploy-trigger.txt b/deploy-trigger.txt
index ea36e3f..8096230 100644
--- a/deploy-trigger.txt
+++ b/deploy-trigger.txt
@@ -1 +1 @@
-First Cloudflare autodeploy
+Timeweb verification trigger 2026-09-12 08:15
diff --git a/ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql b/ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql
new file mode 100644
index 0000000..fa92a8d
--- /dev/null
+++ b/ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql
@@ -0,0 +1,471 @@
+-- Caterium v31: one company owner account, owner-only employee administration,
+-- and a self-service profile for every authenticated user.
+
+create or replace function public.caterium_is_workspace_owner(p_workspace uuid)
+returns boolean
+language sql
+stable
+security definer
+set search_path='public'
+as $$
+ select auth.uid() is not null
+ and exists(
+ select 1
+ from public.sun_workspaces w
+ join public.sun_workspace_members m
+ on m.workspace_id=w.id
+ and m.user_id=auth.uid()
+ and m.is_active=true
+ where w.id=p_workspace
+ and w.created_by=auth.uid()
+ );
+$$;
+
+revoke all on function public.caterium_is_workspace_owner(uuid) from public,anon;
+grant execute on function public.caterium_is_workspace_owner(uuid) to authenticated;
+
+create or replace function public.caterium_account_snapshot(p_workspace uuid)
+returns jsonb
+language plpgsql
+stable
+security definer
+set search_path='public','auth'
+as $$
+declare
+ v_user uuid:=auth.uid();
+ v_row record;
+begin
+ if v_user is null then raise exception 'Сначала войдите в Caterium'; end if;
+
+ select w.id as workspace_id,
+ w.name as company_name,
+ w.company_number,
+ w.created_by,
+ m.display_name,
+ m.role,
+ m.is_active,
+ coalesce(m.permissions,'{}'::jsonb) as permissions,
+ u.email::text as email
+ into v_row
+ from public.sun_workspaces w
+ join public.sun_workspace_members m
+ on m.workspace_id=w.id
+ and m.user_id=v_user
+ join auth.users u on u.id=v_user
+ where w.id=p_workspace
+ and m.is_active=true
+ limit 1;
+
+ if not found then raise exception 'Нет доступа к компании'; end if;
+
+ return jsonb_build_object(
+ 'user_id',v_user,
+ 'email',v_row.email,
+ 'display_name',coalesce(nullif(v_row.display_name,''),split_part(coalesce(v_row.email,''),'@',1)),
+ 'role',v_row.role,
+ 'is_owner',(v_row.created_by=v_user),
+ 'workspace_id',v_row.workspace_id,
+ 'company_name',v_row.company_name,
+ 'company_number',v_row.company_number,
+ 'permissions',v_row.permissions
+ );
+end;
+$$;
+
+revoke all on function public.caterium_account_snapshot(uuid) from public,anon;
+grant execute on function public.caterium_account_snapshot(uuid) to authenticated;
+
+create or replace function public.caterium_update_my_name(p_display_name text)
+returns text
+language plpgsql
+security definer
+set search_path='public','auth'
+as $$
+declare
+ v_user uuid:=auth.uid();
+ v_name text:=trim(coalesce(p_display_name,''));
+begin
+ if v_user is null then raise exception 'Сначала войдите в Caterium'; end if;
+ if char_length(v_name)<1 then raise exception 'Введите имя'; end if;
+ if char_length(v_name)>120 then raise exception 'Имя слишком длинное'; end if;
+
+ update public.sun_workspace_members
+ set display_name=v_name,
+ updated_at=now()
+ where user_id=v_user;
+
+ update auth.users
+ set raw_user_meta_data=jsonb_set(coalesce(raw_user_meta_data,'{}'::jsonb),'{name}',to_jsonb(v_name),true)
+ where id=v_user;
+
+ return v_name;
+end;
+$$;
+
+revoke all on function public.caterium_update_my_name(text) from public,anon;
+grant execute on function public.caterium_update_my_name(text) to authenticated;
+
+-- Existing company creators are the only company administrators.
+update public.sun_workspace_members m
+ set role='admin',
+ permissions=jsonb_set(coalesce(m.permissions,public.sun_role_default_permissions('admin')),'{users.manage}','true'::jsonb,true),
+ updated_at=now()
+ from public.sun_workspaces w
+ where w.id=m.workspace_id
+ and m.user_id=w.created_by;
+
+-- Any non-owner account is an employee account and can never manage users.
+update public.sun_workspace_members m
+ set role=case when m.role='admin' then 'manager' else m.role end,
+ permissions=jsonb_set(coalesce(m.permissions,public.sun_role_default_permissions(case when m.role='admin' then 'manager' else m.role end)),'{users.manage}','false'::jsonb,true),
+ updated_at=now()
+ from public.sun_workspaces w
+ where w.id=m.workspace_id
+ and m.user_id<>w.created_by;
+
+-- No pending invitation may create a second company administrator.
+delete from public.sun_workspace_invites where used_at is null and role='admin';
+
+create or replace function public.sun_list_workspace_members(p_workspace uuid)
+returns table(user_id uuid,email text,display_name text,role text,is_active boolean,permissions jsonb,created_at timestamptz,updated_at timestamptz)
+language plpgsql
+stable
+security definer
+set search_path='public','auth'
+as $$
+begin
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then
+ raise exception 'Только владелец компании может управлять пользователями';
+ end if;
+ return query
+ select m.user_id,u.email::text,m.display_name,m.role,m.is_active,
+ coalesce(m.permissions,public.sun_role_default_permissions(m.role)),m.created_at,m.updated_at
+ from public.sun_workspace_members m
+ join auth.users u on u.id=m.user_id
+ join public.sun_workspaces w on w.id=m.workspace_id
+ where m.workspace_id=p_workspace
+ order by (m.user_id=w.created_by) desc,coalesce(m.display_name,u.email::text);
+end;
+$$;
+
+create or replace function public.sun_admin_update_member(
+ p_workspace uuid,
+ p_user uuid,
+ p_display_name text,
+ p_role text,
+ p_is_active boolean,
+ p_permissions jsonb
+)
+returns void
+language plpgsql
+security definer
+set search_path='public'
+as $$
+declare
+ v_role text:=lower(coalesce(p_role,''));
+ v_owner uuid;
+ v_max integer;
+ v_active_count integer;
+ v_permissions jsonb;
+begin
+ perform pg_advisory_xact_lock(hashtext(p_workspace::text));
+
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then
+ raise exception 'Только владелец компании может управлять сотрудниками';
+ end if;
+ if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
+ if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if;
+ if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if;
+ if p_permissions is null or jsonb_typeof(p_permissions)<>'object' then raise exception 'Некорректные права пользователя'; end if;
+
+ select created_by into v_owner from public.sun_workspaces where id=p_workspace;
+ if v_owner is null then raise exception 'Компания не найдена'; end if;
+ if p_user=v_owner then raise exception 'Главный аккаунт компании нельзя изменить через управление сотрудниками'; end if;
+ if not exists(select 1 from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user) then raise exception 'Пользователь не найден'; end if;
+
+ if coalesce(p_is_active,false)
+ and not coalesce((select is_active from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user),false) then
+ select p.max_members into v_max
+ from public.sun_workspace_subscriptions s
+ join public.sun_plans p on p.id=s.plan_id
+ where s.workspace_id=p_workspace;
+ select count(*)::int into v_active_count
+ from public.sun_workspace_members
+ where workspace_id=p_workspace and is_active=true;
+ if v_max is not null and v_active_count>=v_max then raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max; end if;
+ end if;
+
+ v_permissions:=coalesce(p_permissions,'{}'::jsonb)||jsonb_build_object('users.manage',false);
+
+ update public.sun_workspace_members
+ set display_name=nullif(trim(coalesce(p_display_name,'')),''),
+ role=v_role,
+ is_active=coalesce(p_is_active,false),
+ permissions=v_permissions,
+ updated_at=now()
+ where workspace_id=p_workspace and user_id=p_user;
+end;
+$$;
+
+create or replace function public.sun_admin_remove_member(p_workspace uuid,p_user uuid)
+returns void
+language plpgsql
+security definer
+set search_path='public'
+as $$
+declare
+ v_owner uuid;
+begin
+ perform pg_advisory_xact_lock(hashtext(p_workspace::text));
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then
+ raise exception 'Только владелец компании может удалять сотрудников';
+ end if;
+ if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
+ if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if;
+ select created_by into v_owner from public.sun_workspaces where id=p_workspace;
+ if p_user=v_owner then raise exception 'Главный аккаунт компании удалить нельзя'; end if;
+ delete from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user;
+end;
+$$;
+
+create or replace function public.sun_employee_prepare_v28(
+ p_workspace uuid,
+ p_email text,
+ p_display_name text default '',
+ p_role text default 'manager'
+)
+returns jsonb
+language plpgsql
+security definer
+set search_path='public','auth'
+as $$
+declare
+ v_email text:=lower(trim(coalesce(p_email,'')));
+ v_name text:=trim(coalesce(p_display_name,''));
+ v_role text:=lower(coalesce(p_role,'manager'));
+ v_user uuid;
+ v_member public.sun_workspace_members%rowtype;
+ v_max integer;
+ v_count integer;
+ v_mode text;
+begin
+ if auth.uid() is null then raise exception 'Сначала войдите в Caterium'; end if;
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
+ if v_email='' or v_email !~* '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$' then raise exception 'Введите корректный email сотрудника'; end if;
+ if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if;
+ if v_name='' then v_name:=split_part(v_email,'@',1); end if;
+
+ v_mode:=public.sun_workspace_access_mode_internal_v28(p_workspace);
+ if v_mode<>'full' then raise exception 'Подписка компании не позволяет добавлять сотрудников'; end if;
+ if not public.sun_workspace_feature_internal_v28(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
+
+ select p.max_members into v_max
+ from public.sun_workspace_subscriptions s join public.sun_plans p on p.id=s.plan_id
+ where s.workspace_id=p_workspace;
+
+ select count(*)::int into v_count from public.sun_workspace_members
+ where workspace_id=p_workspace and is_active=true;
+
+ select u.id into v_user from auth.users u where lower(coalesce(u.email,''))=v_email order by u.created_at asc limit 1;
+
+ if v_user is not null then
+ select * into v_member from public.sun_workspace_members
+ where workspace_id=p_workspace and user_id=v_user limit 1;
+ if found and v_member.is_active then
+ return jsonb_build_object('status','already_member','user_id',v_user,'email',v_email,'display_name',coalesce(nullif(v_member.display_name,''),v_name),'role',v_member.role);
+ end if;
+ end if;
+
+ if v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if;
+
+ return jsonb_build_object(
+ 'status',case when v_user is null then 'new' else 'existing' end,
+ 'user_id',v_user,
+ 'email',v_email,
+ 'display_name',v_name,
+ 'role',v_role,
+ 'max_members',v_max,
+ 'active_members',v_count
+ );
+end;
+$$;
+
+create or replace function public.sun_employee_finalize_v28(
+ p_workspace uuid,
+ p_user_id uuid,
+ p_display_name text default '',
+ p_role text default 'manager'
+)
+returns jsonb
+language plpgsql
+security definer
+set search_path='public','auth'
+as $$
+declare
+ v_name text:=trim(coalesce(p_display_name,''));
+ v_role text:=lower(coalesce(p_role,'manager'));
+ v_email text;
+ v_max integer;
+ v_count integer;
+ v_already boolean:=false;
+begin
+ if auth.uid() is null then raise exception 'Сначала войдите в Caterium'; end if;
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
+ if public.sun_workspace_access_mode_internal_v28(p_workspace)<>'full' then raise exception 'Подписка компании не позволяет добавлять сотрудников'; end if;
+ if not public.sun_workspace_feature_internal_v28(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
+ if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if;
+
+ select lower(email) into v_email from auth.users where id=p_user_id;
+ if v_email is null then raise exception 'Аккаунт сотрудника не найден'; end if;
+ if v_name='' then v_name:=split_part(v_email,'@',1); end if;
+
+ select exists(select 1 from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user_id and is_active=true) into v_already;
+ select p.max_members into v_max
+ from public.sun_workspace_subscriptions s join public.sun_plans p on p.id=s.plan_id
+ where s.workspace_id=p_workspace;
+ select count(*)::int into v_count from public.sun_workspace_members where workspace_id=p_workspace and is_active=true;
+ if not v_already and v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if;
+
+ insert into public.sun_workspace_members(workspace_id,user_id,role,display_name,is_active,permissions,updated_at)
+ values(p_workspace,p_user_id,v_role,left(v_name,120),true,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),now())
+ on conflict(workspace_id,user_id) do update
+ set role=excluded.role,display_name=excluded.display_name,is_active=true,permissions=excluded.permissions,updated_at=now();
+
+ delete from public.sun_workspace_invites where workspace_id=p_workspace and used_at is null and lower(coalesce(email,''))=v_email;
+
+ return jsonb_build_object('status',case when v_already then 'updated' else 'added' end,'user_id',p_user_id,'email',v_email,'display_name',v_name,'role',v_role);
+end;
+$$;
+
+create or replace function public.sun_create_invite(p_workspace uuid,p_role text default 'manager')
+returns uuid
+language plpgsql
+security definer
+set search_path='public'
+as $$
+declare
+ v_token uuid;
+ v_role text:=lower(coalesce(p_role,'manager'));
+ v_max integer;
+ v_count integer;
+begin
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
+ if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
+ if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
+ if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Некорректная роль сотрудника'; end if;
+ select p.max_members into v_max from public.sun_workspace_subscriptions s join public.sun_plans p on p.id=s.plan_id where s.workspace_id=p_workspace;
+ select count(*)::int into v_count from public.sun_workspace_members where workspace_id=p_workspace and is_active=true;
+ if v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max; end if;
+ insert into public.sun_workspace_invites(workspace_id,role,permissions,created_by)
+ values(p_workspace,v_role,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),auth.uid()) returning token into v_token;
+ return v_token;
+end;
+$$;
+
+create or replace function public.sun_create_invite_v27(
+ p_workspace uuid,
+ p_email text,
+ p_display_name text default '',
+ p_role text default 'manager'
+)
+returns uuid
+language plpgsql
+security definer
+set search_path='public','auth'
+as $$
+declare
+ v_token uuid;
+ v_role text:=lower(coalesce(p_role,'manager'));
+ v_email text:=lower(trim(coalesce(p_email,'')));
+ v_name text:=trim(coalesce(p_display_name,''));
+ v_max integer;
+ v_active integer;
+ v_pending integer;
+begin
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
+ if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
+ if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
+ if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Некорректная роль сотрудника'; end if;
+ if v_email='' or v_email !~* '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$' then raise exception 'Введите корректный email сотрудника'; end if;
+ if v_name='' then v_name:=split_part(v_email,'@',1); end if;
+
+ if exists(
+ select 1 from public.sun_workspace_members m
+ join auth.users u on u.id=m.user_id
+ where m.workspace_id=p_workspace and m.is_active=true and lower(coalesce(u.email,''))=v_email
+ ) then raise exception 'Пользователь с этим email уже добавлен в компанию'; end if;
+
+ delete from public.sun_workspace_invites
+ where workspace_id=p_workspace and used_at is null
+ and (expires_at<=now() or lower(coalesce(email,''))=v_email);
+
+ select p.max_members into v_max
+ from public.sun_workspace_subscriptions s
+ join public.sun_plans p on p.id=s.plan_id
+ where s.workspace_id=p_workspace;
+
+ if v_max is not null then
+ select count(*)::int into v_active from public.sun_workspace_members where workspace_id=p_workspace and is_active=true;
+ select count(*)::int into v_pending from public.sun_workspace_invites where workspace_id=p_workspace and used_at is null and expires_at>now();
+ if v_active+v_pending>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if;
+ end if;
+
+ insert into public.sun_workspace_invites(workspace_id,role,permissions,created_by,email,display_name)
+ values(p_workspace,v_role,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),auth.uid(),v_email,left(v_name,120))
+ returning token into v_token;
+ return v_token;
+end;
+$$;
+
+create or replace function public.sun_list_workspace_invites_v27(p_workspace uuid)
+returns table(token uuid,email text,display_name text,role text,created_at timestamptz,expires_at timestamptz,status text)
+language plpgsql
+stable
+security definer
+set search_path='public'
+as $$
+begin
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может просматривать приглашения'; end if;
+ return query
+ select i.token,i.email,i.display_name,i.role,i.created_at,i.expires_at,
+ case when i.expires_at<=now() then 'expired' else 'pending' end::text
+ from public.sun_workspace_invites i
+ where i.workspace_id=p_workspace and i.used_at is null
+ order by i.created_at desc;
+end;
+$$;
+
+create or replace function public.sun_cancel_invite_v27(p_workspace uuid,p_token uuid)
+returns boolean
+language plpgsql
+security definer
+set search_path='public'
+as $$
+declare v_deleted integer;
+begin
+ if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может отменять приглашения'; end if;
+ delete from public.sun_workspace_invites where workspace_id=p_workspace and token=p_token and used_at is null;
+ get diagnostics v_deleted=row_count;
+ return v_deleted>0;
+end;
+$$;
+
+revoke all on function public.sun_list_workspace_members(uuid) from public,anon;
+revoke all on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) from public,anon;
+revoke all on function public.sun_admin_remove_member(uuid,uuid) from public,anon;
+revoke all on function public.sun_employee_prepare_v28(uuid,text,text,text) from public,anon;
+revoke all on function public.sun_employee_finalize_v28(uuid,uuid,text,text) from public,anon;
+revoke all on function public.sun_create_invite(uuid,text) from public,anon;
+revoke all on function public.sun_create_invite_v27(uuid,text,text,text) from public,anon;
+revoke all on function public.sun_list_workspace_invites_v27(uuid) from public,anon;
+revoke all on function public.sun_cancel_invite_v27(uuid,uuid) from public,anon;
+
+grant execute on function public.sun_list_workspace_members(uuid) to authenticated;
+grant execute on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) to authenticated;
+grant execute on function public.sun_admin_remove_member(uuid,uuid) to authenticated;
+grant execute on function public.sun_employee_prepare_v28(uuid,text,text,text) to authenticated;
+grant execute on function public.sun_employee_finalize_v28(uuid,uuid,text,text) to authenticated;
+grant execute on function public.sun_create_invite(uuid,text) to authenticated;
+grant execute on function public.sun_create_invite_v27(uuid,text,text,text) to authenticated;
+grant execute on function public.sun_list_workspace_invites_v27(uuid) to authenticated;
+grant execute on function public.sun_cancel_invite_v27(uuid,uuid) to authenticated;
diff --git a/package.json b/package.json
index 5bf3ccb..1b65342 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
{
"name": "caterium-app",
"private": true,
- "version": "17.7.3",
+ "version": "17.8.0",
"type": "module",
"scripts": {
- "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/auth-security-v1774.js && node --check public/core/order-enhancements-v1775.js && node --check public/core/data-layer-v1773.js && node --check public/core/server-automation-v1770.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js",
+ "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/account-center-v1780.js && node --check public/core/performance.js && node --check public/core/auth-security-v1774.js && node --check public/core/order-enhancements-v1775.js && node --check public/core/data-layer-v1773.js && node --check public/core/server-automation-v1770.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js",
"test:static": "node tests/static-security.mjs && node tests/auth-security-v1774.mjs && node tests/employee-create-v1774.mjs && node tests/html-integrity-v1774.mjs && node tests/edge-security-v1774.mjs && node tests/branding-v1774.mjs && node tests/order-enhancements-v1775.mjs",
"check:release": "node tests/release-check.mjs",
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release",
@@ -17,4 +17,4 @@
"http-server": "^14.1.1",
"wrangler": "4.131.0"
}
-}
+}
\ No newline at end of file
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..4d7c95d
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,19 @@
+
Создавайте сотрудников, назначайте им рабочую роль и включайте только нужные права.
Загружаю аккаунт…
'; + try{await loadSnapshot();render()}catch(e){body.innerHTML=`${esc(e?.message||e||'Не удалось загрузить аккаунт.')}
`} +} +function close(){modal?.classList.remove('on')} +function sanitizeRbac(){ + const body=$('#sunRbacBody');if(!body)return; + qa('select option[value="admin"]',body).forEach(o=>o.remove()); + qa('[data-rbac-perm="users.manage"]',body).forEach(i=>{i.checked=false;i.disabled=true;const row=i.closest('label');if(row)row.title='Это право принадлежит только главному аккаунту компании';}); + if(snapshot?.is_owner&&snapshot?.user_id){ + const btn=body.querySelector(`[data-rbac-edit="${CSS.escape(String(snapshot.user_id))}"]`); + if(btn){const row=btn.closest('.sun-rbac-member');if(row){row.dataset.cateriumOwnerRow='1';const role=row.querySelector('.role-chip');if(role)role.textContent='Владелец';btn.outerHTML='Главный аккаунт';}} + } + const addRole=$('#sunRbacEmployeeRole',body);if(addRole)qa('option[value="admin"]',addRole).forEach(o=>o.remove()); +} +function ensureSidebarEntry(){ + const footer=$('.sun-side-footer');if(!footer||!session()?.user)return; + let button=$('#cateriumAccountButton'); + if(!button){ + button=document.createElement('button'); + button.id='cateriumAccountButton';button.type='button';button.className='sun-side-action';button.title='Профиль, email и пароль'; + button.innerHTML='◎Мой аккаунт'; + footer.insertBefore(button,footer.querySelector('#sunLogoutBtn')||footer.firstChild); + } + if(!button.dataset.cateriumAccountBound){button.dataset.cateriumAccountBound='1';button.addEventListener('click',open)} +} +function bindEntry(){ + const label=$('#sunCurrentUserLabel');if(label&&!label.dataset.cateriumAccountBound){label.dataset.cateriumAccountBound='1';label.title='Открыть личный кабинет';label.addEventListener('click',open)} + const ss=session();const uid=ss?.user?.id||'';if(uid&&uid!==lastUserId){lastUserId=uid;snapshot=null;} + ensureSidebarEntry();sanitizeRbac(); +} +installStyle();ensureModal();bindEntry(); +const obs=new MutationObserver(bindEntry);obs.observe(document.documentElement,{childList:true,subtree:true}); +window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(bindEntry,0)); +setInterval(()=>{if(!document.hidden)bindEntry()},3000); +window.CateriumAccountCenterV1780=Object.freeze({VERSION,open,close,reload:async()=>{await loadSnapshot();render();return snapshot}}); +})(); diff --git a/public/core/login-signature-v1776.js b/public/core/login-signature-v1776.js index 791711a..d5acf67 100644 --- a/public/core/login-signature-v1776.js +++ b/public/core/login-signature-v1776.js @@ -1,112 +1,30 @@ (()=>{ - 'use strict'; - if(window.CateriumLoginSignatureV1776)return; - const VERSION='17.7.6-signature-login-v2'; - const $=(s,r=document)=>r.querySelector(s); - const boot=()=>$('#cateriumAuthBootV1776'); - const removeBoot=()=>{const b=boot();if(b){b.classList.add('leave');setTimeout(()=>b.remove(),180)}}; - - function installStyle(){ - if($('#caterium-login-signature-v1776-style'))return; - const style=document.createElement('style'); - style.id='caterium-login-signature-v1776-style'; - style.textContent=` - body.sun-cloud-auth-required{overflow:hidden!important;background:#091019!important} - body.sun-cloud-auth-required>header,body.sun-cloud-auth-required>.view{visibility:hidden!important} - #sunCloudAuthGateV3.sun-cloud-auth-gate{ - position:fixed!important;inset:0!important;z-index:20000!important;overflow:hidden!important; - display:grid!important;grid-template-columns:minmax(520px,47%) 1fr!important;place-items:stretch!important; - padding:0!important;background:#091019!important;color:#fff!important; - } - #sunCloudAuthGateV3.sun-cloud-auth-gate::before{ - content:'';position:absolute;z-index:0;inset:0 47% 0 0;pointer-events:none; - background:radial-gradient(circle at 74% 48%,rgba(226,169,59,.13),transparent 18%),linear-gradient(125deg,#091019 0%,#111922 62%,#17191b 100%); - } - #sunCloudAuthGateV3.sun-cloud-auth-gate::after{ - content:'';position:absolute;z-index:0;top:0;right:0;bottom:0;width:53%;pointer-events:none; - background-image:linear-gradient(90deg,#111922 0%,rgba(17,25,34,.52) 12%,rgba(0,0,0,.12) 44%,rgba(0,0,0,.08) 100%),url('offer-gallery/002.jpg'); - background-size:cover;background-position:center;filter:saturate(.92) contrast(1.04); - } - #sunCloudAuthGateV3 .sun-cloud-auth-card{ - grid-column:1!important;align-self:center!important;justify-self:center!important;position:relative!important;z-index:3!important; - width:min(470px,calc(100% - 72px))!important;margin:0!important;padding:24px 0 34px!important; - border:0!important;border-radius:0!important;background:transparent!important;color:#f7f4ee!important; - box-shadow:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important; - } - #sunCloudAuthGateV3 .sun-cloud-auth-card::before{ - content:'C';position:fixed;z-index:-1;left:-7vw;top:50%;transform:translateY(-53%);pointer-events:none; - font:400 min(68vw,790px)/.72 Georgia,'Times New Roman',serif;color:transparent; - -webkit-text-stroke:min(5.8vw,82px) rgba(255,255,255,.09);text-stroke:min(5.8vw,82px) rgba(255,255,255,.09); - } - #sunCloudAuthGateV3 .caterium-signature-brand{display:flex;align-items:center;gap:12px;margin:0 0 42px;color:#fff} - #sunCloudAuthGateV3 .caterium-signature-c{position:relative;display:inline-grid;place-items:center;width:52px;height:52px;font:500 58px/1 Georgia,'Times New Roman',serif;letter-spacing:-8px;color:#fff} - #sunCloudAuthGateV3 .caterium-signature-c i{position:absolute;right:-2px;top:23px;width:9px;height:9px;border-radius:50%;background:#e3aa3f;box-shadow:0 0 20px rgba(227,170,63,.3)} - #sunCloudAuthGateV3 .caterium-signature-word{font:500 30px/1 Georgia,'Times New Roman',serif;color:#fff} - #sunCloudAuthGateV3 .sun-cloud-auth-brand{display:block!important;margin:0 0 27px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand img{display:none!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{margin:0!important;max-width:440px;color:#fff!important;font:500 clamp(37px,3.25vw,52px)/1.04 Georgia,'Times New Roman',serif!important;letter-spacing:-.025em!important} - #sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:10px!important;color:rgba(255,255,255,.67)!important;font-size:15px!important;line-height:1.5!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card>p.hint{color:rgba(255,255,255,.68)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card label{display:block!important;margin:14px 0 0!important;color:rgba(255,255,255,.72)!important;font-size:12px!important;font-weight:700!important;letter-spacing:.015em!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card input,#sunCloudAuthGateV3 .sun-cloud-auth-card select,#sunCloudAuthGateV3 .sun-cloud-auth-card textarea{ - width:100%!important;height:48px!important;margin-top:6px!important;padding:0 15px!important;border:1px solid rgba(255,255,255,.27)!important;border-radius:9px!important; - background:rgba(8,12,16,.36)!important;color:#fff!important;box-shadow:none!important;outline:none!important; - } - #sunCloudAuthGateV3 .sun-cloud-auth-card input:focus{border-color:#e3aa3f!important;box-shadow:0 0 0 3px rgba(227,170,63,.12)!important;background:rgba(8,12,16,.58)!important} - #sunCloudAuthGateV3 .sun-auth-eye{right:7px!important;bottom:7px!important;color:rgba(255,255,255,.78)!important;background:transparent!important} - #sunCloudAuthGateV3 .sun-auth-eye:hover{background:rgba(255,255,255,.08)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions{display:block!important;margin-top:20px!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary,#sunCloudAuthGateV3 #sunGateSubmitV3{ - width:100%!important;min-height:50px!important;border:0!important;border-radius:9px!important;background:linear-gradient(100deg,#dba137,#efc45f)!important; - color:#111!important;font-size:15px!important;font-weight:900!important;box-shadow:0 13px 30px rgba(207,145,37,.17)!important; - } - #sunCloudAuthGateV3 .sun-cloud-auth-actions .primary:hover,#sunCloudAuthGateV3 #sunGateSubmitV3:hover{filter:brightness(1.05)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-actions .outline{width:100%!important;margin-top:8px!important;border-color:rgba(255,255,255,.22)!important;background:rgba(255,255,255,.04)!important;color:#fff!important} - #sunCloudAuthGateV3 .sun-cloud-auth-error{min-height:18px!important;margin-top:8px!important;color:#ffb6ad!important;font-size:12px!important} - #sunCloudAuthGateV3 .sun-auth-switch{margin:20px 0 0!important;padding-top:19px!important;border-top:1px solid rgba(255,255,255,.16)!important;color:rgba(255,255,255,.7)!important;font-size:13px!important} - #sunCloudAuthGateV3 .sun-auth-switch button{border:0!important;background:transparent!important;color:#e8b34c!important;text-decoration:none!important;font-weight:900!important} - #sunCloudAuthGateV3 .caterium-signature-caption{position:fixed;left:42px;bottom:32px;z-index:4;color:rgba(255,255,255,.6);font-size:9px;letter-spacing:.3em;text-transform:uppercase;pointer-events:none} - #sunCloudAuthGateV3 .caterium-signature-caption::before{content:'';display:block;width:45px;height:2px;margin-bottom:12px;background:#e3aa3f} - #sunCloudAuthGateV3 .caterium-signature-manifesto{position:fixed;right:42px;top:36px;z-index:4;width:150px;color:rgba(255,255,255,.7);font-size:9px;line-height:1.9;letter-spacing:.23em;text-transform:uppercase;pointer-events:none;text-shadow:0 1px 10px #0008} - #sunCloudAuthGateV3 .caterium-signature-manifesto::after{content:'';display:block;width:42px;height:2px;margin-top:12px;background:#e3aa3f} - @media(max-width:900px){ - #sunCloudAuthGateV3.sun-cloud-auth-gate{display:grid!important;grid-template-columns:1fr!important;background:#091019!important} - #sunCloudAuthGateV3.sun-cloud-auth-gate::before{inset:0;background:linear-gradient(180deg,rgba(7,13,20,.72),rgba(7,13,20,.9))} - #sunCloudAuthGateV3.sun-cloud-auth-gate::after{width:100%;opacity:.55;background-image:linear-gradient(180deg,rgba(5,10,15,.38),rgba(5,10,15,.88)),url('offer-gallery/002.jpg');background-position:center} - #sunCloudAuthGateV3 .sun-cloud-auth-card{grid-column:1!important;width:min(460px,calc(100% - 36px))!important;padding:20px 20px 24px!important;border:1px solid rgba(255,255,255,.12)!important;border-radius:18px!important;background:rgba(7,12,17,.66)!important;backdrop-filter:blur(13px)!important;-webkit-backdrop-filter:blur(13px)!important} - #sunCloudAuthGateV3 .sun-cloud-auth-card::before{left:-52vw;top:50%;font-size:120vw;-webkit-text-stroke:14vw rgba(255,255,255,.07)} - #sunCloudAuthGateV3 .caterium-signature-brand{margin-bottom:28px} - #sunCloudAuthGateV3 .caterium-signature-word{font-size:25px} - #sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:32px!important} - #sunCloudAuthGateV3 .caterium-signature-caption,#sunCloudAuthGateV3 .caterium-signature-manifesto{display:none!important} - } - `; - document.head.appendChild(style); - } - - function decorate(gate){ - if(!gate)return false; - const card=$('.sun-cloud-auth-card',gate);if(!card)return false; - gate.classList.add('caterium-signature-ready'); - if(!$('.caterium-signature-brand',card)){ - const brand=document.createElement('div');brand.className='caterium-signature-brand';brand.setAttribute('aria-label','Caterium'); - brand.innerHTML='CCaterium';card.prepend(brand); - } - if(!$('.caterium-signature-caption',gate)){const n=document.createElement('div');n.className='caterium-signature-caption';n.textContent='Caterium · управление кейтерингом';gate.appendChild(n)} - if(!$('.caterium-signature-manifesto',gate)){const n=document.createElement('div');n.className='caterium-signature-manifesto';n.textContent='Создаём впечатления, которые объединяют';gate.appendChild(n)} - const title=$('#sunGateTitleV3',gate),subtitle=$('#sunGateSubtitleV3',gate); - if(title&&title.textContent.trim()==='Вход в Caterium')title.textContent='Войти в рабочее пространство'; - if(subtitle&&subtitle.textContent.trim()==='Введите данные своего аккаунта')subtitle.textContent='Ваши заказы. Ваша команда. Ваш результат.'; - removeBoot();return true; - } - - function scan(){return decorate($('#sunCloudAuthGateV3'))} - function releaseForSignedIn(){ - try{const st=window.SunCloudV2?.status?.();if(st?.signedIn&&window.SunCloudV2?.getWorkspace?.()&&!$('#sunCloudAuthGateV3'))removeBoot()}catch(_){} - } - installStyle();scan(); - const observer=new MutationObserver(()=>{scan();releaseForSignedIn()});observer.observe(document.documentElement,{childList:true,subtree:true,characterData:true}); - window.addEventListener('sun:cloud-state-applied',()=>{scan();releaseForSignedIn()}); - const poll=setInterval(()=>{if(scan()||!boot()){clearInterval(poll);return}releaseForSignedIn()},80);setTimeout(()=>{clearInterval(poll);if(boot()&&!$('#sunCloudAuthGateV3'))removeBoot()},10000); - window.CateriumLoginSignatureV1776=Object.freeze({VERSION,scan,removeBoot,disconnect:()=>{observer.disconnect();clearInterval(poll)}}); +'use strict'; +if(window.CateriumLoginSignatureV1776)return; +const VERSION='17.7.7-cream-login-v2'; +const $=(s,r=document)=>r.querySelector(s); +const boot=()=>$('#cateriumAuthBootV1776'); +const removeBoot=()=>{const b=boot();if(b){b.classList.add('leave');setTimeout(()=>b.remove(),180)}}; +const FLOW=''; +function styles(){if($('#caterium-login-signature-v1776-style'))return;const s=document.createElement('style');s.id='caterium-login-signature-v1776-style';s.textContent=` +body.sun-cloud-auth-required{overflow:hidden!important;background:#f5f0e7!important}body.sun-cloud-auth-required>header,body.sun-cloud-auth-required>.view{visibility:hidden!important} +#sunCloudAuthGateV3.sun-cloud-auth-gate{position:fixed!important;inset:0!important;z-index:20000!important;overflow:auto!important;display:grid!important;place-items:center!important;padding:clamp(32px,6vh,78px) clamp(24px,8vw,120px)!important;background:radial-gradient(circle at 18% 18%,rgba(255,255,255,.92),transparent 34%),radial-gradient(circle at 77% 22%,rgba(255,255,255,.5),transparent 28%),linear-gradient(135deg,#f8f4ec 0%,#f2ece1 56%,#f7f3eb 100%)!important;color:#282621!important} +#sunCloudAuthGateV3.sun-cloud-auth-gate:before{content:'C';position:fixed;z-index:0;left:-7vw;top:49%;transform:translateY(-50%);pointer-events:none;font:400 min(72vw,860px)/.72 Georgia,'Times New Roman',serif;color:rgba(73,67,59,.032)} +#sunCloudAuthGateV3.sun-cloud-auth-gate:after{content:'';position:fixed;z-index:0;right:-7vw;bottom:-14vh;width:min(48vw,720px);height:min(70vh,850px);pointer-events:none;background:radial-gradient(ellipse at 58% 20%,rgba(67,64,58,.14) 0 7%,transparent 8%),radial-gradient(ellipse at 36% 35%,rgba(67,64,58,.13) 0 6%,transparent 7%),radial-gradient(ellipse at 68% 47%,rgba(67,64,58,.12) 0 8%,transparent 9%),linear-gradient(106deg,transparent 43%,rgba(67,64,58,.08) 44% 46%,transparent 47%);filter:blur(18px);transform:rotate(-17deg);opacity:.45} +#sunCloudAuthGateV3 .sun-cloud-auth-card{position:relative!important;z-index:3!important;width:min(790px,100%)!important;margin:0!important;padding:0 0 58px!important;border:0!important;border-radius:0!important;background:transparent!important;color:#2c2924!important;box-shadow:none!important;backdrop-filter:none!important} +#sunCloudAuthGateV3 .caterium-signature-brand{display:flex;align-items:center;gap:13px;margin:0 0 clamp(46px,7vh,88px)!important}.caterium-signature-mark{display:block;width:62px;height:62px;object-fit:contain;flex:0 0 62px}.caterium-signature-word{font:500 36px/1 Georgia,'Times New Roman',serif;color:#26231f;letter-spacing:-.02em} +#sunCloudAuthGateV3 .sun-cloud-auth-brand{display:block!important;margin:0 0 35px!important}#sunCloudAuthGateV3 .sun-cloud-auth-brand img{display:none!important}#sunCloudAuthGateV3 .sun-cloud-auth-brand h2{margin:0!important;max-width:720px;color:#24211d!important;font:500 clamp(48px,5.2vw,76px)/.99 Georgia,'Times New Roman',serif!important;letter-spacing:-.045em!important}#sunCloudAuthGateV3 .sun-cloud-auth-brand .hint{margin-top:18px!important;color:#8e887f!important;font-size:clamp(16px,1.55vw,21px)!important;line-height:1.45!important;font-weight:400!important} +#sunCloudAuthGateV3 .sun-cloud-auth-card>p.hint{color:#817b72!important}#sunCloudAuthGateV3 .sun-cloud-auth-card label{display:block!important;margin:14px 0 0!important;color:#716b62!important;font-size:12px!important;font-weight:700!important}#sunCloudAuthGateV3 label.caterium-clean-field{font-size:0!important;color:transparent!important;position:relative!important} +#sunCloudAuthGateV3 .sun-cloud-auth-card input,#sunCloudAuthGateV3 .sun-cloud-auth-card select,#sunCloudAuthGateV3 .sun-cloud-auth-card textarea{width:100%!important;min-height:58px!important;margin-top:7px!important;padding:0 18px!important;border:1px solid #bdb5aa!important;border-radius:13px!important;background:rgba(255,255,255,.13)!important;color:#312e29!important;box-shadow:none!important;outline:none!important;font-size:16px!important;font-weight:500!important}#sunCloudAuthGateV3 label.caterium-clean-field input{margin-top:0!important;padding-left:58px!important}#sunCloudAuthGateV3 input::placeholder{color:#9c958c!important;opacity:1!important}#sunCloudAuthGateV3 input:focus{border-color:#d7a632!important;box-shadow:0 0 0 4px rgba(216,167,50,.12)!important;background:rgba(255,255,255,.5)!important} +#sunCloudAuthGateV3 .caterium-email-field:before{content:'✉';position:absolute;z-index:2;left:20px;top:15px;color:#777168;font-size:25px;font-weight:400}#sunCloudAuthGateV3 .caterium-password-field:before{content:'♙';position:absolute;z-index:2;left:21px;top:14px;color:#777168;font-size:24px;transform:rotate(180deg);opacity:.8} +#sunCloudAuthGateV3 .sun-auth-password{display:block!important;position:relative!important}#sunCloudAuthGateV3 .sun-auth-password input{padding-right:58px!important}#sunCloudAuthGateV3 .sun-auth-eye{right:9px!important;bottom:10px!important;width:38px!important;height:38px!important;color:#777168!important;background:transparent!important;border-radius:9px!important} +#sunCloudAuthGateV3 .sun-cloud-auth-actions{display:block!important;margin-top:25px!important}#sunCloudAuthGateV3 .sun-cloud-auth-actions .primary,#sunCloudAuthGateV3 #sunGateSubmitV3{width:100%!important;min-height:62px!important;border:0!important;border-radius:13px!important;background:linear-gradient(100deg,#d9aa42,#efca69)!important;color:#171512!important;font-size:18px!important;font-weight:900!important;box-shadow:0 14px 34px rgba(184,135,34,.14)!important}#sunCloudAuthGateV3 #sunGateSubmitV3:after{content:' →';font-size:24px;font-weight:500;margin-left:13px} +#sunCloudAuthGateV3 .sun-cloud-auth-actions .outline{width:100%!important;margin-top:9px!important;min-height:48px!important;border:1px solid #c9c1b6!important;border-radius:11px!important;background:rgba(255,255,255,.25)!important;color:#37332d!important}#sunCloudAuthGateV3 .sun-cloud-auth-error{min-height:19px!important;margin-top:10px!important;color:#a64c40!important;font-size:12px!important}#sunCloudAuthGateV3 .sun-auth-switch{margin:26px 0 0!important;padding-top:25px!important;border-top:1px solid #cfc7bc!important;color:#898279!important;font-size:14px!important;text-align:center!important}#sunCloudAuthGateV3 .sun-auth-switch button{border:0!important;background:transparent!important;color:#c18f24!important;text-decoration:none!important;font-weight:900!important} +.caterium-signature-caption{position:fixed;left:42px;bottom:31px;z-index:4;color:#9d968d;font-size:9px;letter-spacing:.31em;text-transform:uppercase;pointer-events:none}.caterium-signature-caption:before{content:'';display:block;width:48px;height:2px;margin-bottom:13px;background:#d3a43c}.caterium-signature-manifesto{position:fixed;right:56px;top:44px;z-index:4;width:270px;color:#a19a91;font-size:10px;line-height:1.9;letter-spacing:.28em;text-transform:uppercase;pointer-events:none;white-space:pre-line}.caterium-flow-top{position:fixed;z-index:1;right:5.5vw;top:13vh;width:min(37vw,470px);opacity:.86;pointer-events:none;color:#a7a097}.caterium-flow-top svg{width:100%;height:auto}.caterium-flow-top path{fill:none;stroke:currentColor;stroke-width:1.6}.caterium-flow-top circle{fill:currentColor}.caterium-flow-top circle.gold{fill:#e5a313} +@media(max-width:980px){#sunCloudAuthGateV3.sun-cloud-auth-gate{padding:34px 28px 70px!important;place-items:start center!important}#sunCloudAuthGateV3 .sun-cloud-auth-card{width:min(720px,100%)!important;padding-top:12px!important}.caterium-signature-manifesto{display:none!important}.caterium-flow-top{right:-60px;top:125px;width:360px;opacity:.45}} +@media(max-width:620px){body.sun-cloud-auth-required{overflow:auto!important}#sunCloudAuthGateV3.sun-cloud-auth-gate{min-height:100dvh!important;padding:25px 18px 50px!important;place-items:start center!important}#sunCloudAuthGateV3 .sun-cloud-auth-card{width:100%!important;padding:0!important}.caterium-signature-brand{margin-bottom:42px!important}.caterium-signature-mark{width:50px;height:50px;flex-basis:50px}.caterium-signature-word{font-size:29px}#sunCloudAuthGateV3 .sun-cloud-auth-brand h2{font-size:clamp(38px,12vw,52px)!important}.caterium-flow-top{right:-125px;top:100px;width:330px;opacity:.25}.caterium-signature-caption{display:none!important}} +`;document.head.appendChild(s)} +function field(g,id,ph,type){const i=$('#'+id,g);if(!i)return;i.placeholder=ph;const l=i.closest('label');if(l)l.classList.add('caterium-clean-field',type==='email'?'caterium-email-field':'caterium-password-field')} +function decorate(g){if(!g)return false;const c=$('.sun-cloud-auth-card',g);if(!c)return false;if(!$('.caterium-signature-brand',c)){const b=document.createElement('div');b.className='caterium-signature-brand';b.innerHTML='Удаляем старый экран входа и подключаем свежую версию…
+