mirror of
https://github.com/acamarata/solar-spa.git
synced 2026-07-03 12:20:40 +00:00
style: fix prettier table formatting in wiki
This commit is contained in:
parent
f21be803fe
commit
a57b4502b2
7 changed files with 256 additions and 258 deletions
|
|
@ -7,7 +7,7 @@ Returns a `Promise<SpaResult>` with raw numeric values.
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| --- | --- | --- |
|
| ----------- | -------- | ------------------------------------------------- |
|
||||||
| `date` | `Date` | Date and time for the calculation |
|
| `date` | `Date` | Date and time for the calculation |
|
||||||
| `latitude` | `number` | Observer latitude, -90 to 90 (negative = south) |
|
| `latitude` | `number` | Observer latitude, -90 to 90 (negative = south) |
|
||||||
| `longitude` | `number` | Observer longitude, -180 to 180 (negative = west) |
|
| `longitude` | `number` | Observer longitude, -180 to 180 (negative = west) |
|
||||||
|
|
@ -16,7 +16,7 @@ Returns a `Promise<SpaResult>` with raw numeric values.
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --------------- | -------- | --------- | ------------------------------------------------------------- |
|
||||||
| `timezone` | `number` | auto | Hours from UTC. Auto-detected from the Date object if omitted |
|
| `timezone` | `number` | auto | Hours from UTC. Auto-detected from the Date object if omitted |
|
||||||
| `elevation` | `number` | `0` | Meters above sea level |
|
| `elevation` | `number` | `0` | Meters above sea level |
|
||||||
| `pressure` | `number` | `1013.25` | Atmospheric pressure in millibars |
|
| `pressure` | `number` | `1013.25` | Atmospheric pressure in millibars |
|
||||||
|
|
@ -31,7 +31,7 @@ Returns a `Promise<SpaResult>` with raw numeric values.
|
||||||
### Result Fields
|
### Result Fields
|
||||||
|
|
||||||
| Field | Type | Unit | Description |
|
| Field | Type | Unit | Description |
|
||||||
| --- | --- | --- | --- |
|
| ----------------- | -------- | ---------------- | ------------------------------------------------------------------ |
|
||||||
| `zenith` | `number` | degrees | Topocentric zenith angle (0 = directly overhead) |
|
| `zenith` | `number` | degrees | Topocentric zenith angle (0 = directly overhead) |
|
||||||
| `azimuth` | `number` | degrees | Topocentric azimuth, eastward from north (navigational convention) |
|
| `azimuth` | `number` | degrees | Topocentric azimuth, eastward from north (navigational convention) |
|
||||||
| `azimuth_astro` | `number` | degrees | Topocentric azimuth, westward from south (astronomical convention) |
|
| `azimuth_astro` | `number` | degrees | Topocentric azimuth, westward from south (astronomical convention) |
|
||||||
|
|
@ -48,7 +48,7 @@ Returns a `Promise<SpaResult>` with raw numeric values.
|
||||||
When `timezone` is omitted, the value is derived from the `Date` object's local timezone offset:
|
When `timezone` is omitted, the value is derived from the `Date` object's local timezone offset:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
timezone = -(date.getTimezoneOffset() / 60)
|
timezone = -(date.getTimezoneOffset() / 60);
|
||||||
```
|
```
|
||||||
|
|
||||||
This works correctly in most cases. Provide an explicit value when computing for a location whose timezone differs from the machine's local timezone.
|
This works correctly in most cases. Provide an explicit value when computing for a location whose timezone differs from the machine's local timezone.
|
||||||
|
|
@ -72,11 +72,9 @@ A null result pointer (WASM memory allocation failure) also throws.
|
||||||
Same parameters and behavior as `spa()`. Returns a result object with the same fields, but `sunrise`, `sunset`, and `suntransit` are `HH:MM:SS` strings instead of fractional hours. During polar day or polar night, these strings are `"N/A"`:
|
Same parameters and behavior as `spa()`. Returns a result object with the same fields, but `sunrise`, `sunset`, and `suntransit` are `HH:MM:SS` strings instead of fractional hours. During polar day or polar night, these strings are `"N/A"`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const result = await spaFormatted(
|
const result = await spaFormatted(new Date(2025, 5, 21, 12, 0, 0), 40.7128, -74.006, {
|
||||||
new Date(2025, 5, 21, 12, 0, 0),
|
timezone: -4,
|
||||||
40.7128, -74.006,
|
});
|
||||||
{ timezone: -4 }
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(result.sunrise); // "05:25:12"
|
console.log(result.sunrise); // "05:25:12"
|
||||||
console.log(result.sunset); // "20:30:42"
|
console.log(result.sunset); // "20:30:42"
|
||||||
|
|
@ -117,7 +115,7 @@ Calling `init()` multiple times is safe. The second and subsequent calls return
|
||||||
The `function` option controls which outputs the SPA computes. Lower codes skip the rise/transit/set and incidence calculations, which are the most expensive part.
|
The `function` option controls which outputs the SPA computes. Lower codes skip the rise/transit/set and incidence calculations, which are the most expensive part.
|
||||||
|
|
||||||
| Constant | Value | Computes |
|
| Constant | Value | Computes |
|
||||||
| --- | --- | --- |
|
| ------------ | ----- | ------------------------------------------- |
|
||||||
| `SPA_ZA` | `0` | Zenith and azimuth only |
|
| `SPA_ZA` | `0` | Zenith and azimuth only |
|
||||||
| `SPA_ZA_INC` | `1` | Zenith, azimuth, and incidence angle |
|
| `SPA_ZA_INC` | `1` | Zenith, azimuth, and incidence angle |
|
||||||
| `SPA_ZA_RTS` | `2` | Zenith, azimuth, and rise/transit/set times |
|
| `SPA_ZA_RTS` | `2` | Zenith, azimuth, and rise/transit/set times |
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ The C source is compiled with Emscripten to produce a single JavaScript file tha
|
||||||
### Build flags
|
### Build flags
|
||||||
|
|
||||||
| Flag | Purpose |
|
| Flag | Purpose |
|
||||||
| --- | --- |
|
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `-O3 -flto` | Maximum optimization with link-time optimization. The compiler inlines across translation units and eliminates dead code |
|
| `-O3 -flto` | Maximum optimization with link-time optimization. The compiler inlines across translation units and eliminates dead code |
|
||||||
| `--no-entry` | No `main()` function exists. The module exposes only the exported wrapper functions |
|
| `--no-entry` | No `main()` function exists. The module exposes only the exported wrapper functions |
|
||||||
| `-sSINGLE_FILE=1` | Inlines the WASM binary as a base64 string inside the JavaScript file. Eliminates the `.wasm` file entirely |
|
| `-sSINGLE_FILE=1` | Inlines the WASM binary as a base64 string inside the JavaScript file. Eliminates the `.wasm` file entirely |
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ import { spa } from 'solar-spa';
|
||||||
const result = await spa(
|
const result = await spa(
|
||||||
new Date(2025, 5, 21, 12, 0, 0), // June 21, 2025 at noon
|
new Date(2025, 5, 21, 12, 0, 0), // June 21, 2025 at noon
|
||||||
40.7128, // latitude (NYC)
|
40.7128, // latitude (NYC)
|
||||||
-74.0060, // longitude
|
-74.006, // longitude
|
||||||
{ timezone: -4, elevation: 10 } // EDT (UTC-4), 10m elevation
|
{ timezone: -4, elevation: 10 }, // EDT (UTC-4), 10m elevation
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(result.zenith); // ~27 (degrees from vertical)
|
console.log(result.zenith); // ~27 (degrees from vertical)
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ The SPA's accuracy far exceeds the precision of any practical measurement system
|
||||||
The SPA validates all inputs and returns a non-zero error code if any are out of range:
|
The SPA validates all inputs and returns a non-zero error code if any are out of range:
|
||||||
|
|
||||||
| Parameter | Valid range |
|
| Parameter | Valid range |
|
||||||
| --- | --- |
|
| ---------------------- | --------------------------- |
|
||||||
| Year | -2000 to 6000 |
|
| Year | -2000 to 6000 |
|
||||||
| Month | 1 to 12 |
|
| Month | 1 to 12 |
|
||||||
| Day | 1 to 31 |
|
| Day | 1 to 31 |
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Memory growth is disabled (`ALLOW_MEMORY_GROWTH=0`). This means the ArrayBuffer
|
||||||
Not all callers need every output. The `function` option controls how much work the SPA does:
|
Not all callers need every output. The `function` option controls how much work the SPA does:
|
||||||
|
|
||||||
| Code | Computation | Relative cost |
|
| Code | Computation | Relative cost |
|
||||||
| --- | --- | --- |
|
| ---------------- | ------------------ | -------------------------------- |
|
||||||
| `SPA_ZA` (0) | Zenith and azimuth | ~1x |
|
| `SPA_ZA` (0) | Zenith and azimuth | ~1x |
|
||||||
| `SPA_ZA_INC` (1) | + incidence angle | ~1x (incidence is cheap) |
|
| `SPA_ZA_INC` (1) | + incidence angle | ~1x (incidence is cheap) |
|
||||||
| `SPA_ZA_RTS` (2) | + rise/transit/set | ~3x (three position evaluations) |
|
| `SPA_ZA_RTS` (2) | + rise/transit/set | ~3x (three position evaluations) |
|
||||||
|
|
@ -70,7 +70,7 @@ These flags together produce a ~60KB output file, down from the ~150KB that a de
|
||||||
## When to use solar-spa vs nrel-spa
|
## When to use solar-spa vs nrel-spa
|
||||||
|
|
||||||
| Scenario | Recommended |
|
| Scenario | Recommended |
|
||||||
| --- | --- |
|
| ------------------------------------------------------ | -------------------------------------------------------------------- |
|
||||||
| Single position lookup (e.g., sunrise for today) | Either. Both are fast enough |
|
| Single position lookup (e.g., sunrise for today) | Either. Both are fast enough |
|
||||||
| Batch computation (hundreds or thousands of positions) | solar-spa (WASM) |
|
| Batch computation (hundreds or thousands of positions) | solar-spa (WASM) |
|
||||||
| Animation or real-time tracking | solar-spa (WASM) |
|
| Animation or real-time tracking | solar-spa (WASM) |
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ Pre-release validation of the solar-spa v2.0.0 WASM implementation. All results
|
||||||
100 scenarios covering seven categories:
|
100 scenarios covering seven categories:
|
||||||
|
|
||||||
| Category | Scenarios | Description |
|
| Category | Scenarios | Description |
|
||||||
| --- | --- | --- |
|
| ------------------------- | --------- | --------------------------------------------------------------- |
|
||||||
| Cities worldwide | 1-40 | 20 cities across every continent, summer and winter solstice |
|
| Cities worldwide | 1-40 | 20 cities across every continent, summer and winter solstice |
|
||||||
| Boundary conditions | 41-55 | Poles, equator, date line, extreme elevation, date range limits |
|
| Boundary conditions | 41-55 | Poles, equator, date line, extreme elevation, date range limits |
|
||||||
| Polar regions | 56-65 | Polar day, polar night, midnight sun, Antarctic stations |
|
| Polar regions | 56-65 | Polar day, polar night, midnight sun, Antarctic stations |
|
||||||
|
|
@ -23,7 +23,7 @@ Pre-release validation of the solar-spa v2.0.0 WASM implementation. All results
|
||||||
20 major cities tested at both the June 21 and December 21 solstices, 2025, local noon. Each scenario validates that the zenith angle falls within a physically reasonable range for the latitude and season.
|
20 major cities tested at both the June 21 and December 21 solstices, 2025, local noon. Each scenario validates that the zenith angle falls within a physically reasonable range for the latitude and season.
|
||||||
|
|
||||||
| # | City | Season | Zenith | Azimuth | Time |
|
| # | City | Season | Zenith | Azimuth | Time |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | ------------ | ------ | ------ | ------- | ----- |
|
||||||
| 1 | New York | Summer | 21.12 | 140.47 | 242us |
|
| 1 | New York | Summer | 21.12 | 140.47 | 242us |
|
||||||
| 2 | New York | Winter | 65.35 | 166.30 | 168us |
|
| 2 | New York | Winter | 65.35 | 166.30 | 168us |
|
||||||
| 3 | London | Summer | 30.52 | 150.96 | 110us |
|
| 3 | London | Summer | 30.52 | 150.96 | 110us |
|
||||||
|
|
@ -72,7 +72,7 @@ Note: "Summer" and "Winter" refer to Northern Hemisphere seasons. For Southern H
|
||||||
Tests at the mathematical limits of the algorithm's input domain.
|
Tests at the mathematical limits of the algorithm's input domain.
|
||||||
|
|
||||||
| # | Scenario | Zenith | Note |
|
| # | Scenario | Zenith | Note |
|
||||||
| --- | --- | --- | --- |
|
| --- | ------------------------- | ------ | ----------------------------------- |
|
||||||
| 41 | North Pole, June solstice | 66.53 | Midnight sun: zenith < 90 |
|
| 41 | North Pole, June solstice | 66.53 | Midnight sun: zenith < 90 |
|
||||||
| 42 | North Pole, Dec solstice | 113.44 | Polar night: zenith > 90 |
|
| 42 | North Pole, Dec solstice | 113.44 | Polar night: zenith > 90 |
|
||||||
| 43 | South Pole, Dec solstice | 66.53 | Midnight sun (southern summer) |
|
| 43 | South Pole, Dec solstice | 66.53 | Midnight sun (southern summer) |
|
||||||
|
|
@ -96,7 +96,7 @@ The equinox results confirm the algorithm's accuracy: a zenith of 1.84 degrees a
|
||||||
Polar day/night conditions test the algorithm's handling of non-standard sunrise/sunset scenarios.
|
Polar day/night conditions test the algorithm's handling of non-standard sunrise/sunset scenarios.
|
||||||
|
|
||||||
| # | Location | Condition | Zenith |
|
| # | Location | Condition | Zenith |
|
||||||
| --- | --- | --- | --- |
|
| --- | ------------------------- | -------------------------- | ------ |
|
||||||
| 56 | Tromso, Norway (69.6N) | Polar day (June) | 46.70 |
|
| 56 | Tromso, Norway (69.6N) | Polar day (June) | 46.70 |
|
||||||
| 57 | Tromso, Norway | Polar night (Dec) | 93.14 |
|
| 57 | Tromso, Norway | Polar night (Dec) | 93.14 |
|
||||||
| 58 | Murmansk, Russia (69.0N) | Polar day (June) | 46.12 |
|
| 58 | Murmansk, Russia (69.0N) | Polar day (June) | 46.12 |
|
||||||
|
|
@ -113,7 +113,7 @@ Scenario 64 is notable: at Svalbard at midnight on the June solstice, the sun is
|
||||||
## Category 4: Time Edge Cases (66-75)
|
## Category 4: Time Edge Cases (66-75)
|
||||||
|
|
||||||
| # | Scenario | Zenith | Note |
|
| # | Scenario | Zenith | Note |
|
||||||
| --- | --- | --- | --- |
|
| --- | --------------------------------- | ------ | -------------------------------- |
|
||||||
| 66 | Exact midnight UTC (London, June) | 105.05 | Sun well below horizon |
|
| 66 | Exact midnight UTC (London, June) | 105.05 | Sun well below horizon |
|
||||||
| 67 | Dawn, 5 AM summer London | 88.47 | Near horizon |
|
| 67 | Dawn, 5 AM summer London | 88.47 | Near horizon |
|
||||||
| 68 | Dusk, 9 PM summer London | 87.93 | Near horizon |
|
| 68 | Dusk, 9 PM summer London | 87.93 | Near horizon |
|
||||||
|
|
@ -130,7 +130,7 @@ Scenario 64 is notable: at Svalbard at midnight on the June solstice, the sun is
|
||||||
All four function codes (`SPA_ZA`, `SPA_ZA_INC`, `SPA_ZA_RTS`, `SPA_ALL`) produce identical zenith and azimuth values within 0.01 degree tolerance. This confirms that the function code parameter only affects which outputs are computed, not the core position algorithm.
|
All four function codes (`SPA_ZA`, `SPA_ZA_INC`, `SPA_ZA_RTS`, `SPA_ALL`) produce identical zenith and azimuth values within 0.01 degree tolerance. This confirms that the function code parameter only affects which outputs are computed, not the core position algorithm.
|
||||||
|
|
||||||
| # | Test | Result |
|
| # | Test | Result |
|
||||||
| --- | --- | --- |
|
| --- | ------------------------------------- | ------------------------------------- |
|
||||||
| 76 | SPA_ZA matches SPA_ALL | zenith identical |
|
| 76 | SPA_ZA matches SPA_ALL | zenith identical |
|
||||||
| 77 | SPA_ZA_INC matches SPA_ALL | zenith, azimuth, incidence identical |
|
| 77 | SPA_ZA_INC matches SPA_ALL | zenith, azimuth, incidence identical |
|
||||||
| 78 | SPA_ZA_RTS matches SPA_ALL | zenith, azimuth identical |
|
| 78 | SPA_ZA_RTS matches SPA_ALL | zenith, azimuth identical |
|
||||||
|
|
@ -140,7 +140,7 @@ All four function codes (`SPA_ZA`, `SPA_ZA_INC`, `SPA_ZA_RTS`, `SPA_ALL`) produc
|
||||||
## Category 6: Atmospheric Conditions (81-90)
|
## Category 6: Atmospheric Conditions (81-90)
|
||||||
|
|
||||||
| # | Condition | Zenith | Note |
|
| # | Condition | Zenith | Note |
|
||||||
| --- | --- | --- | --- |
|
| --- | --------------------------------------- | ------ | ------------------------------ |
|
||||||
| 81 | Standard atmosphere (1013.25 mbar, 15C) | 21.12 | Reference baseline |
|
| 81 | Standard atmosphere (1013.25 mbar, 15C) | 21.12 | Reference baseline |
|
||||||
| 82 | Low pressure (300 mbar, -30C, 9000m) | 21.12 | High altitude conditions |
|
| 82 | Low pressure (300 mbar, -30C, 9000m) | 21.12 | High altitude conditions |
|
||||||
| 83 | High pressure (1100 mbar) | 21.12 | Dense atmosphere |
|
| 83 | High pressure (1100 mbar) | 21.12 | Dense atmosphere |
|
||||||
|
|
@ -159,7 +159,7 @@ Scenario 89 confirms that different atmospheric pressures produce measurably dif
|
||||||
The SPA is valid for years -2000 to 6000. These scenarios confirm correct behavior across the full range, with era-appropriate delta_t values.
|
The SPA is valid for years -2000 to 6000. These scenarios confirm correct behavior across the full range, with era-appropriate delta_t values.
|
||||||
|
|
||||||
| # | Year | Context | Zenith | delta_t |
|
| # | Year | Context | Zenith | delta_t |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | ---------------------- | ------------------------------ | ------ | ------- |
|
||||||
| 91 | 1000 CE | Medieval era | 17.25 | 1574s |
|
| 91 | 1000 CE | Medieval era | 17.25 | 1574s |
|
||||||
| 92 | 1582 | Gregorian calendar switch | 50.37 | 120s |
|
| 92 | 1582 | Gregorian calendar switch | 50.37 | 120s |
|
||||||
| 93 | 1900 | Turn of century | 25.45 | -3s |
|
| 93 | 1900 | Turn of century | 25.45 | -3s |
|
||||||
|
|
@ -182,7 +182,7 @@ Measured on Apple Silicon (Node.js), single-threaded.
|
||||||
From the 100 validation scenarios:
|
From the 100 validation scenarios:
|
||||||
|
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
| --- | --- |
|
| ------ | ----- |
|
||||||
| Min | 7us |
|
| Min | 7us |
|
||||||
| Max | 242us |
|
| Max | 242us |
|
||||||
| Mean | 46us |
|
| Mean | 46us |
|
||||||
|
|
@ -197,7 +197,7 @@ The first call is slowest (242us) because it includes WASM module initialization
|
||||||
10,000 consecutive calls to the same location and date:
|
10,000 consecutive calls to the same location and date:
|
||||||
|
|
||||||
| Function Code | Time | Throughput |
|
| Function Code | Time | Throughput |
|
||||||
| --- | --- | --- |
|
| ------------- | ----- | ------------------ |
|
||||||
| SPA_ALL | 201ms | ~50,000 calls/sec |
|
| SPA_ALL | 201ms | ~50,000 calls/sec |
|
||||||
| SPA_ZA | 46ms | ~219,000 calls/sec |
|
| SPA_ZA | 46ms | ~219,000 calls/sec |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Emscripten supports a `locateFile` callback that lets the consumer specify where
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const Module = await createModule({
|
const Module = await createModule({
|
||||||
locateFile: (path) => '/static/wasm/' + path
|
locateFile: (path) => '/static/wasm/' + path,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -54,10 +54,8 @@ Webpack can be configured to copy `.wasm` files to the output directory and rewr
|
||||||
// webpack.config.js
|
// webpack.config.js
|
||||||
module.exports = {
|
module.exports = {
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [{ test: /\.wasm$/, type: 'asset/resource' }],
|
||||||
{ test: /\.wasm$/, type: 'asset/resource' }
|
},
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -90,10 +88,12 @@ let _pending: Promise<void> | null = null;
|
||||||
export function init(): Promise<void> {
|
export function init(): Promise<void> {
|
||||||
if (_module) return Promise.resolve();
|
if (_module) return Promise.resolve();
|
||||||
if (_pending) return _pending;
|
if (_pending) return _pending;
|
||||||
_pending = createSpaModule().then((mod) => {
|
_pending = createSpaModule()
|
||||||
|
.then((mod) => {
|
||||||
_module = mod;
|
_module = mod;
|
||||||
_pending = null;
|
_pending = null;
|
||||||
}).catch((err) => {
|
})
|
||||||
|
.catch((err) => {
|
||||||
_pending = null; // Allow retry on next call
|
_pending = null; // Allow retry on next call
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
@ -118,9 +118,9 @@ For small to medium WASM binaries (under a few hundred KB), inlining as base64 i
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
| Approach | Universal? | Consumer config? | Size overhead |
|
| Approach | Universal? | Consumer config? | Size overhead |
|
||||||
| --- | --- | --- | --- |
|
| ------------------------------------- | ----------- | ----------------- | ------------- |
|
||||||
| Separate `.wasm` + default resolution | No | No | None |
|
| Separate `.wasm` + default resolution | No | No | None |
|
||||||
| `locateFile` callback | Yes* | Yes (per-bundler) | None |
|
| `locateFile` callback | Yes\* | Yes (per-bundler) | None |
|
||||||
| Bundler-specific config | Per-bundler | Yes | None |
|
| Bundler-specific config | Per-bundler | Yes | None |
|
||||||
| `import.meta.url` | Partial | No | None |
|
| `import.meta.url` | Partial | No | None |
|
||||||
| **SINGLE_FILE (base64 inline)** | **Yes** | **No** | **~33%** |
|
| **SINGLE_FILE (base64 inline)** | **Yes** | **No** | **~33%** |
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue