-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 474 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 474 KB
1
{"posts":[{"title":"Hello World 👋","text":"After sitting on this for a long time and wanting to blog / write down my thoughts, I’ve finally got my act together and started this. There were so many times I was asked some very good questions which I am sure not just the person asking me but a lot more would have been interested in knowing the answer/solution/thoughts around the matter. This is a way to write about that and help the wider community who are searching for similar solutions. I regularly answer in Stack Overflow and in some cases I wrote a question and answered it myself just incase some one was looking for something similar, that wasn’t really the ideal platform to do that. There have been so many times that going through and reading other people’s blogs have helped me and unlocked me in problems that I was stuck with; this is a in a way trying to give back to the community and helping people that are on the look out for a solution for a similar problem. How to power the blogThere were so many choices out there when it came to what frameworks and libraries to use to build the blog and what to use to host the blog. My requirements when it came to building were simple Easy to author posts Easy to build Easy to maintain Most customizations (eg: search, ads, tags, categories etc) should come out of the box My requirements when it came to hosting were even simpler Has to be free Has to be able to handle ‘some’ level of load Easy to CI/CD Main choices here boiled down to: Orchard CMS Hugo Ghost Jekyll With Github Pages Hexo All the options were good, I really liked Hugo, it was so easy to create a site. But all of them were geared towards creating a CMS / generic site. I was looking for something that had all the things needed for a blog out of the box with out having to grab lots of plugins or write something custom. Jekyll and GitHub pages were really good, it nailed most of the things, but I didn’t really want to go down the road of learning Jekyll just to host a blog. This left one and Hexo fit my requirements beautifully. It was a dedicated Javascript framework that has all the things I was looking for out of the box and it had 360+ themes available all community built and free. One thing I loved about Hexo is the fact its builds the source to a static site and you can use GitHub to host the static site and use GitHub Actions to build the static site from source. This is what I went with in the end, Hexo to build the blog. I write everything in markdown files and Hexo builds it out into a nice static site and I host it using GitHub pages as a public repo There are some limits of hosting with GitHub Pages, the main one is the 100GB of bandwidth as a soft limit. Since this is just a static site 100GB should be plenty but if and when it comes to that I will look at putting a CDN in front. Final Result Hexo to build the blog into a static site Icarus theme GitHub Pages to host the site Bulma to help enrich the markdown files with styling ReferencesAs always a big thank you to Unsplash for providing a huge range of images for free Cover image has been taken from https://unsplash.com/photos/3SIXZisims4","link":"/Blog/hello-world-%F0%9F%91%8B/"},{"title":"Custom Voices in Azure OpenAI Realtime with Azure Speech Services","text":"🎯 TL;DR: Hybrid GPT-4o Realtime with Azure Speech Services Custom Voices This post demonstrates bypassing GPT-4o Realtime’s built-in voice limitations by creating a hybrid architecture that combines GPT-4o’s conversational intelligence with Azure Speech Services’ extensive voice catalog. The solution configures GPT-4o Realtime for text-only output (ContentModalities.Text) and routes responses through Azure Speech Services, enabling access to 400+ neural voices, custom neural voices (CNV), and SSML control. The implementation includes intelligent barge-in functionality using real-time audio amplitude monitoring, allowing users to interrupt the assistant naturally mid-response. Technical implementation: C# application using Azure.AI.OpenAI and Microsoft.CognitiveServices.Speech SDKs, NAudio for audio I/O, streaming text collection from GPT-4o responses, RMS-based speech detection with configurable thresholds, and concurrent audio management for seamless interruption handling. Complete C# source code with audio helpers available here Building realtime voice-enabled applications with Azure OpenAI’s GPT-4o Realtime model is incredibly powerful, but there’s one significant limitation that can be a deal-breaker for many use cases: you’re stuck with OpenAI’s predefined voices like “sage”, “alloy”, “echo”, “fable”, “onyx”, and “nova”. What if you’re building a branded customer service bot that needs to match your company’s voice identity? Or developing a therapeutic application for children with autism where the voice quality and tone are crucial for engagement? What if your users need to interrupt the assistant naturally, just like in real human conversations? In this comprehensive guide, I’ll show you exactly how I solved these challenges by building a hybrid solution that combines the conversational intelligence of GPT-4o Realtime with the voice flexibility of Azure Speech Services. We’ll dive deep into the implementation, covering everything from the initial problem to the complete working solution. flowchart TD A[👤 User speaks] --> B[🎤 Microphone Input] B --> C{Barge-in DetectionAudio Level > Threshold?} C -->|Yes| D[🛑 Stop Azure Speech] C -->|No| E[📡 Stream to GPT-4o Realtime] E --> F[🧠 GPT-4o Processing] F --> G[📝 Text ResponseContentModalities.Text] G --> H[🗣️ Azure Speech ServicesCustom/Neural Voice] H --> I[🔊 Audio Output] D --> E I --> J[👂 User hears response] J --> A style A fill:#e1f5fe style D fill:#ffebee style G fill:#f3e5f5 style H fill:#e8f5e8 style I fill:#fff3e0 The real problem: Why GPT-4o Realtime’s voice limitations matterWhen you’re working with Azure OpenAI’s GPT-4o Realtime API, the standard approach involves configuring a RealtimeConversationSession with one of the predefined voices. While these voices are high-quality, they create several significant limitations: 1. Limited voice selectionYou’re restricted to just six built-in voices. There’s no access to Azure Speech Services’ extensive catalog of 400+ neural voices across 140+ languages and locales. You can’t use premium voices like Jenny Neural (en-US) or specialized voices optimized for different use cases. 2. No custom neural voicesPerhaps most importantly, you can’t integrate custom neural voices (CNV) that you’ve trained in Azure Speech Studio. This is crucial for: Brand consistency: Companies that have invested in custom voice branding Specialized applications: Healthcare, education, or accessibility apps requiring specific voice characteristics Multilingual scenarios: Custom voices trained on specific accents or dialects 3. No natural interruption (barge-in)The built-in system doesn’t provide a way for users to naturally interrupt the assistant mid-response. In real conversations, we constantly interrupt each other it’s natural and expected. Without this capability, your bot feels robotic and frustrating to use. 4. Limited voice controlYou can’t dynamically adjust speech rate, pitch, or emphasis using SSML (Speech Synthesis Markup Language) that Azure Speech Services supports. The solution: Hybrid architecture with Azure Speech ServicesThe solution I’ve developed bypasses GPT-4o’s built-in text-to-speech entirely and routes the conversation text through Azure Speech Services. Here’s the high-level architecture: Configure GPT-4o for text-only output: Disable built-in audio synthesis Stream and capture text responses: Collect the assistant’s text as it streams Route text to Azure Speech Services: Use any voice from Azure’s catalog or your custom neural voices Implement intelligent barge-in: Monitor microphone input and stop speech when user starts talking Seamless audio management: Handle audio playback and interruption smoothly This approach gives you the best of both worlds: GPT-4o’s intelligent conversation handling with Azure Speech Services’ superior voice options and control. Deep dive: Implementation walkthroughLet me walk you through the complete implementation, explaining each component and how they work together. Project structure and dependenciesFirst, let’s look at the project structure. The solution consists of several key components: 12345678RealtimeChat/├── Program.cs # Main application logic├── AppSettings.cs # Configuration classes├── Constants.cs # Application constants└── Helpers/ ├── AudioInputHelper.cs # Microphone input and barge-in detection ├── AudioOutputHelper.cs # Audio playback management └── ConsoleHelper.cs # Console UI utilities The key NuGet packages you’ll need: Azure.AI.OpenAI - For GPT-4o Realtime API Microsoft.CognitiveServices.Speech - For Azure Speech Services NAudio - For audio input/output handling Microsoft.Extensions.Configuration.Json - For configuration management Configuration setupThe configuration is designed to be flexible and environment-specific. Here’s the complete AppSettings.cs structure: 1234567891011121314151617181920212223242526272829public class AppSettings{ public AzureOpenAISettings AzureOpenAI { get; set; } = new(); public AzureSpeechSettings AzureSpeech { get; set; } = new(); public ConversationSettings Conversation { get; set; } = new(); public double BargeInThreshold { get; set; }}public class AzureOpenAISettings{ public string Endpoint { get; set; } = ""; public string ApiKey { get; set; } = ""; public string ChatModelName { get; set; } = ""; public string RealtimeModelName { get; set; } = "";}public class AzureSpeechSettings{ public string SubscriptionKey { get; set; } = ""; public string Region { get; set; } = ""; public string VoiceName { get; set; } = "";}public class ConversationSettings{ public string OpenAIBuiltInVoice { get; set; } = "sage"; public float ServerDetectionThreshold { get; set; } = 0.1f; public int ServerSilenceMs { get; set; } = 150;} And your appsettings.json: 12345678910111213141516171819{ "AzureOpenAI": { "Endpoint": "https://your-openai-resource.openai.azure.com/", "ApiKey": "your-openai-api-key", "ChatModelName": "gpt-4o", "RealtimeModelName": "gpt-4o-realtime-preview" }, "AzureSpeech": { "SubscriptionKey": "your-speech-service-key", "Region": "australiaeast", "VoiceName": "en-US-AnaNeural" }, "Conversation": { "OpenAIBuiltInVoice": "sage", "ServerDetectionThreshold": 0.1, "ServerSilenceMs": 150 }, "BargeInThreshold": 0.02} The heart of the solution: Program.csThe main program orchestrates all the components. Let’s break down the key sections: Service initialization1234567891011121314151617static (SpeechConfig, AzureOpenAIClient) InitializeServices(AppSettings appSettings){ // Configure Azure Speech Services SpeechConfig speechConfig = SpeechConfig.FromSubscription( appSettings.AzureSpeech.SubscriptionKey, appSettings.AzureSpeech.Region ); speechConfig.SpeechSynthesisVoiceName = appSettings.AzureSpeech.VoiceName; // Configure Azure OpenAI client var aoaiClient = new AzureOpenAIClient( new Uri(appSettings.AzureOpenAI.Endpoint), new ApiKeyCredential(appSettings.AzureOpenAI.ApiKey) ); return (speechConfig, aoaiClient);} Critical: Text-only configurationThis is the key breakthrough configuring GPT-4o Realtime to output only text, not audio: 123456789101112await session.ConfigureSessionAsync(new ConversationSessionOptions(){ Voice = new ConversationVoice(appSettings.Conversation.OpenAIBuiltInVoice), ContentModalities = ConversationContentModalities.Text, // 🔥 This is crucial! Instructions = Constants.MainPrompt, InputTranscriptionOptions = new() { Model = "whisper-1" }, TurnDetectionOptions = ConversationTurnDetectionOptions .CreateServerVoiceActivityTurnDetectionOptions( detectionThreshold: appSettings.Conversation.ServerDetectionThreshold, silenceDuration: TimeSpan.FromMilliseconds(appSettings.Conversation.ServerSilenceMs) ),}); By setting ContentModalities = ConversationContentModalities.Text, we tell GPT-4o to only send us text responses, not audio bytes. This is what allows us to route the text through Azure Speech Services instead. Advanced barge-in implementationThe barge-in feature is implemented in AudioInputHelper.cs and is one of the most sophisticated parts of the solution. Here’s how it works: Real-time amplitude monitoring1234567891011121314151617181920private bool IsSpeechAboveThreshold(byte[] buffer, int length, double threshold){ double sum = 0.0; int sampleCount = length / 2; // 16-bit samples for (int i = 0; i < length; i += 2) { short sample = BitConverter.ToInt16(buffer, i); sum += sample * (double)sample; } // Calculate RMS (Root Mean Square) of the audio double rms = Math.Sqrt(sum / sampleCount); // Normalize to [0..1] range double normalized = rms / 32768.0; // Compare to threshold return normalized > threshold;} Smart barge-in event handling1234567891011121314151617181920_waveInEvent.DataAvailable += (_, e) =>{ // 1. Always copy to ring buffer for GPT-4o input lock (_bufferLock) { // ... buffer management code ... } // 2. Check for user speech (barge-in detection) if (IsSpeechAboveThreshold(e.Buffer, e.BytesRecorded, _bargeInThreshold)) { var now = DateTime.UtcNow; // Prevent event spam with cooldown period if ((now - _lastSpeechDetected).TotalMilliseconds > 500) { _lastSpeechDetected = now; UserSpeechDetected?.Invoke(); // Trigger barge-in! } }}; Barge-in event wiringIn the main session handler, we wire up the barge-in detection: 12345678910111213141516171819static void HandleSessionStartedUpdate(RealtimeConversationSession session, AppSettings appSettings, SpeechSynthesizer? currentSynthesizer){ _ = Task.Run(async () => { using AudioInputHelper audioInputHelper = AudioInputHelper.Start(appSettings.BargeInThreshold); audioInputHelper.UserSpeechDetected += () => { ConsoleHelper.DisplayMessage("<<< USER INTERRUPTION DETECTED! Stopping speech...", true); if (currentSynthesizer != null) { currentSynthesizer.StopSpeakingAsync().Wait(); // Stop immediately! } }; await session.SendInputAudioAsync(audioInputHelper); });} Streaming text processing and Azure Speech integrationThe magic happens in how we handle the streaming response from GPT-4o and route it to Azure Speech Services: Collecting streaming text1234567891011121314151617181920static void HandleStreamingPartDeltaUpdate( ConversationItemStreamingPartDeltaUpdate deltaUpdate, Dictionary<string, StringBuilder> partialTextByItemId, AudioOutputHelper audioOutputHelper){ string chunk = deltaUpdate.Text ?? deltaUpdate.AudioTranscript; if (!string.IsNullOrWhiteSpace(chunk)) { if (!partialTextByItemId.ContainsKey(deltaUpdate.ItemId)) { partialTextByItemId[deltaUpdate.ItemId] = new StringBuilder(); } partialTextByItemId[deltaUpdate.ItemId].Append(chunk); } // NOTE: We completely ignore deltaUpdate.AudioBytes since we're using Azure Speech // Uncomment the next line if you want to fall back to built-in voice: // audioOutputHelper.EnqueueForPlayback(deltaUpdate.AudioBytes);} Converting text to speech with Azure Speech Services12345678910111213141516171819202122232425262728293031323334353637383940414243444546static async Task HandleStreamingFinishedUpdate( ConversationItemStreamingFinishedUpdate itemFinishedUpdate, Dictionary<string, StringBuilder> partialTextByItemId, SpeechConfig speechConfig, SpeechSynthesizer? currentSynthesizer){ if (partialTextByItemId.TryGetValue(itemFinishedUpdate.ItemId, out var sb)) { string finalAssistantText = sb.ToString(); ConsoleHelper.DisplayMessage($"Assistant: {finalAssistantText}", true); // Route to Azure Speech Services await SpeakWithAzureSpeechAsync(finalAssistantText, speechConfig, currentSynthesizer); partialTextByItemId.Remove(itemFinishedUpdate.ItemId); }}static async Task<SpeechSynthesizer?> SpeakWithAzureSpeechAsync( string text, SpeechConfig speechConfig, SpeechSynthesizer? synthesizer){ if (string.IsNullOrWhiteSpace(text)) return synthesizer; // Stop any current speech before starting new if (synthesizer != null) { await synthesizer.StopSpeakingAsync(); } // Synthesize with Azure Speech Services var result = await synthesizer.SpeakTextAsync(text); if (result.Reason == ResultReason.SynthesizingAudioCompleted) { Console.WriteLine("✅ Speech synthesis completed successfully"); } else if (result.Reason == ResultReason.Canceled) { var cancellation = SpeechSynthesisCancellationDetails.FromResult(result); Console.WriteLine($"❌ Speech canceled: {cancellation.Reason}, {cancellation.ErrorDetails}"); } return synthesizer;} Advanced scenarios and customizationUsing custom neural voicesTo use a custom neural voice you’ve trained in Azure Speech Studio, simply update your configuration: 1234567{ "AzureSpeech": { "VoiceName": "YourCustomVoiceName", // Your CNV endpoint name "Region": "eastus", // Region where your CNV is deployed "SubscriptionKey": "your-key" }} SSML support for advanced voice controlYou can enhance the speech synthesis with SSML for better control: 12345678910111213static async Task SpeakWithSSMLAsync(string text, SpeechConfig speechConfig, SpeechSynthesizer synthesizer){ string ssml = $@" <speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-US'> <voice name='{speechConfig.SpeechSynthesisVoiceName}'> <prosody rate='medium' pitch='medium'> {System.Security.SecurityElement.Escape(text)} </prosody> </voice> </speak>"; await synthesizer.SpeakSsmlAsync(ssml);} Fine-tuning barge-in sensitivityThe barge-in threshold is crucial for a good user experience. Too sensitive, and background noise triggers interruptions. Too high, and users can’t interrupt naturally: 123{ "BargeInThreshold": 0.02 // Start here and adjust based on your environment} Values to try: 0.01: Very sensitive (good for quiet environments) 0.02: Balanced (recommended starting point) 0.05: Less sensitive (noisy environments) Error handling and resilienceThe solution includes comprehensive error handling: 1234567891011121314151617181920212223242526272829static void HandleErrorUpdate(ConversationErrorUpdate errorUpdate){ ConsoleHelper.DisplayError($"❌ GPT-4o Error: {errorUpdate.Message}", true); // Log full error details for debugging ConsoleHelper.DisplayError($"Full error details: {errorUpdate.GetRawContent()}", false); // Could implement retry logic here}static async Task HandleSpeechStartedUpdate( ConversationInputSpeechStartedUpdate speechStartedUpdate, SpeechSynthesizer? currentSynthesizer){ ConsoleHelper.DisplayMessage($"🎤 Speech detected @ {speechStartedUpdate.AudioStartTime}", true); // Always stop current speech when user starts talking if (currentSynthesizer != null) { try { await currentSynthesizer.StopSpeakingAsync(); } catch (Exception ex) { ConsoleHelper.DisplayError($"Error stopping speech: {ex.Message}", false); } }} Performance considerations and optimizationLatency optimizationThe hybrid approach adds minimal latency: GPT-4o streaming: Near real-time text streaming Azure Speech synthesis: 100-300ms for typical responses Barge-in detection: <50ms response time Memory managementThe ring buffer implementation efficiently manages audio data: 12// ~10 seconds buffer to handle network variationsprivate readonly byte[] _buffer = new byte[BYTES_PER_SAMPLE * SAMPLES_PER_SECOND * CHANNELS * 10]; Concurrent operationsThe solution handles multiple concurrent operations smoothly: Microphone input streaming to GPT-4o Real-time text streaming from GPT-4o Audio synthesis and playback via Azure Speech Barge-in detection and response Deployment and production considerationsSecurity best practices API key management: Use Azure Key Vault for production Network security: Implement proper firewall rules Authentication: Add user authentication for production apps Scaling considerations Connection limits: Both services have concurrent connection limits Regional deployment: Deploy Speech Services in the same region as OpenAI Cost optimization: Monitor token usage and synthesis characters Monitoring and loggingImplement comprehensive logging for production: 123456// Add structured loggingservices.AddLogging(builder =>{ builder.AddConsole(); builder.AddApplicationInsights(); // For production monitoring}); Conclusion and next stepsThis hybrid approach solves the key limitations of GPT-4o Realtime’s built-in voices by providing: ✅ Unlimited voice selection: Access to 400+ Azure Speech neural voices✅ Custom neural voice support: Use your own trained voices✅ Natural barge-in capability: Users can interrupt naturally✅ SSML support: Advanced voice control and customization✅ Production-ready architecture: Robust error handling and performance The complete sample code is available in my custom-voice-sample-code folder, which you can use as a starting point for your own applications. What’s next?Consider these enhancements for your implementation: Multiple voice support: Let users choose their preferred voice Emotion detection: Adjust voice characteristics based on conversation sentiment Multi-language support: Dynamically switch languages and voices Integration with Teams/Bot Framework: Extend to enterprise chat platforms The combination of GPT-4o’s conversational intelligence with Azure Speech Services’ voice flexibility opens up entirely new possibilities for voice-enabled applications. Whether you’re building customer service bots, educational tools, or therapeutic applications, this approach gives you the control and quality you need for professional deployments. References Azure Speech Services Custom Neural Voice Azure Speech Services Voice Gallery NAudio Documentation Main image generated by DALL-E","link":"/Azure/AI/Speech/custom-voices-in-azure-openai-realtime-with-azure-speech-services/"},{"title":"How We United 8 Developers Across Restricted Environments Using Azure VMs and Dev Containers","text":"🎯 TL;DR: Distributed Development with Azure VMs and Dev Containers This post details solving a distributed development challenge where 8 developers from different organizations needed to collaborate on an AutoGen AI project - 4 from restricted corporate environments unable to install development tools, and 4 external developers without access to client systems. The solution uses a shared Azure VM (Standard D8s v3) with individual user accounts, certificate-based SSH authentication, and VS Code Remote Development connected to a shared Dev Container environment. The architecture eliminates “works on my machine” issues by providing consistent development environments, shared resources (datasets, models, configs), and enables real-time collaboration. Implementation highlights: Automated user provisioning scripts, VS Code Remote-SSH configuration, comprehensive devcontainer.json with pre-installed Python 3.12/AutoGen/Azure CLI, shared directory structures, and security hardening with fail2ban and UFW. Development environment setup scripts and configurations documented here Introduction: When Traditional Solutions Hit a WallLast month, I found myself facing a challenge that I’m sure many of you have encountered: How do you enable seamless collaboration for a development team when half of them work in a locked-down environment where they can’t install any development tools, and the other half can’t access the client’s systems? Our team of eight developers was tasked with building a proof-of-concept (PoC) for an AI-powered agentic system using Microsoft’s AutoGen framework. Here’s the kicker: this was a 3-week PoC sprint bringing together two teams from different organizations who had never worked together before. We needed a collaborative environment that could be spun up quickly, require minimal setup effort, and allow everyone to hit the ground running from day one. The project requirements were complex enough, but the real challenge? Four developers worked from a highly restricted corporate environment where installing Python, VS Code, or any development tools was strictly prohibited. The remaining four worked from our offices but couldn’t access the client’s internal systems directly. We tried the usual approaches: RDP connections: Blocked by security policies VPN access: Denied due to compliance requirements Local development with file sharing: Immediate sync issues and “works on my machine” problems Cloud IDEs: Didn’t meet the client’s security requirements Just when we thought we’d have to resort to the dreaded “develop locally and pray it works in production” approach, we discovered a solution that not only solved our immediate problem but revolutionized how we approach distributed development. The Architecture That Worked For UsHere’s a visual representation of what we built, everyone had to work on their personal (non-corporate) laptops for this to work. flowchart TD A[\"� 8 Developers on Personal Laptops4 Restricted + 4 External Teams\"] B[\"� SSH + VS Code Remote ConnectionCertificate-based Authentication\"] C[\"☁️ Azure VM (Standard D8s v3)8 vCPUs • 32GB RAM • Ubuntu 22.04\"] D[\"👤 Individual User Accountsuser1, user2, user3... user8\"] E[\"🐳 Shared Dev ContainerPython 3.12 + AutoGen + Azure CLIAll Dependencies Pre-installed\"] F[\"📂 Shared Development Resources• Project Repository• Datasets & Models• Configuration Files\"] G[\"✅ Results Achieved94% Faster Onboarding$400/month vs $16k laptopsEnhanced Security\"] A --> B B --> C C --> D D --> E E --> F F --> G style A fill:#e3f2fd,stroke:#1976d2,stroke-width:3px,color:#000 style B fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000 style C fill:#e1f5fe,stroke:#0277bd,stroke-width:3px,color:#000 style D fill:#fff3e0,stroke:#f57c00,stroke-width:3px,color:#000 style E fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000 style F fill:#fff3e0,stroke:#f57c00,stroke-width:3px,color:#000 style G fill:#e8f5e8,stroke:#388e3c,stroke-width:3px,color:#000 Lets check out how this was built and setup… The Deep Dive: How We Built ItStep 1: Provisioning the Azure VMWe started with a Linux VM in Azure. After some testing, we settled on a Standard D8s v3 instance (8 vCPUs, 32 GB RAM) which provided enough resources for all eight developers to work simultaneously without performance issues. 123456789# VM Creation (simplified for clarity)az vm create \\ --resource-group DevEnvironmentRG \\ --name SharedDevVM \\ --image Ubuntu2204 \\ --size Standard_D8s_v3 \\ --admin-username azureuser \\ --generate-ssh-keys \\ --public-ip-address-allocation static Step 2: User Account ArchitectureInstead of having everyone share a single account (security nightmare!), we created individual Linux user accounts for each developer. This approach gave us: Audit trails: We could track who did what and when Personalized environments: Each developer could customize their shell, aliases, and local configs Security isolation: Problems with one account wouldn’t affect others Resource monitoring: We could track resource usage per developer if needed Here’s how we automated user creation: 12345678910111213141516171819202122232425#!/bin/bash# create_dev_users.shDEVELOPERS=("alice" "bob" "charlie" "david" "eve" "frank" "grace" "henry")for dev in "${DEVELOPERS[@]}"; do # Create user with home directory sudo useradd -m -s /bin/bash $dev # Create .ssh directory sudo mkdir -p /home/$dev/.ssh sudo chmod 700 /home/$dev/.ssh # Set up for SSH key authentication sudo touch /home/$dev/.ssh/authorized_keys sudo chmod 600 /home/$dev/.ssh/authorized_keys # Set ownership sudo chown -R $dev:$dev /home/$dev/.ssh # Add to docker group (for container access) sudo usermod -aG docker $dev echo "Created user: $dev"done Step 3: Certificate-Based AuthenticationPassword authentication over the internet? Not on our watch. We implemented certificate-based SSH authentication for each developer: 1234# On each developer's local machinessh-keygen -t ed25519 -C "developer@project" -f ~/.ssh/project_dev_key# The public key was then added to their respective authorized_keys file on the VM The beauty of this approach: No passwords to remember or rotate Certificates could be revoked instantly if needed Multi-factor authentication could be added via Azure AD if required Worked seamlessly even from the restricted environment (SSH client was available) Step 4: VS Code Remote Development MagicThis is where the magic happened. VS Code’s Remote-SSH extension turned our Linux VM into a powerful development environment. Each developer configured their VS Code with: 123456// .ssh/config on developer machineHost azure-dev-vm HostName <VM-PUBLIC-IP> User alice IdentityFile ~/.ssh/project_dev_key ForwardAgent yes Once connected, developers had the full VS Code experience: IntelliSense working perfectly Debugging capabilities Extension support Integrated terminal Git integration But we didn’t stop there… Step 5: The Dev Container RevolutionHere’s where we went from “good” to “game-changing.” We created a Dev Container that encapsulated our entire development environment. This meant: No more “pip install” parties: Everything was pre-installedNo more version conflicts: Everyone used the exact same versionsNo more missing dependencies: If it worked for one, it worked for all Our .devcontainer/devcontainer.json: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162{ "name": "Autogen Development Environment", "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", "features": { "ghcr.io/devcontainers/features/anaconda:1": {}, "ghcr.io/devcontainers/features/azure-cli:1": { "version": "latest" }, "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { "moby": true, "installDockerBuildx": true, "installDockerComposeSwitch": true, "version": "latest", "dockerDashComposeVersion": "v2" }, "ghcr.io/devcontainers-extra/features/apt-get-packages:1": { "packages": [ "tig" ] } }, "customizations": { "vscode": { "extensions": [ "github.copilot", "ms-azuretools.vscode-docker", "ms-python.python", "ms-python.vscode-pylance", "redhat.vscode-yaml", "editorconfig.editorconfig", "ms-azure-devops.azure-pipelines", "ms-azure-load-testing.microsoft-testing", "ms-azuretools.azure-dev", "ms-azuretools.vscode-azure-github-copilot", "ms-azuretools.vscode-azureappservice", "ms-azuretools.vscode-azurecontainerapps", "ms-azuretools.vscode-azurefunctions", "eamodio.gitlens", "ms-azuretools.vscode-azurelogicapps", "ms-azuretools.vscode-azureresourcegroups", "ms-azuretools.vscode-azurestaticwebapps", "ms-azuretools.vscode-azurestorage", "ms-azuretools.vscode-azurevirtualmachines", "ms-azuretools.vscode-bicep", "ms-azuretools.vscode-containers", "ms-azuretools.vscode-cosmosdb", "ms-azuretools.vscode-docker", "ms-python.black-formatter", "ms-python.debugpy", "ms-python.isort", "ms-python.pylint", "ms-python.python", "ms-python.vscode-pylance", "ms-vscode.azure-repos", "ms-vscode.azurecli", "ms-azuretools.azure-dev", "ms-azuretools.vscode-azure-github-copilot" ] } } // "postCreateCommand": "pip3 install --user -r requirements.txt",} Step 6: Shared Resources and CollaborationWith everyone working on the same VM, we could leverage shared resources effectively: 123456789101112# Shared directories for common resources/home/shared/├── datasets/ # Common datasets for AI training├── models/ # Shared model artifacts├── configs/ # Shared configuration files└── scripts/ # Utility scripts# Permissions set for group collaborationsudo groupadd developerssudo usermod -a -G developers alice bob charlie... # all developerssudo chown -R :developers /home/sharedsudo chmod -R 775 /home/shared The Unexpected Benefits1. Lightning-Fast OnboardingOur typical onboarding process used to take 2-3 days: Day 1: Install Python, configure environment Day 2: Debug dependency issues, version conflicts Day 3: Finally start actual development With our new setup: Hour 1: Receive SSH certificate and connection instructions Hour 2: Connect VS Code, open project in container Hour 3: Writing production code That’s a 94% reduction in onboarding time! 2. Compute Power DemocracyPreviously, developers with older laptops struggled with AI model training and testing. Now everyone had access to: 8 vCPUs for parallel processing 32 GB RAM for large datasets Fast SSD storage for quick I/O Azure’s network backbone for downloading models and datasets 3. Cost Optimization That Surprised FinanceOur finance team loved this approach: Traditional approach: 8 high-spec laptops = ~$16,000 Our approach: 1 Azure VM = ~$400/month Even accounting for the VM running 24/7, we saved money within the first year. 4. Security Without SufferingThe restricted environment developers could finally contribute without compromising security: No software installed on their local machines All code remained in the cloud Audit logs for every action Easy to revoke access when project ended Real-World Results: The AutoGen ProjectLet me share some specific wins from our AutoGen AI agent project: Development Velocity Before: 2-3 features per sprint (too much time on environment issues) After: 8-10 features per sprint (focus on actual development) Code Quality Before: “Works on my machine” was a daily phrase After: If it worked in the dev container, it worked everywhere Team Morale Before: Frustration with environment setup and restrictions After: Developers focused on solving interesting AI problems Specific AutoGen BenefitsWorking with AutoGen requires multiple AI models, API keys, and complex configurations. Our setup handled this beautifully: 12345678910111213141516# Shared configuration file accessible to all# /home/shared/configs/autogen_config.pyconfig_list = [ { "model": "gpt-4", "api_key": os.environ.get("OPENAI_API_KEY"), }, { "model": "gpt-3.5-turbo", "api_key": os.environ.get("OPENAI_API_KEY"), }]# Each developer could test with the same models and configurations# No "I don't have API access" blockers Lessons Learned and Best PracticesWhat Worked Well Start with more resources than you think you need: We initially tried a smaller VM and hit performance issues. Better to scale down than suffer with poor performance. Invest time in the Dev Container setup: Every hour spent perfecting the container saved days of debugging later. Document everything: We created a comprehensive wiki with: Connection instructions Troubleshooting guides Best practices for shared development Git workflow for the shared environment Regular backups: We automated daily backups of the entire VM and home directories. Challenges We Faced Concurrent file editing: We needed clear Git workflows to prevent conflicts Solution: Feature branches and frequent commits Resource contention: Occasionally, one developer’s process would hog resources Solution: Implemented resource limits using cgroups SSH connection drops: Some developers faced connection issues Solution: Configured SSH keep-alive and implemented tmux for session persistence Security ConsiderationsDon’t forget these crucial security aspects: 123456789101112# Implement fail2ban for SSH protectionsudo apt-get install fail2ban# Configure firewall rulessudo ufw allow from <OFFICE_IP_RANGE> to any port 22sudo ufw enable# Regular security updatessudo unattended-upgrades# Audit loggingsudo apt-get install auditd ConclusionWhat started as a desperate attempt to enable collaboration across restricted environments turned into a revolutionary approach to distributed development. By leveraging Azure VMs, Dev Containers, and VS Code Remote Development, we not only solved our immediate problem but discovered a solution that offers: 94% faster onboarding for new team members Significant cost savings compared to traditional hardware approaches Enhanced security without sacrificing developer productivity True collaboration through shared resources and environments Consistent development experience across all team members The key insight was recognizing that personal laptops could serve as the bridge between restricted corporate environments and cloud-based development infrastructure. Sometimes the best solutions come from thinking outside the traditional corporate IT box. Whether you’re dealing with similar restrictions or simply want to improve your team’s development experience, this architecture pattern could be the game-changer you’re looking for. The combination of Azure infrastructure, containerized development environments, and modern remote development tools creates a powerful platform that scales with your team’s needs. References VS Code Remote Development Dev Containers Documentation Azure Virtual Machines Microsoft AutoGen Framework SSH Key-Based Authentication Main image generated by DALL-E","link":"/Azure/DevOps/Development/how-we-united-8-developers-across-restricted-environments-using-azure-vms-and-dev-containers/"},{"title":"Ignoring Azurite Files","text":"🎯 TL;DR: Managing Azurite Storage Emulation Files in VS Code Local development with Azure Functions often requires Azurite (Azure Storage Emulator replacement) which generates storage files that clutter VS Code workspace. Problem: __azurite__, __blobstorage__, and __queuestorage__ directories appear in project explorer making navigation difficult. Solution: Configure VS Code files.exclude settings to hide these emulation artifacts while preserving their functionality for local development and testing. In the old days, developers relied on the Azure Storage Emulator to emulate Azure Storage services locally. However, Azure Storage Emulator has been deprecated and replaced with Azurite, which is now the recommended way to emulate Azure Blob, Queue, and Table storage locally. In this post, let’s see how to set up exclusions in Visual Studio Code to prevent unwanted Azurite files from cluttering your workspace while working with Function Apps. Starting Azurite ServicesIn Visual Studio Code, you can start Azurite services Visual Studio Code: Setting Up File ExclusionsAzurite’s local emulation files, while essential, can quickly overpopulate your project. To keep them hidden, Visual Studio Code’s files.exclude feature allows you to filter them out. Here’s how to add the necessary configuration to hide these files. Open the settings.json file in your project. Add the following block to exclude Azurite files: 12345"files.exclude": { "__azurite__": true, "__blobstorage__": true, "__queuestorage__": true} This will automatically hide Azurite-related files from the VS Code explorer. ConclusionBy setting up file exclusions in Visual Studio Code and .gitignore, you can prevent clutter from unnecessary Azurite files. This streamlines your development process and keeps your project cleaner. References Thumbnail image was taken from Azure SVG icons Main image generated by DALL-E","link":"/Azure/Storage/Azurite/ignoring-azurite-files/"},{"title":"Getting TFVC Repository Structure via Azure DevOps Server API","text":"🎯 TL;DR: Retrieving TFVC Repository Structure via REST API This post demonstrates how to programmatically enumerate TFVC repository folders using Azure DevOps Server REST APIs. Unlike Git repositories, TFVC follows a one-repository-per-project model with hierarchical folder structures starting at $/ProjectName. The solution uses the TFVC Items API with specific parameters: scopePath=$/ProjectName to target the project root, and recursionLevel=OneLevel to retrieve immediate children. The implementation handles authentication via Personal Access Tokens, filters results to show only folders (excluding the root), and includes error handling for projects without TFVC repositories or insufficient permissions. Key technical details: PowerShell script implementation, proper API parameter usage, authentication setup, and handling edge cases like empty repositories and access permissions. Complete PowerShell script and utilities available here Recently, I was asked an interesting question by a developer who was struggling with Azure DevOps Server APIs around fetching repository metadata for legacy TFVC structures as part of a GitHub migration from ADO Server. This was a nice little problem to solve because, let’s be honest, we don’t really deal with these legacy TFVC repositories much anymore. Most teams have migrated to Git, and the documentation around TFVC API interactions has become somewhat sparse over the years. The challenge was straightforward but frustrating: they could retrieve project information just fine, but getting the actual TFVC folder structure within each project? That’s where things got tricky. After doing a bit of digging through the API documentation and testing different approaches, I’m happy to say that yes, it is absolutely possible to enumerate all TFVC repositories and their folder structures programmatically. This blog post shares the solution I put together - a practical approach to retrieve TFVC repository structure using the Azure DevOps Server REST APIs. If you’re working with legacy TFVC repositories and need to interact with them programmatically, this one’s for you. The Challenge: Understanding TFVC API LimitationsUnlike Git repositories where each project can contain multiple repos, TFVC follows a different model where each project contains exactly one TFVC repository. This fundamental difference affects how you interact with the API and retrieve repository information. The main challenge developers face is distinguishing between project metadata and actual TFVC repository structure. When calling the standard Projects API, you receive project information but not the folder structure within the TFVC repository itself. Common Misconceptions About TFVC APIsMany developers make the mistake of thinking that the Projects API or the Items API with default parameters will return the TFVC folder structure. Here’s what typically happens: What doesn’t work: Using only the Projects API - returns project metadata, not TFVC structure Calling Items API without proper scopePath parameter - returns all items across the organization Using the Branches API - doesn’t apply to TFVC repositories the same way as Git The root cause: The API requires specific parameters to traverse the TFVC hierarchy correctly. Understanding TFVC Repository StructureIn TFVC, the repository structure follows this pattern: Each project has one TFVC repository The repository root is always $/ProjectName Folders are organized hierarchically under this root You need to specify recursion levels to control how deep the API traverses The Solution: Targeted API Calls with Proper ParametersThe key to retrieving TFVC folder structure lies in using the correct combination of API endpoints and parameters. Here’s the step-by-step approach: Step 1: Get All ProjectsFirst, retrieve all projects in your Azure DevOps Server instance using the Projects API. Step 2: Query TFVC Items for Each ProjectFor each project, call the TFVC Items API with these specific parameters: scopePath=$/ProjectName - Sets the starting point to the project’s TFVC root recursionLevel=OneLevel - Returns immediate children only (not recursive) Filter results to show only folders, excluding the root itself PowerShell Script ImplementationHere’s a complete PowerShell script that implements this solution. You can find the full script and additional TFVC utilities in my TFS/TFVC Scripts collection: 1234567891011121314151617181920212223242526272829303132333435363738394041# Azure DevOps Server configuration$tfsUrl = "https://mytfs.com"$PAT = "your-personal-access-token"# Create authentication headers$headers = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$PAT"))}# Get all projects$projects = Invoke-RestMethod -Uri "$tfsUrl/DefaultCollection/_apis/projects?api-version=5.0" -Headers $headersforeach ($project in $projects.value) { Write-Host "Project: $($project.name)" # Construct TFVC path for this project $tfvcPath = "`$/$($project.name)" $itemsUrl = "$tfsUrl/DefaultCollection/$($project.id)/_apis/tfvc/items?scopePath=$tfvcPath&recursionLevel=OneLevel&api-version=5.0" try { # Get TFVC items for this project $items = Invoke-RestMethod -Uri $itemsUrl -Headers $headers # Filter to get only folders (excluding the root itself) $folders = $items.value | Where-Object { $_.isFolder -eq $true -and $_.path -ne $tfvcPath } foreach ($folder in $folders) { Write-Host " - $($folder.path)" } if ($folders.Count -eq 0) { Write-Host " No top-level folders found" } } catch { Write-Host " No TFVC content or access denied" }} Script Configuration and SecurityPersonal Access Token SetupTo use this script, you’ll need to create a Personal Access Token (PAT) with appropriate permissions: Navigate to your Azure DevOps Server user settings Create a new Personal Access Token Grant Code (read) permissions at minimum Copy the token and replace your-personal-access-token in the script URL ConfigurationReplace https://mytfs.com with your actual Azure DevOps Server URL. The format should be: On-premises TFS: https://your-tfs-server Azure DevOps Server: https://your-server-name Understanding the API ResponseThe TFVC Items API returns objects with these key properties: 123456{ "path": "$/ProjectName/FolderName", "isFolder": true, "version": 12345, "size": 0} Important properties: path: The full TFVC path to the item isFolder: Boolean indicating if the item is a folder version: The changeset number when this item was last modified Error Handling and Edge CasesThe script includes error handling for common scenarios: No TFVC Repository: Some projects might not have TFVC repositories initialized. The script catches these cases and displays an appropriate message. Access Permissions: If your PAT doesn’t have sufficient permissions for a project, the API call will fail gracefully. Empty Repositories: Projects with TFVC repositories but no folders will display “No top-level folders found.” Advanced CustomizationsFiltering Specific ProjectsTo target specific projects, you can filter the projects array: 1$projects = $projects.value | Where-Object { $_.name -like "*YourFilter*" } Deeper RecursionTo get more than just top-level folders, change the recursionLevel parameter: OneLevel: Immediate children only Full: Complete hierarchy (use with caution on large repositories) Output FormattingYou can modify the output format to suit your needs: 12345678910# Export to CSV$results = @()foreach ($folder in $folders) { $results += [PSCustomObject]@{ Project = $project.name FolderPath = $folder.path LastModified = $folder.version }}$results | Export-Csv -Path "tfvc-folders.csv" -NoTypeInformation Performance ConsiderationsFor organizations with many projects, consider implementing: Parallel Processing: Use PowerShell jobs or runspaces to query multiple projects simultaneously. Pagination: For large result sets, implement pagination using the $skip and $top parameters. Caching: Store results locally if you need to run the script frequently. Troubleshooting Common IssuesAuthentication Failures Verify your PAT is not expired Ensure the PAT has sufficient permissions Check that your TFS URL is correct and accessible Empty Results Confirm the project actually uses TFVC (not Git) Verify you have read permissions on the project Check if the TFVC repository has been initialized API Version CompatibilityThe script uses API version 5.0, which is compatible with: Team Foundation Server 2019 and later Azure DevOps Server 2019 and later For older TFS versions, you might need to use API version 1.0 or 2.0. Best Practices for TFVC API IntegrationUse Specific API Versions: Always specify the API version to ensure consistent behavior across different server versions. Implement Proper Error Handling: TFVC repositories can have various states, and not all projects may have TFVC initialized. Respect Rate Limits: While on-premises servers typically don’t have strict rate limits, implement appropriate delays if querying large numbers of projects. Secure Credential Management: Store PATs securely and rotate them regularly according to your organization’s security policies. Integration with CI/CD PipelinesThis script can be integrated into DevOps workflows for: Repository Auditing: Generate reports of TFVC repository structures across your organization. Migration Planning: Identify repository structures before migrating from TFVC to Git. Compliance Reporting: Document your source control structure for regulatory requirements. Key TakeawaysWorking with TFVC repositories via REST API requires understanding the fundamental differences between TFVC and Git repository models. The key insights are: TFVC has one repository per project, not multiple like Git Use scopePath and recursionLevel parameters to control API traversal Always filter results to distinguish between folders and the root item Implement proper error handling for projects without TFVC repositories This solution provides a robust foundation for any TFVC repository management tasks you might need to automate. Whether you’re auditing your source control landscape, planning migrations, or building custom tooling, this approach will help you successfully retrieve TFVC repository structure data. References Azure DevOps REST API - TFVC Items Azure DevOps REST API - Projects Complete PowerShell script on GitHub Gist TFS/TFVC Scripts Collection - Additional utilities and examples Main image generated by DALL-E","link":"/Azure-DevOps/Azure-DevOps-API/getting-tfvc-repository-structure-via-azure-devops-server-api/"},{"title":"Building Voice Agents with Azure Communication Services Voice Live API and Azure AI Agent Service","text":"🎯 TL;DR: Real-time Voice Agent Implementation This post walks through building a voice agent that connects traditional phone calls to Azure’s AI services. The system intercepts incoming calls via Azure Communication Services, streams audio in real-time to the Voice Live API, and processes conversations through pre-configured AI agents in Azure AI Studio. The implementation uses FastAPI for webhook handling, WebSocket connections for bidirectional audio streaming, and Azure Managed Identity for authentication (no API keys to manage). The architecture handles multiple concurrent calls on a single Python thread using asyncio. Implementation details: Audio resampling between 16kHz (ACS requirement) and 24kHz (Voice Live requirement), connection resilience for preview services, and production deployment considerations. Full source code and documentation available here Recently, I found myself co-leading an innovation project that pushed me into uncharted territory. The challenge? Developing a voice-based agentic solution with an ambitious goal - routing at least 25% of current contact center calls to AI voice agents. This was bleeding-edge stuff, with both the Azure Voice Live API and Azure AI Agent Service voice agents still in preview at the time of writing. When you’re working with preview services, documentation is often sparse, and you quickly learn that reverse engineering network calls and maintaining close relationships with product teams becomes part of your daily routine. This blog post shares the practical lessons learned and the working solution we built to integrate these cutting-edge services. The Innovation ChallengeBuilding a voice agent system that could handle real customer interactions meant tackling several complex requirements: Real-time voice processing with minimal latency Natural conversation flow without awkward pauses Integration with existing contact center infrastructure Scalability to handle multiple concurrent calls Reliability for production use cases With both Azure Voice Live API and Azure AI Voice Agent Service in preview, we were essentially building on shifting sands. But that’s what innovation is about - pushing boundaries and finding solutions where documentation doesn’t yet exist. Understanding the ArchitectureOur solution bridges Azure Communication Services (ACS) with Azure AI services to create an intelligent voice agent. Here’s how the pieces fit together: graph TB subgraph \"Phone Network\" PSTN[📞 PSTN Number+1-555-123-4567] end subgraph \"Azure Communication Services\" ACS[🔗 ACS Call AutomationEvent Grid Webhooks] MEDIA[🎵 Media StreamingWebSocket Audio] end subgraph \"Python FastAPI App\" API[🐍 FastAPI Serverlocalhost:49412] WS[🔌 WebSocket HandlerAudio Processing] HANDLER[⚡ Media HandlerAudio Resampling] end subgraph \"Azure OpenAI\" VOICE[🤖 Voice Live APIAgent Modegpt-4o Realtime] AGENT[👤 Pre-configured AgentAzure AI Studio] end subgraph \"Dev Infrastructure\" TUNNEL[🚇 Dev TunnelPublic HTTPS Endpoint] end PSTN -->|Incoming Call| ACS ACS -->|Webhook Events| TUNNEL TUNNEL -->|HTTPS| API ACS -->|WebSocket Audio| WS WS -->|PCM 16kHz| HANDLER HANDLER -->|PCM 24kHz| VOICE VOICE -->|Agent Processing| AGENT AGENT -->|AI Response| VOICE VOICE -->|AI Response| HANDLER HANDLER -->|PCM 16kHz| WS WS -->|Audio Stream| ACS ACS -->|Audio| PSTN style PSTN fill:#ff9999 style ACS fill:#87CEEB style API fill:#90EE90 style VOICE fill:#DDA0DD style TUNNEL fill:#F0E68C Core Components Azure Communication Services: Handles the telephony infrastructure, providing phone numbers and call routing Voice Live API: Enables real-time speech recognition and synthesis with WebRTC streaming Azure AI Agent Service: Provides the intelligence layer for understanding and responding to customer queries WebSocket Bridge: Our custom Python application that connects these services The FlowWhen a customer calls, here’s what happens behind the scenes: 123Customer Call → ACS Phone Number → Webhook to Our Service → WebSocket Connection → Voice Live API ↔ AI Agent Service → Real-time Voice Response → Customer Setting Up the FoundationLet’s walk through the practical implementation. You can find the complete code in my GitHub repository. PrerequisitesFirst, you’ll need to set up several Azure services. Here’s what we discovered through trial and error: 12345# Required Azure services- Azure Communication Services (with phone number provisioning)- Azure AI Services (Speech Service enabled)- Azure AI Agent Service (with voice capabilities)- Azure App Service or Container Instance (for hosting) Environment ConfigurationOne of the first challenges was figuring out all the required configuration parameters. Here’s what you’ll need: 123456# Essential environment variables (Using Azure Managed Identity - No API Keys!)ACS_CONNECTION_STRING = "endpoint=https://your-acs.communication.azure.com/;accesskey=your-key"AZURE_VOICE_LIVE_ENDPOINT = "https://your-aoai.cognitiveservices.azure.com/"AGENT_ID = "your_agent_id_from_azure_ai_studio"AGENT_PROJECT_NAME = "your_project_name"BASE_URL = "https://your-tunnel-url.asse.devtunnels.ms" # Dev Tunnel URL Building the WebSocket BridgeThe heart of our solution is a Python application that acts as a bridge between ACS and the Voice Live API. This wasn’t documented anywhere - we had to figure it out by analyzing network traffic and experimenting. Handling Incoming Calls12345678910111213141516171819202122232425262728293031323334from fastapi import FastAPI, WebSocketfrom azure.communication.callautomation import CallAutomationClientfrom azure.identity import DefaultAzureCredentialimport asyncioimport websocketsapp = FastAPI()call_automation_client = CallAutomationClient.from_connection_string( ACS_CONNECTION_STRING)@app.post("/api/incomingCall")async def incoming_call(request: dict): """Handle incoming call webhook from ACS""" try: # Parse the incoming call context incoming_call_context = request.get("incomingCallContext") # Answer the call call_connection = call_automation_client.answer_call( incoming_call_context=incoming_call_context, callback_url=f"{CALLBACK_URI}/api/callbacks/{call_id}", ) # Start WebSocket connection to Voice Live API asyncio.create_task( establish_voice_connection(call_connection.call_connection_id) ) return {"status": "success"} except Exception as e: logger.error(f"Error handling incoming call: {e}") return {"error": str(e)} Establishing the Voice ConnectionThis is where things got interesting. The Voice Live API uses WebRTC for real-time audio streaming, but the documentation was minimal. Here’s what we discovered: 123456789101112131415161718192021222324252627282930313233async def establish_voice_connection(call_connection_id): """Establish WebSocket connection to Voice Live API using Azure Managed Identity""" # Get access token using managed identity from azure.identity import DefaultAzureCredential credential = DefaultAzureCredential() token = credential.get_token("https://cognitiveservices.azure.com/.default") # Construct the WebSocket URL for Voice Live API ws_url = f"wss://your-region.cognitiveservices.azure.com/openai/realtime?api-version=2024-10-01-preview" headers = { "Authorization": f"Bearer {token.token}", "OpenAI-Beta": "realtime=v1" } async with websockets.connect(ws_url, extra_headers=headers) as websocket: # Initialize session with Agent ID await websocket.send(json.dumps({ "type": "session.update", "session": { "agent": { "agent_id": AGENT_ID, "project_name": AGENT_PROJECT_NAME } } })) # Handle bidirectional audio streaming await asyncio.gather( receive_audio_from_caller(websocket, call_connection_id), send_audio_to_caller(websocket, call_connection_id) ) Integrating with Azure AI Agent ServiceThe AI Agent Service provides the intelligence for our voice agent. Here’s how we connected it: Processing Voice Input1234567891011121314151617async def process_voice_with_agent(audio_data, session_id): """Send audio directly to Voice Live API in Agent Mode""" # Using Azure Managed Identity - no API keys needed from azure.identity import DefaultAzureCredential credential = DefaultAzureCredential() token = credential.get_token("https://cognitiveservices.azure.com/.default") # Send audio input event to Voice Live API audio_event = { "type": "input_audio_buffer.append", "audio": base64.b64encode(audio_data).decode() } # Voice Live API will handle agent processing automatically # when configured with agent_id in session.update return audio_event Handling Real-World ChallengesWorking with preview services meant encountering numerous undocumented behaviors. Here are some key challenges we solved: 1. Audio Format CompatibilityThe Voice Live API expects specific audio formats. We discovered through trial and error: 12345678910111213# Audio configuration that actually works (Voice Live API format)AUDIO_CONFIG = { "format": "pcm16", # 16-bit PCM for Voice Live API "sample_rate": 24000, # 24kHz required by Voice Live "channels": 1 # Mono}# ACS requires 16kHz, so we need resamplingACS_AUDIO_CONFIG = { "format": "pcm16", "sample_rate": 16000, # ACS requirement "channels": 1} 2. Latency OptimizationTo achieve natural conversation flow, we implemented several optimizations: 123456789101112# Start voice synthesis before full response is readyasync def stream_synthesize_speech(text_stream): """Synthesize speech in chunks for lower latency""" buffer = "" async for chunk in text_stream: buffer += chunk # Send to synthesis when we have a complete sentence if any(punct in buffer for punct in ['.', '!', '?']): await synthesize_and_send(buffer) buffer = "" 3. Connection ResiliencePreview services can be unstable. We added robust error handling: 123456789101112131415161718async def maintain_connection(websocket, call_id): """Maintain WebSocket connection with automatic reconnection""" retry_count = 0 max_retries = 3 while retry_count < max_retries: try: await websocket.ping() await asyncio.sleep(30) # Ping every 30 seconds except websockets.ConnectionClosed: logger.warning(f"Connection lost for call {call_id}") retry_count += 1 await asyncio.sleep(2 ** retry_count) # Exponential backoff # Attempt reconnection websocket = await reconnect_websocket(call_id) Deployment ConsiderationsWhen deploying this solution, we learned several important lessons: Container DeploymentWe packaged our Python application as a container for easier deployment: 12345678910111213141516FROM python:3.11-slimWORKDIR /app# Install system dependencies for audio processingRUN apt-get update && apt-get install -y \\ libopus0 \\ libopus-dev \\ && rm -rf /var/lib/apt/lists/*COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txtCOPY . .CMD ["python", "start.py"] Scaling ConsiderationsFor handling multiple concurrent calls: Use Azure Container Instances or App Service with autoscaling Implement connection pooling for WebSocket connections Monitor memory usage - audio processing can be memory-intensive Monitoring and DebuggingWorking with preview services means extensive logging is crucial: 12345678910import loggingfrom azure.monitor.opentelemetry import configure_azure_monitor# Configure Azure Monitor for production debuggingconfigure_azure_monitor( connection_string=APPLICATIONINSIGHTS_CONNECTION_STRING)# Log all WebSocket eventslogging.getLogger('websockets').setLevel(logging.DEBUG) Lessons LearnedAfter weeks of development and close collaboration with Azure product teams, here are our key takeaways: Preview Services Require Patience: Be prepared for undocumented features and changing APIs Network Analysis is Your Friend: Tools like Wireshark helped us understand the protocol Build in Resilience: Assume connections will drop and services will be intermittently unavailable Start Simple: Get basic voice working before adding complex AI interactions Monitor Everything: You’ll need extensive logging to debug issues in production Get StartedReady to build your own voice agent? Check out the complete implementation in my GitHub repository. The repository includes: Complete Python application code Deployment scripts and Docker configuration Environment setup instructions Troubleshooting guide Remember, innovation often means venturing into undocumented territory. Don’t be afraid to experiment, reverse-engineer, and collaborate with product teams. The future of voice-based AI agents is being written right now, and you can be part of it. References Azure Voice Live API Documentation Azure AI Agent Service Overview Complete Code Repository Azure Communication Services Documentation Main image generated by DALL-E","link":"/Azure/AI/Voice-Live-API/building-voice-agents-with-azure-communication-services-voice-live-api-and-azure-ai-agent-service/"},{"title":"Automating Searchable Branch Configuration in Azure DevOps Repos via REST API","text":"🎯 TL;DR: Bulk Configure Searchable Branches in Azure DevOps via Hidden Policy API Azure DevOps code search only indexes the default branch (master/main) by default, causing issues when teams use develop branches for JFrog Artifactory detection scripts. Problem: No documented API exists for bulk updating searchable branches across thousands of repositories. Solution: Use the undocumented Policy Configuration API with policy type 0517f88d-4ec5-4343-9d26-9930ebd53069 to programmatically add branches to the searchable list. This approach leverages the same API calls the Azure DevOps UI uses internally, enabling automation of what would otherwise require manual configuration across massive repository collections. Recently, I encountered an interesting challenge while working on a JFrog Artifactory adoption tracking project across a large Azure DevOps organization. The requirement was to scan repositories for JFrog URL references to determine which teams had successfully onboarded to their new artifact management system. The problem? Some development teams exclusively work in develop branches instead of master or main, and Azure DevOps code search only indexes the default branch by default. This seemingly simple requirement - adding develop to the searchable branches for thousands of repositories - turned into a fascinating exploration of Azure DevOps’ undocumented APIs. While there’s no official documentation for bulk updating searchable branches, I discovered that the Azure DevOps UI uses a specific Policy Configuration API under the hood that we can leverage for automation. This blog post shares a practical approach to programmatically configure searchable branches across large Azure DevOps organizations using REST APIs that Microsoft doesn’t officially document but absolutely supports. The Challenge: Azure DevOps Code Search LimitationsAzure DevOps code search is a powerful feature, but it comes with a significant limitation that affects many organizations: by default, only the repository’s default branch (typically master or main) is indexed for search operations. This creates problems in several scenarios: JFrog Adoption Tracking: Organizations implementing JFrog Artifactory need to scan all repositories for configuration files and dependency references, but teams using feature branches or develop as their primary branch won’t be detected. Multi-Branch Development: Teams practicing GitFlow or similar branching strategies may have critical code in develop, release/*, or feature branches that needs to be searchable. Compliance and Security Scanning: Security tools and compliance scripts that rely on code search may miss important files if they’re not in the default branch. Understanding Azure DevOps Searchable BranchesIn Azure DevOps, searchable branches are configured at the repository level through the UI: Manual Configuration Path: Navigate to Project Settings → Repositories Select your repository Go to Settings → Searchable Branches Add additional branches (maximum of 5 extra branches) What Actually Happens:When you configure searchable branches through the UI, Azure DevOps creates or updates a policy configuration with a specific policy type. This policy type - 0517f88d-4ec5-4343-9d26-9930ebd53069 - controls which branches are indexed for code search. The Discovery: Hidden Policy Configuration APIAfter extensive research and reverse engineering the Azure DevOps UI network traffic, I discovered that searchable branch configuration is managed through the Policy Configuration API with a specific, undocumented policy type. Key Insights: Azure DevOps doesn’t expose a direct “searchable branches” API endpoint The UI uses the Policy Configuration API with policy type 0517f88d-4ec5-4343-9d26-9930ebd53069 This API is officially supported but not documented for this specific use case The same approach works for both Azure DevOps Server and Azure DevOps Cloud The Solution: Automated Policy ConfigurationThe approach involves three main steps: Step 1: Retrieve Existing Policy ConfigurationFirst, we need to check if a searchable branches policy already exists for the repository: 123456789# Get existing searchable branch policy for a repository$policyUrl = "https://dev.azure.com/{organization}/{project}/_apis/policy/configurations"$params = @{ 'repositoryId' = $repositoryId 'policyType' = '0517f88d-4ec5-4343-9d26-9930ebd53069' 'api-version' = '7.1-preview.1'}$existingPolicy = Invoke-RestMethod -Uri $policyUrl -Headers $headers -Method Get -Body $params Step 2: Create or Update Policy ConfigurationIf a policy exists, we update it. If not, we create a new one: 12345678910111213141516171819202122# Policy configuration for searchable branches$policySettings = @{ 'type' = @{ 'id' = '0517f88d-4ec5-4343-9d26-9930ebd53069' } 'isEnabled' = $true 'isBlocking' = $false 'settings' = @{ 'searchBranches' = @( 'refs/heads/master', 'refs/heads/main', 'refs/heads/develop' ) } 'scope' = @( @{ 'repositoryId' = $repositoryId 'refName' = 'refs/heads/master' 'matchKind' = 'exact' } )} Step 3: Apply the ConfigurationSend the policy configuration to Azure DevOps: 12345678910if ($existingPolicy.count -gt 0) { # Update existing policy $configId = $existingPolicy.value[0].id $updateUrl = "https://dev.azure.com/{organization}/{project}/_apis/policy/configurations/$configId" Invoke-RestMethod -Uri $updateUrl -Headers $headers -Method Put -Body ($policySettings | ConvertTo-Json -Depth 10) -ContentType "application/json"} else { # Create new policy $createUrl = "https://dev.azure.com/{organization}/{project}/_apis/policy/configurations" Invoke-RestMethod -Uri $createUrl -Headers $headers -Method Post -Body ($policySettings | ConvertTo-Json -Depth 10) -ContentType "application/json"} Complete PowerShell ImplementationHere’s a complete script that implements this solution for bulk updating searchable branches across multiple repositories: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798# Azure DevOps configuration$organization = "your-org"$project = "your-project" $pat = "your-personal-access-token"# Create authentication headers$headers = @{ 'Authorization' = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat")) 'Content-Type' = 'application/json'}# Function to update searchable branches for a repositoryfunction Update-SearchableBranches { param( [string]$organizationName, [string]$projectName, [string]$repositoryId, [array]$branches, [hashtable]$authHeaders ) $policyTypeId = '0517f88d-4ec5-4343-9d26-9930ebd53069' $baseUrl = "https://dev.azure.com/$organizationName/$projectName/_apis/policy/configurations" try { # Check for existing policy $getUrl = "$baseUrl" + "?repositoryId=$repositoryId&policyType=$policyTypeId&api-version=7.1-preview.1" $existingPolicy = Invoke-RestMethod -Uri $getUrl -Headers $authHeaders -Method Get # Format branches with refs/heads/ prefix $searchBranches = $branches | ForEach-Object { if ($_ -like "refs/heads/*") { $_ } else { "refs/heads/$_" } } # Create policy configuration $policyConfig = @{ 'type' = @{ 'id' = $policyTypeId } 'isEnabled' = $true 'isBlocking' = $false 'settings' = @{ 'searchBranches' = $searchBranches } 'scope' = @( @{ 'repositoryId' = $repositoryId 'refName' = $searchBranches[0] 'matchKind' = 'exact' } ) } $jsonBody = $policyConfig | ConvertTo-Json -Depth 10 if ($existingPolicy.count -gt 0) { # Update existing policy $configId = $existingPolicy.value[0].id $updateUrl = "$baseUrl/$configId" + "?api-version=7.1-preview.1" $result = Invoke-RestMethod -Uri $updateUrl -Headers $authHeaders -Method Put -Body $jsonBody Write-Host "Updated searchable branches for repository $repositoryId" -ForegroundColor Green } else { # Create new policy $createUrl = "$baseUrl" + "?api-version=7.1-preview.1" $result = Invoke-RestMethod -Uri $createUrl -Headers $authHeaders -Method Post -Body $jsonBody Write-Host "Created searchable branches policy for repository $repositoryId" -ForegroundColor Green } return $result } catch { Write-Error "Failed to update searchable branches for repository $repositoryId`: $($_.Exception.Message)" return $null }}# Get all repositories in the project$reposUrl = "https://dev.azure.com/$organization/$project/_apis/git/repositories?api-version=7.1"$repositories = Invoke-RestMethod -Uri $reposUrl -Headers $headers# Define branches to make searchable$searchableBranches = @('master', 'main', 'develop')# Update searchable branches for each repositoryforeach ($repo in $repositories.value) { Write-Host "Processing repository: $($repo.name)" -ForegroundColor Yellow $result = Update-SearchableBranches -organizationName $organization -projectName $project -repositoryId $repo.id -branches $searchableBranches -authHeaders $headers if ($result) { Write-Host "Successfully configured searchable branches for $($repo.name)" -ForegroundColor Green } else { Write-Host "Failed to configure searchable branches for $($repo.name)" -ForegroundColor Red } # Add delay to avoid rate limiting Start-Sleep -Milliseconds 500}Write-Host "Searchable branches configuration complete!" -ForegroundColor Cyan Important Considerations and LimitationsAPI Version and StabilityPreview API: The Policy Configuration API is in preview (7.1-preview.1), which means: The API may change without notice Microsoft doesn’t guarantee backward compatibility Monitor for API updates and test thoroughly before production use Repository LimitationsBranch Limits: Azure DevOps allows a maximum of 6 searchable branches per repository (including the default branch). Indexing Delays: After updating searchable branches, Azure DevOps may take several hours to index the new branches. Search results won’t be immediately available. Performance ConsiderationsRate Limiting: Implement appropriate delays between API calls to avoid hitting rate limits, especially when processing thousands of repositories. Batch Processing: For large organizations, consider processing repositories in batches and implementing retry logic for failed requests. Error HandlingRepository States: Some repositories may not have branches configured or may be in archived states. Implement proper error handling for these scenarios. Permission Issues: Ensure your Personal Access Token has sufficient permissions for policy configuration (Project Settings, Read & Manage). Authentication and Security SetupPersonal Access Token ConfigurationCreate a Personal Access Token with the following permissions: Code (Read) - To access repository information Project and Team (Read) - To list projects and repositories Policy Configuration (Read & Manage) - To update searchable branch policies Security Best PracticesToken Storage: Store Personal Access Tokens securely and rotate them regularly according to your organization’s security policies. Least Privilege: Use dedicated service accounts with minimal required permissions for automation scripts. Audit Logging: Log all policy changes for compliance and troubleshooting purposes. Advanced Usage ScenariosConditional Branch ConfigurationConfigure different searchable branches based on repository naming conventions or team requirements: 12345678910111213141516171819202122# Example: Configure different branches based on repository name patterns$branchConfig = @{ 'web-*' = @('master', 'develop', 'staging') 'api-*' = @('master', 'develop', 'release') 'mobile-*' = @('master', 'develop', 'feature/*')}foreach ($repo in $repositories.value) { $branches = @('master', 'main') # Default branches # Add specific branches based on repository name foreach ($pattern in $branchConfig.Keys) { if ($repo.name -like $pattern) { $branches += $branchConfig[$pattern] break } } # Remove duplicates and configure $uniqueBranches = $branches | Select-Object -Unique Update-SearchableBranches -repositoryId $repo.id -branches $uniqueBranches} Integration with CI/CD PipelinesIntegrate searchable branch configuration into your DevOps workflows: 12345678# Azure DevOps Pipeline example- task: PowerShell@2 displayName: 'Configure Searchable Branches' inputs: targetType: 'inline' script: | # Your PowerShell script here # Use pipeline variables for organization, project, and PAT Troubleshooting Common IssuesPolicy Configuration Not FoundIf the GET request returns empty results, the repository may not have searchable branches configured yet. Create a new policy instead of updating an existing one. Invalid Branch ReferencesEnsure branch names use the correct format: Correct: refs/heads/develop Incorrect: develop or origin/develop Permission Denied ErrorsVerify that your Personal Access Token has the required permissions and that you have administrative access to the project. Indexing DelaysAfter configuration, search indexing may take several hours. Test with small repositories first to verify the configuration is working before processing large numbers of repositories. Real-World Use CasesJFrog Artifactory Adoption TrackingThe original use case that sparked this solution: 12345678910111213# Search for JFrog references across all configured branches$searchQuery = "jfrog.yourcompany.com"$searchUrl = "https://dev.azure.com/$organization/$project/_apis/search/codesearchresults?api-version=7.1-preview.1"$searchBody = @{ searchText = $searchQuery includeFacets = $true top = 1000} | ConvertTo-Json$searchResults = Invoke-RestMethod -Uri $searchUrl -Headers $headers -Method Post -Body $searchBody -ContentType "application/json"# Process results to identify repositories using JFrog Security Compliance ScanningScan for security-sensitive patterns across multiple branches: 1234567891011# Search for potential security issues across all searchable branches$securityPatterns = @( "password\\s*=", "api[_-]?key\\s*[:=]", "secret[_-]?key\\s*[:=]")foreach ($pattern in $securityPatterns) { # Search across all configured branches # Generate compliance reports} Code Quality AssessmentAnalyze code patterns and best practices across development branches: 12345678# Search for deprecated patterns across main and develop branches$deprecatedPatterns = @( "System.Web.HttpContext", "ConfigurationManager.AppSettings", "HttpResponse.Write")# Generate modernization reports Future Considerations and AlternativesAzure DevOps CLI IntegrationWhile the Azure DevOps CLI doesn’t have native support for searchable branches, you can use az devops invoke to call the REST APIs: 1az devops invoke --area policy --resource configurations --route-parameters project=YourProject --http-method GET --api-version 7.1-preview.1 PowerShell Module DevelopmentConsider creating a dedicated PowerShell module for searchable branch management: 12345# Future PowerShell module structureImport-Module AzureDevOpsSearchableBranchesGet-AdoSearchableBranches -Organization "YourOrg" -Project "YourProject" -Repository "YourRepo"Set-AdoSearchableBranches -Organization "YourOrg" -Project "YourProject" -Repository "YourRepo" -Branches @("master", "develop") Microsoft Graph IntegrationFor organizations using Microsoft Graph, consider integrating searchable branch configuration with broader DevOps governance workflows. Key TakeawaysWorking with Azure DevOps searchable branches requires understanding the underlying Policy Configuration API that Microsoft uses internally but doesn’t officially document for this purpose. The key insights from this solution are: Hidden APIs Exist: Azure DevOps has powerful APIs that aren’t always documented for every use case UI Reverse Engineering: Network traffic analysis can reveal API patterns for automation Policy-Based Configuration: Many Azure DevOps features use the Policy Configuration API under the hood Batch Processing Considerations: Large-scale automation requires careful attention to rate limiting and error handling This solution provides a robust foundation for any Azure DevOps organization needing to manage searchable branches at scale. Whether you’re tracking technology adoption, performing security scanning, or implementing code quality initiatives, this approach enables the automation that makes these tasks feasible across large repository collections. The undocumented nature of this API means it should be used with appropriate caution and monitoring, but for organizations with thousands of repositories, it’s currently the only viable approach for bulk searchable branch configuration. References Azure DevOps REST API - Policy Configurations Azure DevOps REST API - Git Repositories Stack Overflow: Cross Search in all repositories and branches in Azure DevOps Repos Azure DevOps Code Search Documentation Main image generated by DALL-E","link":"/Azure-DevOps/Azure-DevOps-API/automating-searchable-branch-configuration-in-azure-devops-repos-via-rest-api/"},{"title":"Microsoft Foundry Cross-Region with Private Endpoints (Part 1)","text":"🎯 TL;DR: Deploy Microsoft Foundry Cross-Region with Private Endpoints Microsoft Foundry isn’t available in every Azure region, but data residency requirements often mandate that all data at rest stays within specific regions. This post demonstrates how to keep your data in your compliant region (e.g., New Zealand North) while leveraging Microsoft Foundry in another region (e.g., Australia East) purely for AI inferencing. Using cross-region Private Endpoints over Azure’s backbone network, applications securely access Foundry’s AI capabilities without data traversing the public internet maintaining both regional compliance and zero-trust security posture. The Solution: All data at rest, applications, and Private Endpoints remain in NZN. Microsoft Foundry deployed in AUE provides AI inferencing only. Private connectivity ensures secure, compliant architecture across regions. When deploying Microsoft Foundry (formerly Azure AI Foundry) in enterprise environments, you’ll face a critical constraint: Microsoft Foundry isn’t available in every Azure region, yet data residency requirements mandate that all data at rest remains within specific regions. Imagine this scenario: Your organization must keep all data in New Zealand North due to regulatory compliance, but Microsoft Foundry is only available in Australia East. You can’t move data to AUE, but you need Foundry’s AI capabilities. How do you maintain compliance while accessing AI inferencing services? The solution is architectural: Keep all data at rest in your compliant region (NZN) and use Microsoft Foundry in the available region (AUE) purely for AI inferencing. By deploying cross-region Private Endpoints, applications in NZN securely access Foundry’s AI services over Azure’s backbone network, no public internet, no data residency violations, no compromises. This guide walks through the complete architecture, DNS configuration, security considerations, and implementation steps for deploying this cross-region private endpoint pattern. ⚠️ Important: Foundry Agents Service Limitation If you plan to use the Foundry Agents service specifically, there is a known limitation at the time of writing: all Foundry workspace resources (Cosmos DB, Storage Account, AI Search, Foundry Account, Project, Managed Identity, Azure OpenAI, or other Foundry resources used for model deployments) must be deployed in the same region as the VNet. This means the cross-region pattern described in this post will not work for Foundry Agents deployments you would need to deploy everything in the same region (e.g., all resources in Australia East where Foundry is available). However, if you are NOT using the Foundry Agents service (i.e., you’re only using Foundry for AI inferencing via API calls, OpenAI models, Speech Services, Vision, etc.), then the cross-region private endpoint pattern works perfectly, and all your data can reside in your chosen compliant region as described in this post. For more details, see Microsoft Learn - Virtual Networks with Foundry Agents - Known Limitations flowchart TB subgraph azure[\"☁️ Azure Backbone\"] direction TB subgraph NZN[\"🌏 NZN - Data Residency Region\"] direction TB subgraph vnet[\"VNet: 10.1.0.0/16\"] subgraph appsnet[\"Subnet: snet-apps • 10.1.1.0/24\"] client[👤 Client App / VM10.1.1.10] data[(💾 Data at RestStorage, SQL, etc.)] end subgraph pesnet[\"Subnet: snet • 10.1.2.0/24\"] pe[🔒 Private Endpoint10.1.2.4] end end dns[🔐 Private DNS ZonesResolves to Private IP] end subgraph AUE[\"🌏 AUE - AI Inferencing\"] foundry[[🤖 Microsoft FoundrymyFoundry. cognitiveservices.azure.com]] end pe ==>|\"🔐 Private Link\"| foundry end internet[/\"🌐 Public Internet❌ Blocked\"/] client --> dns dns -.->|10.1.2.4| pe client -->|HTTPS| pe foundry -.-x internet style azure fill:#f5f5f5,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5 style NZN fill:#e3f2fd,stroke:#1976d2,stroke-width:3px style AUE fill:#e8f5e9,stroke:#388e3c,stroke-width:3px style internet fill:#ffebee,stroke:#c62828,stroke-width:2px style vnet fill:#e1f5fe,stroke:#0288d1,stroke-width: 2px style dns fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style pe fill:#fff3e0,stroke:#ef6c00,stroke-width:3px style data fill:#e8f5e9,stroke:#388e3c,stroke-width:2px Understanding Microsoft FoundryMicrosoft Foundry is a unified platform-as-a-service for enterprise AI that brings together agents, models (including Azure OpenAI, Speech, Vision), and AI development tools under one managed resource. Think of it as a comprehensive AI studio environment where developers can build, evaluate, and deploy generative AI applications without the complexity of stitching together multiple Azure services manually. Why Foundry Matters for Enterprise AIFoundry provides several enterprise-grade capabilities out of the box: Unified AI Platform: Instead of managing separate Azure OpenAI, Cognitive Services, and ML resources, Foundry consolidates these into a single managed environment with consistent APIs and management experiences. Enterprise Security & Compliance: Built-in support for managed identities, RBAC, audit logging, and network isolation ensures your AI applications meet corporate security requirements. Observability & Monitoring: Integrated tracing, logging, and monitoring capabilities help you understand how your AI applications are performing and troubleshoot issues quickly. Development Productivity: A unified development experience through Azure AI Studio portal and SDKs accelerates AI application development by reducing integration overhead. Because Foundry encapsulates many Azure AI services and provides such comprehensive capabilities, it has specific networking requirements when you want to integrate it into a secure, enterprise cloud environment. This is where Private Endpoints become critical. Azure Private Endpoints: The Foundation of Secure ConnectivityAn Azure Private Endpoint is essentially a network interface (NIC) with a private IP address from your Azure Virtual Network that connects privately to a supported Azure service. When you create a private endpoint for a service like Foundry, you’re bringing that service into your VNet’s address space, allowing VNet resources to reach the service via an internal IP address instead of traversing the public internet. How Private Link Works Under the HoodAzure Private Link is the technology that makes Private Endpoints possible. Here’s what happens when you enable private connectivity: Private IP Assignment: A network interface is created in your VNet subnet with a private IP (e.g., 10.1.2.4) DNS Resolution: The service’s FQDN (e.g., myFoundry.cognitiveservices.azure.com) is configured to resolve to the private IP instead of the public endpoint Backbone Network Transit: Traffic between your VNet and the service travels entirely on Microsoft’s backbone network, never touching public internet routes Regional Flexibility: The private endpoint can be in a different region than the target service, enabling cross-region private connectivity Key Benefits of Private EndpointsEnhanced Security: Traffic never leaves Azure’s internal network, eliminating internet-based attack vectors and data exfiltration risks. Supported Across Azure PaaS: Many Azure services support private endpoints including Storage accounts, SQL databases, Cosmos DB, Cognitive Services (which Foundry uses), Key Vault, and more. Cross-Region Capability: Critically, Azure allows the private endpoint’s NIC to reside in a different region than the service’s region. The private endpoint must be in the same region as the VNet you place it in, but the target resource can be in any Azure region. This is the key feature that enables our solution. DNS Integration: Azure Private DNS Zones provide seamless name resolution, making private endpoint connectivity transparent to applications. DNS Configuration RequirementsUsing a private endpoint requires configuring DNS so that the service’s FQDN resolves to the private IP instead of the public IP. For Azure Cognitive Services (which Foundry is built on), this typically involves: Creating a Private DNS Zone: privatelink.cognitiveservices.azure.com Linking the zone to your VNets Adding an A record mapping the Foundry hostname to the private endpoint IP Without proper DNS configuration, clients will continue resolving the public IP address and bypass the private link entirely, defeating the purpose of the security setup. The Cross-Region Challenge: When Foundry Isn’t Available LocallyUnderstanding the Regional Availability ProblemMicrosoft Foundry is not yet available in every Azure region. At the time of writing, Foundry has limited regional availability as Microsoft continues rolling out the service globally. This creates a common scenario for many organizations: The Scenario: Let’s imagine an organization with Azure infrastructure primarily deployed in New Zealand North (NZN) – perhaps due to data residency requirements, latency optimization for local users, or established governance policies. However, Microsoft Foundry is only available in Australia East (AUE), with New Zealand North not yet on the supported regions list. The Challenge: The organization needs to leverage Foundry’s powerful AI capabilities while maintaining private network connectivity and keeping all traffic within the Azure environment. Simply deploying everything in Australia East isn’t feasible due to data residency requirements etc. So all data at rest needs to be in New Zealand North region. Microsoft Foundry will be used for AI inferencing only and all data at rest must remain in New Zealand North. Why Standard Approaches Don’t WorkLet’s consider the typical alternatives and why they fall short: Deploy Everything in AUE: This requires relocating or duplicating existing infrastructure, increasing costs and complexity. Data residency requirements may prohibit this approach entirely. Use Public Endpoints: Exposing Foundry via public endpoints contradicts enterprise zero-trust security policies and increases attack surface unnecessarily. Accept Regional Limitations: Waiting for Foundry to become available in your region delays AI initiatives and competitive advantages. The Solution: Cross-Region Private EndpointsThe solution leverages Azure’s support for cross-region Private Endpoints. By deploying a Private Endpoint in your New Zealand North VNet that connects to the Foundry service in Australia East, you achieve several critical outcomes: Private Connectivity: All traffic between NZN and AUE travels on Azure’s backbone network Regional Compliance: Resources remain in their designated regions with private interconnection Transparent Access: Applications in NZN access Foundry as if it’s a local service via private IP Security Posture: Foundry’s public endpoints can be disabled, enforcing private-only access Architecture Deep Dive: Cross-Region Foundry DeploymentHigh-Level Architecture OverviewThe architecture deploys Microsoft Foundry in Australia East while enabling secure access from resources in New Zealand North. Here’s how the components fit together: Foundry Service (Australia East): The actual Microsoft Foundry resource deployed in AUE, configured as a Cognitive Services account with AI capabilities enabled. Private Endpoint (New Zealand North): A network interface in your NZN VNet with a private IP address (e.g., 10.1.2.4) that acts as the gateway to the AUE Foundry service. Private DNS Zone: A DNS zone (privatelink.cognitiveservices.azure.com) linked to your VNets that resolves the Foundry FQDN to the private endpoint IP. Client Applications (New Zealand North): VMs, App Services, containers, or other resources in NZN that consume the Foundry APIs. sequenceDiagram participant C as 👤 Client10.1.1.10 participant D as 🔐 Private DNS participant P as 🔒 Private EP10.1.2.4 participant B as 🌐 Public Internet participant F as 🤖 Foundry(AUE) rect rgb(227, 242, 253) Note over C,D: New Zealand North Region end rect rgb(232, 245, 232) Note over F: Australia East Region end C->>D: 1. DNS Query: myFoundry.cognitiveservices. azure.com D->>C: 2. Returns Private IP: 10.1.2.4 Note over C,P: Traffic stays on Azure backbone C->>P: 3. HTTPS Request to 10.1.2.4:443 P->>F: 4. Azure Private Link (backbone) F->>P: 5. AI Response P->>C: 6. Response delivered rect rgb(255, 235, 238) Note over B: Public path blocked C--xB: ❌ No public internet route B--xF: ❌ Public access disabled end Traffic Flow ExplanationLet’s trace what happens when a client application in NZN makes a request to Foundry: Application Request: The application initiates a connection to myFoundry.cognitiveservices.azure.com DNS Resolution: The VNet’s DNS configuration queries the Private DNS Zone Private IP Return: DNS returns 10.1.2.4 (the private endpoint IP) instead of the public IP Private Endpoint Connection: The application connects to the private endpoint in NZN Cross-Region Transit: Azure Private Link routes the traffic across the backbone network to AUE Foundry Processing: The Foundry service in AUE receives and processes the request Response Path: The response follows the same path back through the private endpoint From the application’s perspective, it’s communicating with a local service in NZN. All the cross-region complexity is abstracted by Azure’s networking layer. Critical Architectural ConsiderationsAzure Backbone Network TransitThe connection between NZN and AUE is entirely internal to Azure. No traffic exits to the public internet, even though the regions are geographically separated across the Tasman Sea. Azure’s global backbone network handles the routing, ensuring: Security: Traffic never traverses public networks Reliability: Azure’s backbone offers higher SLAs than internet routes Performance: Optimized routing between Azure datacenters Private DNS Resolution RequirementsDNS configuration is the linchpin that makes private endpoints work correctly. Microsoft Foundry (AIServices kind) requires multiple Private DNS zones for full functionality: Required Private DNS Zones: privatelink.cognitiveservices.azure.com - Primary Cognitive Services endpoint privatelink.openai.azure.com - Azure OpenAI service endpoints privatelink.services.ai.azure.com - AI Foundry management endpoints Configuration Steps: DNS Zone Creation: Create all required Private DNS zones in your subscription VNet Linking: Link the DNS zones to your NZN VNet (and any other VNets that need access) A Record Mapping: Azure automatically creates the necessary A records when using DNS zone groups with private endpoints, or you can manually add A records for your Foundry resource pointing to the private endpoint IP Multi-VNet Scenarios: If you have separate VNets (e.g., hub-and-spoke topology), ensure all VNets are linked to the DNS zones or use DNS forwarding Without proper DNS configuration across all zones, clients will resolve public IPs and bypass your private endpoint entirely, rendering the security setup ineffective. Performance and Latency ImplicationsWhile traffic stays private, the cross-region architecture introduces additional latency: Typical Latency: NZN to AUE is a trans-Tasman hop, usually adding 30-60ms compared to in-region calls Acceptable Use Cases: Most AI API calls (text generation, embeddings, etc.) can tolerate this latency Latency-Sensitive Workloads: Real-time voice applications or ultra-low-latency requirements may need careful evaluation Bandwidth Charges: Azure charges for cross-region data transfer. Egress from AUE to NZN incurs bandwidth costs. Budget accordingly for high-volume scenarios. Security Posture EnhancementPrivate Endpoints enable several security improvements: Disable Public Access: Configure Foundry to reject all public network connections, forcing traffic through approved private endpoints Network Security Groups (NSGs): Apply NSGs to the private endpoint subnet to control which source IPs can reach the Foundry service Azure Firewall Integration: Route private endpoint traffic through Azure Firewall for additional inspection and logging On-Premises Connectivity: If you have ExpressRoute or VPN connecting on-premises to Azure, those networks can also access Foundry privately through the NZN VNet Step-by-Step Implementation Overview ⚠️ Important: Choose Your Own Unique Name The name myFoundryDemo used throughout these examples is already taken in Azure’s global namespace. Cognitive Services accounts require globally unique names across all Azure subscriptions worldwide. Before running any commands below: Replace myFoundryDemo with your own unique name (e.g., myFoundryProd2025, contoso-ai-foundry, etc.) in all the Azure CLI commands. This name will become part of your endpoint URL: <your-unique-name>.cognitiveservices.azure.com The following sections walk through the high-level steps to implement this architecture. Part 2 will provide detailed scripts and Infrastructure-as-Code templates. Step 1: Deploy Foundry in Australia EastStart by creating your Microsoft Foundry resource in the Australia East region: 1234567891011# Create resource group in Australia Eastaz group create --name rg-foundry-aue --location australiaeast# Create Foundry resource (Cognitive Services account)az cognitiveservices account create \\ --name myFoundryDemo \\ --resource-group rg-foundry-aue \\ --kind AIServices \\ --sku S0 \\ --location australiaeast \\ --custom-domain myFoundryDemo Important Notes: Initially allow public network access during setup Note the resource name and ID for the next steps Foundry appears as a Cognitive Services account of kind AIServices Step 2: Create Virtual Network in New Zealand NorthSet up a VNet in NZN where the private endpoint will reside: 12345678910111213141516171819# Create resource group in New Zealand Northaz group create --name rg-networking-nzn --location newzealandnorth# Create virtual networkaz network vnet create \\ --name vnet-nzn-main \\ --resource-group rg-networking-nzn \\ --location newzealandnorth \\ --address-prefix 10.1.0.0/16 \\ --subnet-name snet-apps \\ --subnet-prefix 10.1.1.0/24# Create subnet for private endpointsaz network vnet subnet create \\ --name snet-private-endpoints \\ --resource-group rg-networking-nzn \\ --vnet-name vnet-nzn-main \\ --address-prefix 10.1.2.0/24 \\ --disable-private-endpoint-network-policies true Network Planning Considerations: Ensure address space doesn’t conflict with other VNets you’ll peer Create a dedicated subnet for private endpoints Disable network policies on the private endpoint subnet Step 3: Create the Cross-Region Private EndpointNow create the private endpoint in NZN that connects to the AUE Foundry resource: 12345678910111213141516# Get Foundry resource IDFOUNDRY_ID=$(az cognitiveservices account show \\ --name myFoundryDemo \\ --resource-group rg-foundry-aue \\ --query id -o tsv)# Create private endpointaz network private-endpoint create \\ --name pe-foundry-nzn \\ --resource-group rg-networking-nzn \\ --location newzealandnorth \\ --vnet-name vnet-nzn-main \\ --subnet snet-private-endpoints \\ --private-connection-resource-id $FOUNDRY_ID \\ --group-id account \\ --connection-name foundry-connection Key Parameters: --location must match the VNet’s region (newzealandnorth) --private-connection-resource-id points to the AUE Foundry resource --group-id account specifies the Cognitive Services sub-resource type Connection auto-approves since you own both resources Step 4: Configure Private DNS IntegrationSet up DNS to resolve the Foundry FQDN to the private endpoint IP. Microsoft Foundry requires multiple Private DNS zones: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546# Create all required Private DNS Zones for Microsoft Foundryaz network private-dns zone create \\ --resource-group rg-networking-nzn \\ --name privatelink.cognitiveservices.azure.comaz network private-dns zone create \\ --resource-group rg-networking-nzn \\ --name privatelink.openai.azure.comaz network private-dns zone create \\ --resource-group rg-networking-nzn \\ --name privatelink.services.ai.azure.com# Link DNS zones to VNetfor zone in "privatelink.cognitiveservices.azure.com" "privatelink.openai.azure.com" "privatelink.services.ai.azure.com"do az network private-dns link vnet create \\ --resource-group rg-networking-nzn \\ --zone-name $zone \\ --name "link-vnet-nzn-main-${zone%%.azure.com}" \\ --virtual-network vnet-nzn-main \\ --registration-enabled falsedone# Alternative: Use DNS Zone Group for automatic configuration# This automatically creates A records in all required zonesaz network private-endpoint dns-zone-group create \\ --resource-group rg-networking-nzn \\ --endpoint-name pe-foundry-nzn \\ --name default \\ --private-dns-zone privatelink.cognitiveservices.azure.com \\ --zone-name cognitiveservicesaz network private-endpoint dns-zone-group add \\ --resource-group rg-networking-nzn \\ --endpoint-name pe-foundry-nzn \\ --name default \\ --private-dns-zone privatelink.openai.azure.com \\ --zone-name openaiaz network private-endpoint dns-zone-group add \\ --resource-group rg-networking-nzn \\ --endpoint-name pe-foundry-nzn \\ --name default \\ --private-dns-zone privatelink.services.ai.azure.com \\ --zone-name aiservices DNS Configuration Options: Option 1 - DNS Zone Groups (Recommended): Using DNS zone groups (shown above) automatically creates and maintains A records in all zones. This is the preferred approach as Azure manages the DNS records lifecycle. Option 2 - Manual A Records: If you need manual control, get the private endpoint IP and create A records manually: 123456789101112# Get private endpoint IPPE_IP=$(az network private-endpoint show \\ --name pe-foundry-nzn \\ --resource-group rg-networking-nzn \\ --query 'customDnsConfigs[0].ipAddresses[0]' -o tsv)# Create DNS A record in the primary zoneaz network private-dns record-set a add-record \\ --resource-group rg-networking-nzn \\ --zone-name privatelink.cognitiveservices.azure.com \\ --record-set-name myFoundryDemo \\ --ipv4-address $PE_IP DNS Verification: All DNS zones must be created and linked to VNets needing access DNS zone groups automatically handle A record creation and updates For manual records, ensure the record name matches your Foundry resource name Multi-VNet deployments require linking all zones to each VNet Step 5: Test Private ConnectivityDeploy a test VM in the NZN VNet and verify connectivity: 12345678910111213141516# Create test VMaz vm create \\ --resource-group rg-networking-nzn \\ --name vm-test-nzn \\ --location newzealandnorth \\ --vnet-name vnet-nzn-main \\ --subnet snet-apps \\ --image Ubuntu2204 \\ --admin-username azureuser \\ --generate-ssh-keys# SSH to VM and test DNS resolutionssh azureuser@<vm-public-ip># Verify DNS resolves to private IPnslookup myFoundryDemo.cognitiveservices.azure.com Expected DNS Output: 123456Server: 10.1.0.4Address: 10.1.0.4#53Name: myFoundryDemo.privatelink.cognitiveservices.azure.comAddress: 10.1.2.4Aliases: myFoundryDemo.cognitiveservices.azure.com The resolution to 10.1.2.4 (private IP) confirms DNS is working correctly. Test API Connectivity: 123456# Get Foundry API key (from Azure portal or CLI)API_KEY="your-foundry-api-key"# Test API callcurl -X GET "https://myFoundryDemo.cognitiveservices.azure.com/openai/deployments?api-version=2024-02-01" \\ -H "api-key: $API_KEY" If you receive a valid response (list of deployments or empty array), private connectivity is working. If you disabled public access on Foundry, this same curl command from your local machine should fail with a network error. Step 6: Lock Down Public AccessWith private connectivity confirmed, enhance security by disabling public access: 12345# Disable public network accessaz cognitiveservices account update \\ --name myFoundryDemo \\ --resource-group rg-foundry-aue \\ --public-network-access Disabled Additional Security Hardening: Apply NSGs to the private endpoint subnet restricting source IPs Enable Azure Firewall for additional traffic inspection Configure diagnostic logs to monitor access patterns Use managed identities instead of API keys for authentication Real-World Considerations and Trade-OffsCost ImplicationsCross-Region Data Transfer: Azure charges for data egress between regions. For NZN ↔ AUE, expect approximately $0.02-0.05 per GB transferred (approx pricing at the time of this writing). Monitor your Foundry usage patterns and budget accordingly. Private Endpoint Costs: Private endpoints incur a small hourly charge plus data processing charges per GB. Foundry Service Costs: The Foundry service itself has per-transaction or per-token costs depending on the AI services used (OpenAI, Speech, etc.). Latency ConsiderationsBaseline Latency: Expect 30-60ms additional latency for cross-region calls compared to in-region Impact Assessment: For most AI workloads (text generation, embeddings, document analysis), this latency is acceptable Optimization Strategies: Use async/await patterns to parallelize independent API calls Implement request batching where applicable Cache frequently-requested results Consider regional caching layers for static content On-Premises IntegrationIf your organization has on-premises datacenters connected to Azure via ExpressRoute or Site-to-Site VPN, you can extend private connectivity to those environments: DNS ForwardingConfigure on-premises DNS servers to forward queries for *.cognitiveservices.azure.com to Azure’s DNS resolvers: 12345# Example BIND configurationzone "cognitiveservices.azure.com" { type forward; forwarders { 10.1.0.4; }; # Azure VNet DNS server}; Network ConnectivityEnsure your on-premises network has routes to the NZN VNet where the private endpoint resides: ExpressRoute: Private peering enables private IP connectivity Site-to-Site VPN: VPN gateway provides encrypted connectivity Route Tables: Verify routes exist for the private endpoint subnet (10.1.2.0/24) With proper DNS and routing configuration, on-premises applications can access Foundry privately through the Azure backbone network. Cleanup ResourcesIf you’re finished testing and want to remove all resources created in this guide, you can delete the resource groups: 12345678910111213# Delete the networking resource group in New Zealand North# This removes the VNet, subnets, private endpoint, DNS zones, and test VMaz group delete \\ --name rg-networking-nzn \\ --yes \\ --no-wait# Delete the Foundry resource group in Australia East# This removes the Microsoft Foundry (Cognitive Services) accountaz group delete \\ --name rg-foundry-aue \\ --yes \\ --no-wait Important Notes: The --yes flag skips confirmation prompts The --no-wait flag allows the command to return immediately without waiting for deletion to complete Resource group deletion is permanent and cannot be undone Deletion can take several minutes; you can check status in the Azure portal or with az group show Wrapping Up Part 1This architecture solves a critical challenge: Microsoft Foundry isn’t available in all Azure regions, but data residency requirements often mandate that data at rest remains within specific regions. The solution demonstrated here keeps all data at rest in New Zealand North (your region of choice), while leveraging Microsoft Foundry in Australia East purely for AI inferencing capabilities. By deploying Private Endpoints in the NZN region, applications access Foundry’s AI services securely over Azure’s backbone network without any data ever traversing the public internet. Key Takeaway: Data stays in your compliant region (NZN), AI inferencing happens in the Foundry region (AUE), and all communication flows privately through Azure’s backbone. This pattern works for any region pair where Foundry availability doesn’t align with your data residency requirements. In Part 2, we’ll look at Infrastructure-as-Code templates using Bicep and Terraform to automate this deployment. References Microsoft Docs – What is Azure AI Foundry? Microsoft Docs – What is a private endpoint? Azure Architecture Center – Baseline Azure AI Foundry Architecture Microsoft Q&A – VNet-Integrated AI Deployment Main image generated by GPT-Image-1.5","link":"/Azure/AI/Networking/microsoft-foundry-cross-region-with-private-endpoints-part-1/"},{"title":"Pimp My Terminal - Terminal Customization with Oh My Posh - A Cloud Native Terminal Setup","text":"🎯 TL;DR: Automated Oh My Posh Terminal Setup for Cloud Native Development Every new machine or fresh Windows install means reconfiguring your terminal environment from scratch. Problem: Manually setting up Oh My Posh, installing Nerd Fonts, and configuring custom themes is tedious and error-prone across multiple machines. Solution: (A single PowerShell script available on GitHub https://github.com/Ricky-G/script-library/blob/main/pimp-my-terminal.ps1) that automates the entire process - installing Oh My Posh via winget, deploying a Nerd Font, Terminal-Icons module, creating a custom “Cloud Native Azure” theme optimized for Kubernetes and Azure workflows, and configuring your PowerShell profile with PSReadLine enhancements. Prerequisites: Enable script execution with Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser before running. This approach transforms the multi-hour setup process into a one-command operation, providing immediate visual context for Git branches, Kubernetes clusters, Azure subscriptions, and command execution times - critical information for modern cloud native development. Recently, I found myself setting up yet another development machine, and as I stared at the blank PowerShell terminal, I realized I’d reached my limit with manual terminal configuration. Every new machine or clean install meant the same tedious process: download Oh My Posh, find a Nerd Font installer, copy configuration files, edit PowerShell profiles, and spend 30 minutes getting everything just right. The frustration wasn’t just about aesthetics - a properly configured terminal is a productivity multiplier. When you’re constantly switching between multiple Git repositories, Kubernetes clusters, and Azure subscriptions throughout the day, having that contextual information immediately visible saves countless keystrokes and eliminates mental overhead. This blog post shares my automated solution: a single PowerShell script that takes a bare Windows terminal and transforms it into a fully-configured, cloud native-ready development environment in under 5 minutes. Whether you’re setting up a new machine, rebuilding after a Windows update disaster, or just want to standardize terminal configuration across your team, this automation eliminates the manual work. Quick Start - Get Up and Running in 5 MinutesWant to skip the details and just get started? Here’s everything you need to run the automation script: Step 1: Enable Script ExecutionOpen PowerShell as Administrator and run: 1Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser When prompted, type Y and press Enter. Step 2: Download and Run the Script123# Download and run the automation scriptInvoke-WebRequest -Uri "https://raw.githubusercontent.com/Ricky-G/script-library/main/pimp-my-terminal.ps1" -OutFile "$env:TEMP\\pimp-my-terminal.ps1"& "$env:TEMP\\pimp-my-terminal.ps1" The script will automatically install: ✅ Oh My Posh via winget ✅ MesloLGM Nerd Font ✅ Terminal-Icons PowerShell module ✅ Cloud Native Azure theme ✅ PSReadLine enhancements ✅ Custom keyboard shortcuts Step 3: Configure Your Terminal FontAfter the script completes, configure your terminal font: Windows Terminal: Open Settings (Ctrl + ,) Go to Profiles → Defaults → Appearance Set Font face to: MesloLGM Nerd Font Save and restart terminal VS Code: Open Settings (Ctrl + ,) Search for “terminal font” Set Terminal › Integrated: Font Family to: MesloLGM Nerd Font Done! Open a new terminal and enjoy your beautiful, cloud native-ready prompt. Understanding Oh My Posh: The Modern Prompt EngineBefore diving into the automation, it’s worth understanding what Oh My Posh brings to the table and why it’s become the de facto standard for PowerShell prompt customization. What Is Oh My Posh?Oh My Posh is a custom prompt theme engine that works across multiple shells including PowerShell, Bash, Zsh, Fish, and more. Originally inspired by the popular Oh My Zsh project for Linux/macOS, Oh My Posh brings the same level of customization and visual polish to Windows terminals while maintaining cross-platform compatibility. The key differentiator of Oh My Posh is its segment-based architecture. Rather than being a monolithic prompt generator, it provides a framework for composing different “segments” of information into your prompt. Each segment represents a different piece of contextual information: Version Control Segments: Display Git branch names, ahead/behind status, working directory changes, stash counts, and more. Supports multiple version control systems. Cloud Context Segments: Show your active Kubernetes cluster and namespace, AWS profile, Azure subscription, or Google Cloud project. Essential for multi-cloud development. Development Environment Segments: Display programming language versions (Python, Node.js, Go, .NET), virtual environment status, package manager context, and more. System Information Segments: Show current directory, execution time of the last command, error status, battery level, and system load. The Nerd Fonts RequirementOne aspect of Oh My Posh that initially confuses newcomers is the requirement for Nerd Fonts. Standard fonts don’t include the special glyphs and icons that Oh My Posh uses to display information compactly and beautifully. Nerd Fonts are patches of popular programming fonts that add thousands of additional glyphs from various icon sets including: Font Awesome icons Devicons for programming languages Octicons from GitHub Material Design icons Weather icons Powerline extra symbols Without a Nerd Font installed, you’ll see blank squares or question marks instead of the beautiful icons that make Oh My Posh themes shine. This is why the installation script specifically handles font installation as a critical step. The Solution: Automated Setup ScriptThe core idea behind this automation is simple: replicate exactly what you’d do manually, but in a repeatable, version-controlled script that can be run on any Windows machine. The script handles five critical steps in sequence. What The Script Installs and ConfiguresThe PowerShell script automates the installation and configuration of several components: Step Component Purpose 1 Oh My Posh Prompt theme engine that makes your terminal beautiful and informative 2 Meslo Nerd Font Special font with icons and glyphs required for theme display 3 Terminal-Icons Module PowerShell module that shows file/folder icons when you run ls or Get-ChildItem 4 Custom Theme Cloud Native Azure theme optimized for Kubernetes and Azure workflows 5 PowerShell Profile Configures everything to load automatically on every terminal session PowerShell Profile EnhancementsThe script creates a comprehensive PowerShell profile ($PROFILE) that loads automatically every time you open a terminal: Feature Component Benefit Oh My Posh Theme Cloud Native Azure theme Beautiful prompt with contextual information at a glance Terminal-Icons File type icons Quickly identify file types: 📁 folders, 🐍 Python files, 📄 docs, etc. PSReadLine History ↑ / ↓ arrow keys Search command history based on what you’ve typed PSReadLine Predictions Auto-suggestions Shows grayed-out suggestions from history as you type F7 History Grid F7 key Opens searchable popup of entire command history Dotnet Shortcuts Ctrl+Shift+B / Ctrl+Shift+T Instantly run dotnet build and dotnet test Tab Completion Winget & Dotnet Intelligent tab completion for package managers PSReadLine: Enhanced Command-Line EditingPSReadLine is a PowerShell module that dramatically improves command-line editing. The script configures these productivity features: Feature Shortcut Description Example Use Case History Search ↑ / ↓ Searches history based on current input Type git then press ↑ to cycle through all previous git commands Inline Predictions (automatic) Shows grayed-out suggestions from history Faster command entry - just press → to accept History Grid F7 Opens searchable popup of command history Visual history browsing - select any command to insert Quick Build Ctrl+Shift+B Instantly runs dotnet build One keystroke to build your .NET project Quick Test Ctrl+Shift+T Instantly runs dotnet test One keystroke to run your test suite These features work together to minimize typing and maximize efficiency. For example, if you previously ran kubectl get pods -n production, you can start typing kub and press ↑ to find it immediately, or wait for the inline prediction to appear and press → to accept it. Exploring Oh My Posh ThemesBefore we dive into my custom theme, it’s worth exploring the extensive theme library that Oh My Posh provides out of the box. The project includes over 200 pre-built themes ranging from minimal single-line prompts to elaborate multi-line displays with extensive system information. Browsing the Theme GalleryOh My Posh provides an excellent visual gallery where you can see screenshots of all available themes: 👉 https://ohmyposh.dev/docs/themes The gallery is searchable and filterable, making it easy to find themes that match your preferences: Minimal Themes: Clean, single-line prompts with just essential information (agnoster, paradox, clean-detailed) Powerline Themes: Classic powerline-style prompts with angled segments and rich colors (powerlevel10k_rainbow, hotstick.minimal, jandedobbeleer) Cloud-Native Themes: Themes specifically designed for cloud development with Kubernetes and Azure context (cloud-native-azure, night-owl, atomic) Language-Specific Themes: Optimized for specific programming languages or frameworks (kushal, amro, lambda) Fun and Whimsical: Themes with unique character or personality (di4am0nd, emodipt-extend, iterm2) Testing Themes Before CommittingOh My Posh makes it easy to preview themes before making them permanent: 12345# Preview a specific theme temporarilyoh-my-posh init pwsh --config "$env:POSH_THEMES_PATH\\jandedobbeleer.omp.json" | Invoke-Expression# View all available themes with Get-PoshThemesGet-PoshThemes This is particularly useful when setting up a new machine - you can quickly cycle through themes to find one that resonates with your workflow before committing to a permanent configuration. The Cloud Native Azure Theme: Design PhilosophyThe script uses the official Cloud Native Azure theme from Oh My Posh. It’s specifically designed for developers working in the Azure and Kubernetes ecosystem. What The Theme DisplaysAt a glance, your prompt shows comprehensive contextual information: Segment Icon Information Displayed Why It’s Useful Session 👤 Username & hostname Know which machine/user you’re logged in as Path 📁 Current directory Always know where you are in the filesystem Git 🔀 Branch, status, ahead/behind See uncommitted changes and unpushed commits at a glance Status ✅/❌ Last command success/failure Immediately know if your last command worked Kubernetes ☸️ Cluster name & namespace Prevent running commands against the wrong cluster Azure ☁️ Active subscription name Know which Azure subscription is active Battery 🔋 Charge level Keep an eye on laptop battery during long sessions Time 🕐 Current time Timestamp your terminal sessions Theme Color SchemeThe theme uses carefully chosen colors for instant visual recognition: Segment Color Hex Code Purpose Session Purple #c386f1 User context Path Pink #ff479c Navigation Git (clean) Yellow #fffb38 Version control (clean state) Git (dirty) Orange #FF9248 Version control (uncommitted changes) Kubernetes Yellow #ebcc34 Cloud infrastructure Azure Light Blue #9ec3f0 Cloud subscription Status (success) Teal #2e9599 Success indicator Status (error) Red #f1184c Error indicator While Oh My Posh ships with many excellent themes, this one is specifically optimized for cloud native development workflows. The design philosophy centers on three core principles: Priority Information FirstThe most critical contextual information - path, Git status, Kubernetes context, and Azure subscription - is always visible without requiring any additional commands. This eliminates the “where am I?” moment that costs seconds of mental processing dozens of times per day. Visual Hierarchy Through ColorThe theme uses the official Azure brand colors strategically: Azure Blue (#0078D4) for operating system and Azure context Cyan (#00A4EF) for file path navigation Green (#7FBA00) for Git status with dynamic background colors indicating repository state Kubernetes Blue (#326CE5) for cluster context Gray (#505050) for secondary information like execution time This color-coding creates instant visual recognition - your eyes learn to find specific information based on color alone. Cloud Native Workflow OptimizationThe theme specifically addresses common scenarios in cloud native development: Multi-Cluster Scenarios: When working with development, staging, and production Kubernetes clusters, the prominent cluster name prevents accidentally running destructive commands in production. Multi-Subscription Development: Azure developers often switch between client subscriptions, personal subscriptions, or different environments. The Azure segment shows which subscription is active to prevent resource creation in the wrong tenant. Git-Heavy Workflows: With branch name, ahead/behind indicators, working directory changes, staging status, and stash count all visible, you have complete Git situational awareness without running git status. Theme Anatomy: Understanding the ConfigurationThe Cloud Native Azure theme is defined as a JSON configuration file that Oh My Posh parses to render your prompt. Understanding this structure allows you to customize the theme to your specific needs. Block StructureThe theme uses Oh My Posh’s block system to organize segments into logical groupings: Block 1 (Left-Aligned): Contains the primary context segments that appear on the main prompt line: Operating System indicator Current path with folder-style display Git repository information with status Block 2 (Right-Aligned): Displays cloud and infrastructure context: Kubernetes cluster and namespace Azure subscription name Command execution time Block 3 (Left-Aligned, New Line): Simple prompt character for command entry This three-block structure creates a balanced layout where essential information doesn’t crowd the area where you’re typing commands, while cloud context is visible but not intrusive. Segment Configuration DetailsEach segment in the theme has specific configuration properties that control its behavior and appearance: 123456789101112{ "type": "kubectl", "style": "powerline", "powerline_symbol": "\\ue0b2", "invert_powerline": true, "foreground": "#ffffff", "background": "#326CE5", "template": " \\ufd31 {{ .Context }}{{ if .Namespace }} :: {{ .Namespace }}{{ end }} ", "properties": { "parse_kubeconfig": true }} Breaking down this Kubernetes segment: Type Property: Specifies which Oh My Posh segment provider to use (kubectl for Kubernetes context) Style and Powerline Symbol: Creates the angled transitions between segments that give the prompt its distinctive look Color Configuration: Foreground and background colors using hex codes for precise brand matching Template String: Defines what information to display using Go template syntax with conditional logic Properties: Segment-specific settings like parse_kubeconfig which tells the segment to read from your kubectl config file Dynamic Git Status IndicatorsThe Git segment includes sophisticated logic for changing appearance based on repository state: 123456"background_templates": [ "{{ if or (.Working.Changed) (.Staging.Changed) }}#FFB900{{ end }}", "{{ if and (gt .Ahead 0) (gt .Behind 0) }}#F25022{{ end }}", "{{ if gt .Ahead 0 }}#B4009E{{ end }}", "{{ if gt .Behind 0 }}#F25022{{ end }}"] These background templates create visual warnings: Yellow (#FFB900): Uncommitted changes in working directory or staging area Orange/Red (#F25022): Branch is behind the remote (need to pull) Purple (#B4009E): Branch is ahead of remote (need to push) This color-coding provides instant feedback about repository state without reading the status text. The Complete Automation ScriptThe full automation script is maintained in a GitHub repository for easy access and version control. You can find the complete, up-to-date script here: 📜 Script Location: https://github.com/Ricky-G/script-library/blob/main/pimp-my-terminal.ps1 The script handles the entire setup process including: Oh My Posh installation via winget Nerd Font (MesloLGM) installation Terminal-Icons PowerShell module installation Cloud Native Azure theme configuration PowerShell profile setup with PSReadLine enhancements Custom keyboard shortcuts for dotnet commands Intelligent tab completion for winget and dotnet CLI Quick StartTo run the script, open PowerShell as Administrator and execute: 123# Download and run the script directlyInvoke-WebRequest -Uri "https://raw.githubusercontent.com/Ricky-G/script-library/main/pimp-my-terminal.ps1" -OutFile "$env:TEMP\\pimp-my-terminal.ps1"& "$env:TEMP\\pimp-my-terminal.ps1" Or if you’ve cloned the repository locally: 12# Run from local clone.\\pimp-my-terminal.ps1 Script Breakdown: Key ComponentsThe automation script includes several critical sections that ensure a smooth, repeatable setup process. Let’s examine the key components: Administrative Privileges Requirement1#Requires -RunAsAdministrator The script requires administrative privileges for two reasons: Font Installation: Installing system-wide fonts requires elevated permissions to write to C:\\Windows\\Fonts Winget Operations: While winget can run without admin rights, some installations require elevation for proper PATH configuration If you run the script without elevation, PowerShell will automatically prompt for admin credentials before execution. Winget Installation with Accept Flags1winget install JanDeDobbeleer.OhMyPosh -s winget --accept-package-agreements --accept-source-agreements The --accept-package-agreements and --accept-source-agreements flags automate the acceptance of license terms, enabling the script to run non-interactively. This is crucial for CI/CD scenarios or remote machine setup where manual interaction isn’t possible. The -s winget parameter explicitly specifies the winget repository, preventing potential conflicts if you have multiple package sources configured. Environment Path Refresh1$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") This is a critical step that’s often overlooked in automation scripts. When winget installs Oh My Posh, it modifies the system PATH variable, but PowerShell sessions don’t automatically refresh their environment. This line explicitly reloads the PATH, making the oh-my-posh command immediately available without requiring a terminal restart. Without this refresh, the subsequent oh-my-posh font install command would fail with a “command not found” error. Terminal-Icons Module Installation1Install-Module -Name Terminal-Icons -Repository PSGallery -Force The script also installs the Terminal-Icons module, which adds beautiful file type icons to your directory listings. When you run ls or Get-ChildItem, you’ll see: 📁 Folder icons for directories 🐍 Python icon for .py files 📄 Document icons for text files ⚙️ Config icons for .json, .xml, .yaml files And many more language-specific icons Nerd Font Installation1oh-my-posh font install Meslo Oh My Posh includes a built-in font installer that downloads and installs Nerd Fonts from their GitHub releases. The Meslo font (MesloLGM Nerd Font) is recommended because: Excellent readability at small and large sizes Wide character coverage including all necessary glyphs Good distinction between similar characters (1, l, I, 0, O) Proper line height and spacing for terminal use Alternative Nerd Fonts you might consider: CascadiaCode - Microsoft’s open-source programming font FiraCode - Popular font with extensive ligature support JetBrainsMono - Optimized for long coding sessions Hack - Minimal, clean appearance Profile Configuration with IdempotencyThe script checks if Oh My Posh is already configured before making changes, allowing safe repeated execution: 123456if (Test-Path $PROFILE) { $existingProfile = Get-Content $PROFILE -Raw if ($existingProfile -notmatch "oh-my-posh") { Add-Content -Path $PROFILE -Value $profileContent }} This idempotency is essential for: Updating the theme configuration without duplicating profile entries Re-running the script after failed installations Using the script in configuration management systems Step-by-Step Implementation GuidePrerequisites and PreparationBefore running the script, ensure you have:5. Click Appearance in the sub-menu6. Under Font face, select MesloLGM Nerd Font7. Optionally adjust Font size (I recommend 10-11pt for readability)8. Click Save Pro Tip: You can also configure this per-profile (PowerShell, Command Prompt, Ubuntu, etc.) if you want different fonts for different shells. However, setting it in Defaults applies to all profiles consistently. Visual Studio Code Terminal ConfigurationIf you spend significant time in VS Code’s integrated terminal, you’ll want the same beautiful prompt there: Open VS Code Press Ctrl + , to open Settings Search for terminal font Find Terminal › Integrated: Font Family Enter: MesloLGM Nerd Font Restart any open terminal instances Alternatively, edit your settings.json directly: 1234{ "terminal.integrated.fontFamily": "MesloLGM Nerd Font", "terminal.integrated.fontSize": 11} Verifying Font InstallationAfter configuring your terminal, restart it and open a new PowerShell session. You should see the themed prompt with proper icons. If you see squares, question marks, or missing characters, the font isn’t properly applied. Common fixes: Ensure you spelled the font name exactly: MesloLGM Nerd Font (not MesloLGM NF) Try restarting Windows Terminal completely (close all windows) Verify the font appears in Windows Settings → Personalization → Fonts Advanced Customization OptionsModifying the ThemeThe beauty of this automated setup is that your theme is now stored as a JSON file at $HOME\\.config\\ohmyposh\\cloud-native-azure.omp.json. You can modify this file to customize the prompt to your preferences. Adding Additional SegmentsWant to display your Python virtual environment or Node.js version? Add segments to the appropriate block: 12345678{ "type": "python", "style": "powerline", "powerline_symbol": "\\ue0b0", "foreground": "#ffffff", "background": "#306998", "template": " \\ue235 {{ if .Venv }}{{ .Venv }} {{ end }}{{ .Full }} "} Available segment types include: aws, battery, cmake, dart, docker, dotnet, elixir, flutter, go, java, julia, kotlin, lua, node, perl, php, python, ruby, rust, scala, swift, terraform, and many more. Changing ColorsModify the foreground and background properties to use your preferred color scheme: 12"foreground": "#ffffff","background": "#your-hex-color" You can also use color definitions for dark/light terminal themes: 12"foreground": "p:white","background": "p:blue" Adjusting Git Status IndicatorsCustomize which Git information displays by modifying the template: 1"template": " {{ .HEAD }}{{ if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \\uf044 {{ .Working.String }}{{ end }} " Remove sections you don’t need or add additional information like: {{ .UpstreamIcon }} - Shows if branch has an upstream {{ .StashCount }} - Number of stashed changes {{ .WorktreeCount }} - Number of worktrees Creating Multiple Theme ConfigurationsYou might want different themes for different scenarios - perhaps a minimal theme for screen recordings or presentations, and a detailed theme for daily work. Create multiple theme files: 123456789101112131415161718192021222324252627282930313233343536# Create a minimal theme$minimalTheme = @'{ "$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json", "version": 2, "final_space": true, "blocks": [ { "type": "prompt", "alignment": "left", "segments": [ { "type": "path", "style": "plain", "foreground": "#00A4EF", "template": "{{ .Path }} " }, { "type": "git", "style": "plain", "foreground": "#7FBA00", "template": "{{ .HEAD }} " }, { "type": "text", "style": "plain", "foreground": "#0078D4", "template": "\\u276f " } ] } ]}'@$minimalTheme | Out-File -FilePath "$HOME\\.config\\ohmyposh\\minimal.omp.json" -Encoding utf8 Switch themes by modifying your profile or creating PowerShell functions: 12345678# Add to your PowerShell profilefunction Set-CloudNativeTheme { oh-my-posh init pwsh --config "$HOME\\.config\\ohmyposh\\cloud-native-azure.omp.json" | Invoke-Expression}function Set-MinimalTheme { oh-my-posh init pwsh --config "$HOME\\.config\\ohmyposh\\minimal.omp.json" | Invoke-Expression} Conditional Theme LoadingLoad different themes based on context - for example, use a detailed theme on your workstation but a minimal theme when SSH’d into servers: 123456# In your PowerShell profileif ($env:SSH_CONNECTION) { oh-my-posh init pwsh --config "$HOME\\.config\\ohmyposh\\minimal.omp.json" | Invoke-Expression} else { oh-my-posh init pwsh --config "$HOME\\.config\\ohmyposh\\cloud-native-azure.omp.json" | Invoke-Expression} Keyboard Shortcuts ReferenceAfter setup, you’ll have enhanced keyboard shortcuts available in PowerShell thanks to PSReadLine configuration: Shortcut Action Description ↑ History Search Backward Searches command history based on what you’ve already typed ↓ History Search Forward Continues searching forward through matching history F7 History Grid View Opens a searchable popup grid of your entire command history Ctrl+Shift+B Dotnet Build Instantly runs dotnet build in the current directory Ctrl+Shift+T Dotnet Test Instantly runs dotnet test for your test suite Tab Smart Completion Context-aware tab completion for winget, dotnet, and more → Accept Prediction Accepts the grayed-out inline prediction from history Ctrl+RightArrow Accept Next Word Accepts only the next word from the prediction Using History Search EffectivelyThe history search feature is particularly powerful for repetitive commands: Example 1 - Git Commands: Type git and press ↑ Cycles through all previous git commands Much faster than retyping or searching entire history Example 2 - Kubectl Commands: Type kubectl get pods -n and press ↑ Finds all previous kubectl commands starting with that pattern Quickly switch between namespaces you’ve used before Example 3 - Complex Commands: Type the first few characters of a long command Press ↑ to find it immediately No need to remember the entire command syntax PSReadLine Prediction ModesThe script configures PSReadLine to show predictions as you type: 1234# Predictions appear grayed out based on your command historyPS> git checkout ma|ain # Gray text shows prediction ↑ Press → to accept This feature learns from your command patterns and becomes more useful over time as you build up command history. Customization and Theme SwitchingSwitching to a Different ThemeTo use a different Oh My Posh theme after installation, edit your PowerShell profile: 1notepad $PROFILE Replace the theme URL with any theme from the themes gallery: 12# Example: Switch to 'agnoster' themeoh-my-posh init pwsh --config 'https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/agnoster.omp.json' | Invoke-Expression Adding Custom Keyboard ShortcutsYou can add more PSReadLine shortcuts to your profile: 12345678910111213# Example: Ctrl+Shift+R for dotnet runSet-PSReadLineKeyHandler -Key Ctrl+Shift+r -ScriptBlock { [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine() [Microsoft.PowerShell.PSConsoleReadLine]::Insert("dotnet run") [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()}# Example: Ctrl+Shift+G for git statusSet-PSReadLineKeyHandler -Key Ctrl+Shift+g -ScriptBlock { [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine() [Microsoft.PowerShell.PSConsoleReadLine]::Insert("git status") [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()} Troubleshooting Common IssuesOh My Posh Command Not FoundSymptom: After running the script, oh-my-posh command isn’t recognized. Causes and Fixes: PATH not refreshed: Close and reopen your terminal, or run: 1$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") Winget installation failed: Check if Oh My Posh was actually installed: 1winget list | Select-String "OhMyPosh" If not present, manually install: 1winget install JanDeDobbeleer.OhMyPosh Installation location issues: Verify the executable location: 1Get-Command oh-my-posh Script Execution Is DisabledSymptom: Error message “running scripts is disabled on this system” Fix: 1Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser When prompted, type Y and press Enter. This policy allows locally-created scripts to run while maintaining security for downloaded scripts. Font Rendering IssuesSymptom: Boxes, question marks, or missing icons in your prompt. Fixes: Verify font installation: Open Windows Settings → Personalization → Fonts Search for “MesloLGM” If not present, manually run: oh-my-posh font install Meslo Correct font name in terminal: The exact font name is MesloLGM Nerd Font Some terminals are case-sensitive Don’t use abbreviations like “MesloLGM NF” Terminal compatibility: Windows Terminal, VS Code, and modern terminals support Nerd Fonts well Legacy terminals (cmd.exe window) may have poor support Consider upgrading to Windows Terminal Manual font installation:If automatic installation fails, download manually: Go to https://github.com/ryanoasis/nerd-fonts/releases Download Meslo.zip Extract and right-click the .ttf files Select Install for all users PSReadLine Version ErrorSymptom: Error about -PredictionSource parameter Fix:The script includes version checking, but if you manually edited your profile, ensure PSReadLine 2.1+ is installed: 12345# Check version(Get-Module PSReadLine).Version# Update if neededInstall-Module -Name PSReadLine -Force -SkipPublisherCheck Kubernetes Segment Not AppearingSymptom: The kubectl segment doesn’t show even though you have kubectl configured. Causes: No current context: Run kubectl config current-context to verify you have an active context Kubeconfig not in default location: Oh My Posh looks for ~/.kube/config. If you use KUBECONFIG environment variable with custom paths, the segment may not find it. Performance optimization: Oh My Posh might disable slow segments. Check if kubectl commands are responsive: 1Measure-Command { kubectl config current-context } If this takes more than 500ms, Oh My Posh may timeout the segment. Azure Segment Not ShowingSymptom: No Azure subscription information in your prompt. Fixes: Azure CLI not installed: The segment requires Azure CLI: 1winget install Microsoft.AzureCLI Not logged in: Authenticate with Azure: 1az login No active subscription: Set a default subscription: 1az account set --subscription "Your Subscription Name" Slow Prompt PerformanceSymptom: Noticeable delay before prompt appears, especially after pressing Enter. Common Causes: Slow Git operations: Large repositories or network-based Git remotes can slow the Git segment. Disable fetch_status for specific repositories: 123"properties": { "fetch_status": false} Multiple cloud segments: Each cloud segment (kubectl, az, aws) makes system calls. Remove segments you don’t actively use. Network timeouts: If segments query network resources (like Kubernetes API servers), timeouts can cause delays. Consider adjusting timeout settings or removing problematic segments. Performance Considerations and OptimizationsSegment CachingOh My Posh caches segment results to improve performance. You can adjust cache durations in the segment properties: 123"properties": { "cache_timeout": 5} This tells the segment to cache results for 5 minutes, reducing repeated system calls. Selective Segment EnablementNot every developer needs every segment. Consider creating role-specific variants: Backend Developers: Focus on Git, Azure, and database contextFrontend Developers: Emphasize Node.js version, Git, and build tool statusDevOps Engineers: Full cloud context with Kubernetes, Azure, and AWSFull Stack: Balanced approach with programming language versions and cloud context Async Segment UpdatesFor segments that make network calls, consider using Oh My Posh’s background update feature to prevent blocking the prompt: 123"properties": { "async": true} Wrapping UpWhat started as frustration with repeatedly configuring terminals across new machines evolved into a robust automation solution that eliminates setup friction entirely. One PowerShell script, a few minutes of execution time, and your terminal transforms from a blank slate into a fully-configured, cloud native development environment. The script and theme are opinionated - they reflect my specific workflow and preferences. But that’s the beauty of Oh My Posh’s flexibility. Take this automation as a starting point, customize the theme to match your daily tasks, add or remove segments based on your tech stack, and create your perfect terminal environment. No more hunting for font installers. No more manually editing profile files. No more copying configuration snippets from old machines. Just run the script, configure your font settings, and get back to building amazing things. Happy terminal pimping! 🎉 References Oh My Posh Official Website Oh My Posh GitHub Repository Windows Package Manager (winget) Nerd Fonts Project Windows Terminal Documentation PowerShell Profile Documentation Main image generated by GPT-Image-1.5","link":"/Engineering/Tooling/pimp-my-terminal-terminal-customization-with-oh-my-posh-a-cloud-native-terminal-setup/"},{"title":"Running FLUX.1 OmniControl on a Consumer GPU: A Docker Implementation tested on RTX 3060","text":"🎯 TL;DR: Subject-Driven Image Generation on 12GB VRAM Large AI models like FLUX.1-schnell typically require datacenter GPUs with 48GB+ VRAM. Problem: Most developers and hobbyists only have access to consumer RTX cards which vary from 6 - 12GB VRAM in most cases (with the exception of the expensive 4090/5090 cards which can go up to 32gb). Solution: Using mmgp (Memory Management for GPU Poor) with Docker containerization enables FLUX.1 OmniControl to run on RTX 3060 12GB through 8-bit quantization, dynamic VRAM/RAM offloading, and selective layer loading. The implementation provides a Gradio web interface generating 512x512 images in ~10 seconds after initial model loading, with models persisting in system RAM to avoid reload overhead. Technical Approach: Profile 3 configuration quantizes the T5 text encoder (8.8GB → ~4.4GB), pins the FLUX transformer (22.7GB) to reserved system RAM, and dynamically loads only active layers to VRAM during inference. Tested and validated on RTX 3060 12GB with 64GB system RAM running Windows 11 + WSL2 + Docker Desktop. Complete Implementation: All code, Dockerfile, and setup instructions are available at github.com/Ricky-G/docker-ai-models/omnicontrol Recently, I wanted to experiment with OmniControl, a subject-driven image generation model that extends FLUX.1-schnell with LoRA adapters for precise control over object placement. The challenge? The model requirements listed 48GB+ VRAM, and I only had an RTX 3060 with 12GB sitting in my workstation. This is a common frustration in the AI development community. Research papers showcase impressive results on expensive datacenter hardware, but practical implementation on consumer GPUs requires significant engineering effort. Could I actually run this model locally without upgrading to an RTX 4090/5090 or pay for a VM in Azure with A100? The answer turned out to be yes - with some clever memory management and containerization. This blog post walks through the complete process of dockerizing OmniControl to run efficiently on a 12GB consumer GPU. What is FLUX.1 OmniControl?Before diving into the technical implementation, let’s understand what we’re working with. FLUX.1 OmniControl is a subject-driven image generation model that extends the base FLUX.1-schnell diffusion model with LoRA (Low-Rank Adaptation) adapters for precise control over object placement and composition. Unlike traditional text-to-image models where you only provide a text prompt, OmniControl allows you to: Subject Consistency: Provide a reference image of a specific object (like a toy, person, or product) and have it accurately reproduced in generated images Spatial Control: Specify exactly where in the scene you want objects placed Style Preservation: Maintain the visual characteristics of the reference object across different contexts and environments Think of it as “Photoshop + AI” - you can place your specific objects into any scene you can describe with text. This makes it incredibly powerful for product visualization, creative content generation, and prototyping visual concepts. The trade-off? The model is massive - requiring over 30GB of model weights to achieve this level of control and quality. This is where the engineering challenge begins. The Challenge: Model Size vs Available VRAMLet’s start with the hard numbers: FLUX.1-schnell model components: Transformer: 22.7GB (torch.bfloat16) T5 Text Encoder: 8.8GB CLIP Text Encoder: 162MB VAE: ~1GB OminiControl LoRA: 200MB Total: ~32.8GB of model weights Available hardware:I am constrained by my existing workstation specs: RTX 3060: 12GB VRAM System RAM: 64GB DDR4 Storage: 1TB NVMe SSD + 2TB HDD CPU: Intel i7-11700K OS: Windows 11 + WSL2 (Ubuntu 22.04) Docker Desktop with NVIDIA Container Toolkit The gap is obvious - we need nearly 3x more VRAM than the GPU provides. Traditional approaches like FP16 precision or model pruning weren’t going to cut it. We needed something more aggressive. Understanding mmgp: Memory Management for GPU PoorThe key enabler for this project is mmgp (Memory Management for GPU Poor), a Python library specifically designed to run large models on consumer hardware. Here’s how it works: 8-Bit Quantizationmmgp uses quanto to quantize large model components from 16-bit to 8-bit precision: T5 encoder: 8.8GB → ~4.4GB (50% reduction) Quality impact: Minimal for text encoding tasks Speed impact: Slight increase in encoding time (~10-15%) Dynamic VRAM/RAM OffloadingInstead of keeping all model weights in VRAM, mmgp maintains a “working set”: Critical layers: Loaded to VRAM during active use Inactive layers: Offloaded to pinned system RAM Transfers: Handled automatically during forward passes RAM Pinning StrategyModels are loaded once from disk to system RAM (one-time cost), then: Pinned memory allocation: 75% of system RAM reserved (48GB in my case) Fast transfers: Pinned RAM → VRAM takes ~200ms for 1GB Persistent storage: Models stay in RAM across generations Profile Systemmmgp provides 5 preconfigured profiles: Profile Target VRAM Strategy Use Case 1 16-24GB Full model in VRAM Maximum speed 2 12-16GB Partial VRAM + RAM Balanced 3 12GB Quantization + pinning RTX 3060 sweet spot 4 8-12GB Aggressive quantization Lower-end cards 5 6-8GB Minimal VRAM usage GPU Poor mode For RTX 3060, Profile 3 provides the best balance between speed and stability. Prerequisites: What You’ll NeedBefore starting the implementation, ensure you have the following components set up: Hardware RequirementsMinimum Configuration: NVIDIA GPU: 12GB VRAM (RTX 3060, 3060 Ti, or better) System RAM: 64GB DDR4/DDR5 (48GB will be pinned for model storage) Storage: 50GB free space (35GB for models + overhead) CPU: Any modern multi-core processor Recommended Configuration: GPU: RTX 3060 12GB or RTX 4060 Ti 16GB RAM: 64GB or more Storage: NVMe SSD for faster startup times (HDD works but adds 2-3 min to load times) Software RequirementsWindows Users: Windows 11 (Windows 10 with WSL2 also works) WSL2 installed and configured Docker Desktop for Windows (latest version) NVIDIA Container Toolkit (installed via Docker Desktop) Linux Users: Ubuntu 22.04 or similar distribution Docker Engine (latest version) NVIDIA Container Toolkit NVIDIA drivers (version 525+) Account Requirements HuggingFace Account: Required to download models HuggingFace Token: Generate a read-access token at huggingface.co/settings/tokens Verification StepsBefore proceeding, verify your setup: 12345678# Check GPU availabilitynvidia-smi# Verify Docker installationdocker --version# Test GPU access in Dockerdocker run --rm --gpus all nvidia/cuda:12.1.1-base-ubuntu22.04 nvidia-smi If all commands execute successfully, you’re ready to begin! Docker Architecture: Why Containerization?With prerequisites confirmed, let’s talk about why Docker is the right choice for this project. Running large AI models involves complex dependency chains - specific versions of PyTorch, CUDA libraries, Python packages, and system libraries that can conflict with your existing environment. 💡 Want to Skip Ahead? The complete Docker implementation, including the Dockerfile, all Python code, and deployment scripts, is available in my GitHub repository: docker-ai-models/omnicontrol You can clone and run it immediately, or continue reading to understand how it works under the hood. Containerization solves this by: Isolating dependencies from your host system Ensuring reproducibility across different machines Simplifying deployment - one command to run the entire stack Enabling version control of the entire environment The containerization approach provides several additional benefits: Eliminates dependency conflicts Ensures reproducible builds Simplifies deployment across machines Isolates model storage from application code Container Structure1234567nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04├── Python 3.10 + CUDA libraries├── PyTorch 2.0 with CUDA support├── Diffusers + Transformers├── mmgp for memory management├── Gradio for web interface└── Custom FLUX integration code Volume Mounts1-v D:\\_Models\\omnicontrol:/app/models # Persistent model storage (34GB) Models download once to the host system and persist across container rebuilds. This is critical for development iteration - rebuilding the container doesn’t trigger 30-minute model downloads. GPU Access1--gpus all # Exposes all NVIDIA GPUs to container Docker Desktop + NVIDIA Container Toolkit handles GPU passthrough automatically on Windows via WSL2. Implementation Details: From Code to Running SystemNow that we understand the architecture and tools, let’s dive into how everything works in practice. This section covers the actual startup sequence, performance characteristics, and a critical optimization that makes this entire approach viable. ⚡ Key Performance Insight Ahead One of the biggest challenges in this implementation was preventing VRAM from being cleared after each generation, which would cause 80+ second reload times. The solution? A single line of code change that reduced subsequent generation times from 120s to 10s. We’ll cover this critical fix in detail below. Startup SequenceThe container initialization follows this sequence: 1. GPU Detection (~1 second) 12nvidia-smi --query-gpu=name,memory.total --format=csv# Output: NVIDIA GeForce RTX 3060, 12288 MiB 2. Profile Selection (automatic) 123vram = get_gpu_memory()if vram >= 11000: profile = 3 # 12GB optimized 3. Model Loading (2-3 minutes from HDD) FLUX.1-schnell: Downloads from HuggingFace (~22.7GB) OminiControl LoRA: Downloads adapter weights (~200MB) Loads to CPU first, then applies mmgp profiling 4. mmgp Profiling (1-2 minutes) Quantizes T5 encoder to 8-bit Allocates 48GB pinned RAM (75% of 64GB) Hooks model layers for dynamic offloading 5. Gradio Launch (~5 seconds) Web interface starts on port 7860 Ready to accept generation requests Total first-run time: 5-10 minutes (mostly downloading models)Subsequent runs: ~3 minutes (loading from disk to RAM) Generation PerformanceFirst generation after startup: Time: ~110 seconds Breakdown: VRAM loading: 80 seconds (22.7GB from RAM → VRAM) Actual inference: 30 seconds (8 steps @ 512x512) GPU memory: Climbs from 3GB → 10-12GB Subsequent generations: Time: ~10 seconds (target achieved!) VRAM stays at 10-12GB between generations No reload overhead Critical Fix: Preventing VRAM Clearing 🚨 This Section Contains The Key Optimization Initial testing revealed a major performance bottleneck that would have made this entire approach impractical. Understanding and fixing this issue is critical for achieving acceptable performance. The Problem: During initial testing, the first image generation took about 110 seconds (expected), but every subsequent generation also took 110+ seconds. Monitoring GPU memory usage revealed the issue: After generation completes: VRAM drops from 10-12GB back to 3GB Next generation starts: 80 seconds spent reloading models from RAM to VRAM Inference runs: 30 seconds of actual generation Total: 110 seconds per image, no matter how many you generate This made the system unusable for practical work - imagine waiting nearly 2 minutes for every single image! The Root Cause: Diagnosis revealed that FLUX’s generation code was calling maybe_free_model_hooks() after every inference pass. This function is designed to free memory for systems running multiple models or tight memory scenarios, but in our case where we want to generate multiple images in sequence, it was counterproductive. The culprit was in src/flux/generate.py: 12345678910# BEFORE (problematic)def generate(): # ... generation code ... self.maybe_free_model_hooks() # ❌ Unloads everything from VRAM!# AFTER (fixed)def generate(): # ... generation code ... # DISABLED: Keep models in VRAM between generations # self.maybe_free_model_hooks() # ✅ Models stay loaded The Impact: This single line change transformed the performance profile: Metric Before Fix After Fix Improvement First generation 110s 110s (same) Second generation 110s 10s 11x faster Third generation 110s 10s 11x faster VRAM after gen 3GB 10-12GB (persistent) Suddenly, generating 10 images went from 18 minutes to just 2 minutes (110s + 9 × 10s). This made the difference between “technically possible but impractical” and “actually usable for real work.” 📂 See the Implementation: The complete modified FLUX generation code with this optimization is available in the GitHub repository at src/flux/generate.py. You can see exactly how the model loading and generation pipeline is structured, along with all the mmgp integration code. Real-World Testing ResultsTest ConfigurationHardware: RTX 3060 12GB, Intel i7-11700K, 64GB DDR4, Micron NVMe main drive, 7200RPM HDD secondary OS: Windows 11 + WSL2 (Ubuntu 22.04) Docker: Desktop 4.28 with NVIDIA Container Toolkit Model: FLUX.1-schnell + OminiControl subject_512.safetensors Settings: 8 inference steps, 512x512 resolution Generation TestsTest 1: Cold start 123456Prompt: "A film photography shot. This item is placed on a wooden desk in a cozy study room. Warm afternoon sunlight streams through the window."Subject: Toy robot figureTime: 108 secondsQuality: Excellent, subject preserved with accurate placement Test 2: Immediate follow-up 12345Prompt: "On Christmas evening, on a crowded sidewalk, this item sits covered in snow wearing a Santa hat."Subject: Same toy robotTime: 11 secondsQuality: Excellent, consistent subject representation Test 3: Third generation 12345Prompt: "Underwater photography. This item sits on a coral reef with tropical fish swimming around it."Subject: Same toy robotTime: 10 secondsQuality: Good, some water distortion artifacts (expected) Resource MonitoringDuring active generation: GPU Utilization: 95-100% VRAM Usage: 10.2GB / 12GB (85%) System RAM: 52GB / 64GB (model pinning) CPU Usage: 15-20% (mainly data preprocessing) Power Draw: 170W (RTX 3060 TDP) Storage ImpactHDD vs SSD comparison (estimated): HDD: 2-3 minutes initial load from disk SSD: 30-45 seconds initial load (2.5x faster) During generation: No difference (models in RAM) Recommendation: SSD for faster startup, but not required for generation performance. Lessons Learned: What Works and What Doesn’tAfter extensive testing and iteration, here are the key insights organized by category. These lessons can save you hours of troubleshooting if you’re implementing something similar. Memory Management Insights✅ Profile 3 is the Sweet Spot for 12GB Cards Tested all five mmgp profiles extensively. Profile 3 provides the perfect balance: Stable VRAM usage at 85% capacity (10.2GB / 12GB) Fast inference times (10s per image) No OOM errors or crashes across 100+ test generations Profiles 1-2 required more VRAM than available, Profiles 4-5 were unnecessarily slow. ✅ RAM Pinning Eliminates the Disk Bottleneck The 75% RAM allocation strategy (48GB pinned) was crucial: First load: 2-3 minutes from HDD to RAM (one-time cost) Subsequent loads: <5 seconds from pinned RAM to VRAM Models persist across generations with zero disk I/O Without pinning, every generation would require disk access - absolutely impractical. ⚠️ WSL2 Memory Limits Are Deceiving Initial attempts with default WSL2 settings failed. The issue: Host system: 64GB RAM available WSL2 container: Only sees ~31GB (50% default limit) mmgp profile calculation: Incorrectly assumes full RAM available Solution: Explicitly configure .wslconfig to allocate more memory to WSL2, or force mmgp to use perc_reserved_mem_max=0.75 parameter. ❌ Auto-Offloading Strategies Don’t Work Well Tried mmgp’s offloadAfterEveryCall feature - it caused frequent crashes: Unpredictable VRAM usage patterns Race conditions between loading/offloading No performance benefit over persistent loading Lesson: For sequential generation workloads, keep models loaded. Storage and I/O Optimization📊 HDD vs SSD Impact Analysis Phase HDD SSD Impact Initial model download 5-10 min 5-10 min Network-bound First load (disk → RAM) 2-3 min 30-45 sec 4x faster RAM → VRAM transfer 200ms/GB 200ms/GB RAM speed During generation 0 disk I/O 0 disk I/O No difference Key Insight: SSD only matters for startup time. Once models are in RAM, storage speed is irrelevant. If you’re doing many generations in one session, HDD is perfectly acceptable. 💾 Volume Mount Strategy Was Critical Storing models on the host filesystem (-v D:\\_Models:/app/models) provided: Persistence across container rebuilds Ability to share models between different containers Easy backup and version management No re-downloading during development iterations Without this, every code change would require re-downloading 35GB of models. Configuration and Deployment✅ Gradio Provided Zero-Effort UI Using Gradio for the web interface was brilliant: 20 lines of Python for complete web UI Automatic file upload handling Built-in image preview and download No frontend development required Alternative approaches (Flask, React frontend) would have taken days vs hours. ✅ Docker Isolated the Complexity Containerization proved invaluable: No conflicts with host Python environment Reproducible across machines (tested on 3 different PCs) Easy version control of entire stack Simple deployment (docker run and done) ❌ Profile 1 Caused Out-of-Memory Errors Attempted to use Profile 1 (full model in VRAM) for maximum speed: Required 16GB+ VRAM RTX 3060’s 12GB couldn’t handle it Resulted in CUDA OOM errors mid-generation Lesson: Always profile your actual available memory, not theoretical specs. Quality and Performance Trade-offs✅ 8-Bit Quantization Had Minimal Quality Impact Side-by-side comparison of T5 encoder outputs: FP16 (original): Baseline quality INT8 (quantized): <5% subjective quality difference Memory savings: 8.8GB → 4.4GB (50% reduction) Conclusion: For text encoding tasks, 8-bit quantization is essentially free VRAM. 📈 Generation Speed Met Targets Goal Result Status First gen < 2 min 110s ✅ Achieved Subsequent gen < 15s 10s ✅ Exceeded VRAM stable 10-12GB consistent ✅ Achieved Quality acceptable Excellent outputs ✅ Achieved The 10-second generation time makes this practical for real creative work. Production ConsiderationsFor Personal UseThis setup works great for: Hobbyist AI experimentation Content creation (social media, art projects) Proof-of-concept development Learning FLUX.1 architecture For Commercial UseConsider these factors: Generation time: 10s/image × 1000 images = 2.8 hours Scalability: Single GPU, no batch processing Reliability: Consumer GPU thermal throttling under sustained load Support: mmgp is community-maintained, not enterprise-supported For production workloads, consider: Cloud GPUs (Azure N-Series VMs or Azure Container Apps with GPU nodes) - minimum A40/A100 Local GPU upgrade to RTX 4090 or A6000 Batch processing optimizations Multiple parallel containers Docker Hub & RepositoryThe complete implementation is available: GitHub: docker-ai-models/omnicontrol README: Full setup instructions and troubleshooting Dockerfile: Production-ready container definition Source code: Custom FLUX integration with mmgp Quick Start12345678910111213141516# Clone repositorygit clone https://github.com/Ricky-G/docker-ai-models.gitcd docker-ai-models/omnicontrol# Build containerdocker build -t omnicontrol .# Run with HuggingFace tokendocker run -d --gpus all --name omnicontrol \\ -p 7860:7860 \\ -v D:\\_Models\\omnicontrol:/app/models \\ -e HF_TOKEN=your_token_here \\ omnicontrol# Access web interface# http://localhost:7860 ConclusionRunning FLUX.1 OmniControl on a 12GB RTX 3060 is not only possible but practical. Through careful memory management with mmgp, strategic quantization, and container optimization, we achieved: ✅ 10-second generation times (after initial load) ✅ Stable VRAM usage across multiple generations ✅ No quality degradation from quantization ✅ Reproducible Docker deployment The key insight: Memory management is more important than raw VRAM capacity. With the right tools and configuration, consumer GPUs can run models designed for datacenter hardware. If you have an RTX 3060 (or similar 12GB card) collecting dust because you thought it couldn’t handle modern AI models, give this approach a try. The democratization of AI isn’t just about open-source models - it’s about making them runnable on hardware people actually own. Hardware tested: RTX 3060 12GB, 64GB RAM, Windows 11 + WSL2 Software stack: Docker Desktop, NVIDIA Container Toolkit, mmgp 3.6.9 Model: FLUX.1-schnell + OminiControl LoRA Performance: 10s per 512x512 image (8 steps) References: Memory optimization via mmgp (Memory Management for GPU Poor) FLUX.1-schnell Model OminiControl LoRA Docker Implementation Repository Image Credits: Main image generated by GPT-Image-1.5","link":"/AI/LLMs/running-flux-1-omnicontrol-on-a-consumer-gpu-a-docker-implementation-tested-on-rtx-3060/"},{"title":"The Four Types of GitHub Copilot Agents: Local, Background, Cloud, and Sub-Agents Explained","text":"🎯 TL;DR: Four Agent Types, Four Different Workflows GitHub Copilot in VS Code now supports four distinct agent types, each designed for different workflows and levels of autonomy. Local Agent is your interactive coding partner, running in VS Code with full access to all your tools, MCP servers, and three personas (Agent, Plan, Ask). Coding Agent (Cloud) runs on GitHub’s cloud infrastructure via Actions runners, works fully autonomously on issues, and creates PRs while you’re away. Background Agent (Copilot CLI) runs locally but outside the VS Code process; it survives restarts, supports parallel sessions, and can hand off work to cloud agents with /delegate. Sub-Agents are the secret weapon for context management, running as isolated subtasks within a parent agent session, keeping the main agent’s context window clean while handling research, analysis, or parallel tasks. Key insight: If you’re using a 1x premium model like Claude Sonnet 4, sub-agent calls are effectively free, making them the most cost-efficient way to scale complex multi-step workflows without burning through your premium request budget. GitHub Copilot has evolved far beyond simple code completions. With agent mode in VS Code, developers gained an autonomous coding assistant that could plan, execute, and iterate on complex tasks. But as workflows grew more sophisticated, a single agent type wasn’t enough to cover every scenario, from quick interactive debugging to full autonomous issue resolution that runs while you sleep. Today, GitHub Copilot in VS Code supports four distinct agent types, each optimized for different workflows, contexts, and levels of autonomy. Understanding when to use each one, and how they interact, is the difference between fighting your tools and having them work seamlessly for you. The Four Agent Types at a GlanceBefore diving deep into each agent type, here’s a high-level comparison: Feature Local Agent Background Agent (Copilot CLI) Coding Agent (Cloud) Sub-Agent Where it runs VS Code (local) Local machine (outside VS Code) GitHub Actions runner (cloud) Within parent agent session How to invoke Chat view (Ctrl+Alt+I) Chat dropdown → “Copilot CLI” or terminal copilot Chat dropdown → “Cloud”, GitHub Issues, PRs, CLI Auto-invoked by parent agent or #runSubAgent Autonomy Interactive Autonomous (local) Fully autonomous (remote) Task-scoped Survives VS Code close ❌ No ✅ Yes ✅ Yes N/A (tied to parent) Access to VS Code tools ✅ All tools, MCP servers, extensions ⚠️ Built-in tools only (no extension tools) ⚠️ Limited (cloud environment) ✅ Inherits parent’s tools Model support All models + BYOK All models + BYOK Limited models Inherits parent model Parallel sessions ✅ Multiple sessions possible ✅ Multiple parallel sessions ✅ Multiple parallel sessions ✅ Multiple parallel sub-agents Creates PRs ❌ No ❌ No ✅ Yes (draft PRs) ❌ No Cost Premium requests Premium requests Premium requests + Actions minutes Shares parent’s premium requests Context Main chat window Independent per session Issue/PR/repo context Isolated (key benefit!) 💡 Key concept: The session type dropdown in VS Code’s Chat view is your primary control for switching between Local, Background (Copilot CLI), and Cloud agents. Sub-agents are invoked programmatically within any session. 1. Local Agent (Standard Agent Mode)The Local Agent is the standard interactive agent in VS Code. It’s the workhorse of day-to-day Copilot interactions, running directly in your VS Code instance with full access to your workspace, tools, and extensions. How It WorksYou open it with Ctrl+Alt+I (or Cmd+Shift+I on macOS) and interact with it directly in the Chat view. It can read and edit files, run terminal commands, and leverage any tools you’ve configured. Three Built-In Personas Persona Behavior Best For Agent Autonomous multi-step coding: plans, executes, iterates, and validates Implementing features, fixing bugs, refactoring code Plan Creates structured implementation plans without making changes Understanding scope, architecture planning Ask Q&A about your codebase: reads code, explains patterns Learning a new codebase, understanding existing code Full Tool AccessThe Local Agent’s biggest advantage is unrestricted access to tools: built-in tools (file editing, terminal, search, debugging), any configured MCP servers, extension-provided tools, and BYOK models. This makes it the most versatile of all four types. Autopilot ModeFor tasks where you want minimal interruption, enable Autopilot mode. The agent automatically approves tool calls and continues executing without per-step confirmation. ⚠️ Use Autopilot mode with caution. The agent will execute terminal commands and edit files without asking for permission. Always review the results when it’s done. When to Use the Local Agent Interactive coding sessions needing back-and-forth conversation Tasks requiring extension tools (test runners, debuggers, specialized MCP servers) BYOK model usage when you need a specific model not available in cloud mode Sensitive codebases where you don’t want code leaving your machine Quick fixes that take a few minutes Limitations Tied to VS Code: closing VS Code stops the agent Not collaborative: other team members can’t see or interact with your session 2. Coding Agent (Cloud)The Coding Agent is GitHub Copilot’s fully autonomous cloud-based agent. It runs on GitHub’s infrastructure using Actions runners and is designed for tasks that don’t require your active involvement. You can assign it an issue and walk away. How It WorksYou can invoke it from multiple entry points: VS Code Chat view (“Cloud” session type), GitHub Issues (assign to @copilot), Pull Request comments, GitHub CLI, or external integrations (Jira, Slack, Teams). Once triggered, the Coding Agent analyzes the task, reads the repository code, creates a branch with a copilot/ prefix, implements changes autonomously, runs security checks (CodeQL, dependency scanning, secret scanning), and creates a draft Pull Request. Fully AsynchronousThis is the defining characteristic: you can close your laptop. The agent runs entirely on GitHub’s cloud infrastructure, so it keeps working whether or not you’re at your desk. 🔒 Security by default. The Coding Agent enforces CodeQL analysis, dependency scanning, and secret scanning on every PR it creates. These checks are non-negotiable. When to Use the Coding Agent Well-defined issues with clear acceptance criteria Tasks you want done while you’re away (overnight, during meetings) Bug fixes with clear reproduction steps Boilerplate or repetitive tasks (CRUD endpoints, adding tests, updating configs) Team workflows where any team member can assign issues to @copilot LimitationsThe Coding Agent is scoped to one repository per task, produces exactly one draft PR per task, cannot access your VS Code extensions, and uses both premium requests and Actions minutes (the most expensive agent type per task). You also can’t guide it mid-task, though you can comment on the PR afterward. The Coding Agent is available on Pro, Pro+, Business, and Enterprise plans. 3. Background Agent (Copilot CLI)The Background Agent bridges the gap between local and cloud agents. It runs on your local machine but outside the VS Code process, meaning it survives VS Code restarts and can run multiple sessions in parallel. How It WorksThe Background Agent is powered by the Copilot CLI agent harness. You can invoke it from the Chat view dropdown (“Copilot CLI”), Command Palette, or by typing copilot directly in your terminal. Isolation Modes Mode Behavior Use Case Worktree Creates a Git worktree in a separate folder; changes are isolated from your working directory Safe experimentation, parallel feature development Workspace Makes changes directly in your current workspace Quick tasks where you want changes applied immediately 💡 Worktree mode is the safer option. It creates a separate copy of your repository, so the Background Agent’s changes never interfere with your active work. Parallel SessionsUnlike the Local Agent, the Background Agent can run multiple sessions simultaneously, each with its own independent context window and worktree. This is powerful for parallelizing work across different tasks. The /delegate Command and Hand-OffThe /delegate command hands off a task to the Coding Agent (Cloud), creating a smooth escalation path: start working locally, realize the task needs full autonomy and a PR, and delegate to the cloud without losing context. From To How Local Agent Background Agent Change session type dropdown to “Copilot CLI” Local Agent Coding Agent (Cloud) Change session type dropdown to “Cloud” Background Agent Coding Agent (Cloud) Enter /delegate in the chat input ⚡ Conversation history carries over during hand-offs. The new agent receives the full conversation context so it can continue where the previous agent left off. Cost: Premium Requests OnlyThe Background Agent uses only Copilot premium requests, with no GitHub Actions minutes. This makes it significantly cheaper than the Coding Agent for comparable tasks. When to Use the Background Agent Long-running tasks that you don’t want tied to your VS Code session Parallel work where you need multiple agents on different tasks simultaneously Tasks you want to start and check on later Exploratory work using Worktree mode to safely experiment Pipeline to cloud: start locally, get context, then /delegate for PR creation Limitations No extension-provided tools: runs outside the VS Code process, so it can’t access extension tools Requires machine to stay running: survives VS Code restarts but not machine shutdown or sleep Local resources only: uses your machine’s CPU, memory, and network 4. Sub-Agents: The Context Management Secret WeaponSub-Agents are the most underappreciated feature in the Copilot agent ecosystem. They’re spawned as isolated subtasks within a parent agent session, and their primary superpower is context isolation. The Context ProblemEvery AI agent has a finite context window. As your conversation grows with file reads, search results, and intermediate reasoning, that window fills up and the agent starts losing important details. For complex multi-step tasks, the research phases can consume so much context that the agent forgets critical details by the time it begins implementation. How Sub-Agents Solve ThisSub-Agents run in a completely isolated context window: The parent agent spawns a sub-agent with a specific task prompt (the sub-agent does NOT inherit the parent’s conversation history) The sub-agent performs all its work (file reads, searches, analysis) in its own isolated context It returns only a final summary/result to the parent; all intermediate data is discarded The result: the parent agent gets concise answers without its context window being polluted by hundreds of lines of intermediate data. Visual: How the #runSubAgent Tool Works The image above illustrates the sub-agent flow. When you invoke the #runSubAgent tool, it creates a separate context window for the sub-agent. The sub-agent performs all tool calls independently, and only the final result summary is passed back to the main agent. The sub-agent appears as a collapsible tool call in the Chat UI. Invoking Sub-AgentsSub-Agents can be triggered in two ways: Agent-initiated: The main agent autonomously decides to spawn a sub-agent when it recognizes a task that would benefit from context isolation. User-hinted: You can explicitly request one: 12You: Run a subagent to research all the authentication patterns used in this codebase and give me a summary of the approaches used. Or reference the tool directly: #runSubAgent The Cost AdvantageIf you’re using a 1x premium model, sub-agent calls are effectively free. You can run dozens of sub-agents in parallel without worrying about your premium request budget. Instead of using one expensive model call that reads 50 files and floods context, spawn multiple cheap sub-agents to research different aspects in parallel, collect their concise summaries, then use a single focused model call for the final implementation. Orchestration Pattern: Coordinator & Worker12345Main Agent (Coordinator):├── SubAgent 1: "Analyze the authentication module and list all auth strategies used"├── SubAgent 2: "Analyze the database layer and list all query patterns" ├── SubAgent 3: "Analyze the API layer and list all middleware in the pipeline"└── Main Agent: Uses summaries from all three to implement a new feature What Sub-Agents CAN and CANNOT Do ✅ Can Do ❌ Cannot Do Read files in the workspace Access the parent agent’s conversation history Run terminal commands Modify the parent agent’s context Search the codebase Persist state between invocations Use MCP server tools (inherits parent’s tools) Run as a standalone session Run in parallel with other sub-agents Create PRs or branches Return structured results to parent Directly communicate with the user When to Use Which Agent: Quick Decision Matrix Scenario Recommended Agent Why Quick bug fix while coding Local Agent Interactive, immediate feedback, full tool access “Explain this code to me” Local Agent (Ask persona) Read-only, conversational Plan a multi-file refactor Local Agent (Plan persona) Creates structured plan before changes Long-running test generation Background Agent Survives VS Code restart, doesn’t block your editor Working on 3 features in parallel Background Agent Multiple parallel sessions with Worktree isolation Well-defined GitHub issue Coding Agent Fully autonomous, creates PR, runs while you’re away “Fix this overnight” Coding Agent Asynchronous cloud execution Research codebase patterns Sub-Agent Context-isolated research Complex task with pre-research Sub-Agent → Parent Research in sub-agents, implement in main agent Start local, realize it needs a PR Background → /delegate → Cloud Seamless hand-off Best Practices ✅ Do ❌ Don’t Match agent type to task size: Local for < 5 min, Background for 5-30 min, Coding Agent for 30+ min Use the Coding Agent for exploratory work (it creates a PR every time) Use sub-agents for research-heavy tasks to keep main context clean Ignore context window limits; if the agent starts “forgetting,” offload research to sub-agents Default to 1x premium models for sub-agents (research rarely needs the most powerful models) Run Coding Agent tasks for multi-repo changes (it’s scoped to a single repo per task) Write clear, detailed issue descriptions for the Coding Agent with acceptance criteria and edge cases Use a single long agent session for everything; break complex work across agent types instead Use the Research-Delegate Pipeline: Ask → Plan → Background + Sub-Agents → /delegate → Cloud PR Key Takeaways Local Agent is your interactive coding partner: versatile, full-featured, but tied to your VS Code session Coding Agent (Cloud) is your autonomous worker: fire and forget, creates PRs, runs on GitHub’s infrastructure Background Agent (Copilot CLI) is the bridge: local execution with persistence, parallelism, and cloud delegation Sub-Agents are the context management secret weapon: keep your main agent’s context clean while parallelizing research and analysis Cost optimization is key: use 1x premium models for sub-agents and routine tasks, reserve premium models for complex work Hand-off between agents is seamless: start local, escalate to background, delegate to cloud as needed The most effective Copilot users don’t just use one agent type. They orchestrate all four to match the right tool to the right task. References GitHub Copilot Coding Agent GitHub Copilot Plans and Pricing Image Credits: Main image generated by GPT-Image-1.5","link":"/GitHub/Copilot/the-four-types-of-github-copilot-agents-local-background-cloud-and-sub-agents-explained/"},{"title":"AKS Static Egress Gateway: Per-Namespace Static Egress IPs","text":"🎯 TL;DR: Unique Static Egress IP per Kubernetes Namespace in AKS AKS now has a native equivalent to OpenShift’s EgressIP: Static Egress Gateway. One dedicated gateway node pool + a StaticGatewayConfiguration CRD per namespace = stable egress IPs (public or private). No more separate node pools, subnets, and NAT Gateways per namespace. Pods opt in via annotation, IPs are stable across restarts/upgrades, supports public and private egress, and layers cleanly with Azure Firewall. Requires aks-preview CLI extension and StaticEgressGatewayPreview feature flag. Private IP mode requires Kubernetes 1.34+. Full working demo: github.com/Ricky-G/azure-scenario-hub/tree/main/src/aks-unique-egress-ip-per-namespace If you’ve used OpenShift’s EgressIP CRD to assign static egress IPs per namespace for firewall allowlisting, you know how critical this is for security compliance. The first question in any OpenShift-to-AKS migration is always: “How do we get per-namespace static egress IPs?” Until recently, you needed a separate node pool, subnet, and NAT Gateway per namespace. Ten namespaces = ten of each. It didn’t scale. AKS Static Egress Gateway fixes this: one gateway node pool, one subnet, one CRD per namespace. graph LR A[\"Namespace A\"] --> GW[\"Gateway Pool\"] B[\"Namespace B\"] --> GW C[\"Namespace C\"] --> GW GW -->|\"Unique IP per NS\"| EXT[\"External Services\"] Why Per-Namespace Egress IP Matters Firewall allowlisting: Downstream services identify traffic by source IP “Team Alpha = 10.224.0.20” is a rule your firewall team can work with Compliance: Regulatory frameworks require demonstrable network boundary controls Multi-tenant isolation: Network-level separation that extends beyond the cluster boundary OpenShift migration: Teams expect EgressIP parity without it, migration stalls at architecture review The Old Way: Per-Namespace Node Pools123Namespace A → Node Pool A → Subnet A → NAT Gateway A → Egress IP ANamespace B → Node Pool B → Subnet B → NAT Gateway B → Egress IP B...repeat for every namespace... For 10 namespaces: 10 node pools, 10 subnets, 10 NAT Gateways (~$320/month just for NAT), 10 sets of affinity/taint rules. Doesn’t scale. The New Way: AKS Static Egress Gateway1234Namespace A ─┐Namespace B ──┤→ Gateway Node Pool (2 nodes) → Unique IP per namespaceNamespace C ──┤ (single subnet)... ─┘ How It Works Enable the feature: az aks update --enable-static-egress-gateway (requires aks-preview CLI extension). Create a Gateway Node Pool dedicated 2+ node pool in Gateway mode (egress traffic only). 12345678az aks nodepool add \\ --cluster-name my-cluster \\ --name gateway \\ --resource-group my-rg \\ --mode gateway \\ --node-count 2 \\ --gateway-prefix-size 28 \\ --vm-size Standard_D2s_v5 Create a StaticGatewayConfiguration per namespace binds a namespace to the gateway pool and triggers IP allocation. 1234567891011apiVersion: egressgateway.kubernetes.azure.com/v1alpha1kind: StaticGatewayConfigurationmetadata: name: egress-config namespace: team-alphaspec: gatewayNodepoolName: gateway excludeCidrs: - 10.0.0.0/8 - 172.16.0.0/12 - 169.254.169.254/32 Annotate your pods to opt into the gateway: 123metadata: annotations: kubernetes.azure.com/static-gateway-configuration: egress-config AKS provisions the egress IP automatically public mode creates an Azure Public IP Prefix per config; private mode (provisionPublicIps: false) allocates secondary private IPs from the gateway subnet. Under the Hood CNI plugin intercepts the pod’s egress traffic Traffic tunnels via eBPF/WireGuard to a gateway node Gateway node SNATs using the namespace’s assigned egress IP Traffic exits with the predictable, static source IP excludeCidrs ensures cluster-internal traffic (pod-to-pod, pod-to-service, Azure IMDS) bypasses the gateway. Demo: 5 Namespaces, 5 Unique Egress IPsI built a complete demo with 5 namespaces to validate this end-to-end. Architecturegraph TD subgraph AKS[\"AKS Cluster Azure CNI Overlay, Static Egress Gateway\"] direction TB subgraph NS[\"Namespaces each with StaticGatewayConfiguration\"] A[\"egress-team-alphaegress-checker → calls ipsimpleEgress IP: 20.168.88.96\"] B[\"egress-team-bravoegress-checker → calls ipsimpleEgress IP: 20.163.34.144\"] C[\"egress-team-charlieegress-checker → calls ipsimpleEgress IP: 20.171.16.241\"] D[\"egress-team-deltaegress-checker → calls ipsimpleEgress IP: 20.171.54.241\"] E[\"egress-team-echoegress-checker → calls ipsimpleEgress IP: 20.171.14.129\"] end GW[\"Gateway Node Pool2 nodes · HA · eBPF/WireGuardSNAT per namespace\"] DASH[\"DashboardPolls all 5 checkersShows egress IPs in real-time\"] end IPSIMPLE[\"api.ipsimple.orgReturns caller's public IP\"] A -->|\"annotated pod\"| GW B -->|\"annotated pod\"| GW C -->|\"annotated pod\"| GW D -->|\"annotated pod\"| GW E -->|\"annotated pod\"| GW GW -->|\"unique IP per NS\"| IPSIMPLE DASH -.->|\"polls via ClusterIP\"| A DASH -.->|\"polls via ClusterIP\"| B DASH -.->|\"polls via ClusterIP\"| C DASH -.->|\"polls via ClusterIP\"| D DASH -.->|\"polls via ClusterIP\"| E style AKS fill:#1e293b,stroke:#38bdf8,color:#e2e8f0 style NS fill:#334155,stroke:#94a3b8,color:#e2e8f0 style GW fill:#065f46,stroke:#34d399,color:#d1fae5 style DASH fill:#1e1b4b,stroke:#818cf8,color:#c7d2fe style IPSIMPLE fill:#78350f,stroke:#fbbf24,color:#fef3c7 style A fill:#0f172a,stroke:#fbbf24,color:#fef3c7 style B fill:#0f172a,stroke:#fbbf24,color:#fef3c7 style C fill:#0f172a,stroke:#fbbf24,color:#fef3c7 style D fill:#0f172a,stroke:#fbbf24,color:#fef3c7 style E fill:#0f172a,stroke:#fbbf24,color:#fef3c7 Each egress-checker is a Python Flask app that calls IpSimple every 10 seconds to detect its public egress IP. The dashboard aggregates all 5 results: Namespace Egress IP egress-team-alpha 20.168.88.96 egress-team-bravo 20.163.34.144 egress-team-charlie 20.171.16.241 egress-team-delta 20.171.54.241 egress-team-echo 20.171.14.129 Five namespaces, five unique IPs, one gateway pool, one subnet. Public vs Private EgressPublic Mode (Default)123spec: gatewayNodepoolName: gateway # provisionPublicIps defaults to true AKS auto-creates an Azure Public IP Prefix (e.g., /28 = 16 IPs) per StaticGatewayConfiguration. Use this for internet/external API egress. Public Mode with BYOIP123spec: gatewayNodepoolName: gateway publicIpPrefixId: /subscriptions/.../publicIPPrefixes/my-known-prefix Pre-create the Public IP Prefix for full control over the IP range. Multiple namespaces can share the same prefix. Private Mode (Enterprise/On-Prem)123spec: gatewayNodepoolName: gateway provisionPublicIps: false No public IPs. The controller allocates secondary private IPs from the gateway subnet. Use this for private clusters egressing to on-prem via ExpressRoute/VPN or peered VNets. Important: Private mode requires Kubernetes 1.34+ and --vm-set-type VirtualMachines on the gateway node pool. Gotchas Egress IP prefix ≠ your cluster VNet: The /28 public IP prefixes are separate Azure Public IP Prefix resources unrelated to your node subnet, pod CIDR, or service CIDR. IPs are stable but not user-selectable (private mode): The controller picks automatically, but once assigned, IPs persist across restarts, upgrades, and scaling. Only changes if you recreate the StaticGatewayConfiguration. Opt-in via pod annotation: Only annotated pods use the gateway. Unannotated pods use normal cluster egress you can mix within the same namespace. Gateway nodes are VMs, not VMSS: LoadBalancer services can fail if gateway nodes join the backend pool. Use ClusterIP + kubectl port-forward or an Ingress controller. Always set excludeCidrs: Include 10.0.0.0/8, 172.16.0.0/12, 169.254.169.254/32 to prevent breaking pod-to-pod communication and Azure IMDS access. OpenShift EgressIP vs AKS Static Egress Gateway: A Direct ComparisonFor teams coming from OpenShift, here’s how the features map: Feature OpenShift EgressIP AKS Static Egress Gateway CRD to assign egress IP EgressIP on namespace StaticGatewayConfiguration + pod annotation Granularity Per namespace Per namespace or per pod Dedicated egress nodes No (uses worker nodes) Yes (gateway node pool) Public IP support Limited Full (auto or BYOIP) Private IP support Yes (node IPs) Yes (provisionPublicIps: false) HA / failover Auto-reassignment Dedicated pool (2+ nodes) IP sharing across namespaces Not natively Yes (via shared publicIpPrefixId) Multiple IPs per namespace No Yes (prefix-based) AKS is more flexible overall per-pod opt-in via annotation, BYOIP support, and IP sharing across namespaces. Layering with Azure FirewallYou can layer Azure Firewall on top for defense in depth: Each namespace gets a static egress IP via StaticGatewayConfiguration All cluster egress routes through Azure Firewall via UDR (0.0.0.0/0 → Azure Firewall) Firewall applies per-source-IP rules: network rules, application/FQDN rules, threat intelligence, TLS inspection, and IDPS (Premium SKU) Two layers of control: Layer 1 (Kubernetes): Which namespace/pod → which IP Layer 2 (Azure Firewall): What that IP can reach Both layers are cloud-native, managed, and integrated with Azure Monitor. Cost Comparison Component Legacy (10 namespaces) Static Egress Gateway Node pools 10 × 1+ node each 1 × 2 nodes (HA) Subnets 10 1 NAT Gateways 10 × ~$32/mo = $320/mo 0 Gateway VMs 0 2 × D2s_v5 = ~$140/mo Total egress infra ~$1,020+/mo ~$140/mo Operational complexity Very high Low 86% cost reduction on egress infrastructure alone. Getting StartedRepository: github.com/Ricky-G/azure-scenario-hub/tree/main/src/aks-unique-egress-ip-per-namespace Includes Bicep templates, Kubernetes manifests for 5 namespaces, Python egress-checker apps, dashboard, PowerShell/Bash deployment scripts, private cluster guide, and architecture review docs. 1.\\deploy-infra.ps1 -ResourceGroupName "rg-aks-egress-demo" -Location "westus3" Key Takeaways AKS Static Egress Gateway is the native equivalent of OpenShift EgressIP Simpler, cheaper, and more flexible than the legacy multi-subnet approach Security control parity (or better) with Azure Firewall layering as a bonus Currently in preview ready for validation and PoC work today Official docs for GA timelines References AKS Static Egress Gateway Documentation OpenShift EgressIP Documentation Azure CNI Overlay Azure Firewall Documentation Demo Repository Azure Scenario Hub","link":"/AKS/Networking/aks-static-egress-gateway-per-namespace-static-egress-ips/"},{"title":"Deploying To IP Restricted Azure Function Apps Using GitHub Actions","text":"🎯 TL;DR: Dynamic IP Management for CI/CD to Secured Azure Functions IP-restricted Function Apps block GitHub Actions runners causing HTTP 403 deployment failures since runners use dynamic IP addresses. Problem: Cannot whitelist entire GitHub IP range due to frequent changes. Solution: Dynamic IP management in GitHub Actions workflow using Azure CLI to temporarily add runner IP to SCM site access restrictions, deploy code, then remove IP. Implementation uses ipify API for IP detection, --use-same-restrictions-for-scm-site false for SCM isolation, and automated cleanup to maintain security posture. In the previous post we blocked our function app to be available only to the APIM via ip restrictions. This secures our function app and it isn’t available publicly, any one that tries to access our function app url will get “HTTP 403 Forbidden”. This secures our function app; now what about deploying code changes to the function app via GitHub Actions? we should be able to CI/CD to our function app, but there is a problem here. The GitHub action will fail with the same “HTTP 403 Forbidden”, this is because GitHub actions run on runners (its a hosted virtual environment), each time we run the Action we get a new runner and it can have a different ip address. So how can we get around this? do we white list the entire GitHub ip range? GitHub’s ip ranges can change any time, so will have to keep scanning for changes to these ranges and proactively update our ip restrictions, this is not very scalable or practical. So what are other ways of getting around this? we have a couple of ways to get around this. Possible SolutionsThere are two viable solutions here 1. Use a self-hosted runner Where you bring your own VM’s with static ip’s and whitelist these static ip’s Pros: Full control over your devops agents Can optimize/reuse these agents for various CI/CD workloads for your cloud and on-prem deployments Cons: You have to provision and maintain your own VM’s, there will be time and effort required for this Extra costs to maintain your own VM(s), although this could be optimized by turning them off after hours etc You miss out on the free GitHub Action minutes you get Extra work of provisioning VM’s, installing all the tooling for builds, maintaining and paying for them 2. Do some extra steps in the existing GitHub Actions Use the Azure CLI Do an az login Grab the public ip of the GitHub runner, you could use a simple public api like the ipify api to grab the public ip of the Github Runner Use az cli to update ip restriction to add this additional ip Do-your-normal-Deployment Use az cli to remove the ip added in step 4 Pros: You use the same GitHub runner and workflow No effort in provisioning or maintaining extra virtual machines yourself Little bit of extra code is all that is needed Cons: There is a possibility that the GitHub action runner fails/crashes after doing step 4 but before it had a chance to get to step 5, you could be left with an extra ip address white listed in your app until you run the workflow again. This post is all about how to go about doing option 2 (do some extra steps in the existing GitHub Actions), although there is one con (ie: the GitHub runner crashing during step 5 and leaving an ip address of the runner there), in my view this is a very small risk. The chances of a crash precisely at that point are low and even it does happen the risk of having the runner ip (only 1 extra ip) for a short duration until your next run happens is very low. Show me the codeIf you want to skip and just get to the code: Here is the sample hello world function app (written in .net 6) Here is the GitHub Action that is deploying to ip restricted app In the above GitHub Action it is deploying a hello world function app; it is doing a dotnet build, package and deploy. Those are all the standard bits of deploying a function app; lets go over the interesting bits Getting the GitHub Runners public ip Whitelisting this ip After a successful deploy of our app, we remove the ip added in step 2 For the first step we are using a public package haythem/[email protected] to get the ip. We can also manually do a curl our >selves to the ipify api and grab the public ip ourselves. For the purposes of this demo we will use this package. Step 1 - getting the GitHub runners public ip 123- name: Public IP id: ip uses: haythem/[email protected] Next for the second step we use the az cli to add the ip address. First we use az webapp config to set the –use-same-restrictions-for-scm-site false, here we are saying don’t apply the same restriction as the main site to the scm site Our main site is still safe with the right ip restrictions, our scm site is now ready for changes Next we use az functionapp config access-restriction to add the GitHub runner ip to just the scm site Step 2 - white listing the GitHub runner’s public ip 1234567- name: 'Allow Github Runner IpAddress' uses: azure/CLI@v1 with: azcliversion: 2.37.0 inlineScript: | az webapp config access-restriction set -g $ -n func-app-iprest-demo --use-same-restrictions-for-scm-site false az functionapp config access-restriction add -g $ -n func-app-iprest-demo --rule-name github_runner --action Allow --ip-address $ --priority 100 --scm-site true Finally we remove the ip address we added from the previous step and set the scm site access the same as our main site Step 3 - after successful deploy, remove the GitHub runner’s public ip 1234567- name: 'Remove Github Runner IpAddress' uses: azure/CLI@v1 with: azcliversion: 2.37.0 inlineScript: | az functionapp config access-restriction remove -g $ -n func-app-iprest-demo --rule-name github_runner --scm-site true az webapp config access-restriction set -g $ -n func-app-iprest-demo --use-same-restrictions-for-scm-site true Finally 👏! we can now deploy using GitHub Actions to ip restricted function apps 🙌. ReferencesAs always a big thank you to Unsplash for providing a huge range of images for free Cover image has been taken from https://unsplash.com/photos/842ofHC6MaI","link":"/GitHub/Actions/deploying-to-ip-restricted-azure-function-apps-using-github-actions/"},{"title":"Securing Azure Functions and Logic Apps","text":"🎯 TL;DR: Cost-Optimized Security for Serverless Microservices Consumption plan Function Apps and APIM Standard lack VNet integration for cost optimization but expose services publicly. Problem: Serverless microservices accessible directly bypassing API Management security policies. Solution: IP restriction-based security using APIM’s public IP address to whitelist only API Management access, configuring both main site and SCM site restrictions. Architecture includes Azure Front Door for WAF capabilities since APIM Standard lacks native WAF protection. Here is a scenario that I recently encountered. Imagine we are building micro-services using serverless (a mix on Azure Function Apps and Logic Apps) with APIM in the front. Lets say we went with the APIM standard instance and all the logic and function apps are going to be running on consumption plan (for cost reasons as its cheaper). This means we wont be getting any vnet capability and our function and logic apps will be exposed out to the world (remember to get vnet with APIM we have to go with the premium version, we are going APIM standard here for cost saving reasons). So how do we restrict our function and logic apps to only go through the APIM, in another words all our function and logic apps must only go through the APIM and if anyone tries to access them directly they should be getting a “HTTP 403 Forbidden”. Lets visualize this scenario; We have some WAF capable ingress endpoint, in this case its Azure Front Door, that is forwarding traffic to APIM which then sends the requests to the serverless apps.Reason for having Front Door before APIM is because APIM doesn’t have WAF natively so we will need to put something in front of it that has that capability to be secure. There are few options like Azure Firewall, Application Gateway etc, but for the purposes of this scenario we have Azure Front Door in front of APIM (and we can have an APIM policy that will only accept traffic from Azure Font Door, we wont be going in to that, we will keep it to securing our function apps to just being available via APIM for today) Securing the function app First we will need to get the public ip address of the APIM White-list this address in our function app network restrictions Getting the public ip of APIMYou can go to the APIM resource in the Azure portal and get it from there Or you can use the CLI and run 1az apim show --name "apim-name" --resource-group "resource-group-name" White-listing the function app You need to go into networking -> access restriction Only allow the APIM ip (once you enter this, the deny all will automatically come ie: all other ip’s are denied) Its important that the SCM site is also blocked. More about Kudu service that powers the SCM site here What happens if you try to access this functionNow its all blocked we get a nice HTTP 403 Forbidden What about deploying code to this function via GitHub ActionsWhen you try to deploy to these functions using GitHub Actions or even via Azure Devops you will get the same HTTP 403 and wont be able to deploy. This is because the GitHub runner’s ip address will be blocked; remember we are only allowing APIM in, all others are blocked. There are a couple of ways to get around this. I talk about this in the next post, you can check it out here References Cover image has been taken from https://azure.microsoft.com/en-us/services/functions/#overview","link":"/Azure/Function-Apps/Security/securing-azure-functions-and-logic-apps/"},{"title":"Create A Multi User Experience For Single Threaded Applications Using Azure Container Apps","text":"🎯 TL;DR: Simulating Multi-User Experience for Legacy Single-Threaded Apps Legacy single-threaded applications (one request per process) require multi-user support without costly re-architecture. Problem: Applications with static locks block entire process during request handling. Solution: Azure Container Apps with HTTP-based scaling rules that spawn new container instances per concurrent request. Configuration uses min-replicas=0, max-replicas=30 with HTTP scale triggers, achieving 70-90% request isolation across separate container instances for pseudo-multithreaded behavior without code changes. How to make a single-threaded app multi-threaded? This is the scenario I faced very recently. These were legacy web app(s) written to be single-threaded; in this context single-threaded means can only serve one request at a time. I know this goes against everything that a web app should be, but it what it is. So if we have a single threaded web app (legacy) now all of a sudden we have a requirement to support multiple users at the same time. What are our options: Re-architect the app to be multi threaded Find a way to simulate multi threaded behavior Both are great options, but in this scenario option 1 was out, due to the cost involved in re-writing this app to support multi threading. So that leaves us with option 2; how can we at a cloud infra level easily simulate multi threaded behavior. Turns out if we containerize the app (in this case it was easy enough to do) we orchestrate the app such that for each http request is routed to a new container (ie: every new http request should spin up a new container and request send to it) Options For Running ContainersSo when it comes to running a container in Azure our main options are below Here we need to orchestrate containers, ie: at a minimum for every new http request spin a new one), which means we only have two viable options, Azure Kubernetes Service (AKS) or Azure Container Apps (ACA). Both are valid options, each with their own pros/cons, with AKS its a lot more complex we will need to : Think of networking Think of vm’s/vm scale sets for nodes Choose ingress controller and set up ingress rules Identity Plus many more, here is the baseline reference for AKS So in short, as flexible as AKS is its not as easy as something like ACA which is a fully managed version of AKS that abstracts all the complexities of Kubernetes. So for this scenario to prove we can simulate multi threaded experience lets go ahead with ACA. Sample Single Threaded ProgramFor this demo below is a simple C# DotNet app that simulates a single threaded behavior, essentially its doing a lock on a static variable which blocks the whole process for 6 seconds. So when we visit the /test endpoint we lock the whole app. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748public class Program{ private static readonly object LockObject = new(); public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddAuthorization(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddApplicationInsightsTelemetry(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseAuthorization(); app.MapGet("/test", (HttpContext httpContext) => { if (Monitor.TryEnter(LockObject, new TimeSpan(0, 0, 6))) { try { Thread.Sleep(5000); } finally { Monitor.Exit(LockObject); } } return ("Hello From Container: " + System.Environment.MachineName); }); app.Run(); }} Azure Container AppsFor this demo the easiest way to create the Azure Container Apps environment is through Visual Studio, you right click, publish and go through the menus and in the end VS will create a Container Apps Environment and deploy the code as a container to ACA. Once this is all done, we should have a resource group like below Azure Container Apps ScalingNext we go to the container app (the single threaded api we just deployed) and set up a simple http scale rule that will spin up a new container for every 1 http incoming request. In the example below we set min-replicas to 0 and max to 30 this means that when there is no traffic it will scale down to 0 and at peak it will hit 30. TestingNow go to the url of the container app and hit it simultaneously in browser tabs, when I opened it in multiple browser tabs out of 10 tabs about 7 were served by unique containers and based on the test code above I see it being served by different container ids 1234Tab1: Hello From Container: single-threaded-api-app-20220731--ps4yjjp-66f4885b65-w5s6hTab2: Hello From Container: single-threaded-api-app-20220731--ps4yjjp-66f4885b65-gs8qfTab3: Hello From Container: single-threaded-api-app-20220731--ps4yjjp-66f4885b65-x7grletc So its not 100% every single request goes to a brand new container, but very easily and very quickly with out too much complexity we were able to achieve a 70 - 90% of requests being served with new containers, so in essence we found a quick way to simulate a pseudo - multi threaded experience for our legacy single threaded app with out too much effort.","link":"/Azure/Container-Apps/create-a-multi-user-experience-for-single-threaded-applications-using-azure-container-apps/"},{"title":"Application Gateway Ingress Controller For AKS","text":"🎯 TL;DR: AGIC Direct Pod Ingress for High-Performance AKS Workloads AGIC provides direct pod ingress bypassing Kubernetes ClusterIP for up to 50% lower network latency compared to in-cluster solutions. Problem: Traditional ingress controllers add network hops and consume AKS compute resources. Solution: Application Gateway routes directly to pod IPs via Azure Resource Manager integration, offering WAF, SSL termination, and managed updates as AKS add-on. Critical limitation: 100 backend pool limit means 2000+ services require 20 Application Gateways, making cost-effective deployment challenging for large-scale clusters. Recently I ran into an interesting issue with an AKS cluster running 2000+ services. There is nothing wrong in running 2000+ services that’s what Kubernetes is there for, scale! but the interesting aspect that caught my attention was trying to get the Applicaiton Gateway Ingress Controller (AGIC) to ingress to all these services. I had worked with Istio and NGINX for ingress into AKS with no issues and never AGIC, so I had to try this to see where it worked well, what the advantages are and where the limitations are. Application GatewayApplication Gateway (App Gateway) is a well-established layer 7 service that has been around for a while, some of the major features are: URL routing Cookie-based affinity SSL termination End-to-end SSL Support for public, private, and hybrid web sites Integrated web application firewall Zone redundancy Connection draining This post isn’t focused on the App Gateway itself, it’s more about how and what it can do as an ingress controller for AKS. You can find out more about App Gateway and all abouts its features here TLDR;Benefits of AGIC Direct connection to the pods without an extra hop, this results in a performance benefit up to 50% lower network latency compared to in-cluster ingress Could make a huge difference in performance and latency sensitive applications and workloads If going the AKS add-on route then it becomes fully managed and updated In cluster ingress consumes and competes for AKS compute/memory resources where was with App Gateway separated from the cluster it won’t be leeching any of the AKS compute Full benefits of the Application Gateway such as WAF, cookie-based affinity, ssl termination amongst many others Limitations Application Gateway has some backend limits. Backend pools are limited to 100. Application Gateway does have a pricing implication Routing is directly to pod IP’s rather than the ClusterIP of the service. There is a feature request open for this Application Gateway Ingress Controller (AGIC)AGIC went to GA around the end of 2019 and offered the possibilities of hooking up an App Gateway as an attractive alternative for ingress into an AKS cluster. Before moving any further with AGIC, we need to understand at a high-level how networking works in AKS. There are two main network models: Kubenet networking Default option for Kubernetes out of the box Each Node receives an IP from the Azure virtual network subnet Pods in the node are not associated to the Azure vnet, they are assigned an IP address from the PodIPCidr and a route table is created by AKS Azure Container Networking Interface networking (CNI) Each pod itself receives an IPaddress from the Azure virtual network subnet Pods can be directly reached via their private IP from connected networks Pods can access resources in the vnet directly with out issues (e.g.: function app in the same vnet) It’s important to note, once you create an AKS cluster with a given network model you can’t change it; you will have to create a new one. There are advantages and disadvantages in both models which are listed in detail in this link. One key consideration to highlight is: Kubenet - /24 IP range can support up to 251 nodes (each subnet reserves the first 3 IP addresses for management operations). Given the maximum nodes per pod in Kubenet is 110, this configuration can support a maximum of 251 * 110 = 27,610 pods CNI - the same /24 IP range can support a maximum of 8 nodes (CNI has a max of thirty pods per node). So, this configuration can support a maximum of 240 When it comes to CNI you will have to plan for the IP addresses, you might need to a /16 range to get a bigger node count. There are also limitations with the kubenet that will need to be taken into consideration. With the AKS networking models out of the way, let’s look at AGIC; regardless of which model is chosen, the goal for AGIC is to ingress directly to the pod, a simple representation of this can be seen below. AGIC when deployed, runs in a pod in the AKS cluster and watches for changes, when changes are detected (i.e.: a new pod has been added or existing pod removed) these IP changes are propagated to the App Gateway via the Azure Resource Manager. If we went with the CNI networking model, then the pod would get IP address from the vnet and there would be a mapping in the App Gateway. Alternatively, with the Kubenet model this is how App Gateway will be setup, it will try to assign the same routable created by AKS to App Gateway’s subnet. It’s important to note, whichever model you choose the App Gateway will always connect directly to the pod and this is by design. Deploying AGICAGIC can be deployed in two ways either using Helm or as an AKS add-on. Each has their pros and cons, the key benefit of going via an AKS add-on will be that it will be fully managed and auto updated by Azure (i.e.: all updates, patching etc. for the AGIC will be taken care of automatically) whereas with Helm you will have to do that yourself. Let’s go ahead and deploy a demo AKS cluster with AGIC and see it in action to understand exactly what is going on. For the sake of simplicity, this demo will be creating an AKS cluster with CNI networking model and deploying the AGIC as and AKS add-on. Create an AKS clusterLogin and set the right subscription 12az loginaz account set -s "your-subcription-id" Create a new resource group 1az group create --name agicTestResourceGroup --location eastus Here we are creating a new AKS cluster with CNI networking model (–network-plugin azure) and we are setting up App Gateway as ingress and in this instance we are saying our App Gateway’s name is “testAppGateway” which doesn’t exist and will be created for us Create AKS cluster 1az aks create -n agicTestCluster -g agicTestResourceGroup --network-plugin azure --enable-managed-identity -a ingress-appgw --appgw-name testAppGateway --appgw-subnet-cidr "10.225.0.0/16" --generate-ssh-keys If we go into the Azure Portal, we can see two resource groups (one of them is what we created and this where the Azure managed AKS control plane is), the other resource group (MC_agicTestResourceGroup_agicTestCluster_eastus) is where the node pool, vnet, App Gateway etc all live, this resource group gets created automatically for us as part of the az aks create command. Deploy a sample APINow we have the AKS cluster up and running with AGIC deployed as an add-on, let’s deploy a sample API app and set ingress through the App Gateway. Get credentials to the AKS cluster 1az aks get-credentials -n agicTestCluster -g agicTestResourceGroup Deploy a sample API 1kubectl apply -f https://gist.githubusercontent.com/Ricky-G/59eb109913bd45d3e9229f9cf0a97edc/raw/b336047feecd9fd89fbe1a9627ac385b525124fe/sample-api-aks-deployment.yaml The above sample API deployment yaml was taken from the AGIC GitHub repo, the only change made to it was added a minimum of 10 replicas. We are saying we need 10 pods running this API. As soon as you run this you should see the app deployed as a service and 10 pods running successfully and there is a cluster-IPIP set for this (cluster-IP is an IP load balancer that Kubernetes creates, we just need to call this IP and our traffic will be forwarded to one of the 10 pods) Now if we go to the resource group where we have the actual Application Gateway and go to backend pool, we can see there is one here created by AGIC and if we dig into the pool all the IP addresses of the 10 pods are listed here. So, we have direct ingress to the pods from the Application Gateway. Finally, if we run the below command, we should see an ingress IP address for “aspnetapp” which is our sample API. This is the public IP of the Application Gateway, which has been wired up to ingress all the way to the pod. If we paste this IP into the browser, we can see sample aspnet site served from the pod. 1kubectl get ingress Right, so we have successfully ingressed all the way from public ip going via Application Gateway all the way to our pod. Benefits of AGIC Direct connection to the pods without an extra hop, this results in a performance benefit up to 50% lower network latency compared to in-cluster ingress Could make a huge difference in performance and latency sensitive applications and workloads If going the AKS add-on route then it becomes fully managed and updated In cluster ingress consumes and competes for AKS compute/memory resources where was with App Gateway separated from the cluster it won’t be leeching any of the AKS compute Full benefits of the Application Gateway such as WAF, cookie-based affinity, ssl termination amongst many others Limitations Application Gateway has some backend limits. Backend pools are limited to 100. Application Gateway does have a pricing implication Routing is directly to pod IP’s rather than the ClusterIP of the service. There is a feature request open for this Closing ThoughtsKey thing to keep in mind is the backend pool limitation of 100 . If you have more than 100 “ingres-able” services, then you would need multiple Application Gateway’s to cater for this. Although it is a supported scenario and straightforward to set up multiple App Gateways for one AKS cluster, your costs will pile up. At the start of this post, I mentioned a scenario of 2000+ services, in this case we would need 20 App Gateways; 2000 services / 100 = 20. Due to cost implications this won’t be palatable in most cases. On the plus side you get direct connection to the pod and can shave 50% of network latency. So, in this 2000+ services in one cluster scenario we could put the App Gateway as ingress for just latency sensitive apps/API’s and use another traditional in cluster-based ingress for all the other services. This way you get the best of both words while still keeping below the App Gateway max backend pool limits. One neat option for an in cluster-based ingress could be Web Application Routing, which is still in preview at the time of writing this. It’s a managed NGINX based solution that should work well as an in cluster-based ingress controller References AGIC main documentation AGIC GitHub Main image was taken from the Azure site and slightly modified","link":"/AKS/AGIC/application-gateway-ingress-controller-for-aks/"},{"title":"Azure Logic Apps Timeout","text":"🎯 TL;DR: Timeout Control Strategies for Azure Logic Apps Logic Apps default timeout behavior doesn’t match production requirements with HTTP triggers timing out at 3.9 minutes and workflow duration defaulting to 90 days. Problem: No granular timeout control per workflow causing long-running processes in production. Solutions: Global Runtime.Backend.FlowRunTimeout setting (minimum 7 days, affects all workflows) or per-workflow timeout branches using parallel “Delay” action with terminate condition for precise timeout control without impacting other workflows. Recently I got pulled into a production incident where a logic app was running for a long time (long time in this scenario was > 10 minutes), but the intention from the dev crew was they wanted this to time out in 60 seconds. These logic apps were a combination of HTTP triggers and Timer based. Logic App Default Time LimitsFirst things to keep in mind are some default limits. If its a HTTP based trigger the default timeout is around 3.9 minutes For most others the default max run duration of a logic app is 90 days and min is 7 days Ways To Change DefaultsWith that, here are a couple of quick ways to make sure your Logic App times out and terminates within the time frame you set. Lets say if we want our Logic App to run no more than 60 seconds at max then: You can change the setting Runtime.Backend.FlowRunTimeout from the default 90 days to 7 days (keep in mind the minimum for this setting is 7 days which is quite large, refer to this issue : https://github.com/Azure/logicapps/issues/782#issuecomment-1609008805) PRO: This will make sure that the Logic App runs for a maximum of 7 days only (which is quite large) CON: However this will apply to all the Logic Apps in the host/tenant, meaning if you had 15 logic apps then all 15 will have the 7 day limit Have a branch with in the Logic App itself to control the timeout (shown in the below diagram) PRO: You have full control of timeout per Logic App, so some can have 30 second time outs while others 60 seconds etc CON: There will be an extra branch/logic in your logic app Time-Out Branch In Logic AppBelow is how a potential timeout out setting in a Logic App could look like. You create a “Delay” branch and set the desired time limit, in the example below its 2 minutes so if the other flow takes longer than two minutes then the delay will finish, logic app will be terminated and a cancelled status will be returned to the user in the below example. Shout out to my colleague John B for this awesome idea. References Main image was taken from the Azure site Thumbnail image was taken from Azure SVG icons","link":"/Azure/Logic-Apps/azure-logic-apps-timeout/"},{"title":"Extracting GZip & Tar Files Natively in .NET Without External Libraries","text":"🎯 TL;DR: Native .tar.gz Extraction in .NET 7 Without External Dependencies Processing compressed .tar.gz files in Azure Functions traditionally required external libraries like SharpZipLib. Problem: External dependencies increase complexity and security surface area. Solution: .NET 7 introduces native System.Formats.Tar namespace alongside existing System.IO.Compression for GZip, enabling complete .tar.gz extraction without external dependencies. Implementation uses GZipStream for decompression and TarReader for archive extraction with proper entry type filtering and async operations. IntroductionImagine being in a scenario where a file of type .tar.gz lands in your Azure Blob Storage container. This file, when uncompressed, yields a collection of individual files. The trigger event for the arrival of this file is an Azure function, which springs into action, decompressing the contents and transferring them into a different container. In this context, a team may instinctively reach out for a robust library like SharpZipLib. However, what if there is a mandate to accomplish this without external dependencies? This becomes a reality with .NET 7. In .NET 7, native support for Tar files has been introduced, and GZip is catered to via System.IO.Compression. This means we can decompress a .tar.gz file natively in .NET 7, bypassing any need for external libraries. This post will walk you through this process, providing a practical example using .NET 7 to show how this can be achieved. .NET 7: Native TAR SupportAs of .NET 7, the System.Formats.Tar namespace was introduced to deal with TAR files, adding to the toolkit of .NET developers: System.Formats.Tar.TarFile to pack a directory into a TAR file or extract a TAR file to a directory System.Formats.Tar.TarReader to read a TAR file System.Formats.Tar.TarWriter to write a TAR file These new capabilities significantly simplify the process of working with TAR files in .NET. Lets dive in an have a look at a code sample that demonstrates how to extract a .tar.gz file natively in .NET 7. A Simple Example In .NET 7Below is an example demonstrating the extraction of a .tar.gz file natively in .NET 7 in a simple console app to extract the contents of a .tar.gz file to a directory 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253using System;using System.IO;using System.IO.Compression;using System.Formats.Tar;class Program{ static void Main(string[] args) { string sourceTarGzFilePath = @"C:\\_Temp\\test.tar.gz"; string targetDirectory = @"C:\\_Temp\\ExtractedFiles\\"; string tarFilePath = Path.ChangeExtension(sourceTarGzFilePath, ".tar"); Directory.CreateDirectory(targetDirectory); // Decompress the .gz file using (FileStream originalFileStream = File.OpenRead(sourceTarGzFilePath)) { using (FileStream decompressedFileStream = File.Create(tarFilePath)) { using (GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress)) { decompressionStream.CopyTo(decompressedFileStream); } } } // Extract the .tar file using (FileStream tarStream = File.OpenRead(tarFilePath)) { using (TarReader tarReader = new TarReader(tarStream)) { TarEntry entry; while ((entry = tarReader.GetNextEntryAsync().Result) != null) { if (entry.EntryType is TarEntryType.SymbolicLink or TarEntryType.HardLink or TarEntryType.GlobalExtendedAttributes) { continue; } Console.WriteLine($"Extracting {entry.Name}"); entry.ExtractToFileAsync(Path.Combine(targetDirectory, entry.Name), true).Wait(); } } } // Delete the temporary .tar file File.Delete(tarFilePath); Console.WriteLine("Extraction Completed"); }} You can also find this on GitHub Gist. Wrapping UpThe introduction of System.Formats.Tar in .NET 7 marks a significant milestone for developers dealing with .tar.gz files. It provides us with the ability to decompress these file types natively, without relying on external libraries. This functionality is a game-changer as it reduces complexity, minimizes external dependencies, and enhances the versatility of .NET applications. The new namespace System.Formats.Tar, along with the established System.IO.Compression, effectively handle TAR and GZip files. This considerably simplifies the process, making the .NET environment more self-contained and versatile. References Thumbnail image [was taken from the DotNet brand repo]https://github.com/dotnet/brand) Main image generated by [was taken from the DotNet brand repo]https://github.com/dotnet/brand)","link":"/Azure/Function-Apps/NET/extracting-gzip-tar-files-natively-in-net-without-external-libraries/"},{"title":"Azure DevTest Labs Policies","text":"🎯 TL;DR: DevTest Labs Policy Configuration with Bicep IaC Azure DevTest Labs documentation covers basic lab deployment but lacks policy configuration examples in Bicep. Problem: Missing guidance on linking policies to DevTest Labs using Infrastructure as Code. Solution: Use Microsoft.DevTestLab/labs/policysets resource with ‘default’ name as parent for policy definitions. Implementation includes VM size restrictions, user VM quotas, and premium SSD limits using evaluator types like AllowedValuesPolicy and MaxValuePolicy with proper threshold configurations. Azure DevTest Labs offers a powerful cloud-based development workstation environment and great alternative to a local development workstation/laptop when it comes to software development. This blog post is not so much talking about the benefits of DevTest Lab, but more about how to create policies for DevTest Labs using Bicep. Although there is a good support for deploying DevTest labs with Bicep, there is little to no documentation when it comes to creating policies for DevTest Labs in Bicep. In this blog post, we will focus on creating policies for DevTest Labs using Bicep and how to go about doing this. A Brief Overview of Azure DevTest LabsAzure DevTest Labs is a managed service that enables developers to quickly create, manage, and share development and test environments. It provides a range of features and tools designed to streamline the development process, minimize costs, and improve overall productivity. By leveraging the power of the cloud, developers can easily spin up virtual machines (VMs) pre-configured with the necessary tools, frameworks, and software needed for their projects. Existing Documentation LimitationsWhile the existing documentation covers various aspects of Azure DevTest Labs, it lacks clear guidance on setting up policies with DevTest Labs in Bicep. This blog post aims to address that gap by providing a Bicep script for creating a DevTest Lab and applying policies to it. Shout out to my colleague Illian Y for persisting and not giving up and finding a away around undocumented features and showing me. Existing Documentation For Creating a DevTest LabThe existing documentation for creating a DevTest Lab is pretty good, but when it comes to creating policies for DevTest Lab this is where the documentation falls short. The documentation does not provide a Bicep script for creating policies for DevTest Labs. Vanilla DevTest Lab1234567891011121314151617181920212223resource lab 'Microsoft.DevTestLab/labs@2018-09-15' = { name: 'testLab' location: 'australiacentral' tags: { tagName1: 'test-tag' tagName2: 'test-tag1' } properties: { environmentPermission: 'Contributor' labStorageType: 'Premium' mandatoryArtifactsResourceIdsLinux: [] mandatoryArtifactsResourceIdsWindows: [] premiumDataDisks: 'Disabled' announcement: { enabled: 'Disabled' expired: false } support: { enabled: 'Enabled' markdown: 'Test' } }} Creating Policies for DevTest Labs in BicepThe documentation states all the possible policies that can be created under the fact name in PolicyProperties Below is a list of three of those policies that can be created in Bicep. Allowed VM Sizes Allowed VMs Per User Allowed Premium SSD Per User Linking the policies to the DevTest LabsThis is the important glue that is missing from the documentation, how to link the policies to the DevTest Labs. The way to do this is to create a resource policySetParent and link it to the DevTest Labs. The policySetParent resource is then used as the parent for the policies. 1234resource policySetParent 'Microsoft.DevTestLab/labs/policysets@2018-09-15' existing = { parent: lab name: 'default'} Allowed VM Sizes1234567891011resource allowedVmSizesPolicies 'Microsoft.DevTestLab/labs/policysets/policies@2018-09-15' = { name: 'allowedVmSizesPolicy' location: location parent: policySetParent properties: { evaluatorType: 'AllowedValuesPolicy' factName: 'LabVmSize' status: 'Enabled' threshold: '["Standard_D4_v2","Standard_E4_v2"]' }} Allowed VM’s per user1234567891011resource allowedVmsPerUserPolicies 'Microsoft.DevTestLab/labs/policysets/policies@2018-09-15' = { name: 'allowedVmsPerUserPolicy' location: location parent: policySetParent properties: { evaluatorType: 'MaxValuePolicy' factName: 'UserOwnedLabVmCount' status: 'Enabled' threshold: '4' }} Allowed Premium SSD Per User1234567891011resource allowedPremiumSSDPerUserPolicies 'Microsoft.DevTestLab/labs/policysets/policies@2018-09-15' = { name: 'allowedPremiumSSDPerUserPolicy' location: location parent: policySetParent properties: { evaluatorType: 'MaxValuePolicy' factName: 'UserOwnedLabPremiumVmCount' status: 'Enabled' threshold: '4' }} References Main & thumbnail image was taken from the Azure site","link":"/Azure/DevTest-Labs/azure-devtest-labs-policies/"},{"title":"Unzipping and Shuffling GBs of Data Using Azure Functions","text":"🎯 TL;DR: Stream-Based Large File Processing in Azure Functions Processing multi-gigabyte zip files in Azure Functions requires streaming approach due to 1.5GB memory limit on Consumption plan. Problem: Large compressed files cannot be loaded entirely into memory for extraction. Solution: Stream-based unzipping using blob triggers with two implementation options: native .NET ZipArchive (slower but dependency-free) vs SharpZipLib (faster with custom buffer sizes). Architecture includes separate blob containers for zipped/unzipped files with Function App triggered by blob storage events for scalable data processing. Consider this situation: you have a zip file stored in an Azure Blob Storage container (or any other location for that matter). This isn’t just any zip file; it’s large, containing gigabytes of data. It could be big data sets for your machine learning projects, log files, media files, or backups. The specific content isn’t the focus - the size is. The task? We need to unzip this massive file(s) and relocate its contents to a different Azure Blob storage container. This task might seem daunting, especially considering the size of the file and the potential number of files that might be housed within it. Why do we need to do this? The use cases are numerous. Handling large data sets, moving data for analysis, making backups more accessible - these are just a few examples. The key here is that we’re looking for a scalable and reliable solution to handle this task efficiently. Azure Data Factory is arguably a better fit for this sort of task, but In this blog post, we will specifically demonstrate how to establish this process using Azure Functions. Specifically we will try to achieve this within the constraints of the Consumption plan tier, where the maximum memory is capped at 1.5GB, with the supporting roles of Azure CLI and PowerShell in our setup. Setting Up Our Azure EnvironmentBefore we dive into scripting and code, we need to set the stage - that means setting up our Azure environment. We’re going to create a storage account with two containers, one for our Zipped files and the other for Unzipped files. To create this setup, we’ll be using the Azure CLI. Why? Because it’s efficient and lets us script out the whole process if we need to do it again in the future. Install Azure CLI: If you haven’t already installed Azure CLI on your local machine, you can get it from here. Login to Azure: Open your terminal and type the following command to login to your Azure account. You’ll be prompted to enter your credentials. 1az login Create a Resource Group: We’ll need a Resource Group to keep our resources organized. We’ll call this rg-function-app-unzip-test and create it in the eastus location (you can ofcourse choose which ever region you like). 1az group create --name rg-function-app-unzip-test --location eastus Create a Storage Account: Next, we’ll create a storage account within our Resource Group. We’ll name it unziptststorageacct. 1az storage account create --name unziptststorageacct --resource-group rg-function-app-unzip-test --location eastus --sku Standard_LRS Create the Blob Containers: Finally, we’ll create our two containers, ‘Zipped’ and ‘Unzipped’ in the unziptststorageacct storage account. 12az storage container create --name zipped --account-name unziptststorageacctaz storage container create --name unzipped --account-name unziptststorageacct Now your Azure environment is ready with the specific resource group and storage account names you provided! We’ve got our storage account unziptststorageacct and two containers ‘Zipped’ and ‘Unzipped’ set up for our operations. The next step is to create our zip file. Concocting Our Data With PowerShellOur next task is to create a large zip file filled with multiple 100MB files, all brimming with random text. In a real world scenario you would already have these large files, but since we are simulating lets use PowerShell to create them. If you already have an existing zip file with large’ish files for testing, you can skip this step and use that file instead. 123456789101112131415161718192021# Set the number of files we want to create$fileCount = 10# The path where you want to create the TestFiles directory$directory = "C:\\_Temp\\TestFiles"# Create a new directory for our files if it doesn't already existif(-not (Test-Path -Path $directory)){ New-Item -ItemType Directory -Path $directory}# Loop through and create our filesfor ($i=1; $i -le $fileCount; $i++){ # Generate a 100MB file filled with random text and save it in our new directory $fileContent = New-Object byte[] 104857600 (New-Object Random).NextBytes($fileContent) [System.IO.File]::WriteAllBytes("$directory\\File$i.txt", $fileContent)}# Now that we have all our files, let's zip them upCompress-Archive -Path "$directory\\*" -DestinationPath "$directory.zip" This is a simple script that is creating 10 files, each 100MB in size, and then zipping them up into a single file. The resulting zip file should be around the 1GB in size. Incase you are wondering how we end up with a 1GB+ file by compressing 1GB worth of data? we are generating files filled with random bytes. Compression algorithms work by finding and eliminating redundancy in the data. Since random data has no redundancy, it cannot be compressed. In fact, trying to compress random data can even result in output that is slightly larger than the input, due to the overhead of the compression format. We’ll use this file to test our Azure Function. Azure Function To UnzipWe’re going to create a Function that magically springs into action the moment a blob (our zipped file) lands in the ‘Zipped’ container. This function will stream the data, unzip the files, and stores them neatly as individual files in the ‘Unzipped’ container. Before we begin, ensure that you’ve installed the Azure Functions Core Tools locally. You’d also need the Azure Functions Extension for Visual Studio Code. First lets use the CLI to create our consumption plan function app. We’ll call it unzipfunctionapp and use the unziptststorageacct storage account we created earlier. We’ll also specify the runtime as dotnet and the functions version as 4. We are using the consumption plan to demonstrate that this solution can work within the constraints of the consumption plan, where the maximum memory is capped at 1.5GB. 1az functionapp create --resource-group rg-function-app-unzip-test --consumption-plan-location eastus --runtime dotnet --functions-version 4 --name unzipfunctionapp123 --storage-account unziptststorageacct You might need to change the function name in the example about from ‘unzipfunctionapp123’. This could already be taken; this is because, Azure function app name must have Globally unique name.When you create Azure function app, you specify the name which becomes part of URL .azurewebsites.net If the function app name is already taken you will get an error ‘Website with given name unzipfunctionapp already exists.’ when you run the cli command above. Now that we have a consumption plan function infra, lets see the full code that will do the actual task of unzipping and uploadingThere are two code samples and both are quite similar in their basic approach. They both handle the data in a streaming manner, which allows them to deal with large files without consuming a lot of memory. However, there are some differences in the details of how they handle the streaming, which may have implications for their performance and resource usage: The first code sample uses the ZipArchive class from the .NET Framework, which provides a high-level, user-friendly interface for dealing with zip files. The second code sample uses the ZipInputStream class from the SharpZipLib library, which provides a lower-level, more flexible interface. In the first code sample, the ZipArchive automatically takes care of reading from the blob stream and unzipping the data. It provides an Open method for each entry in the zip file, which returns a stream that you can read the unzipped data from. In the second code sample, you manually read from the ZipInputStream and write to the blob stream using the StreamUtils.Copy method. The second code sample manually handles the buffer size with new byte[4096] for copying data from the zip input stream to the blob output stream. In contrast, the first code sample relies on the default buffer size provided by the UploadFromStreamAsync method. Memory wise both are similar (i.e.: they don’t download the entire zip file into memory), but the first script takes around 20 minutes to process a 1GB zip file (with 10 * 100 MB files), whereas the second script takes about 10 minutes for the same 1GB zip file. This mainly comes down to setting the custom buffer size and the optimizations in the SharpZipLib library First script has the benefit of not importing any custom library, but cant not run on an Azure consumption plan, at the time of this writing, consumption plan has a max 10 minute runtime. Second script can potentially run on a consumption plan, but comes at a cost of having to import a 3rd party library. References Thumbnail image was taken from the Azure site Main image generated by DALL-E","link":"/Azure/Function-Apps/unzipping-and-shuffling-gbs-of-data-using-azure-functions/"}],"tags":[{"name":"Azure","slug":"Azure","link":"/tags/Azure/"},{"name":"Azure Container Apps","slug":"Azure-Container-Apps","link":"/tags/Azure-Container-Apps/"},{"name":"Containers","slug":"Containers","link":"/tags/Containers/"},{"name":"Docker","slug":"Docker","link":"/tags/Docker/"},{"name":"DotNet","slug":"DotNet","link":"/tags/DotNet/"},{"name":"Azure DevOps","slug":"Azure-DevOps","link":"/tags/Azure-DevOps/"},{"name":"REST API","slug":"REST-API","link":"/tags/REST-API/"},{"name":"PowerShell","slug":"PowerShell","link":"/tags/PowerShell/"},{"name":"Policy Configuration","slug":"Policy-Configuration","link":"/tags/Policy-Configuration/"},{"name":"Code Search","slug":"Code-Search","link":"/tags/Code-Search/"},{"name":"AI","slug":"AI","link":"/tags/AI/"},{"name":"Microsoft Foundry","slug":"Microsoft-Foundry","link":"/tags/Microsoft-Foundry/"},{"name":"Private Endpoints","slug":"Private-Endpoints","link":"/tags/Private-Endpoints/"},{"name":"Private Link","slug":"Private-Link","link":"/tags/Private-Link/"},{"name":"Networking","slug":"Networking","link":"/tags/Networking/"},{"name":"Security","slug":"Security","link":"/tags/Security/"},{"name":"Hexo","slug":"Hexo","link":"/tags/Hexo/"},{"name":"Personal","slug":"Personal","link":"/tags/Personal/"},{"name":"Blog","slug":"Blog","link":"/tags/Blog/"},{"name":"OpenAI","slug":"OpenAI","link":"/tags/OpenAI/"},{"name":"Speech Services","slug":"Speech-Services","link":"/tags/Speech-Services/"},{"name":"Realtime","slug":"Realtime","link":"/tags/Realtime/"},{"name":"C#","slug":"C","link":"/tags/C/"},{"name":"DevOps","slug":"DevOps","link":"/tags/DevOps/"},{"name":"Development Environment","slug":"Development-Environment","link":"/tags/Development-Environment/"},{"name":"Dev Containers","slug":"Dev-Containers","link":"/tags/Dev-Containers/"},{"name":"Remote Development","slug":"Remote-Development","link":"/tags/Remote-Development/"},{"name":"VS Code","slug":"VS-Code","link":"/tags/VS-Code/"},{"name":"Collaboration","slug":"Collaboration","link":"/tags/Collaboration/"},{"name":"Virtual Machines","slug":"Virtual-Machines","link":"/tags/Virtual-Machines/"},{"name":"AutoGen","slug":"AutoGen","link":"/tags/AutoGen/"},{"name":"AI Development","slug":"AI-Development","link":"/tags/AI-Development/"},{"name":"Storage","slug":"Storage","link":"/tags/Storage/"},{"name":"Azurite","slug":"Azurite","link":"/tags/Azurite/"},{"name":"Function Apps","slug":"Function-Apps","link":"/tags/Function-Apps/"},{"name":"Logic Apps","slug":"Logic-Apps","link":"/tags/Logic-Apps/"},{"name":"TFVC","slug":"TFVC","link":"/tags/TFVC/"},{"name":"TFS","slug":"TFS","link":"/tags/TFS/"},{"name":"Azure Communication Services","slug":"Azure-Communication-Services","link":"/tags/Azure-Communication-Services/"},{"name":"Azure AI Agent Service","slug":"Azure-AI-Agent-Service","link":"/tags/Azure-AI-Agent-Service/"},{"name":"Voice Live API","slug":"Voice-Live-API","link":"/tags/Voice-Live-API/"},{"name":"AI Voice Agents","slug":"AI-Voice-Agents","link":"/tags/AI-Voice-Agents/"},{"name":"Contact Center","slug":"Contact-Center","link":"/tags/Contact-Center/"},{"name":"Python","slug":"Python","link":"/tags/Python/"},{"name":"WebRTC","slug":"WebRTC","link":"/tags/WebRTC/"},{"name":"Kubernetes","slug":"Kubernetes","link":"/tags/Kubernetes/"},{"name":"LLMs","slug":"LLMs","link":"/tags/LLMs/"},{"name":"Local GPUs","slug":"Local-GPUs","link":"/tags/Local-GPUs/"},{"name":"GitHub","slug":"GitHub","link":"/tags/GitHub/"},{"name":"GitHub Copilot","slug":"GitHub-Copilot","link":"/tags/GitHub-Copilot/"},{"name":"AKS","slug":"AKS","link":"/tags/AKS/"},{"name":"Azure App Service","slug":"Azure-App-Service","link":"/tags/Azure-App-Service/"},{"name":"CI/CD","slug":"CI-CD","link":"/tags/CI-CD/"},{"name":"IP Restrictions","slug":"IP-Restrictions","link":"/tags/IP-Restrictions/"},{"name":"Serverless","slug":"Serverless","link":"/tags/Serverless/"},{"name":"Single Threaded Apps","slug":"Single-Threaded-Apps","link":"/tags/Single-Threaded-Apps/"},{"name":"Ingress","slug":"Ingress","link":"/tags/Ingress/"},{"name":"AGIC","slug":"AGIC","link":"/tags/AGIC/"},{"name":"Application Gateway","slug":"Application-Gateway","link":"/tags/Application-Gateway/"},{"name":"Azure Logic Apps","slug":"Azure-Logic-Apps","link":"/tags/Azure-Logic-Apps/"},{"name":".NET","slug":"NET","link":"/tags/NET/"},{"name":"Azure Blob Storage","slug":"Azure-Blob-Storage","link":"/tags/Azure-Blob-Storage/"},{"name":"GZip","slug":"GZip","link":"/tags/GZip/"},{"name":"Tar","slug":"Tar","link":"/tags/Tar/"},{"name":"Azure Dev Test Labs","slug":"Azure-Dev-Test-Labs","link":"/tags/Azure-Dev-Test-Labs/"},{"name":"Developer Environments","slug":"Developer-Environments","link":"/tags/Developer-Environments/"},{"name":"Azure Policy","slug":"Azure-Policy","link":"/tags/Azure-Policy/"},{"name":"Azure CLI","slug":"Azure-CLI","link":"/tags/Azure-CLI/"}],"categories":[{"name":"Azure DevOps","slug":"Azure-DevOps","link":"/categories/Azure-DevOps/"},{"name":"Azure","slug":"Azure","link":"/categories/Azure/"},{"name":"Azure DevOps API","slug":"Azure-DevOps/Azure-DevOps-API","link":"/categories/Azure-DevOps/Azure-DevOps-API/"},{"name":"Blog","slug":"Blog","link":"/categories/Blog/"},{"name":"Storage","slug":"Azure/Storage","link":"/categories/Azure/Storage/"},{"name":"AI","slug":"Azure/AI","link":"/categories/Azure/AI/"},{"name":"Engineering","slug":"Engineering","link":"/categories/Engineering/"},{"name":"AI","slug":"AI","link":"/categories/AI/"},{"name":"GitHub","slug":"GitHub","link":"/categories/GitHub/"},{"name":"AKS","slug":"AKS","link":"/categories/AKS/"},{"name":"Function Apps","slug":"Azure/Function-Apps","link":"/categories/Azure/Function-Apps/"},{"name":"Container Apps","slug":"Azure/Container-Apps","link":"/categories/Azure/Container-Apps/"},{"name":"DevOps","slug":"Azure/DevOps","link":"/categories/Azure/DevOps/"},{"name":"Logic Apps","slug":"Azure/Logic-Apps","link":"/categories/Azure/Logic-Apps/"},{"name":"Azurite","slug":"Azure/Storage/Azurite","link":"/categories/Azure/Storage/Azurite/"},{"name":"Networking","slug":"Azure/AI/Networking","link":"/categories/Azure/AI/Networking/"},{"name":"Voice Live API","slug":"Azure/AI/Voice-Live-API","link":"/categories/Azure/AI/Voice-Live-API/"},{"name":"Tooling","slug":"Engineering/Tooling","link":"/categories/Engineering/Tooling/"},{"name":"LLMs","slug":"AI/LLMs","link":"/categories/AI/LLMs/"},{"name":"Copilot","slug":"GitHub/Copilot","link":"/categories/GitHub/Copilot/"},{"name":"Networking","slug":"AKS/Networking","link":"/categories/AKS/Networking/"},{"name":"Actions","slug":"GitHub/Actions","link":"/categories/GitHub/Actions/"},{"name":"Speech","slug":"Azure/AI/Speech","link":"/categories/Azure/AI/Speech/"},{"name":"Security","slug":"Azure/Function-Apps/Security","link":"/categories/Azure/Function-Apps/Security/"},{"name":"Development","slug":"Azure/DevOps/Development","link":"/categories/Azure/DevOps/Development/"},{"name":"AGIC","slug":"AKS/AGIC","link":"/categories/AKS/AGIC/"},{"name":".NET","slug":"Azure/Function-Apps/NET","link":"/categories/Azure/Function-Apps/NET/"},{"name":"DevTest Labs","slug":"Azure/DevTest-Labs","link":"/categories/Azure/DevTest-Labs/"}],"pages":[{"title":"Cookie Policy & Privacy Information","text":"Your privacy is respected. This blog is a static website hosted on GitHub Pages. No backend databases are operated, and no personal information is stored on the servers. All content is publicly available on GitHub. The only data collection that occurs is through third-party services (detailed below) that help understand site usage and provide enhanced functionality. To learn more about how this blog is built, see the Hello World 👋 blog post which details all the technology and hosting infrastructure used, including GitHub Pages. This website uses cookies to enhance browsing experience and provide certain functionality. By using this site, consent to the use of cookies in accordance with this policy is given. What Are Cookies?Cookies are small text files that are placed on your computer or mobile device when you visit a website. They are widely used to make websites work more efficiently and provide information to website owners. Learn more about cookies on Wikipedia. Cookies UsedEssential CookiesThese cookies are necessary for the website to function properly: Session cookies: Maintain sessions while browsing Analytics CookiesGoogle Analytics is used to understand how visitors interact with the website: Google Analytics Purpose: Analyze site traffic, page views, and user behavior Data collected: Pages visited, time spent, browser type, device type, geographic location Retention: Up to 26 months Google Analytics Privacy Policy Advertising CookiesAdvertisements are displayed through Google AdSense: Google AdSense Purpose: Display relevant advertisements Data collected: Browsing behavior, interests, demographic information Google Ads Privacy Policy Social Sharing CookiesShareThis is used for social media sharing: ShareThis Purpose: Enable content sharing on social media platforms Data collected: Pages shared, social media interactions ShareThis Privacy Policy Comment System CookiesDisqus is used for comments: Disqus Purpose: Provide commenting functionality Data collected: Username, email, comment content, IP address Disqus Privacy Policy Managing CookiesBrowser SettingsYou can control and/or delete cookies as you wish. You can delete all cookies that are already on your computer and you can set most browsers to prevent them from being placed. Chrome: Settings → Privacy and security → Cookies and other site data Firefox: Settings → Privacy & Security → Cookies and Site Data Safari: Preferences → Privacy → Cookies and website data Edge: Settings → Cookies and site permissions Opt-Out Options Google Analytics: Install the Google Analytics Opt-out Browser Add-on Google Ads: Visit Google Ads Settings Do Not Track: Enable “Do Not Track” in your browser settings Declining CookiesWhen you first visit this site, you’ll see a cookie consent banner. You can choose to: Allow cookies: Accept all cookies for full functionality Decline: Reject non-essential cookies (may limit some features) Third-Party LinksThis website may contain links to third-party websites. No responsibility is taken for the privacy practices or content of those websites. Their privacy policies should be reviewed separately. Data Retention Google Analytics: Data is retained for 26 months AdSense: Data retention varies based on Google’s policies Disqus: Comment data is retained indefinitely unless deleted by the user Contact InformationQuestions about this cookie policy can be directed to: GitHub: github.com/Ricky-G LinkedIn: linkedin.com/in/rickygummadi Changes to This PolicyThis cookie policy may be updated from time to time. Any changes will be posted on this page with an updated revision date. Last Updated: December 29, 2025 Additional Resources About Cookies EU Cookie Law GDPR Information","link":"/cookies/index.html"},{"title":"Hi 👋, I'm Ricky","text":"Thanks for stopping by 🙂!! I am Ricky Gummadi and I am based out of Auckland, New Zealand. This blog is a space for me to document and share my thoughts & ideas. As Scott Hanselman said “if you get asked a good question, you should post it to share rather than reply by email” and this is the medium for me to do just that. During the day I work for Microsoft as a Cloud & AI architect, in the evenings I work on my own projects and private consulting. Please note that all opinions, insights, and content shared on this blog are my personal views and do not represent the official position or policies of Microsoft or any other organization I may be affiliated with. 👍🏻 Feel free to connect with me on LinkedIn Connect on LinkedIn How This Blog Was BuiltBefore AI coding assistants were a thing, I set out to build this blog the old-fashioned way evaluating frameworks, weighing trade-offs, and making deliberate choices. Here is the short version (the full story is in my Hello World 👋 post). Requirements were simple: easy to author (Markdown), easy to build, easy to maintain, and most features (search, tags, categories) should work out of the box. Hosting had to be free, handle reasonable traffic, and support CI/CD. I evaluated: Orchard CMS, Hugo, Ghost, Jekyll + GitHub Pages, and Hexo. Hugo was impressive but geared more towards generic sites. Jekyll + GitHub Pages was strong but meant learning Ruby tooling. Hexo a dedicated JavaScript-based static site generator checked every box and came with 360+ community themes. The stack I landed on: Hexo Generates the entire blog as a static site from Markdown files Icarus theme Clean, feature-rich, and community maintained GitHub Pages Free hosting with a 100 GB bandwidth soft limit GitHub Actions Builds and deploys the site automatically on every push Bulma CSS framework to enrich styling beyond plain Markdown The result is a zero-cost, low-maintenance blog where I write in Markdown, push to GitHub, and the site is live within minutes. No databases, no servers, no ongoing costs. Tech Stack GitHub Presentations & TalksI regularly speak at user groups, webinars, and community events across New Zealand and online. Below is a selection of sessions I have presented. Recorded Sessions Microsoft Auckland Reactor Pop-Up Developing To Azure Kubernetes Service with Draft Microsoft Events & Webinars From Legacy to Cloud-Native AI-Ready Applications Strategies and tools for transitioning legacy apps, processes, and data to cloud-native and AI-ready technologies. Covers automated code upgrades, re-platforming to Azure, refactoring to cloud-native architectures, DevOps modernisation, zero-trust security, and accelerating modernisation with GitHub Copilot AI agents. AZBC: From Legacy to Cloud-Native AI-Ready Applications From Legacy to Cloud-Native AI-Ready Applications (On-Demand) Community User Groups & Meetups Auckland .NET User Group Sessions on .NET modernisation, cloud-native patterns, and building with Azure Auckland Azure User Group Deep dives into Azure services, infrastructure as code, and real-world architecture patterns Software & Developer Community Meetups Talks covering containers, Kubernetes, serverless, DevOps practices, and developer tooling AI & Cloud Sessions Application modernisation with Azure and GitHub Copilot Cloud-native pillars: DevOps, containers, serverless, APIs, and data integration AI-assisted development and deployment on Azure Building AI agents and voice-enabled applications with Azure AI services Running LLMs on consumer hardware","link":"/about/index.html"},{"title":"","text":".icon-animate{color:red;display:inline-block;margin:0 5px;animation:iconAnimate 1.33s ease-in-out infinite}@-moz-keyframes iconAnimate{0%,100%{transform:scale(1)}10%,30%{transform:scale(.9)}20%,40%,60%,80%{transform:scale(1.1)}50%,70%{transform:scale(1.1)}}@-webkit-keyframes iconAnimate{0%,100%{transform:scale(1)}10%,30%{transform:scale(.9)}20%,40%,60%,80%{transform:scale(1.1)}50%,70%{transform:scale(1.1)}}@-o-keyframes iconAnimate{0%,100%{transform:scale(1)}10%,30%{transform:scale(.9)}20%,40%,60%,80%{transform:scale(1.1)}50%,70%{transform:scale(1.1)}}@keyframes iconAnimate{0%,100%{transform:scale(1)}10%,30%{transform:scale(.9)}20%,40%,60%,80%{transform:scale(1.1)}50%,70%{transform:scale(1.1)}}@-moz-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@-webkit-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@-o-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}.breadcrumb,.button,.delete,.file,.is-unselectable,.modal-close,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:\" \";display:block;height:.625em;margin-top:-.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.box:not(:last-child),.breadcrumb:not(:last-child),.content:not(:last-child),.highlight:not(:last-child),.level:not(:last-child),.list:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.pagination:not(:last-child),.progress:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.tabs:not(:last-child),.title:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:290486px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete::after,.delete::before,.modal-close::after,.modal-close::before{background-color:#fff;content:\"\";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:focus,.delete:hover,.modal-close:focus,.modal-close:hover{background-color:rgba(10,10,10,.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,.4)}.delete.is-small,.modal-close.is-small{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.delete.is-medium,.modal-close.is-medium{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.delete.is-large,.modal-close.is-large{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.control.is-loading::after,.loader,.select.is-loading::after{animation:spinAround .5s infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:\"\";display:block;height:1em;position:relative;width:1em}.hero-video,.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img,.is-overlay,.modal,.modal-background{bottom:0;left:0;position:absolute;right:0;top:0}.button,.file-cta,.file-name,.input,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.select select,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.25em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button.is-active,.button.is-focused,.button:active,.button:focus,.file-cta.is-active,.file-cta.is-focused,.file-cta:active,.file-cta:focus,.file-name.is-active,.file-name.is-focused,.file-name:active,.file-name:focus,.input.is-active,.input.is-focused,.input:active,.input:focus,.pagination-ellipsis.is-active,.pagination-ellipsis.is-focused,.pagination-ellipsis:active,.pagination-ellipsis:focus,.pagination-link.is-active,.pagination-link.is-focused,.pagination-link:active,.pagination-link:focus,.pagination-next.is-active,.pagination-next.is-focused,.pagination-next:active,.pagination-next:focus,.pagination-previous.is-active,.pagination-previous.is-focused,.pagination-previous:active,.pagination-previous:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus{outline:0}.button[disabled],.file-cta[disabled],.file-name[disabled],.input[disabled],.pagination-ellipsis[disabled],.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled],.select select[disabled],.textarea[disabled],fieldset[disabled] .button,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .input,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-previous,fieldset[disabled] .select select,fieldset[disabled] .textarea{cursor:not-allowed}blockquote,body,dd,dl,dt,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,html,iframe,legend,li,ol,p,pre,textarea,ul{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,::after,::before{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:left}html{background-color:#f7f7f7;font-size:14px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,select,textarea{font-family:Ubuntu,Roboto,'Open Sans','Microsoft YaHei',sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:'Source Code Pro',monospace,'Microsoft YaHei'}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#3273dc;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#f14668;font-size:.875em;font-weight:400;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:left}table th{color:#363636}.is-clearfix::after{clear:both;content:\" \";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-clipped{overflow:hidden!important}.is-size-1{font-size:3rem!important}.is-size-2{font-size:2.5rem!important}.is-size-3{font-size:2rem!important}.is-size-4{font-size:1.5rem!important}.is-size-5{font-size:1.25rem!important}.is-size-6,article.media .title{font-size:1rem!important}.article-licensing .licensing-meta h6,.article-licensing .licensing-title a,.is-size-7,article.media .categories,article.media .date{font-size:.85rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}.is-size-2-mobile{font-size:2.5rem!important}.is-size-3-mobile{font-size:2rem!important}.is-size-4-mobile{font-size:1.5rem!important}.is-size-5-mobile{font-size:1.25rem!important}.is-size-6-mobile{font-size:1rem!important}.is-size-7-mobile{font-size:.85rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}.is-size-2-tablet{font-size:2.5rem!important}.is-size-3-tablet{font-size:2rem!important}.is-size-4-tablet{font-size:1.5rem!important}.is-size-5-tablet{font-size:1.25rem!important}.is-size-6-tablet{font-size:1rem!important}.is-size-7-tablet{font-size:.85rem!important}}@media screen and (max-width:1087px){.is-size-1-touch{font-size:3rem!important}.is-size-2-touch{font-size:2.5rem!important}.is-size-3-touch{font-size:2rem!important}.is-size-4-touch{font-size:1.5rem!important}.is-size-5-touch{font-size:1.25rem!important}.is-size-6-touch{font-size:1rem!important}.is-size-7-touch{font-size:.85rem!important}}@media screen and (min-width:1088px){.is-size-1-desktop{font-size:3rem!important}.is-size-2-desktop{font-size:2.5rem!important}.is-size-3-desktop{font-size:2rem!important}.is-size-4-desktop{font-size:1.5rem!important}.is-size-5-desktop{font-size:1.25rem!important}.is-size-6-desktop{font-size:1rem!important}.is-size-7-desktop{font-size:.85rem!important}}@media screen and (min-width:1280px){.is-size-1-widescreen{font-size:3rem!important}.is-size-2-widescreen{font-size:2.5rem!important}.is-size-3-widescreen{font-size:2rem!important}.is-size-4-widescreen{font-size:1.5rem!important}.is-size-5-widescreen{font-size:1.25rem!important}.is-size-6-widescreen{font-size:1rem!important}.is-size-7-widescreen{font-size:.85rem!important}}@media screen and (min-width:1472px){.is-size-1-fullhd{font-size:3rem!important}.is-size-2-fullhd{font-size:2.5rem!important}.is-size-3-fullhd{font-size:2rem!important}.is-size-4-fullhd{font-size:1.5rem!important}.is-size-5-fullhd{font-size:1.25rem!important}.is-size-6-fullhd{font-size:1rem!important}.is-size-7-fullhd{font-size:.85rem!important}}.has-text-centered{text-align:center!important}.has-text-justified{text-align:justify!important}.has-text-left{text-align:left!important}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1087px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1087px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1088px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1088px) and (max-width:1279px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1280px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1280px) and (max-width:1471px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1472px){.has-text-centered-fullhd{text-align:center!important}}@media screen and (max-width:768px){.has-text-justified-mobile{text-align:justify!important}}@media screen and (min-width:769px),print{.has-text-justified-tablet{text-align:justify!important}}@media screen and (min-width:769px) and (max-width:1087px){.has-text-justified-tablet-only{text-align:justify!important}}@media screen and (max-width:1087px){.has-text-justified-touch{text-align:justify!important}}@media screen and (min-width:1088px){.has-text-justified-desktop{text-align:justify!important}}@media screen and (min-width:1088px) and (max-width:1279px){.has-text-justified-desktop-only{text-align:justify!important}}@media screen and (min-width:1280px){.has-text-justified-widescreen{text-align:justify!important}}@media screen and (min-width:1280px) and (max-width:1471px){.has-text-justified-widescreen-only{text-align:justify!important}}@media screen and (min-width:1472px){.has-text-justified-fullhd{text-align:justify!important}}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1087px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1087px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1088px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1088px) and (max-width:1279px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1280px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1280px) and (max-width:1471px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1472px){.has-text-left-fullhd{text-align:left!important}}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1087px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1087px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1088px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1088px) and (max-width:1279px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1280px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1280px) and (max-width:1471px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1472px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase,article.media .categories{text-transform:uppercase!important}.is-italic{font-style:italic!important}.has-text-white{color:#fff!important}a.has-text-white:focus,a.has-text-white:hover{color:#e6e6e6!important}.has-background-white{background-color:#fff!important}.has-text-black{color:#0a0a0a!important}a.has-text-black:focus,a.has-text-black:hover{color:#000!important}.has-background-black{background-color:#0a0a0a!important}.has-text-light{color:#f5f5f5!important}a.has-text-light:focus,a.has-text-light:hover{color:#dbdbdb!important}.has-background-light{background-color:#f5f5f5!important}.has-text-dark{color:#363636!important}a.has-text-dark:focus,a.has-text-dark:hover{color:#1c1c1c!important}.has-background-dark{background-color:#363636!important}.has-text-primary{color:#3273dc!important}a.has-text-primary:focus,a.has-text-primary:hover{color:#205bbc!important}.has-background-primary{background-color:#3273dc!important}.has-text-link{color:#3273dc!important}a.has-text-link:focus,a.has-text-link:hover{color:#205bbc!important}.has-background-link{background-color:#3273dc!important}.has-text-info{color:#3298dc!important}a.has-text-info:focus,a.has-text-info:hover{color:#207dbc!important}.has-background-info{background-color:#3298dc!important}.has-text-success{color:#48c774!important}a.has-text-success:focus,a.has-text-success:hover{color:#34a85c!important}.has-background-success{background-color:#48c774!important}.has-text-warning{color:#ffdd57!important}a.has-text-warning:focus,a.has-text-warning:hover{color:#ffd324!important}.has-background-warning{background-color:#ffdd57!important}.has-text-danger{color:#f14668!important}a.has-text-danger:focus,a.has-text-danger:hover{color:#ee1742!important}.has-background-danger{background-color:#f14668!important}.has-text-grey-lightest{color:#ededed!important}a.has-text-grey-lightest:focus,a.has-text-grey-lightest:hover{color:#d4d4d4!important}.has-background-grey-lightest{background-color:#ededed!important}.has-text-black-bis{color:#121212!important}.has-background-black-bis{background-color:#121212!important}.has-text-black-ter{color:#242424!important}.has-background-black-ter{background-color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-background-grey-darker{background-color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-background-grey-dark{background-color:#4a4a4a!important}.article-licensing .licensing-title a,.has-text-grey{color:#7a7a7a!important}.has-background-grey{background-color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-background-grey-light{background-color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-background-grey-lighter{background-color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-background-white-ter{background-color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.has-background-white-bis{background-color:#fafafa!important}.has-text-weight-light{font-weight:300!important}.has-text-weight-normal{font-weight:400!important}.has-text-weight-medium{font-weight:500!important}.has-text-weight-semibold{font-weight:600!important}.has-text-weight-bold{font-weight:700!important}.is-family-primary{font-family:Ubuntu,Roboto,'Open Sans','Microsoft YaHei',sans-serif!important}.is-family-secondary{font-family:Ubuntu,Roboto,'Open Sans','Microsoft YaHei',sans-serif!important}.is-family-sans-serif{font-family:Ubuntu,Roboto,'Open Sans','Microsoft YaHei',sans-serif!important}.is-family-monospace{font-family:monospace!important}.is-family-code{font-family:'Source Code Pro',monospace,'Microsoft YaHei'!important}.is-block{display:block!important}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1087px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1087px){.is-block-touch{display:block!important}}@media screen and (min-width:1088px){.is-block-desktop{display:block!important}}@media screen and (min-width:1088px) and (max-width:1279px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1280px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1280px) and (max-width:1471px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1472px){.is-block-fullhd{display:block!important}}.is-flex{display:flex!important}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1087px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1087px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1088px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1088px) and (max-width:1279px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1280px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1280px) and (max-width:1471px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1472px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline!important}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1087px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1087px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1088px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1088px) and (max-width:1279px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1280px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1280px) and (max-width:1471px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1472px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block!important}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1087px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1087px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1088px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1088px) and (max-width:1279px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1280px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1280px) and (max-width:1471px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1472px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex!important}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1087px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1087px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1088px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1088px) and (max-width:1279px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1280px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1280px) and (max-width:1471px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1472px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}.is-sr-only{border:none!important;clip:rect(0,0,0,0)!important;height:.01em!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:.01em!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1087px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1087px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1088px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1088px) and (max-width:1279px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1280px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1280px) and (max-width:1471px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1472px){.is-hidden-fullhd{display:none!important}}.is-invisible{visibility:hidden!important}@media screen and (max-width:768px){.is-invisible-mobile{visibility:hidden!important}}@media screen and (min-width:769px),print{.is-invisible-tablet{visibility:hidden!important}}@media screen and (min-width:769px) and (max-width:1087px){.is-invisible-tablet-only{visibility:hidden!important}}@media screen and (max-width:1087px){.is-invisible-touch{visibility:hidden!important}}@media screen and (min-width:1088px){.is-invisible-desktop{visibility:hidden!important}}@media screen and (min-width:1088px) and (max-width:1279px){.is-invisible-desktop-only{visibility:hidden!important}}@media screen and (min-width:1280px){.is-invisible-widescreen{visibility:hidden!important}}@media screen and (min-width:1280px) and (max-width:1471px){.is-invisible-widescreen-only{visibility:hidden!important}}@media screen and (min-width:1472px){.is-invisible-fullhd{visibility:hidden!important}}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-relative{position:relative!important}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#3273dc;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:\"\\0002f\"}.breadcrumb ol,.breadcrumb ul{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:\"\\02192\"}.breadcrumb.has-bullet-separator li+li::before{content:\"\\02022\"}.breadcrumb.has-dot-separator li+li::before{content:\"\\000b7\"}.breadcrumb.has-succeeds-separator li+li::before{content:\"\\0227B\"}.card{background-color:#fff;box-shadow:rgba(0,0,0,.15) 0 2px 8px!important;color:#4a4a4a;max-width:100%;position:relative}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:.75rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:left;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#3273dc;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .subtitle,.level-item .title{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width:769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.list{background-color:#fff;border-radius:4px;box-shadow:0 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1)}.list-item{display:block;padding:.5em 1em}.list-item:not(a){color:#4a4a4a}.list-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-item:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px}.list-item:not(:last-child){border-bottom:1px solid #dbdbdb}.list-item.is-active{background-color:#3273dc;color:#fff}a.list-item{background-color:#f5f5f5;cursor:pointer}.media{align-items:flex-start;display:flex;text-align:left}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:left}@media screen and (max-width:768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#eef3fc;color:#3273dc}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#eef3fc}.message.is-primary .message-header{background-color:#3273dc;color:#fff}.message.is-primary .message-body{border-color:#3273dc;color:#2160c4}.message.is-link{background-color:#eef3fc}.message.is-link .message-header{background-color:#3273dc;color:#fff}.message.is-link .message-body{border-color:#3273dc;color:#2160c4}.message.is-info{background-color:#eef6fc}.message.is-info .message-header{background-color:#3298dc;color:#fff}.message.is-info .message-body{border-color:#3298dc;color:#1d72aa}.message.is-success{background-color:#effaf3}.message.is-success .message-header{background-color:#48c774;color:#fff}.message.is-success .message-body{border-color:#48c774;color:#257942}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#947600}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message.is-grey-lightest{background-color:#fafafa}.message.is-grey-lightest .message-header{background-color:#ededed;color:#363636}.message.is-grey-lightest .message-body{border-color:#ededed}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-card,.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px),print{.modal-card,.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-foot,.modal-card-head{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link,.navbar.is-white .navbar-brand>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width:1088px){.navbar.is-white .navbar-end .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-start>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link::after,.navbar.is-white .navbar-start .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand .navbar-link,.navbar.is-black .navbar-brand>.navbar-item{color:#fff}.navbar.is-black .navbar-brand .navbar-link.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width:1088px){.navbar.is-black .navbar-end .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-start>.navbar-item{color:#fff}.navbar.is-black .navbar-end .navbar-link.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-end .navbar-link::after,.navbar.is-black .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link,.navbar.is-light .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1088px){.navbar.is-light .navbar-end .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link::after,.navbar.is-light .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand .navbar-link,.navbar.is-dark .navbar-brand>.navbar-item{color:#fff}.navbar.is-dark .navbar-brand .navbar-link.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width:1088px){.navbar.is-dark .navbar-end .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-start>.navbar-item{color:#fff}.navbar.is-dark .navbar-end .navbar-link.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-end .navbar-link::after,.navbar.is-dark .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#3273dc;color:#fff}.navbar.is-primary .navbar-brand .navbar-link,.navbar.is-primary .navbar-brand>.navbar-item{color:#fff}.navbar.is-primary .navbar-brand .navbar-link.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width:1088px){.navbar.is-primary .navbar-end .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-start>.navbar-item{color:#fff}.navbar.is-primary .navbar-end .navbar-link.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-primary .navbar-end .navbar-link::after,.navbar.is-primary .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link{background-color:#2366d1;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#3273dc;color:#fff}}.navbar.is-link{background-color:#3273dc;color:#fff}.navbar.is-link .navbar-brand .navbar-link,.navbar.is-link .navbar-brand>.navbar-item{color:#fff}.navbar.is-link .navbar-brand .navbar-link.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width:1088px){.navbar.is-link .navbar-end .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-start>.navbar-item{color:#fff}.navbar.is-link .navbar-end .navbar-link.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-end .navbar-link::after,.navbar.is-link .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#3273dc;color:#fff}}.navbar.is-info{background-color:#3298dc;color:#fff}.navbar.is-info .navbar-brand .navbar-link,.navbar.is-info .navbar-brand>.navbar-item{color:#fff}.navbar.is-info .navbar-brand .navbar-link.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width:1088px){.navbar.is-info .navbar-end .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-start>.navbar-item{color:#fff}.navbar.is-info .navbar-end .navbar-link.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-end .navbar-link::after,.navbar.is-info .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3298dc;color:#fff}}.navbar.is-success{background-color:#48c774;color:#fff}.navbar.is-success .navbar-brand .navbar-link,.navbar.is-success .navbar-brand>.navbar-item{color:#fff}.navbar.is-success .navbar-brand .navbar-link.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width:1088px){.navbar.is-success .navbar-end .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-start>.navbar-item{color:#fff}.navbar.is-success .navbar-end .navbar-link.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-end .navbar-link::after,.navbar.is-success .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c774;color:#fff}}.navbar.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link,.navbar.is-warning .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1088px){.navbar.is-warning .navbar-end .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link::after,.navbar.is-warning .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffdd57;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand .navbar-link,.navbar.is-danger .navbar-brand>.navbar-item{color:#fff}.navbar.is-danger .navbar-brand .navbar-link.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width:1088px){.navbar.is-danger .navbar-end .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-start>.navbar-item{color:#fff}.navbar.is-danger .navbar-end .navbar-link.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-end .navbar-link::after,.navbar.is-danger .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar.is-grey-lightest{background-color:#ededed;color:#363636}.navbar.is-grey-lightest .navbar-brand .navbar-link,.navbar.is-grey-lightest .navbar-brand>.navbar-item{color:#363636}.navbar.is-grey-lightest .navbar-brand .navbar-link.is-active,.navbar.is-grey-lightest .navbar-brand .navbar-link:focus,.navbar.is-grey-lightest .navbar-brand .navbar-link:hover,.navbar.is-grey-lightest .navbar-brand>a.navbar-item.is-active,.navbar.is-grey-lightest .navbar-brand>a.navbar-item:focus,.navbar.is-grey-lightest .navbar-brand>a.navbar-item:hover{background-color:#e0e0e0;color:#363636}.navbar.is-grey-lightest .navbar-brand .navbar-link::after{border-color:#363636}.navbar.is-grey-lightest .navbar-burger{color:#363636}@media screen and (min-width:1088px){.navbar.is-grey-lightest .navbar-end .navbar-link,.navbar.is-grey-lightest .navbar-end>.navbar-item,.navbar.is-grey-lightest .navbar-start .navbar-link,.navbar.is-grey-lightest .navbar-start>.navbar-item{color:#363636}.navbar.is-grey-lightest .navbar-end .navbar-link.is-active,.navbar.is-grey-lightest .navbar-end .navbar-link:focus,.navbar.is-grey-lightest .navbar-end .navbar-link:hover,.navbar.is-grey-lightest .navbar-end>a.navbar-item.is-active,.navbar.is-grey-lightest .navbar-end>a.navbar-item:focus,.navbar.is-grey-lightest .navbar-end>a.navbar-item:hover,.navbar.is-grey-lightest .navbar-start .navbar-link.is-active,.navbar.is-grey-lightest .navbar-start .navbar-link:focus,.navbar.is-grey-lightest .navbar-start .navbar-link:hover,.navbar.is-grey-lightest .navbar-start>a.navbar-item.is-active,.navbar.is-grey-lightest .navbar-start>a.navbar-item:focus,.navbar.is-grey-lightest .navbar-start>a.navbar-item:hover{background-color:#e0e0e0;color:#363636}.navbar.is-grey-lightest .navbar-end .navbar-link::after,.navbar.is-grey-lightest .navbar-start .navbar-link::after{border-color:#363636}.navbar.is-grey-lightest .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-grey-lightest .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-grey-lightest .navbar-item.has-dropdown:hover .navbar-link{background-color:#e0e0e0;color:#363636}.navbar.is-grey-lightest .navbar-dropdown a.navbar-item.is-active{background-color:#ededed;color:#363636}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}body.has-navbar-fixed-top,html.has-navbar-fixed-top{padding-top:3.25rem}body.has-navbar-fixed-bottom,html.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:first-child{top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:first-child{transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-.25rem;margin-right:-.25rem}.navbar-link,a.navbar-item{cursor:pointer}.navbar-link.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,a.navbar-item.is-active,a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover{background-color:#fafafa;color:#3273dc}.navbar-item{display:block;flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#3273dc}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#3273dc;border-bottom-style:solid;border-bottom-width:3px;color:#3273dc;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#3273dc;margin-top:-.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width:1087px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}body.has-navbar-fixed-top-touch,html.has-navbar-fixed-top-touch{padding-top:3.25rem}body.has-navbar-fixed-bottom-touch,html.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width:1088px){.navbar,.navbar-end,.navbar-menu,.navbar-start{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-end,.navbar.is-spaced .navbar-start{align-items:center}.navbar.is-spaced .navbar-link,.navbar.is-spaced a.navbar-item{border-radius:4px}.navbar.is-transparent .navbar-link.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover{background-color:transparent!important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent!important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item{display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(.25em,-.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-dropdown{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + -4px);transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar .navbar-brand,.navbar>.container .navbar-brand{margin-left:-.75rem}.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}body.has-navbar-fixed-top-desktop,html.has-navbar-fixed-top-desktop{padding-top:3.25rem}body.has-navbar-fixed-bottom-desktop,html.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}body.has-spaced-navbar-fixed-top,html.has-spaced-navbar-fixed-top{padding-top:5.25rem}body.has-spaced-navbar-fixed-bottom,html.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}.navbar-link.is-active,a.navbar-item.is-active{color:#3273dc}.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown.is-active .navbar-link,.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-next,.pagination.is-rounded .pagination-previous{padding-left:1em;padding-right:1em;border-radius:290486px}.pagination.is-rounded .pagination-link{border-radius:290486px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-link,.pagination-next,.pagination-previous{border-color:#dbdbdb;color:#363636;min-width:2.25em}.pagination-link:hover,.pagination-next:hover,.pagination-previous:hover{border-color:#b5b5b5;color:#363636}.pagination-link:focus,.pagination-next:focus,.pagination-previous:focus{border-color:#3273dc}.pagination-link:active,.pagination-next:active,.pagination-previous:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-next,.pagination-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#3273dc;border-color:#3273dc;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-next,.pagination-previous{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between}.pagination .pagination-previous,.pagination.is-centered .pagination-previous{order:1}.pagination .pagination-list,.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination .pagination-next,.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#3273dc;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#3273dc}.panel.is-primary .panel-block.is-active .panel-icon{color:#3273dc}.panel.is-link .panel-heading{background-color:#3273dc;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#3273dc}.panel.is-link .panel-block.is-active .panel-icon{color:#3273dc}.panel.is-info .panel-heading{background-color:#3298dc;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3298dc}.panel.is-info .panel-block.is-active .panel-icon{color:#3298dc}.panel.is-success .panel-heading{background-color:#48c774;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c774}.panel.is-success .panel-block.is-active .panel-icon{color:#48c774}.panel.is-warning .panel-heading{background-color:#ffdd57;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffdd57}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffdd57}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel.is-grey-lightest .panel-heading{background-color:#ededed;color:#363636}.panel.is-grey-lightest .panel-tabs a.is-active{border-bottom-color:#ededed}.panel.is-grey-lightest .panel-block.is-active .panel-icon{color:#ededed}.panel-block:not(:last-child),.panel-tabs:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#3273dc}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#3273dc;color:#363636}.panel-block.is-active .panel-icon{color:#3273dc}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#3273dc;color:#3273dc}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-radius:4px 0 0 4px}.tabs.is-toggle li:last-child a{border-radius:0 4px 4px 0}.tabs.is-toggle li.is-active a{background-color:#3273dc;border-color:#3273dc;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:290486px;border-top-left-radius:290486px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:290486px;border-top-right-radius:290486px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;display:block;padding:1.25rem}a.box:focus,a.box:hover{box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px #3273dc}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #3273dc}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(.375em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.375em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-large,.button .icon.is-medium,.button .icon.is-small{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.5em - 1px);margin-right:calc(-.5em - 1px)}.button.is-hovered,.button:hover{border-color:#b5b5b5;color:#363636}.button.is-focused,.button:focus{border-color:#3273dc;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-active,.button:active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text.is-focused,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text:hover{background-color:#f5f5f5;color:#363636}.button.is-text.is-active,.button.is-text:active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white.is-hovered,.button.is-white:hover{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white.is-focused,.button.is-white:focus{border-color:transparent;color:#0a0a0a}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white.is-active,.button.is-white:active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-hovered,.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined.is-focused,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined.is-loading.is-focused::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined.is-focused,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined:hover{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black.is-hovered,.button.is-black:hover{background-color:#040404;border-color:transparent;color:#fff}.button.is-black.is-focused,.button.is-black:focus{border-color:transparent;color:#fff}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black.is-active,.button.is-black:active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-hovered,.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined.is-focused,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined.is-loading.is-focused::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined.is-focused,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined:hover{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light,article.article .article-more{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-hovered,.button.is-light:hover,article.article .article-more.is-hovered,article.article .article-more:hover{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused,.button.is-light:focus,article.article .article-more.is-focused,article.article .article-more:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active),article.article .article-more.is-focused:not(:active),article.article .article-more:focus:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light.is-active,.button.is-light:active,article.article .article-more.is-active,article.article .article-more:active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],article.article .article-more[disabled],fieldset[disabled] .button.is-light,fieldset[disabled] article.article .article-more{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted,article.article .article-more.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-hovered,.button.is-light.is-inverted:hover,article.article .article-more.is-inverted.is-hovered,article.article .article-more.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],article.article .article-more.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted,fieldset[disabled] article.article .article-more.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after,article.article .article-more.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined,article.article .article-more.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined.is-focused,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined:hover,article.article .article-more.is-outlined.is-focused,article.article .article-more.is-outlined.is-hovered,article.article .article-more.is-outlined:focus,article.article .article-more.is-outlined:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after,article.article .article-more.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined.is-loading.is-focused::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading:hover::after,article.article .article-more.is-outlined.is-loading.is-focused::after,article.article .article-more.is-outlined.is-loading.is-hovered::after,article.article .article-more.is-outlined.is-loading:focus::after,article.article .article-more.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined[disabled],article.article .article-more.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined,fieldset[disabled] article.article .article-more.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined,article.article .article-more.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined.is-focused,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined:hover,article.article .article-more.is-inverted.is-outlined.is-focused,article.article .article-more.is-inverted.is-outlined.is-hovered,article.article .article-more.is-inverted.is-outlined:focus,article.article .article-more.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading:hover::after,article.article .article-more.is-inverted.is-outlined.is-loading.is-focused::after,article.article .article-more.is-inverted.is-outlined.is-loading.is-hovered::after,article.article .article-more.is-inverted.is-outlined.is-loading:focus::after,article.article .article-more.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-inverted.is-outlined[disabled],article.article .article-more.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined,fieldset[disabled] article.article .article-more.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark.is-hovered,.button.is-dark:hover{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark.is-focused,.button.is-dark:focus{border-color:transparent;color:#fff}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark.is-active,.button.is-dark:active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-hovered,.button.is-dark.is-inverted:hover{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined.is-focused,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined:hover{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined.is-loading.is-focused::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined.is-focused,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined:hover{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#3273dc;border-color:transparent;color:#fff}.button.is-primary.is-hovered,.button.is-primary:hover{background-color:#276cda;border-color:transparent;color:#fff}.button.is-primary.is-focused,.button.is-primary:focus{border-color:transparent;color:#fff}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-primary.is-active,.button.is-primary:active{background-color:#2366d1;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#3273dc;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#3273dc}.button.is-primary.is-inverted.is-hovered,.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3273dc}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#3273dc;color:#3273dc}.button.is-primary.is-outlined.is-focused,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined:hover{background-color:#3273dc;border-color:#3273dc;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-primary.is-outlined.is-loading.is-focused::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#3273dc;box-shadow:none;color:#3273dc}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined.is-focused,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined:hover{background-color:#fff;color:#3273dc}.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#eef3fc;color:#2160c4}.button.is-primary.is-light.is-hovered,.button.is-primary.is-light:hover{background-color:#e3ecfa;border-color:transparent;color:#2160c4}.button.is-primary.is-light.is-active,.button.is-primary.is-light:active{background-color:#d8e4f8;border-color:transparent;color:#2160c4}.button.is-link{background-color:#3273dc;border-color:transparent;color:#fff}.button.is-link.is-hovered,.button.is-link:hover{background-color:#276cda;border-color:transparent;color:#fff}.button.is-link.is-focused,.button.is-link:focus{border-color:transparent;color:#fff}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-link.is-active,.button.is-link:active{background-color:#2366d1;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#3273dc;border-color:transparent;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-hovered,.button.is-link.is-inverted:hover{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3273dc}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;color:#3273dc}.button.is-link.is-outlined.is-focused,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined:hover{background-color:#3273dc;border-color:#3273dc;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-link.is-outlined.is-loading.is-focused::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;box-shadow:none;color:#3273dc}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined.is-focused,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined:hover{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eef3fc;color:#2160c4}.button.is-link.is-light.is-hovered,.button.is-link.is-light:hover{background-color:#e3ecfa;border-color:transparent;color:#2160c4}.button.is-link.is-light.is-active,.button.is-link.is-light:active{background-color:#d8e4f8;border-color:transparent;color:#2160c4}.button.is-info{background-color:#3298dc;border-color:transparent;color:#fff}.button.is-info.is-hovered,.button.is-info:hover{background-color:#2793da;border-color:transparent;color:#fff}.button.is-info.is-focused,.button.is-info:focus{border-color:transparent;color:#fff}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.button.is-info.is-active,.button.is-info:active{background-color:#238cd1;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3298dc;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3298dc}.button.is-info.is-inverted.is-hovered,.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3298dc}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;color:#3298dc}.button.is-info.is-outlined.is-focused,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined:hover{background-color:#3298dc;border-color:#3298dc;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3298dc #3298dc!important}.button.is-info.is-outlined.is-loading.is-focused::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;box-shadow:none;color:#3298dc}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined.is-focused,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined:hover{background-color:#fff;color:#3298dc}.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3298dc #3298dc!important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eef6fc;color:#1d72aa}.button.is-info.is-light.is-hovered,.button.is-info.is-light:hover{background-color:#e3f1fa;border-color:transparent;color:#1d72aa}.button.is-info.is-light.is-active,.button.is-info.is-light:active{background-color:#d8ebf8;border-color:transparent;color:#1d72aa}.button.is-success{background-color:#48c774;border-color:transparent;color:#fff}.button.is-success.is-hovered,.button.is-success:hover{background-color:#3ec46d;border-color:transparent;color:#fff}.button.is-success.is-focused,.button.is-success:focus{border-color:transparent;color:#fff}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.button.is-success.is-active,.button.is-success:active{background-color:#3abb67;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c774;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c774}.button.is-success.is-inverted.is-hovered,.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c774}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c774;color:#48c774}.button.is-success.is-outlined.is-focused,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined:hover{background-color:#48c774;border-color:#48c774;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c774 #48c774!important}.button.is-success.is-outlined.is-loading.is-focused::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c774;box-shadow:none;color:#48c774}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined.is-focused,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined:hover{background-color:#fff;color:#48c774}.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #48c774 #48c774!important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf3;color:#257942}.button.is-success.is-light.is-hovered,.button.is-success.is-light:hover{background-color:#e6f7ec;border-color:transparent;color:#257942}.button.is-success.is-light.is-active,.button.is-success.is-light:active{background-color:#dcf4e4;border-color:transparent;color:#257942}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-hovered,.button.is-warning:hover{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused,.button.is-warning:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.button.is-warning.is-active,.button.is-warning:active{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffdd57;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-hovered,.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined.is-focused,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined:hover{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffdd57 #ffdd57!important}.button.is-warning.is-outlined.is-loading.is-focused::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined.is-focused,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ffdd57 #ffdd57!important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffbeb;color:#947600}.button.is-warning.is-light.is-hovered,.button.is-warning.is-light:hover{background-color:#fff8de;border-color:transparent;color:#947600}.button.is-warning.is-light.is-active,.button.is-warning.is-light:active{background-color:#fff6d1;border-color:transparent;color:#947600}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger.is-hovered,.button.is-danger:hover{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger.is-focused,.button.is-danger:focus{border-color:transparent;color:#fff}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger.is-active,.button.is-danger:active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-hovered,.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined.is-focused,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined:hover{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-outlined.is-loading.is-focused::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined.is-focused,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined:hover{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light.is-hovered,.button.is-danger.is-light:hover{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light.is-active,.button.is-danger.is-light:active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-grey-lightest{background-color:#ededed;border-color:transparent;color:#363636}.button.is-grey-lightest.is-hovered,.button.is-grey-lightest:hover{background-color:#e7e7e7;border-color:transparent;color:#363636}.button.is-grey-lightest.is-focused,.button.is-grey-lightest:focus{border-color:transparent;color:#363636}.button.is-grey-lightest.is-focused:not(:active),.button.is-grey-lightest:focus:not(:active){box-shadow:0 0 0 .125em rgba(237,237,237,.25)}.button.is-grey-lightest.is-active,.button.is-grey-lightest:active{background-color:#e0e0e0;border-color:transparent;color:#363636}.button.is-grey-lightest[disabled],fieldset[disabled] .button.is-grey-lightest{background-color:#ededed;border-color:transparent;box-shadow:none}.button.is-grey-lightest.is-inverted{background-color:#363636;color:#ededed}.button.is-grey-lightest.is-inverted.is-hovered,.button.is-grey-lightest.is-inverted:hover{background-color:#292929}.button.is-grey-lightest.is-inverted[disabled],fieldset[disabled] .button.is-grey-lightest.is-inverted{background-color:#363636;border-color:transparent;box-shadow:none;color:#ededed}.button.is-grey-lightest.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-grey-lightest.is-outlined{background-color:transparent;border-color:#ededed;color:#ededed}.button.is-grey-lightest.is-outlined.is-focused,.button.is-grey-lightest.is-outlined.is-hovered,.button.is-grey-lightest.is-outlined:focus,.button.is-grey-lightest.is-outlined:hover{background-color:#ededed;border-color:#ededed;color:#363636}.button.is-grey-lightest.is-outlined.is-loading::after{border-color:transparent transparent #ededed #ededed!important}.button.is-grey-lightest.is-outlined.is-loading.is-focused::after,.button.is-grey-lightest.is-outlined.is-loading.is-hovered::after,.button.is-grey-lightest.is-outlined.is-loading:focus::after,.button.is-grey-lightest.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-grey-lightest.is-outlined[disabled],fieldset[disabled] .button.is-grey-lightest.is-outlined{background-color:transparent;border-color:#ededed;box-shadow:none;color:#ededed}.button.is-grey-lightest.is-inverted.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-grey-lightest.is-inverted.is-outlined.is-focused,.button.is-grey-lightest.is-inverted.is-outlined.is-hovered,.button.is-grey-lightest.is-inverted.is-outlined:focus,.button.is-grey-lightest.is-inverted.is-outlined:hover{background-color:#363636;color:#ededed}.button.is-grey-lightest.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-grey-lightest.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-grey-lightest.is-inverted.is-outlined.is-loading:focus::after,.button.is-grey-lightest.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ededed #ededed!important}.button.is-grey-lightest.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-grey-lightest.is-inverted.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-small{border-radius:2px;font-size:.75rem}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em / 2));top:calc(50% - (1em / 2));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:290486px;padding-left:calc(1em + .25em);padding-right:calc(1em + .25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){border-radius:2px;font-size:.75rem}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button.is-hovered,.buttons.has-addons .button:hover{z-index:2}.button.is-hovered,.button:hover{color:#b5b5b5!important}.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-focused,.buttons.has-addons .button.is-selected,.buttons.has-addons .button:active,.buttons.has-addons .button:focus{z-index:3}.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button.is-selected:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button:focus:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none;padding-left:64px;padding-right:64px;width:100%}@media screen and (min-width:1088px){.container{max-width:960px}}@media screen and (max-width:1279px){.container.is-widescreen{max-width:1152px}}@media screen and (max-width:1471px){.container.is-fullhd{max-width:1344px}}@media screen and (min-width:1280px){.container{max-width:1152px}}@media screen and (min-width:1472px){.container{max-width:1344px}}.content li+li{margin-top:.25em}.content blockquote:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content p:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child),.content ul:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:400;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sub,.content sup{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:left}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:290486px}.image.is-fullwidth{width:100%}.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img{height:100%;width:100%}.image.is-1by1,.image.is-square{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;padding:1.25rem 2.5rem 1.25rem 1.5rem;position:relative}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:0 0}.notification>.delete{position:absolute;right:.5rem;top:.5rem}.notification .content,.notification .subtitle,.notification .title{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#3273dc;color:#fff}.notification.is-link{background-color:#3273dc;color:#fff}.notification.is-info{background-color:#3298dc;color:#fff}.notification.is-success{background-color:#48c774;color:#fff}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-grey-lightest{background-color:#ededed;color:#363636}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:290486px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right,#fff 30%,#ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right,#0a0a0a 30%,#ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right,#f5f5f5 30%,#ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right,#363636 30%,#ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#3273dc}.progress.is-primary::-moz-progress-bar{background-color:#3273dc}.progress.is-primary::-ms-fill{background-color:#3273dc}.progress.is-primary:indeterminate{background-image:linear-gradient(to right,#3273dc 30%,#ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#3273dc}.progress.is-link::-moz-progress-bar{background-color:#3273dc}.progress.is-link::-ms-fill{background-color:#3273dc}.progress.is-link:indeterminate{background-image:linear-gradient(to right,#3273dc 30%,#ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3298dc}.progress.is-info::-moz-progress-bar{background-color:#3298dc}.progress.is-info::-ms-fill{background-color:#3298dc}.progress.is-info:indeterminate{background-image:linear-gradient(to right,#3298dc 30%,#ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c774}.progress.is-success::-moz-progress-bar{background-color:#48c774}.progress.is-success::-ms-fill{background-color:#48c774}.progress.is-success:indeterminate{background-image:linear-gradient(to right,#48c774 30%,#ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-warning::-ms-fill{background-color:#ffdd57}.progress.is-warning:indeterminate{background-image:linear-gradient(to right,#ffdd57 30%,#ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right,#f14668 30%,#ededed 30%)}.progress.is-grey-lightest::-webkit-progress-value{background-color:#ededed}.progress.is-grey-lightest::-moz-progress-bar{background-color:#ededed}.progress.is-grey-lightest::-ms-fill{background-color:#ededed}.progress.is-grey-lightest:indeterminate{background-image:linear-gradient(to right,#ededed 30%,#ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right,#4a4a4a 30%,#ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-moz-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@-o-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#3273dc;border-color:#3273dc;color:#fff}.table td.is-link,.table th.is-link{background-color:#3273dc;border-color:#3273dc;color:#fff}.table td.is-info,.table th.is-info{background-color:#3298dc;border-color:#3298dc;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c774;border-color:#48c774;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-grey-lightest,.table th.is-grey-lightest{background-color:#ededed;border-color:#ededed;color:#363636}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#3273dc;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table th{color:#363636}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#3273dc;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(2n){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(2n){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-bottom-left-radius:0;border-top-left-radius:0}.tags.has-addons .tag:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#3273dc;color:#fff}.tag:not(body).is-primary.is-light{background-color:#eef3fc;color:#2160c4}.tag:not(body).is-link{background-color:#3273dc;color:#fff}.tag:not(body).is-link.is-light{background-color:#eef3fc;color:#2160c4}.tag:not(body).is-info{background-color:#3298dc;color:#fff}.tag:not(body).is-info.is-light{background-color:#eef6fc;color:#1d72aa}.tag:not(body).is-success{background-color:#48c774;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf3;color:#257942}.tag:not(body).is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffbeb;color:#947600}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-grey-lightest{background-color:#ededed;color:#363636}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::after,.tag:not(body).is-delete::before{background-color:currentColor;content:\"\";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:focus,.tag:not(body).is-delete:hover{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:290486px}a.tag:hover{text-decoration:underline}.subtitle,.title{word-break:break-word}.subtitle em,.subtitle span,.title em,.title span{font-weight:inherit}.subtitle sub,.title sub{font-size:.75em}.subtitle sup,.title sup{font-size:.75em}.subtitle .tag,.title .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:400;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title+.highlight{margin-top:-.75rem}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.85rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.85rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.highlight{font-weight:400;max-width:100%;overflow:hidden;padding:0}.highlight pre{overflow:auto;max-width:100%}.number{align-items:center;background-color:#f5f5f5;border-radius:290486px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.input,.select select,.textarea{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.select select::-moz-placeholder,.textarea::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder,.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder,.select select:-moz-placeholder,.textarea:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder,.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input.is-hovered,.input:hover,.select select.is-hovered,.select select:hover,.textarea.is-hovered,.textarea:hover{border-color:#b5b5b5}.input.is-active,.input.is-focused,.input:active,.input:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus{border-color:#3273dc;box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.input[disabled],.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input,.textarea{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.input.is-white,.textarea.is-white{border-color:#fff}.input.is-white.is-active,.input.is-white.is-focused,.input.is-white:active,.input.is-white:focus,.textarea.is-white.is-active,.textarea.is-white.is-focused,.textarea.is-white:active,.textarea.is-white:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.input.is-black,.textarea.is-black{border-color:#0a0a0a}.input.is-black.is-active,.input.is-black.is-focused,.input.is-black:active,.input.is-black:focus,.textarea.is-black.is-active,.textarea.is-black.is-focused,.textarea.is-black:active,.textarea.is-black:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.input.is-light,.textarea.is-light{border-color:#f5f5f5}.input.is-light.is-active,.input.is-light.is-focused,.input.is-light:active,.input.is-light:focus,.textarea.is-light.is-active,.textarea.is-light.is-focused,.textarea.is-light:active,.textarea.is-light:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.input.is-dark,.textarea.is-dark{border-color:#363636}.input.is-dark.is-active,.input.is-dark.is-focused,.input.is-dark:active,.input.is-dark:focus,.textarea.is-dark.is-active,.textarea.is-dark.is-focused,.textarea.is-dark:active,.textarea.is-dark:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.input.is-primary,.textarea.is-primary{border-color:#3273dc}.input.is-primary.is-active,.input.is-primary.is-focused,.input.is-primary:active,.input.is-primary:focus,.textarea.is-primary.is-active,.textarea.is-primary.is-focused,.textarea.is-primary:active,.textarea.is-primary:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.input.is-link,.textarea.is-link{border-color:#3273dc}.input.is-link.is-active,.input.is-link.is-focused,.input.is-link:active,.input.is-link:focus,.textarea.is-link.is-active,.textarea.is-link.is-focused,.textarea.is-link:active,.textarea.is-link:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.input.is-info,.textarea.is-info{border-color:#3298dc}.input.is-info.is-active,.input.is-info.is-focused,.input.is-info:active,.input.is-info:focus,.textarea.is-info.is-active,.textarea.is-info.is-focused,.textarea.is-info:active,.textarea.is-info:focus{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.input.is-success,.textarea.is-success{border-color:#48c774}.input.is-success.is-active,.input.is-success.is-focused,.input.is-success:active,.input.is-success:focus,.textarea.is-success.is-active,.textarea.is-success.is-focused,.textarea.is-success:active,.textarea.is-success:focus{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.input.is-warning,.textarea.is-warning{border-color:#ffdd57}.input.is-warning.is-active,.input.is-warning.is-focused,.input.is-warning:active,.input.is-warning:focus,.textarea.is-warning.is-active,.textarea.is-warning.is-focused,.textarea.is-warning:active,.textarea.is-warning:focus{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.input.is-danger,.textarea.is-danger{border-color:#f14668}.input.is-danger.is-active,.input.is-danger.is-focused,.input.is-danger:active,.input.is-danger:focus,.textarea.is-danger.is-active,.textarea.is-danger.is-focused,.textarea.is-danger:active,.textarea.is-danger:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.input.is-grey-lightest,.textarea.is-grey-lightest{border-color:#ededed}.input.is-grey-lightest.is-active,.input.is-grey-lightest.is-focused,.input.is-grey-lightest:active,.input.is-grey-lightest:focus,.textarea.is-grey-lightest.is-active,.textarea.is-grey-lightest.is-focused,.textarea.is-grey-lightest:active,.textarea.is-grey-lightest:focus{box-shadow:0 0 0 .125em rgba(237,237,237,.25)}.input.is-small,.textarea.is-small{border-radius:2px;font-size:.75rem}.input.is-medium,.textarea.is-medium{font-size:1.25rem}.input.is-large,.textarea.is-large{font-size:1.5rem}.input.is-fullwidth,.textarea.is-fullwidth{display:block;width:100%}.input.is-inline,.textarea.is-inline{display:inline;width:auto}.input.is-rounded{border-radius:290486px;padding-left:calc(calc(.75em - 1px) + .375em);padding-right:calc(calc(.75em - 1px) + .375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.25em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#3273dc;right:1.125em;z-index:4}.select.is-rounded select{border-radius:290486px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select.is-hovered,.select.is-white select:hover{border-color:#f2f2f2}.select.is-white select.is-active,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select.is-hovered,.select.is-black select:hover{border-color:#000}.select.is-black select.is-active,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select.is-hovered,.select.is-light select:hover{border-color:#e8e8e8}.select.is-light select.is-active,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select.is-hovered,.select.is-dark select:hover{border-color:#292929}.select.is-dark select.is-active,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#3273dc}.select.is-primary select{border-color:#3273dc}.select.is-primary select.is-hovered,.select.is-primary select:hover{border-color:#2366d1}.select.is-primary select.is-active,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.select.is-link:not(:hover)::after{border-color:#3273dc}.select.is-link select{border-color:#3273dc}.select.is-link select.is-hovered,.select.is-link select:hover{border-color:#2366d1}.select.is-link select.is-active,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.select.is-info:not(:hover)::after{border-color:#3298dc}.select.is-info select{border-color:#3298dc}.select.is-info select.is-hovered,.select.is-info select:hover{border-color:#238cd1}.select.is-info select.is-active,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select:focus{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.select.is-success:not(:hover)::after{border-color:#48c774}.select.is-success select{border-color:#48c774}.select.is-success select.is-hovered,.select.is-success select:hover{border-color:#3abb67}.select.is-success select.is-active,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select:focus{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-warning select{border-color:#ffdd57}.select.is-warning select.is-hovered,.select.is-warning select:hover{border-color:#ffd83d}.select.is-warning select.is-active,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select:focus{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select.is-hovered,.select.is-danger select:hover{border-color:#ef2e55}.select.is-danger select.is-active,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-grey-lightest:not(:hover)::after{border-color:#ededed}.select.is-grey-lightest select{border-color:#ededed}.select.is-grey-lightest select.is-hovered,.select.is-grey-lightest select:hover{border-color:#e0e0e0}.select.is-grey-lightest select.is-active,.select.is-grey-lightest select.is-focused,.select.is-grey-lightest select:active,.select.is-grey-lightest select:focus{box-shadow:0 0 0 .125em rgba(237,237,237,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white.is-hovered .file-cta,.file.is-white:hover .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white.is-focused .file-cta,.file.is-white:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white.is-active .file-cta,.file.is-white:active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black.is-hovered .file-cta,.file.is-black:hover .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black.is-focused .file-cta,.file.is-black:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black.is-active .file-cta,.file.is-black:active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-hovered .file-cta,.file.is-light:hover .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-focused .file-cta,.file.is-light:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light.is-active .file-cta,.file.is-light:active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark.is-hovered .file-cta,.file.is-dark:hover .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark.is-focused .file-cta,.file.is-dark:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark.is-active .file-cta,.file.is-dark:active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#3273dc;border-color:transparent;color:#fff}.file.is-primary.is-hovered .file-cta,.file.is-primary:hover .file-cta{background-color:#276cda;border-color:transparent;color:#fff}.file.is-primary.is-focused .file-cta,.file.is-primary:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,115,220,.25);color:#fff}.file.is-primary.is-active .file-cta,.file.is-primary:active .file-cta{background-color:#2366d1;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#3273dc;border-color:transparent;color:#fff}.file.is-link.is-hovered .file-cta,.file.is-link:hover .file-cta{background-color:#276cda;border-color:transparent;color:#fff}.file.is-link.is-focused .file-cta,.file.is-link:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,115,220,.25);color:#fff}.file.is-link.is-active .file-cta,.file.is-link:active .file-cta{background-color:#2366d1;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3298dc;border-color:transparent;color:#fff}.file.is-info.is-hovered .file-cta,.file.is-info:hover .file-cta{background-color:#2793da;border-color:transparent;color:#fff}.file.is-info.is-focused .file-cta,.file.is-info:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,152,220,.25);color:#fff}.file.is-info.is-active .file-cta,.file.is-info:active .file-cta{background-color:#238cd1;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c774;border-color:transparent;color:#fff}.file.is-success.is-hovered .file-cta,.file.is-success:hover .file-cta{background-color:#3ec46d;border-color:transparent;color:#fff}.file.is-success.is-focused .file-cta,.file.is-success:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,116,.25);color:#fff}.file.is-success.is-active .file-cta,.file.is-success:active .file-cta{background-color:#3abb67;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-hovered .file-cta,.file.is-warning:hover .file-cta{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-focused .file-cta,.file.is-warning:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,221,87,.25);color:rgba(0,0,0,.7)}.file.is-warning.is-active .file-cta,.file.is-warning:active .file-cta{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger.is-hovered .file-cta,.file.is-danger:hover .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger.is-focused .file-cta,.file.is-danger:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger.is-active .file-cta,.file.is-danger:active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-grey-lightest .file-cta{background-color:#ededed;border-color:transparent;color:#363636}.file.is-grey-lightest.is-hovered .file-cta,.file.is-grey-lightest:hover .file-cta{background-color:#e7e7e7;border-color:transparent;color:#363636}.file.is-grey-lightest.is-focused .file-cta,.file.is-grey-lightest:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(237,237,237,.25);color:#363636}.file.is-grey-lightest.is-active .file-cta,.file.is-grey-lightest:active .file-cta{background-color:#e0e0e0;border-color:transparent;color:#363636}.file.is-small{font-size:.75rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:0;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:left;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#3273dc}.help.is-link{color:#3273dc}.help.is-info{color:#3298dc}.help.is-success{color:#48c774}.help.is-warning{color:#ffdd57}.help.is-danger{color:#f14668}.help.is-grey-lightest{color:#ededed}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover{z-index:2}.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]):focus{z-index:3}.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:left}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#4a4a4a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.25em;pointer-events:none;position:absolute;top:0;width:2.25em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.25em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.25em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute!important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1087px){.column.is-narrow-touch{flex:none}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1088px){.column.is-narrow-desktop{flex:none}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1280px){.column.is-narrow-widescreen{flex:none}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1472px){.column.is-narrow-fullhd{flex:none}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0!important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1088px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap:0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable .column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap:0rem}@media screen and (max-width:768px){.columns.is-variable.is-0-mobile{--columnGap:0rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-0-tablet{--columnGap:0rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-0-tablet-only{--columnGap:0rem}}@media screen and (max-width:1087px){.columns.is-variable.is-0-touch{--columnGap:0rem}}@media screen and (min-width:1088px){.columns.is-variable.is-0-desktop{--columnGap:0rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-0-desktop-only{--columnGap:0rem}}@media screen and (min-width:1280px){.columns.is-variable.is-0-widescreen{--columnGap:0rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-0-widescreen-only{--columnGap:0rem}}@media screen and (min-width:1472px){.columns.is-variable.is-0-fullhd{--columnGap:0rem}}.columns.is-variable.is-1{--columnGap:0.25rem}@media screen and (max-width:768px){.columns.is-variable.is-1-mobile{--columnGap:0.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-1-tablet{--columnGap:0.25rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-1-tablet-only{--columnGap:0.25rem}}@media screen and (max-width:1087px){.columns.is-variable.is-1-touch{--columnGap:0.25rem}}@media screen and (min-width:1088px){.columns.is-variable.is-1-desktop{--columnGap:0.25rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-1-desktop-only{--columnGap:0.25rem}}@media screen and (min-width:1280px){.columns.is-variable.is-1-widescreen{--columnGap:0.25rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-1-widescreen-only{--columnGap:0.25rem}}@media screen and (min-width:1472px){.columns.is-variable.is-1-fullhd{--columnGap:0.25rem}}.columns.is-variable.is-2{--columnGap:0.5rem}@media screen and (max-width:768px){.columns.is-variable.is-2-mobile{--columnGap:0.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-2-tablet{--columnGap:0.5rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-2-tablet-only{--columnGap:0.5rem}}@media screen and (max-width:1087px){.columns.is-variable.is-2-touch{--columnGap:0.5rem}}@media screen and (min-width:1088px){.columns.is-variable.is-2-desktop{--columnGap:0.5rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-2-desktop-only{--columnGap:0.5rem}}@media screen and (min-width:1280px){.columns.is-variable.is-2-widescreen{--columnGap:0.5rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-2-widescreen-only{--columnGap:0.5rem}}@media screen and (min-width:1472px){.columns.is-variable.is-2-fullhd{--columnGap:0.5rem}}.columns.is-variable.is-3{--columnGap:0.75rem}@media screen and (max-width:768px){.columns.is-variable.is-3-mobile{--columnGap:0.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-3-tablet{--columnGap:0.75rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-3-tablet-only{--columnGap:0.75rem}}@media screen and (max-width:1087px){.columns.is-variable.is-3-touch{--columnGap:0.75rem}}@media screen and (min-width:1088px){.columns.is-variable.is-3-desktop{--columnGap:0.75rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-3-desktop-only{--columnGap:0.75rem}}@media screen and (min-width:1280px){.columns.is-variable.is-3-widescreen{--columnGap:0.75rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-3-widescreen-only{--columnGap:0.75rem}}@media screen and (min-width:1472px){.columns.is-variable.is-3-fullhd{--columnGap:0.75rem}}.columns.is-variable.is-4{--columnGap:1rem}@media screen and (max-width:768px){.columns.is-variable.is-4-mobile{--columnGap:1rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-4-tablet{--columnGap:1rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-4-tablet-only{--columnGap:1rem}}@media screen and (max-width:1087px){.columns.is-variable.is-4-touch{--columnGap:1rem}}@media screen and (min-width:1088px){.columns.is-variable.is-4-desktop{--columnGap:1rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-4-desktop-only{--columnGap:1rem}}@media screen and (min-width:1280px){.columns.is-variable.is-4-widescreen{--columnGap:1rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-4-widescreen-only{--columnGap:1rem}}@media screen and (min-width:1472px){.columns.is-variable.is-4-fullhd{--columnGap:1rem}}.columns.is-variable.is-5{--columnGap:1.25rem}@media screen and (max-width:768px){.columns.is-variable.is-5-mobile{--columnGap:1.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-5-tablet{--columnGap:1.25rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-5-tablet-only{--columnGap:1.25rem}}@media screen and (max-width:1087px){.columns.is-variable.is-5-touch{--columnGap:1.25rem}}@media screen and (min-width:1088px){.columns.is-variable.is-5-desktop{--columnGap:1.25rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-5-desktop-only{--columnGap:1.25rem}}@media screen and (min-width:1280px){.columns.is-variable.is-5-widescreen{--columnGap:1.25rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-5-widescreen-only{--columnGap:1.25rem}}@media screen and (min-width:1472px){.columns.is-variable.is-5-fullhd{--columnGap:1.25rem}}.columns.is-variable.is-6{--columnGap:1.5rem}@media screen and (max-width:768px){.columns.is-variable.is-6-mobile{--columnGap:1.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-6-tablet{--columnGap:1.5rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-6-tablet-only{--columnGap:1.5rem}}@media screen and (max-width:1087px){.columns.is-variable.is-6-touch{--columnGap:1.5rem}}@media screen and (min-width:1088px){.columns.is-variable.is-6-desktop{--columnGap:1.5rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-6-desktop-only{--columnGap:1.5rem}}@media screen and (min-width:1280px){.columns.is-variable.is-6-widescreen{--columnGap:1.5rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-6-widescreen-only{--columnGap:1.5rem}}@media screen and (min-width:1472px){.columns.is-variable.is-6-fullhd{--columnGap:1.5rem}}.columns.is-variable.is-7{--columnGap:1.75rem}@media screen and (max-width:768px){.columns.is-variable.is-7-mobile{--columnGap:1.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-7-tablet{--columnGap:1.75rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-7-tablet-only{--columnGap:1.75rem}}@media screen and (max-width:1087px){.columns.is-variable.is-7-touch{--columnGap:1.75rem}}@media screen and (min-width:1088px){.columns.is-variable.is-7-desktop{--columnGap:1.75rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-7-desktop-only{--columnGap:1.75rem}}@media screen and (min-width:1280px){.columns.is-variable.is-7-widescreen{--columnGap:1.75rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-7-widescreen-only{--columnGap:1.75rem}}@media screen and (min-width:1472px){.columns.is-variable.is-7-fullhd{--columnGap:1.75rem}}.columns.is-variable.is-8{--columnGap:2rem}@media screen and (max-width:768px){.columns.is-variable.is-8-mobile{--columnGap:2rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-8-tablet{--columnGap:2rem}}@media screen and (min-width:769px) and (max-width:1087px){.columns.is-variable.is-8-tablet-only{--columnGap:2rem}}@media screen and (max-width:1087px){.columns.is-variable.is-8-touch{--columnGap:2rem}}@media screen and (min-width:1088px){.columns.is-variable.is-8-desktop{--columnGap:2rem}}@media screen and (min-width:1088px) and (max-width:1279px){.columns.is-variable.is-8-desktop-only{--columnGap:2rem}}@media screen and (min-width:1280px){.columns.is-variable.is-8-widescreen{--columnGap:2rem}}@media screen and (min-width:1280px) and (max-width:1471px){.columns.is-variable.is-8-widescreen-only{--columnGap:2rem}}@media screen and (min-width:1472px){.columns.is-variable.is-8-fullhd{--columnGap:2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:0 0}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width:1087px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white .navbar-link.is-active,.hero.is-white .navbar-link:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e8e3e3 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg,#e8e3e3 0,#fff 71%,#fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width:1087px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black .navbar-link.is-active,.hero.is-black .navbar-link:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black a.navbar-item:hover{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1087px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light .navbar-link.is-active,.hero.is-light .navbar-link:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d8 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg,#dfd8d8 0,#f5f5f5 71%,#fff 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width:1087px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark .navbar-link.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark a.navbar-item:hover{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f1a1a 0,#363636 71%,#463f3f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1f1a1a 0,#363636 71%,#463f3f 100%)}}.hero.is-primary{background-color:#3273dc;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width:1087px){.hero.is-primary .navbar-menu{background-color:#3273dc}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary .navbar-link.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary a.navbar-item:hover{background-color:#2366d1;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3273dc}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#1576c6 0,#3273dc 71%,#4266e5 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1576c6 0,#3273dc 71%,#4266e5 100%)}}.hero.is-link{background-color:#3273dc;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width:1087px){.hero.is-link .navbar-menu{background-color:#3273dc}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link .navbar-link.is-active,.hero.is-link .navbar-link:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link a.navbar-item:hover{background-color:#2366d1;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3273dc}.hero.is-link.is-bold{background-image:linear-gradient(141deg,#1576c6 0,#3273dc 71%,#4266e5 100%)}@media screen and (max-width:768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1576c6 0,#3273dc 71%,#4266e5 100%)}}.hero.is-info{background-color:#3298dc;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width:1087px){.hero.is-info .navbar-menu{background-color:#3298dc}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info .navbar-link.is-active,.hero.is-info .navbar-link:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info a.navbar-item:hover{background-color:#238cd1;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3298dc}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#159cc6 0,#3298dc 71%,#4289e5 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg,#159cc6 0,#3298dc 71%,#4289e5 100%)}}.hero.is-success{background-color:#48c774;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width:1087px){.hero.is-success .navbar-menu{background-color:#48c774}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success .navbar-link.is-active,.hero.is-success .navbar-link:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success a.navbar-item:hover{background-color:#3abb67;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c774}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#29b342 0,#48c774 71%,#56d296 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg,#29b342 0,#48c774 71%,#56d296 100%)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1087px){.hero.is-warning .navbar-menu{background-color:#ffdd57}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning .navbar-link.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffae24 0,#ffdd57 71%,#fffa71 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg,#ffae24 0,#ffdd57 71%,#fffa71 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width:1087px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger .navbar-link.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger a.navbar-item:hover{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#fa0a61 0,#f14668 71%,#f7595f 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg,#fa0a61 0,#f14668 71%,#f7595f 100%)}}.hero.is-grey-lightest{background-color:#ededed;color:#363636}.hero.is-grey-lightest a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-grey-lightest strong{color:inherit}.hero.is-grey-lightest .title{color:#363636}.hero.is-grey-lightest .subtitle{color:rgba(54,54,54,.9)}.hero.is-grey-lightest .subtitle a:not(.button),.hero.is-grey-lightest .subtitle strong{color:#363636}@media screen and (max-width:1087px){.hero.is-grey-lightest .navbar-menu{background-color:#ededed}}.hero.is-grey-lightest .navbar-item,.hero.is-grey-lightest .navbar-link{color:rgba(54,54,54,.7)}.hero.is-grey-lightest .navbar-link.is-active,.hero.is-grey-lightest .navbar-link:hover,.hero.is-grey-lightest a.navbar-item.is-active,.hero.is-grey-lightest a.navbar-item:hover{background-color:#e0e0e0;color:#363636}.hero.is-grey-lightest .tabs a{color:#363636;opacity:.9}.hero.is-grey-lightest .tabs a:hover{opacity:1}.hero.is-grey-lightest .tabs li.is-active a{opacity:1}.hero.is-grey-lightest .tabs.is-boxed a,.hero.is-grey-lightest .tabs.is-toggle a{color:#363636}.hero.is-grey-lightest .tabs.is-boxed a:hover,.hero.is-grey-lightest .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-grey-lightest .tabs.is-boxed li.is-active a,.hero.is-grey-lightest .tabs.is-boxed li.is-active a:hover,.hero.is-grey-lightest .tabs.is-toggle li.is-active a,.hero.is-grey-lightest .tabs.is-toggle li.is-active a:hover{background-color:#363636;border-color:#363636;color:#ededed}.hero.is-grey-lightest.is-bold{background-image:linear-gradient(141deg,#d8cfcf 0,#ededed 71%,#faf9f9 100%)}@media screen and (max-width:768px){.hero.is-grey-lightest.is-bold .navbar-menu{background-image:linear-gradient(141deg,#d8cfcf 0,#ededed 71%,#faf9f9 100%)}}.hero.is-small .hero-body{padding-bottom:1.5rem;padding-top:1.5rem}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding-bottom:9rem;padding-top:9rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding-bottom:18rem;padding-top:18rem}}.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body,.hero.is-halfheight .hero-body{align-items:center;display:flex}.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container,.hero.is-halfheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-foot,.hero-head{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}.section{padding:3rem 1.5rem}@media screen and (min-width:1088px){.section.is-medium{padding:9rem 1.5rem}.section.is-large{padding:18rem 1.5rem}}.footer{background-color:#fff;padding:3rem 1.5rem 6rem}html{height:100%;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}body{min-height:100%;display:flex;flex-direction:column}body>.section{flex-grow:1}@media screen and (min-width:1088px){::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{border-radius:3px;background:rgba(0,0,0,.06);box-shadow:inset 0 0 5px rgba(0,0,0,.1)}::-webkit-scrollbar-thumb{border-radius:3px;background:rgba(0,0,0,.12);box-shadow:inset 0 0 10px rgba(0,0,0,.2)}::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.24)}}.ml-0,.mx-0{margin-left:0!important}.mr-0,.mx-0{margin-right:0!important}.ml-n0,.mx-n0{margin-left:0!important}.mr-n0,.mx-n0{margin-right:0!important}.mt-0,.my-0{margin-top:0!important}.mb-0,.my-0{margin-bottom:0!important}.mt-n0,.my-n0{margin-top:0!important}.mb-n0,.my-n0{margin-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.pr-0,.px-0{padding-right:0!important}.pl-n0,.px-n0{padding-left:0!important}.pr-n0,.px-n0{padding-right:0!important}.pt-0,.py-0{padding-top:0!important}.pb-0,.py-0{padding-bottom:0!important}.pt-n0,.py-n0{padding-top:0!important}.pb-n0,.py-n0{padding-bottom:0!important}.article-licensing .licensing-meta .icons .icon,.ml-1,.mx-1{margin-left:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.article-licensing .licensing-title p:not(:last-child),.mb-1,.my-1{margin-bottom:.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.ml-3,.mx-3{margin-left:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mt-3,.my-3{margin-top:1rem!important}.article-licensing .licensing-title,.mb-3,.my-3{margin-bottom:1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.pl-3,.px-3{padding-left:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.article-licensing .licensing-meta .level-item,.mr-4,.mx-4{margin-right:1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.ml-5,.mx-5{margin-left:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.pl-5,.px-5{padding-left:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.ml-auto,.mx-auto{margin-left:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.pl-auto,.px-auto{margin-left:auto!important}.pr-auto,.px-auto{margin-right:auto!important}.pt-auto,.py-auto{margin-top:auto!important}.pb-auto,.py-auto{margin-bottom:auto!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.justify-content-start{justify-content:start!important}.justify-content-center{justify-content:center!important}.flex-shrink-1{flex-shrink:1!important}.link-muted{color:inherit}.link-muted:hover{color:#3273dc!important}.image.is-7by3{padding-top:42.8%}.image.is-7by3 img{bottom:0;left:0;position:absolute;right:0;top:0}.image .avatar{height:100%;object-fit:cover}.image .fill{object-fit:cover;width:100%!important;height:100%!important}.button.is-transparent{color:inherit;background:0 0;border-color:transparent}.card{overflow:visible;border-radius:4px}.card+.card,.card+.column-right-shadow{margin-top:1.5rem}.card .card-image{overflow:hidden;border-top-left-radius:4px;border-top-right-radius:4px}.card .media+.media{border:none;margin-top:0}article.media{color:#7a7a7a}article.media a{color:inherit}article.media a:hover{color:#3273dc}article.media .image{width:64px;height:64px}article.media .image img{object-fit:cover;width:100%;height:100%}article.media .title{margin-bottom:.25em}article.media .media-content{color:#7a7a7a}article.media .media-content .title{margin:0;line-height:inherit}article.article .article-meta,article.article .article-tags{color:#7a7a7a}article.article .article-meta{overflow-x:auto;margin-bottom:.5rem}article.article .content{word-wrap:break-word;font-size:1.1rem}article.article .content h1{font-size:1.75em}article.article .content h2{font-size:1.5em}article.article .content h3{font-size:1.25em}article.article .content h4{font-size:1.125em}article.article .content h5{font-size:1em}article.article .content pre{font-size:.85em}article.article .content code{padding:0;background:0 0;overflow-wrap:break-word}article.article .content blockquote.pullquote{float:right;max-width:50%;font-size:1.15rem;position:relative}article.article .content blockquote footer strong+cite{margin-left:.5em}article.article .content .message.message-immersive{border-radius:0;margin:0 -1.5rem 1.5rem -1.5rem}article.article .content .message.message-immersive .message-body{border:none}article.article .article-tags{display:flex;flex-wrap:wrap}.rtl{direction:rtl}.rtl .level .level-item:not(:last-child),.rtl .level.is-mobile .level-item:not(:last-child){margin-left:.75rem;margin-right:0}.table-overflow{overflow-x:auto}.table-overflow table{width:auto!important}.table-overflow table th{word-break:keep-all}.video-container{position:relative;padding-bottom:56.25%;padding-top:25px;height:0}.video-container iframe{position:absolute;top:0;left:0;width:100%;height:100%}.article-licensing{position:relative;z-index:1;box-shadow:none;background:#f5f5f5;border-radius:4px;overflow:hidden}.article-licensing:after{position:absolute;z-index:-1;right:-50px;top:-87.87px;content:'\\f25e';font-size:200px;font-family:'Font Awesome 5 Brands';opacity:.1}.article-licensing .level-left{flex-wrap:wrap;max-width:100%}.article-licensing .licensing-title{line-height:1.2}.article-licensing .licensing-meta .icons .icon{width:1.2em;height:1.2em;font-size:1.2em;vertical-align:bottom}.article-licensing .licensing-meta a{color:inherit}a.article-nav-prev span{text-align:left;flex-shrink:1;word-wrap:break-word;white-space:normal}a.article-nav-next span{text-align:right;flex-shrink:1;word-wrap:break-word;white-space:normal}.navbar-main{box-shadow:0 4px 10px rgba(0,0,0,.05)}.navbar-main .navbar-container{overflow-x:auto}.navbar-main .navbar-end,.navbar-main .navbar-menu,.navbar-main .navbar-start{align-items:stretch;display:flex;padding:0;flex-shrink:0}.navbar-main .navbar-menu{flex-grow:1;flex-shrink:0;overflow-x:auto}.navbar-main .navbar-start{justify-content:flex-start;margin-right:auto}.navbar-main .navbar-end{justify-content:flex-end;margin-left:auto}.navbar-main .navbar-item{display:flex;align-items:center;padding:1.25rem .75rem;margin:0 0}.navbar-main .navbar-item.is-active{background-color:transparent}@media screen and (max-width:1087px){.navbar-main .navbar-menu{justify-content:center;box-shadow:none}.navbar-main .navbar-start{margin-right:0}.navbar-main .navbar-end{margin-left:0}}.navbar-logo img{max-height:1.75rem}@media screen and (min-width:1088px){.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:0}}@media screen and (max-width:768px){footer.footer .level-start{text-align:center}}footer.footer .level-end .field{flex-wrap:wrap;align-items:center}@media screen and (max-width:768px){footer.footer .level-end .field{justify-content:center;margin-top:1rem}}.footer-logo img{max-height:1.75rem}.pagination{margin-top:1.5rem}.pagination .pagination-ellipsis a,.pagination .pagination-link a,.pagination .pagination-next a,.pagination .pagination-previous a{color:#363636}.pagination .pagination-link,.pagination .pagination-next,.pagination .pagination-previous{border:none;background:#fff;box-shadow:0 4px 10px rgba(0,0,0,.05),0 0 1px rgba(0,0,0,.1)}.pagination .pagination-link.is-current{background:#3273dc}.post-navigation{color:#7a7a7a;flex-wrap:wrap;justify-content:space-around}.post-navigation .level-item{margin-bottom:0}.timeline{margin-left:1rem;padding:1rem 0 0 1.5rem;border-left:1px solid #dbdbdb}.timeline .media{position:relative}.timeline .media:before,.timeline .media:last-child:after{content:'';display:block;position:absolute;left:calc(-.375rem - 1.5rem - .25px)}.timeline .media:before{width:.75rem;height:.75rem;top:calc(1rem + 1.5 * .85rem / 2 - .75rem / 2);background:#dbdbdb;border-radius:50%}.timeline .media:first-child:before{top:calc(1.5 * .85rem / 2 - .75rem / 2)}.timeline .media:last-child:after{width:.75rem;top:calc(1rem + 1.5 * .85rem / 2 + .75rem / 2);bottom:0;background:#fff}.timeline .media:first-child:last-child:after{top:calc(1.5 * .85rem / 2 + .75rem / 2)}.searchbox{display:none;top:0;left:0;width:100%;height:100%;z-index:100;font-size:1rem;line-height:0;background:rgba(10,10,10,.86)}.searchbox.show{display:flex}.searchbox a,.searchbox a:hover{color:inherit;text-decoration:none}.searchbox input{font-size:1rem;border:none;outline:0;box-shadow:none;border-radius:0}.searchbox,.searchbox .searchbox-container{position:fixed;align-items:center;flex-direction:column;line-height:1.25em}.searchbox .searchbox-container{z-index:101;display:flex;overflow:hidden;box-shadow:0 4px 10px rgba(0,0,0,.05),0 0 1px rgba(0,0,0,.1);border-radius:4px;background-color:#f5f5f5;width:540px;top:100px;bottom:100px}.searchbox .searchbox-body,.searchbox .searchbox-footer,.searchbox .searchbox-header{width:100%}.searchbox .searchbox-header{display:flex;flex-direction:row;line-height:1.5em;font-weight:400;background-color:#fff;min-height:3rem}.searchbox .searchbox-input-container{display:flex;flex-grow:1}.searchbox .searchbox-input{flex-grow:1;color:inherit;box-sizing:border-box;padding:.75em 0 .75em 1.25em;background:#fff}.searchbox .searchbox-close{display:inline-block;font-size:1.5em;padding:.5em .75em;cursor:pointer}.searchbox .searchbox-close:hover{background:#f5f5f5}.searchbox .searchbox-close:active{background:#dbdbdb}.searchbox .searchbox-body{flex-grow:1;overflow-y:auto;border-top:1px solid #dbdbdb}.searchbox .searchbox-result-item,.searchbox .searchbox-result-section header{padding:.75em 1em}.searchbox .searchbox-result-section{border-bottom:1px solid #dbdbdb}.searchbox .searchbox-result-section header{color:#b5b5b5}.searchbox .searchbox-result-item{display:flex;flex-direction:row}.searchbox .searchbox-result-item:not(.disabled):not(.active):not(:active):hover{background-color:#fff}.searchbox .searchbox-result-item.active,.searchbox .searchbox-result-item:active{color:#fff;background-color:#3273dc}.searchbox .searchbox-result-item em{font-style:normal;background:#ffdd57}.searchbox .searchbox-result-icon{margin-right:1em}.searchbox .searchbox-result-content{overflow:hidden}.searchbox .searchbox-result-preview,.searchbox .searchbox-result-title{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.searchbox .searchbox-result-title-secondary{color:#b5b5b5}.searchbox .searchbox-result-preview{margin-top:.25em}.searchbox .searchbox-result-item:not(:active):not(.active) .searchbox-result-preview{color:#b5b5b5}.searchbox .searchbox-footer{padding:.5em 1em}.searchbox .searchbox-pagination{margin:0;padding:0;list-style:none;text-align:center}.searchbox .searchbox-pagination .searchbox-pagination-item{margin:0 .25rem}.searchbox .searchbox-pagination .searchbox-pagination-item,.searchbox .searchbox-pagination .searchbox-pagination-link{display:inline-block}.searchbox .searchbox-pagination .searchbox-pagination-link{overflow:hidden;padding:.5em .8em;box-shadow:0 4px 10px rgba(0,0,0,.05),0 0 1px rgba(0,0,0,.1);border-radius:4px;background-color:#fff}.searchbox .searchbox-pagination .searchbox-pagination-item.active .searchbox-pagination-link{color:#fff;background-color:#3273dc}.searchbox .searchbox-pagination .searchbox-pagination-item.disabled .searchbox-pagination-link{cursor:not-allowed;background-color:#f5f5f5}.searchbox .searchbox-pagination .searchbox-pagination-item:not(.active):not(.disabled) .searchbox-pagination-link:hover{background-color:#f5f5f5}@media screen and (max-width:559px),screen and (max-height:479px){.searchbox .searchbox-container{top:0;left:0;width:100%;height:100%;border-radius:0}}figure.highlight{padding:0;width:100%;position:relative;margin:1em 0 1em!important;border-radius:4px}figure.highlight.folded .highlight-body{height:0}figure.highlight .copy{opacity:.7}figure.highlight pre,figure.highlight table tr:hover{color:inherit;background:0 0}figure.highlight table{width:auto}figure.highlight table tr td{border:none}figure.highlight table tr:not(:first-child) td{padding-top:0}figure.highlight table tr:not(:last-child) td{padding-bottom:0}figure.highlight pre{padding:0;overflow:visible}figure.highlight pre .line,figure.highlight pre code .hljs{line-height:1.5rem}figure.highlight .gutter,figure.highlight figcaption{background:rgba(200,200,200,.15)}figure.highlight figcaption{margin:0!important;padding:.3em 0 .3em .75em;font-style:normal;font-size:.8em}figure.highlight figcaption *{color:inherit}figure.highlight figcaption span{font-weight:500;font-family:'Source Code Pro',monospace,'Microsoft YaHei'}figure.highlight figcaption .level-left :not(:last-child){margin-right:.5em}figure.highlight figcaption .level-right :not(:first-child){margin-left:.5em}figure.highlight figcaption .fold{cursor:pointer}figure.highlight figcaption.level{overflow:auto}figure.highlight figcaption.level .level-right a{padding:0 .75em}figure.highlight .highlight-body{overflow:auto}figure.highlight .gutter{text-align:right}figure.highlight .number,figure.highlight .section,figure.highlight .tag,figure.highlight .title{display:inherit;font:inherit;margin:inherit;padding:inherit;background:inherit;height:inherit;text-align:inherit;vertical-align:inherit;min-width:inherit;border-radius:inherit}figure.highlight.foldable div.level-left{cursor:pointer}.gist table tr:hover{background:0 0}.gist table td{border:none}.gist .file{all:initial}.widget .menu-list li ul{margin-right:0}.widget .menu-list .level{margin-bottom:0}.widget .menu-list .level .level-item,.widget .menu-list .level .level-left,.widget .menu-list .level .level-right{flex-shrink:1}.widget .menu-list .level .level-left,.widget .menu-list .level .level-right{align-items:flex-start}.widget .menu-list .tag{background:$light-grey;color:$white-invert}.widget .tags .tag:first-child{background:#3273dc;color:#fff}.widget .tags .tag:last-child{background:$light-grey;color:$white-invert}.level.is-multiline{flex-wrap:wrap}@media screen and (max-width:768px){.widget.card#toc{display:none;position:fixed;margin:1rem;left:0;right:0;bottom:0;z-index:100}.widget.card#toc .card-content{padding:0}.widget.card#toc .menu{padding:1.5rem;max-height:calc(100vh - 2rem);overflow-y:auto}#toc-mask{display:none;position:fixed;top:0;left:0;right:0;bottom:0;z-index:99;background:rgba(0,0,0,.7)}#toc-mask.is-active,.widget.card#toc.is-active{display:block}}.donate{position:relative}.donate .qrcode{display:none;position:absolute;z-index:99;bottom:2.5em;line-height:0;overflow:hidden;box-shadow:0 4px 10px rgba(0,0,0,.05),0 0 1px rgba(0,0,0,.1);border-radius:4px}.donate .qrcode img{max-width:280px}.donate:hover .qrcode{display:block}.donate:first-child:not(:last-child) .qrcode{left:-.75rem}.donate:last-child:not(:first-child) .qrcode{right:-.75rem}.donate[data-type=afdian]{color:#fff;background-color:#885fd9;border-color:transparent}.donate[data-type=afdian]:active{background-color:#794ad4}.donate[data-type=afdian]:hover{background-color:#8055d7}.donate[data-type=afdian]:focus:not(:active){box-shadow:0 0 0 .125em rgba(136,95,217,.25)}.donate[data-type=alipay]{color:#fff;background-color:#00a0e8;border-color:transparent}.donate[data-type=alipay]:active{background-color:#008ecf}.donate[data-type=alipay]:hover{background-color:#0097db}.donate[data-type=alipay]:focus:not(:active){box-shadow:0 0 0 .125em rgba(0,160,232,.25)}.donate[data-type=buymeacoffee]{color:rgba(0,0,0,.7);background-color:#fd0;border-color:transparent}.donate[data-type=buymeacoffee]:active{background-color:#e6c700}.donate[data-type=buymeacoffee]:hover{background-color:#f2d200}.donate[data-type=buymeacoffee]:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,221,0,.25)}.donate[data-type=paypal]{color:rgba(0,0,0,.7);background-color:#feb700;border-color:transparent}.donate[data-type=paypal]:active{background-color:#e5a500}.donate[data-type=paypal]:hover{background-color:#f1ae00}.donate[data-type=paypal]:focus:not(:active){box-shadow:0 0 0 .125em rgba(254,183,0,.25)}.donate[data-type=patreon]{color:#fff;background-color:#ff424d;border-color:transparent}.donate[data-type=patreon]:active{background-color:#ff2835}.donate[data-type=patreon]:hover{background-color:#ff3541}.donate[data-type=patreon]:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,66,77,.25)}.donate[data-type=wechat]{color:#fff;background-color:#1aad19;border-color:transparent}.donate[data-type=wechat]:active{background-color:#179716}.donate[data-type=wechat]:hover{background-color:#18a217}.donate[data-type=wechat]:focus:not(:active){box-shadow:0 0 0 .125em rgba(26,173,25,.25)}#back-to-top{position:fixed;opacity:0;outline:0;padding:8px 0;line-height:24px;border-radius:4px;transform:translateY(120px);transition:.4s ease opacity,.4s ease width,.4s ease transform,.4s ease border-radius}#back-to-top.is-rounded{border-radius:50%}#back-to-top.fade-in{opacity:1}#back-to-top.rise-up{transform:translateY(0);box-shadow:rgba(240,46,170,.4) 5px 5px,rgba(240,46,170,.3) 10px 10px,rgba(240,46,170,.2) 15px 15px,rgba(240,46,170,.1) 20px 20px,rgba(240,46,170,.05) 25px 25px!important}.gallery-item .caption{color:#7a7a7a}.pace{user-select:none;pointer-events:none}.pace .pace-progress{top:0;right:100%;width:100%;height:2px;z-index:2000;position:fixed;background:#3273dc}.pace-inactive{display:none}.fa,.fab,.fal,.far,.fas{line-height:inherit}.MathJax,.katex-display{overflow-x:auto;overflow-y:hidden}.katex{white-space:nowrap}.katex-display{margin-top:-1em!important}.katex-html{padding-top:1em}.katex-html .tag{align-items:unset;background-color:unset;border-radius:unset;color:unset;display:unset;font-size:unset;height:unset;justify-content:unset;line-height:unset;padding-left:unset;padding-right:unset;white-space:unset}.cc-revoke,.cc-window{font-size:1.1rem!important;font-family:Ubuntu,Roboto,'Open Sans','Microsoft YaHei',sans-serif!important}.cc-window{color:#4a4a4a!important;background-color:#fff!important}.cc-window.cc-floating{border-radius:4px;box-shadow:0 4px 10px rgba(0,0,0,.05),0 0 1px rgba(0,0,0,.1)}.cc-window.cc-banner{background-color:#f9f9f9!important}.cc-window.cc-theme-block .cc-compliance>.cc-btn,.cc-window.cc-theme-classic .cc-compliance>.cc-btn{border-radius:290486px}.cc-window .cc-compliance>.cc-btn{font-weight:400;border:none;color:#fff;background-color:#3273dc}.cc-window .cc-compliance>.cc-btn:focus,.cc-window .cc-compliance>.cc-btn:hover{background-color:#276cda}.cc-window .cc-compliance>.cc-btn.cc-deny:hover{color:#3273dc;text-decoration:none}.cc-revoke{padding:.5rem 1rem!important;color:#fff!important;background-color:#3273dc!important}.cc-revoke:hover{text-decoration:none!important;background-color:#276cda}@media screen and (min-width:1280px){.is-1-column .container,.is-2-column .container{max-width:960px;width:960px}}@media screen and (min-width:1472px){.is-2-column .container{max-width:1152px;width:1152px}.is-1-column .container{max-width:960px;width:960px}}@media screen and (min-width:769px),print{.is-sticky{position:-webkit-sticky;position:sticky;top:1.5rem;z-index:99}.column-left.is-sticky,.column-main.is-sticky,.column-right-shadow.is-sticky,.column-right.is-sticky{top:.75rem;align-self:flex-start}}@media screen and (max-width:768px){.section{padding:1.5rem 1rem}}.read-more-button{padding:1.6em 4em;border:none;outline:0;color:#fff;background:#111;cursor:pointer;position:relative;z-index:0;border-radius:10px;user-select:none;-webkit-user-select:none;touch-action:manipulation;font-size:1rem!important}.read-more-button:before{content:\"\";background:linear-gradient(45deg,red,#ff7300,#fffb00,#48ff00,#00ffd5,#002bff,#7a00ff,#ff00c8,red);position:absolute;top:-2px;left:-2px;background-size:400%;z-index:-1;filter:blur(5px);-webkit-filter:blur(5px);width:calc(100% + 4px);height:calc(100% + 4px);animation:glowing-read-more-button 20s linear infinite;transition:opacity .3s ease-in-out;border-radius:10px}@keyframes glowing-read-more-button{0%{background-position:0 0}50%{background-position:400% 0}100%{background-position:0 0}}.read-more-button:after{z-index:-1;content:\"\";position:absolute;width:100%;height:100%;background:#222;left:0;top:0;border-radius:10px}","link":"/css/default.css"}]}