lightman-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/agent.config.template.json +30 -0
  2. package/bin/cms-agent.js +233 -0
  3. package/nssm/nssm.exe +0 -0
  4. package/package.json +52 -0
  5. package/public/assets/index-CcBNCz6h.css +1 -0
  6. package/public/assets/index-H-8HDl46.js +1 -0
  7. package/public/index.html +19 -0
  8. package/scripts/guardian.ps1 +75 -0
  9. package/scripts/install-linux.sh +134 -0
  10. package/scripts/install-rpi.sh +117 -0
  11. package/scripts/install-windows.ps1 +529 -0
  12. package/scripts/launch-kiosk.vbs +101 -0
  13. package/scripts/lightman-agent.logrotate +12 -0
  14. package/scripts/lightman-agent.service +38 -0
  15. package/scripts/lightman-shell.bat +128 -0
  16. package/scripts/reinstall-windows.ps1 +26 -0
  17. package/scripts/restore-desktop.ps1 +32 -0
  18. package/scripts/setup.ps1 +116 -0
  19. package/scripts/setup.sh +115 -0
  20. package/scripts/uninstall-linux.sh +50 -0
  21. package/scripts/uninstall-windows.ps1 +54 -0
  22. package/src/commands/display.ts +177 -0
  23. package/src/commands/kiosk.ts +113 -0
  24. package/src/commands/maintenance.ts +106 -0
  25. package/src/commands/network.ts +129 -0
  26. package/src/commands/power.ts +163 -0
  27. package/src/commands/rpi.ts +45 -0
  28. package/src/commands/screenshot.ts +166 -0
  29. package/src/commands/serial.ts +17 -0
  30. package/src/commands/update.ts +124 -0
  31. package/src/index.ts +652 -0
  32. package/src/lib/config.ts +69 -0
  33. package/src/lib/identity.ts +40 -0
  34. package/src/lib/logger.ts +137 -0
  35. package/src/lib/platform.ts +10 -0
  36. package/src/lib/rpi.ts +180 -0
  37. package/src/lib/screens.ts +128 -0
  38. package/src/lib/types.ts +176 -0
  39. package/src/services/commands.ts +107 -0
  40. package/src/services/health.ts +161 -0
  41. package/src/services/kiosk.ts +395 -0
  42. package/src/services/localEvents.ts +60 -0
  43. package/src/services/logForwarder.ts +72 -0
  44. package/src/services/multiScreenKiosk.ts +324 -0
  45. package/src/services/oscBridge.ts +186 -0
  46. package/src/services/powerScheduler.ts +260 -0
  47. package/src/services/provisioning.ts +120 -0
  48. package/src/services/serialBridge.ts +230 -0
  49. package/src/services/serviceLauncher.ts +183 -0
  50. package/src/services/staticServer.ts +226 -0
  51. package/src/services/updater.ts +249 -0
  52. package/src/services/watchdog.ts +310 -0
  53. package/src/services/websocket.ts +152 -0
  54. package/tsconfig.json +28 -0
