{{-- The theme and the navigation rail's width, decided before the first paint. Include it in , ahead of @vite. It reads the visitor's choice from localStorage (`livewire-material.theme.storage_key`): `light`, `dark` or `system`, falling back to `theme.default`. `system` follows the operating system, now and whenever it changes. The result is written to , which is the only thing the stylesheet keys on; the choice itself is , where `$store.theme` picks it up. A value under one of `theme.legacy_keys` — an earlier toggle's key, which may be JSON-encoded (maryUI's `"dark"`) — is adopted once and removed. Nothing is written for a visitor who never chose, so changing `theme.default` later reaches them too. M3's contrast level rides the same rails (`livewire-material.theme.contrast`): `standard`, `medium`, `high` or `system`, kept under `contrast.storage_key`. `system` asks the operating system, now and whenever it changes, and takes `more` as high. The result is , which the generated stylesheet keys its medium and high blocks on — standard writes no attribute, because the plain blocks are already it. The choice is , where `$store.theme` picks it up. `livewire-material.motion.scheme` is M3's motion scheme: `standard` is written to (and swaps the spatial springs for the restrained set), `expressive`, the default, writes nothing. With colour profiles (`livewire-material.profiles`, generated by `material:scheme`), the active one — `Scheme::profile()`, which asks the application's resolver — is written to , which the generated stylesheet keys each profile on. The rail rides along for the theme's reason: is `expanded` or `collapsed` (`livewire-material.rail.storage_key`, falling back to `rail.default`), and a collapsible rail's width is CSS keyed on it (the `rail-collapsed:` variant). Set any later, a collapsed rail would paint wide and snap shut on every load. `$store.rail` (resources/js/navigation.js) changes it. With `theme.meta` on, the browser's own chrome follows too: the `content` of every without a `media` attribute — one is added to when there is none — is the resolved theme's `surface`, from the scheme file (`Scheme`), for the profile in (else the active one) and at the level in . A MutationObserver on keeps it in step with whatever changes `data-theme`, `data-scheme` or `data-contrast` afterwards: `$store.theme.set()` and `toggle()`, `setContrast()`, an OS change while `system`, a profile preview, the application's own script. A layout's own theme-color meta belongs before this script: one written after it is only painted on DOMContentLoaded, beside the one added here. Off by default, and then none of it is emitted. wire:navigate swaps the body, merges the head without running this again, and gives the next page's attributes — which the server rendered without any of these, so Livewire removes them. They are put back as the new page is swapped in (`onSwap`, in the same task, before anything paints), so this only has to run on a full load. The head merge also puts the next page's server-rendered theme-color meta in place of the painted one, so it is painted again there, and once more on `livewire:navigated`. --}} @php $theme = config('livewire-material.theme'); $rail = config('livewire-material.rail'); $contrast = $theme['contrast'] ?? []; $levels = \NoNameWeb\LivewireMaterial\Support\Scheme::LEVELS; $settings = [ 'scheme' => \NoNameWeb\LivewireMaterial\Support\Scheme::profile(), 'default' => in_array($theme['default'] ?? null, ['light', 'dark', 'system'], true) ? $theme['default'] : 'system', 'key' => $theme['storage_key'] ?? 'material-theme', 'legacy' => array_values($theme['legacy_keys'] ?? []), 'contrast' => [ 'default' => in_array($contrast['default'] ?? null, [...$levels, 'system'], true) ? $contrast['default'] : 'system', 'key' => $contrast['storage_key'] ?? 'material-contrast', ], 'motion' => (config('livewire-material.motion.scheme') === 'standard') ? 'standard' : null, 'rail' => [ 'default' => ($rail['default'] ?? null) === 'collapsed' ? 'collapsed' : 'expanded', 'key' => $rail['storage_key'] ?? 'material-rail', ], ]; // Only the surfaces the meta can show: the active scheme's, every profile's for a preview, // and each of those at M3's other two contrast levels. `Scheme::load()` resolves the active // profile itself, so it answers for a single scheme and for profiles alike. if ((bool) ($theme['meta'] ?? false)) { $surfaces = fn (array $scheme): array => ['light' => $scheme['light']['surface'], 'dark' => $scheme['dark']['surface']]; // One read of the scheme file per level rather than one per profile and level. $profilesAt = collect($levels)->mapWithKeys(fn (string $level): array => [$level => \NoNameWeb\LivewireMaterial\Support\Scheme::profiles(contrast: $level)]); $otherLevels = fn (callable $of): object => (object) collect($levels)->skip(1)->mapWithKeys(fn (string $level): array => [$level => $of($level)])->all(); $settings['meta'] = [ ...$surfaces(\NoNameWeb\LivewireMaterial\Support\Scheme::load()), 'contrast' => $otherLevels(fn (string $level): array => $surfaces(\NoNameWeb\LivewireMaterial\Support\Scheme::load(contrast: $level))), 'profiles' => (object) collect($profilesAt['standard']) ->map(fn (array $scheme, string $name): array => [ ...$surfaces($scheme), 'contrast' => $otherLevels(fn (string $level): array => $surfaces($profilesAt[$level][$name])), ]) ->all(), ]; } @endphp