Skip to content

Commit

Permalink
Added QubesOS case study
Browse files Browse the repository at this point in the history
  • Loading branch information
AnXh3L0 committed Jan 17, 2025
1 parent 859eb5d commit 7c8d3e9
Show file tree
Hide file tree
Showing 32 changed files with 515 additions and 77 deletions.
102 changes: 76 additions & 26 deletions assets/js/before-after-slider.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
document.addEventListener("DOMContentLoaded", function () {
const toggleSwitch = document.getElementById("view-toggle");
const slider = document.getElementById("slider");
const container = document.querySelector(".ba-slider");
const beforeText = document.getElementById("before-text");
const afterText = document.getElementById("after-text");

// Initialize the state
const initializeState = () => {
slider.value = 0; // Start with "After" at 0%
container.style.setProperty("--position", "0%");
beforeText.classList.add("text-muted");
afterText.classList.remove("text-muted");
toggleSwitch.setAttribute("aria-checked", "true");
};
// Get all instances of the toggle switch, sliders, and containers
const toggleSwitches = document.querySelectorAll(".toggle-input");
const sliders = document.querySelectorAll(".slider");
const containers = document.querySelectorAll(".ba-slider");
const beforeTexts = document.querySelectorAll(".before-text");
const afterTexts = document.querySelectorAll(".after-text");

// Function to animate the slider
const animateSlider = (start, end, duration = 350) => {
const animateSlider = (slider, container, start, end, duration = 350) => {
const stepTime = 10; // Step interval in ms
const steps = duration / stepTime;
const stepValue = (end - start) / steps;
Expand All @@ -36,33 +28,91 @@ document.addEventListener("DOMContentLoaded", function () {
}, stepTime);
};

// Function to handle slider position when toggling
const handleToggle = () => {
// Function to update the toggle switch based on slider position
const updateToggleFromSlider = (
container,
slider,
toggleSwitch,
beforeText,
afterText
) => {
const position =
parseFloat(container.style.getPropertyValue("--position")) || 0;

if (position === 0 && !toggleSwitch.checked) {
toggleSwitch.checked = true;
afterText.classList.remove("text-muted");
beforeText.classList.add("text-muted");
toggleSwitch.setAttribute("aria-checked", "true");
} else if (position === 100 && toggleSwitch.checked) {
toggleSwitch.checked = false;
afterText.classList.add("text-muted");
beforeText.classList.remove("text-muted");
toggleSwitch.setAttribute("aria-checked", "false");
}
};

// Function to handle toggle switch logic
const handleToggle = (
toggleSwitch,
slider,
container,
beforeText,
afterText
) => {
const isChecked = toggleSwitch.checked;

if (isChecked) {
// Show "After" view
animateSlider(parseFloat(slider.value), 0); // Animate to 0% (After)
animateSlider(slider, container, parseFloat(slider.value), 0); // Animate to 0% (After)
beforeText.classList.add("text-muted");
afterText.classList.remove("text-muted");
toggleSwitch.setAttribute("aria-checked", "true");
} else {
// Show "Before" view
animateSlider(parseFloat(slider.value), 100); // Animate to 100% (Before)
animateSlider(slider, container, parseFloat(slider.value), 100); // Animate to 100% (Before)
afterText.classList.add("text-muted");
beforeText.classList.remove("text-muted");
toggleSwitch.setAttribute("aria-checked", "false");
}
};

// Initialize the state on page load
initializeState();
// Initialize the state for each slider instance
toggleSwitches.forEach((toggleSwitch, index) => {
const slider = sliders[index];
const container = containers[index];
const beforeText = beforeTexts[index];
const afterText = afterTexts[index];

// Add event listener for the switch toggle
toggleSwitch.addEventListener("change", handleToggle);
if (slider && container && beforeText && afterText) {
// Initialize slider position
container.style.setProperty("--position", "0%");
slider.value = 0;
beforeText.classList.add("text-muted");
afterText.classList.remove("text-muted");
toggleSwitch.setAttribute("aria-checked", "true");

// Add event listener for the switch toggle
toggleSwitch.addEventListener("change", () => {
handleToggle(toggleSwitch, slider, container, beforeText, afterText);
});

// Add slider logic (if manual adjustment is still needed)
slider.addEventListener("input", (e) => {
container.style.setProperty("--position", `${e.target.value}%`);
// Add event listener for manual slider input
slider.addEventListener("input", () => {
const value = parseFloat(slider.value);
container.style.setProperty("--position", `${value}%`);
updateToggleFromSlider(
container,
slider,
toggleSwitch,
beforeText,
afterText
);
});
} else {
console.error(
"One or more elements (slider, container, beforeText, afterText) are missing."
);
}
});
});
13 changes: 6 additions & 7 deletions assets/scss/_custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1854,13 +1854,12 @@ details[open] summary svg {
.image-container {
max-width: 100%;
max-height: 90vh;
aspect-ratio: 1/1;
aspect-ratio: auto;
}

.slider-image {
& figure {
width: 100%;
height: 100%;
padding-block: 0;
}

Expand Down Expand Up @@ -1897,10 +1896,10 @@ details[open] summary svg {
.slider-line {
position: absolute;
inset: 0;
width: 0.15rem;
width: 0.1rem;
height: 100%;
opacity: 0.4;
background-color: $black;
opacity: 0.3;
background-color: $gray-500;
left: var(--position);
transform: translateX(-50%);
pointer-events: none;
Expand All @@ -1909,9 +1908,9 @@ details[open] summary svg {
.slider-button {
position: absolute;
background-color: $white;
color: $black;
color: $gray-500;
padding: 0.5rem;
border-radius: 100vw;
border-radius: 0;
display: grid;
place-items: center;
top: 50%;
Expand Down
4 changes: 0 additions & 4 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ pluralizeListTitles = false
name = "github"
url = "https://github.com/uradotdesign"

[[params.social]]
name = "x-twitter"
url = "https://twitter.com/uradotdesign"

[[params.social]]
name = "instagram"
url = "https://instagram.com/uradotdesign"
Expand Down
6 changes: 4 additions & 2 deletions content/de/imprint.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ text = "#FFFFFF"

Ura Design wird betrieben von (Impressum nach § 5 TMG, ebenso verantwortlich für den Inhalt nach § 55 Abs. 2 RStV):

Elio Qoshi
Elio Qoshi\
Ura Design e.K.\
c/o Cultivation Space\
Aufgang 4\
Gottschedstraße 4\
13357 Berlin\
13357 Berlin, Deutschland

Steuernummer: 34/481/01445\
USt.-IDNr: DE357155567

Expand Down
104 changes: 104 additions & 0 deletions content/de/work/qubesos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
+++
layout = "work/single"
featured = true
theme = "light"
background = "#FFFFFF"
accent = "#005F9C"
text = "#212529"
title = "Qubes OS Usability Audit"
summary = "Qubes OS is renowned for its security, but its complex interface can be a major hurdle for new users. This project aimed to untangle that complexity while preserving the advanced protections that make it one of the most secure systems available."
date = "2025-01-08 12:00:00 +0200"
images = ["img/work/qubesos.webp", "qubesos"]
tags = []
categories = ["Accessibility", "UX Research", "Illustrations"]
[links]
"Qubes OS Website" = "https://www.qubes-os.org/"
+++

{{< figure class="with-js on-light" src="/img/work/qubesos-logo.svg" alt="QubesOS logo" >}}
{{< figure class="with-js on-dark" src="/img/work/qubesos-logo-dark.svg" alt="QubesOS logo" >}}
{{< figure class="no-js" src="/img/work/qubesos-logo.webp" alt="QubesOS logo" >}}

## Qubes OS Mission

Qubes OS is a security-focused operating system that uses virtualization to isolate tasks and applications in separate environments called ‘qubes’. This means that if one qube is compromised, the rest of the system stays secure. Think of it like a digital fortress, where each room is locked and independently secured.

{{< figure src="/img/work/qubesos-cover.webp" alt="QubesOS cover image" >}}

## Our Approach and Objectives

Qubes OS is known for its strong security but faces significant usability challenges. Technical users often bypass the user interface in favor of the command line, limiting its appeal to non-technical users.

We identified journalists as a key user group who need strong security but lack the technical expertise to navigate Qubes OS. For instance, a journalist investigating government corruption may need a secure system to protect sensitive communications but could struggle with the platform’s complexity.

To address these challenges, we focused on improving critical aspects of the user experience: accessibility, visual consistency, clear communication, and intuitive interface design. Our goal was to lower the learning curve and make Qubes OS accessible to a broader range of users without compromising its core strengths.

## Creating an accessible color system

We adopted the [Tailwind CSS](https://tailwindcss.com/docs/customizing-colors) color system, an open-source framework with a diverse palette. This system improves readability, accessibility, and creates a more cohesive, visually appealing UI with improved WCAG 2.2 compliance.

Each qube is identified by a color to help users differentiate them. The previous color scheme lacked sufficient contrast, making text hard to read. We updated the qube window colors using the Tailwind palette to improve readability and color contrast across Windows.

{{< ba-slider
before_light="/img/work/qubesos-window-before.webp"
before_dark="/img/work/qubesos-window-before.webp"
before_nojs="/img/work/qubesos-window-before.webp"
after_light="/img/work/qubesos-window-after.webp"
after_dark="/img/work/qubesos-window-after.webp"
after_nojs="/img/work/qubesos-window-after.webp"
>}}
## Building a consistent icon set

We reviewed the existing Qubes icons, analyzing their usage to understand how they functioned across the platform. This informed our proposal for a new icon set, sourced mostly from lucide.dev under an MIT license, featuring outlined designs with customizable stroke widths.

{{< ba-slider
before_light="/img/work/qubesos-icons-before.webp"
before_dark="/img/work/qubesos-icons-before-dark.webp"
before_nojs="/img/work/qubesos-icons-before-dark.webp"
after_light="/img/work/qubesos-icons-after.webp"
after_dark="/img/work/qubesos-icons-after-dark.webp"
after_nojs="/img/work/qubesos-icons-after-dark.webp"
>}}
While most icons were sourced from Lucide, we redesigned those for specific qube types, as the previous icons didn't align well with pixel grids. The redesign incorporated the updated qube colors and gave the icons a more modern look.

{{< ba-slider
before_light="/img/work/qubesos-qubeicons-before.svg"
before_dark="/img/work/qubesos-qubeicons-before.svg"
before_nojs="/img/work/qubesos-qubeicons-before.webp"
after_light="/img/work/qubesos-qubeicons-after.svg"
after_dark="/img/work/qubesos-qubeicons-after.svg"
after_nojs="/img/work/qubesos-qubeicons-after.webp"
>}}
## Rewriting UX copy

To improve clarity and accessibility, we conducted a copy audit of the Global Configuration settings. We focused on making the text **necessary**, **clear**, **concise**, and **useful**, ensuring it provided essential, actionable information for users. Based on these criteria, we proposed updated text to replace the old content.

## Bringing it all together

For the final step, we took all these elements; the new color system, icon set, and updated text to redesign the global configurations page.

We chose screens from the global configuration settings that showcased the different design patterns that exist across all the screens. This way when the redesign is implemented they have all the different ux patterns documented so they can be applied across the different screens.

{{< ba-slider
before_light="/img/work/qubesos-general-settings-before.svg"
before_dark="/img/work/qubesos-general-settings-before.svg"
before_nojs="/img/work/qubesos-general-settings-before.webp"
after_light="/img/work/qubesos-general-settings-after.svg"
after_dark="/img/work/qubesos-general-settings-after.svg"
after_nojs="/img/work/qubesos-general-settings-after.webp"
>}}
## How we helped

**Improved Accessibility**: The introduction of the Tailwind color system and the update of qube window colors have improved the clarity of text and ensured compliance with WCAG 2.2 standards, making the interface more usable.

**Visual Consistency**: A refreshed logo has resolved inconsistencies in branding across platforms, reinforcing Qubes OS's visual identity.

**Enhanced Iconography**: The redesigned icon set creates a unified, modern look while improving functionality, making navigation and interaction more intuitive for users.

**Clearer UX Copy**: The audit of the Global Configuration settings copy has led to clearer, more concise text, improving user comprehension and facilitating smoother interactions.

**Unified UI/UX Design**: The redesign of the global configuration pages, incorporating new colors, icons, and updated text, has resulted in a cohesive and visually appealing user experience.
6 changes: 4 additions & 2 deletions content/en/imprint.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ text = "#FFFFFF"

Ura Design is operated by (Impressum nach § 5 TMG, ebenso verantwortlich für den Inhalt nach § 55 Abs. 2 RStV):

Elio Qoshi
Elio Qoshi\
Ura Design e.K.\
c/o Cultivation Space\
Aufgang 4\
Gottschedstraße 4\
13357 Berlin\
13357 Berlin, Germany

Tax ID: 34/481/01445\
VAT: DE357155567

Expand Down
Loading

0 comments on commit 7c8d3e9

Please sign in to comment.