@@ -0,0 +1,30 @@
1
+ {
2
+ "serverUrl": "__SERVER_URL__",
3
+ "deviceSlug": "__DEVICE_SLUG__",
4
+ "healthIntervalMs": 60000,
5
+ "logLevel": "info",
6
+ "logFile": "agent.log",
7
+ "identityFile": ".lightman-identity.json",
8
+ "localServices": false,
9
+ "kiosk": {
10
+ "browserPath": "__BROWSER_PATH__",
11
+ "defaultUrl": "__KIOSK_URL__",
12
+ "extraArgs": [
13
+ "--start-fullscreen",
14
+ "--disable-translate",
15
+ "--disable-extensions",
16
+ "--autoplay-policy=no-user-gesture-required",
17
+ "--user-data-dir=__CHROME_DATA_DIR__"
18
+ ],
19
+ "pollIntervalMs": 10000,
20
+ "maxCrashesInWindow": 10,
21
+ "crashWindowMs": 300000,
22
+ "shellMode": false
23
+ },
24
+ "powerSchedule": {
25
+ "shutdownCron": "0 19 * * *",
26
+ "startupCron": "0 8 * * *",
27
+ "timezone": "Asia/Kolkata",
28
+ "shutdownWarningSeconds": 60
29
+ }
30
+ }
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { networkInterfaces } from 'os';
5
+ import { resolve, dirname } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { spawnSync } from 'child_process';
8
+ import { createInterface } from 'readline/promises';
9
+ import { stdin as input, stdout as output, cwd, platform, exit } from 'process';
10
+
11
+ const DEFAULT_SERVER = 'http://192.168.1.181:3401';
12
+ const INSTALL_CONFIG_PATH = 'C:\\Program Files\\Lightman\\Agent\\agent.config.json';
13
+
14
+ function printUsage() {
15
+ console.log(`
16
+ cms-agent <command> [options]
17
+
18
+ Commands:
19
+ install Prompt slug, install agent with ShellReplace, reboot
20
+ setup Alias of install
21
+ update Reinstall/update using installed config, reboot
22
+
23
+ Options:
24
+ --slug <value> Device slug (example: C-AV01)
25
+ --server <url> Server URL (example: http://192.168.1.181:3401)
26
+ --timezone <tz> Timezone override (default: Asia/Kolkata)
27
+ --no-restart Skip reboot after successful install/update
28
+ -h, --help Show help
29
+ `);
30
+ }
31
+
32
+ function parseArgs(argv) {
33
+ const args = {};
34
+ for (let i = 0; i < argv.length; i += 1) {
35
+ const part = argv[i];
36
+ if (!part.startsWith('-')) {
37
+ continue;
38
+ }
39
+ if (part === '--no-restart') {
40
+ args.noRestart = true;
41
+ continue;
42
+ }
43
+ if (part === '--help' || part === '-h') {
44
+ args.help = true;
45
+ continue;
46
+ }
47
+ const next = argv[i + 1];
48
+ if (!next || next.startsWith('-')) {
49
+ continue;
50
+ }
51
+ if (part === '--slug') {
52
+ args.slug = next.trim();
53
+ i += 1;
54
+ } else if (part === '--server') {
55
+ args.server = next.trim();
56
+ i += 1;
57
+ } else if (part === '--timezone') {
58
+ args.timezone = next.trim();
59
+ i += 1;
60
+ }
61
+ }
62
+ return args;
63
+ }
64
+
65
+ function safeReadJson(path) {
66
+ try {
67
+ if (!existsSync(path)) return null;
68
+ return JSON.parse(readFileSync(path, 'utf8'));
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ function isValidSlug(slug) {
75
+ return /^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(slug);
76
+ }
77
+
78
+ function collectMacAddresses() {
79
+ const nets = networkInterfaces();
80
+ const macs = new Set();
81
+ for (const netName of Object.keys(nets)) {
82
+ const ifaceList = nets[netName] || [];
83
+ for (const iface of ifaceList) {
84
+ const mac = (iface.mac || '').trim().toUpperCase();
85
+ if (!iface.internal && mac && mac !== '00:00:00:00:00:00') {
86
+ macs.add(`${netName}: ${mac}`);
87
+ }
88
+ }
89
+ }
90
+ return [...macs];
91
+ }
92
+
93
+ function isWindowsAdmin() {
94
+ const cmd = '[bool]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)';
95
+ const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', cmd], { encoding: 'utf8' });
96
+ return result.status === 0 && result.stdout.trim().toLowerCase() === 'true';
97
+ }
98
+
99
+ function runOrFail(command, args) {
100
+ const result = spawnSync(command, args, { stdio: 'inherit', shell: false });
101
+ if (result.error) {
102
+ throw result.error;
103
+ }
104
+ if (result.status !== 0) {
105
+ throw new Error(`Command failed with exit code ${result.status}: ${command} ${args.join(' ')}`);
106
+ }
107
+ }
108
+
109
+ async function promptSlug(defaultSlug) {
110
+ const rl = createInterface({ input, output });
111
+ try {
112
+ while (true) {
113
+ const prompt = defaultSlug
114
+ ? `Enter device slug [${defaultSlug}]: `
115
+ : 'Enter device slug (example C-AV01): ';
116
+ const answer = (await rl.question(prompt)).trim();
117
+ const slug = answer || defaultSlug || '';
118
+ if (isValidSlug(slug)) {
119
+ return slug;
120
+ }
121
+ console.error('Invalid slug. Use letters, numbers, and hyphens only.');
122
+ }
123
+ } finally {
124
+ rl.close();
125
+ }
126
+ }
127
+
128
+ function resolveInstallScript() {
129
+ const here = dirname(fileURLToPath(import.meta.url));
130
+ const packaged = resolve(here, '../scripts/install-windows.ps1');
131
+ const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
132
+ if (existsSync(packaged)) return packaged;
133
+ if (existsSync(localRepo)) return localRepo;
134
+ throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
135
+ }
136
+
137
+ function installUsingPowerShell({ scriptPath, slug, server, timezone, noRestart }) {
138
+ console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
139
+ const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
140
+ if (timezone) {
141
+ args.push('-Timezone', timezone);
142
+ }
143
+ runOrFail('powershell.exe', args);
144
+
145
+ if (!noRestart) {
146
+ console.log('Installation completed. Rebooting in 10 seconds...');
147
+ runOrFail('shutdown.exe', ['/r', '/t', '10', '/c', 'CMS Agent installation complete']);
148
+ }
149
+ }
150
+
151
+ async function runInstall(opts) {
152
+ if (platform !== 'win32') {
153
+ throw new Error('cms-agent install is currently supported on Windows only.');
154
+ }
155
+ if (!isWindowsAdmin()) {
156
+ throw new Error('Please run this command from an Administrator PowerShell or CMD.');
157
+ }
158
+
159
+ const localConfig = safeReadJson(resolve(cwd(), 'agent.config.json')) || {};
160
+ const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
161
+ const defaultSlug = opts.slug || localConfig.deviceSlug || installedConfig.deviceSlug || '';
162
+ const server = opts.server || localConfig.serverUrl || installedConfig.serverUrl || DEFAULT_SERVER;
163
+ const timezone = opts.timezone || localConfig?.powerSchedule?.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
164
+ const noRestart = Boolean(opts.noRestart);
165
+
166
+ console.log('Detected MAC addresses:');
167
+ const macs = collectMacAddresses();
168
+ if (macs.length === 0) {
169
+ console.log(' (no active external interface found)');
170
+ } else {
171
+ for (const item of macs) {
172
+ console.log(` ${item}`);
173
+ }
174
+ }
175
+ console.log('');
176
+
177
+ const slug = opts.slug || await promptSlug(defaultSlug);
178
+ const scriptPath = resolveInstallScript();
179
+
180
+ console.log(`Installing with slug=${slug}, server=${server}, shellReplace=true`);
181
+ installUsingPowerShell({ scriptPath, slug, server, timezone, noRestart });
182
+ }
183
+
184
+ async function runUpdate(opts) {
185
+ if (platform !== 'win32') {
186
+ throw new Error('cms-agent update is currently supported on Windows only.');
187
+ }
188
+ if (!isWindowsAdmin()) {
189
+ throw new Error('Please run this command from an Administrator PowerShell or CMD.');
190
+ }
191
+
192
+ const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
193
+ const slug = opts.slug || installedConfig.deviceSlug;
194
+ const server = opts.server || installedConfig.serverUrl || DEFAULT_SERVER;
195
+ const timezone = opts.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
196
+ const noRestart = Boolean(opts.noRestart);
197
+ const scriptPath = resolveInstallScript();
198
+
199
+ if (!slug) {
200
+ throw new Error('No installed slug found. Use cms-agent install first or pass --slug.');
201
+ }
202
+
203
+ console.log(`Updating with slug=${slug}, server=${server}, shellReplace=true`);
204
+ installUsingPowerShell({ scriptPath, slug, server, timezone, noRestart });
205
+ }
206
+
207
+ async function main() {
208
+ const [, , commandRaw, ...rest] = process.argv;
209
+ const command = commandRaw || '';
210
+ const opts = parseArgs(rest);
211
+
212
+ if (!command || opts.help || command === 'help' || command === '--help' || command === '-h') {
213
+ printUsage();
214
+ return;
215
+ }
216
+
217
+ if (command === 'install' || command === 'setup') {
218
+ await runInstall(opts);
219
+ return;
220
+ }
221
+ if (command === 'update') {
222
+ await runUpdate(opts);
223
+ return;
224
+ }
225
+
226
+ printUsage();
227
+ throw new Error(`Unknown command: ${command}`);
228
+ }
229
+
230
+ main().catch((err) => {
231
+ console.error(err instanceof Error ? err.message : String(err));
232
+ exit(1);
233
+ });
package/nssm/nssm.exe ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "lightman-agent",
3
+ "version": "1.0.0",
4
+ "description": "LIGHTMAN Agent - System-level daemon for museum display machines",
5
+ "private": false,
6
+ "type": "module",
7
+ "bin": {
8
+ "cms-agent": "bin/cms-agent.js",
9
+ "lightman-agent": "bin/cms-agent.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "nssm/",
14
+ "public/",
15
+ "scripts/",
16
+ "src/index.ts",
17
+ "src/commands/",
18
+ "src/lib/",
19
+ "src/services/",
20
+ "agent.config.template.json",
21
+ "package-lock.json",
22
+ "tsconfig.json"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "dev": "tsx watch src/index.ts",
29
+ "cms-agent": "node ./bin/cms-agent.js",
30
+ "sync-display": "node -e \"const{cpSync,rmSync,mkdirSync}=require('fs');rmSync('public',{recursive:true,force:true});mkdirSync('public',{recursive:true});cpSync('../display/dist','public',{recursive:true});console.log('Display synced to agent/public/')\"",
31
+ "build": "tsc",
32
+ "start": "node dist/index.js",
33
+ "typecheck": "tsc --noEmit",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest"
36
+ },
37
+ "dependencies": {
38
+ "systeminformation": "^5.23.0",
39
+ "ws": "^8.18.0",
40
+ "zod": "^3.24.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.10.0",
44
+ "@types/ws": "^8.5.13",
45
+ "tsx": "^4.19.2",
46
+ "typescript": "^5.7.2",
47
+ "vitest": "^3.1.1"
48
+ },
49
+ "engines": {
50
+ "node": ">=20.0.0"
51
+ }
52
+ }
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.border{border-style:var(--tw-border-style);border-width:1px}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}}html,body,#root{color:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#000;width:100%;height:100%;margin:0;padding:0;font-family:system-ui,-apple-system,sans-serif;overflow:hidden}body{overscroll-behavior:none;-webkit-overflow-scrolling:touch;touch-action:manipulation}::-webkit-scrollbar{display:none}*{scrollbar-width:none}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}