Browse Source

Added OONI case study

Added Lottie Player and before/after switch toggle
main
Anxhelo Lushka 2 months ago
parent
commit
859eb5d69a
No known key found for this signature in database GPG Key ID: C86D6CF1F7FAAA35
  1. 68
      assets/js/before-after-slider.js
  2. 77
      assets/js/lottie-player.js
  3. 132
      assets/scss/_custom.scss
  4. 95
      content/de/work/ooni.md
  5. 1
      content/de/work/rallly.md
  6. 95
      content/en/work/ooni.md
  7. 1
      content/en/work/rallly.md
  8. 211
      layouts/shortcodes/ba-slider.html
  9. 23
      layouts/shortcodes/lottie.html
  10. 1
      static/img/animations/ooni-eyes.json
  11. BIN
      static/img/work/ooni-cover.webp
  12. 8
      static/img/work/ooni-illustration-system.svg
  13. BIN
      static/img/work/ooni-illustration-system.webp
  14. 101
      static/img/work/ooni-mascot-after-dark.svg
  15. 101
      static/img/work/ooni-mascot-after.svg
  16. BIN
      static/img/work/ooni-mascot-after.webp
  17. 110
      static/img/work/ooni-mascot-before-dark.svg
  18. 110
      static/img/work/ooni-mascot-before.svg
  19. BIN
      static/img/work/ooni-mascot-before.webp
  20. 31
      static/img/work/ooni-octopus-tentacles-mobile.svg
  21. BIN
      static/img/work/ooni-octopus-tentacles-mobile.webp
  22. 31
      static/img/work/ooni-octopus-tentacles.svg
  23. BIN
      static/img/work/ooni-octopus-tentacles.webp
  24. BIN
      static/img/work/ooni-ooniverse.webp
  25. BIN
      static/img/work/ooni-precision.webp
  26. 21
      static/img/work/ooni-tentacles.svg
  27. BIN
      static/img/work/ooni-tentacles.webp

68
assets/js/before-after-slider.js

@ -0,0 +1,68 @@
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");
};
// Function to animate the slider
const animateSlider = (start, end, duration = 350) => {
const stepTime = 10; // Step interval in ms
const steps = duration / stepTime;
const stepValue = (end - start) / steps;
let currentValue = start;
let currentStep = 0;
const interval = setInterval(() => {
currentStep++;
currentValue += stepValue;
slider.value = currentValue;
container.style.setProperty("--position", `${currentValue}%`);
if (currentStep >= steps) {
clearInterval(interval);
slider.value = end; // Ensure it ends exactly at the target
container.style.setProperty("--position", `${end}%`);
}
}, stepTime);
};
// Function to handle slider position when toggling
const handleToggle = () => {
const isChecked = toggleSwitch.checked;
if (isChecked) {
// Show "After" view
animateSlider(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)
afterText.classList.add("text-muted");
beforeText.classList.remove("text-muted");
toggleSwitch.setAttribute("aria-checked", "false");
}
};
// Initialize the state on page load
initializeState();
// Add event listener for the switch toggle
toggleSwitch.addEventListener("change", handleToggle);
// Add slider logic (if manual adjustment is still needed)
slider.addEventListener("input", (e) => {
container.style.setProperty("--position", `${e.target.value}%`);
});
});

77
assets/js/lottie-player.js

File diff suppressed because one or more lines are too long

132
assets/scss/_custom.scss

@ -154,6 +154,16 @@ html[data-theme="dark"] {
display: none !important;
}
.slider-line {
background-color: $white !important;
}
lottie-player {
--lottie-player-toolbar-icon-color: $white;
--lottie-player-toolbar-icon-active-color: #aaa;
--lottie-player-toolbar-icon-hover-color: $white;
}
@include media-breakpoint-down(lg) {
.d-lg-block.d-none.on-light {
display: none !important;
@ -1825,3 +1835,125 @@ details[open] summary svg {
font-size: 1.5rem;
}
}
.ba-slider-container {
display: grid;
place-items: center;
margin-block: 2rem;
}
.ba-slider {
display: grid;
place-content: center;
position: relative;
overflow: hidden;
// border-radius: 1rem;
--position: 50%;
}
.image-container {
max-width: 100%;
max-height: 90vh;
aspect-ratio: 1/1;
}
.slider-image {
& figure {
width: 100%;
height: 100%;
padding-block: 0;
}
& img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: left;
}
}
.image-before {
& figure {
position: absolute;
inset: 0;
width: var(--position);
}
}
.slider {
position: absolute;
inset: 0;
cursor: pointer;
opacity: 0;
width: 100%;
height: 100%;
}
.slider:focus-visible ~ .slider-button {
outline: 5px solid $black;
outline-offset: 3px;
}
.slider-line {
position: absolute;
inset: 0;
width: 0.15rem;
height: 100%;
opacity: 0.4;
background-color: $black;
left: var(--position);
transform: translateX(-50%);
pointer-events: none;
}
.slider-button {
position: absolute;
background-color: $white;
color: $black;
padding: 0.5rem;
border-radius: 100vw;
display: grid;
place-items: center;
top: 50%;
left: var(--position);
transform: translate(-50%, -50%);
pointer-events: none;
box-shadow: 1px 1px 1px hsla(0, 50%, 2%, 0.5);
}
.form-switch {
width: 2.5rem;
height: 1.5rem;
}
.switch-slider {
width: 100%;
height: 100%;
background-color: #ccc;
outline: 1px solid #ccc;
display: inline-block;
position: absolute;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
}
.switch-slider::before {
content: "";
width: 1.25rem;
height: 1.25rem;
background-color: $white;
position: absolute;
border-radius: 50%;
top: 50%;
left: 0.125rem;
transform: translateY(-50%);
transition: transform 0.3s ease;
}
input[type="checkbox"]:checked + .switch-slider {
background-color: $blue-500;
}
input[type="checkbox"]:checked + .switch-slider::before {
transform: translateY(-50%) translateX(1rem);
}

95
content/de/work/ooni.md

@ -0,0 +1,95 @@
+++
layout = "work/single"
featured = true
theme = "light"
background = "#E7F5FF"
accent = "#005F9C"
text = "#212529"
title = "OONI Illustration System"
summary = "We created a modular illustration system for OONI making it easy for both designers and non-designers to craft their own illustrations, bringing the OONIverse to life."
date = "2024-12-16 12:00:00 +0200"
images = ["img/work/ooni-illustration-system.webp", "ooni-illustration-system"]
tags = []
categories = ["Illustrations"]
[links]
"OONI Website" = "https://ooni.org"
"Outreach Kit" = "https://ooni.org/support/ooni-outreach-kit/"
+++
{{< figure src="/img/work/ooni-cover.webp" alt="OONI Illustration System" >}}
## OONI’s Mission
OONI, or the Open Observatory of Network Interference, is an open source project dedicated to measuring internet censorship worldwide. Their work is vital for documenting and understanding the landscape of internet freedom, particularly in regions where access to information is controlled or restricted by governments or other entities.
## Our Approach and Objectives
OONI’s tools offer crucial insights into internet censorship, but the information can sometimes feel dense and hard to navigate. This can make it tough for users to fully engage and understand its implications.
To address this, we used illustrations to clarify the content, making it more engaging and dynamic. Our goal was to transform OONI’s website, tools, and promotional materials into resources that are both informative and inviting, helping their diverse, global audience understand and connect with the information more easily.
## Redesigning the Mascot
OONI’s original mascot played a vital role in their brand identity, but they needed a more streamlined design that could be easily adapted and used in different settings.
{{< ba-slider
before_light="/img/work/ooni-mascot-before.svg"
before_dark="/img/work/ooni-mascot-before-dark.svg"
before_nojs="/img/work/ooni-mascot-before.webp"
after_light="/img/work/ooni-mascot-after.svg"
after_dark="/img/work/ooni-mascot-after-dark.svg"
after_nojs="/img/work/ooni-mascot-after.webp"
>}}
## Refined Design
The new mascot features a simplified design with a balanced look, making it more adaptable to different environments.
**Streamlined Aesthetic**: We removed the suction cup details from the tentacles and adjusted their spread, ensuring a consistent appearance in every use.
**Modular Components**: The mascot includes a separate head and four movable tentacles that can be adjusted or rearranged independently.
**Versatility**: This modular design allows for a wide range of combinations and poses, making the mascot adaptable to many different scenarios.
{{< figure class="with-js d-lg-block d-none" src="/img/work/ooni-octopus-tentacles.svg" alt="OONI octopus tentacles" >}}
{{< figure class="with-js d-block d-lg-none" src="/img/work/ooni-octopus-tentacles-mobile.svg" alt="OONI octopus tentacles" >}}
{{< figure class="no-js d-lg-block d-none" src="/img/work/ooni-octopus-tentacles.webp" alt="OONI octopus tentacles" >}}
{{< figure class="no-js d-block d-lg-none" src="/img/work/ooni-octopus-tentacles-mobile.webp" alt="OONI octopus tentacles" >}}
## Refining the Tentacles
The octopus is the central figure in the OONIverse, notable for its complexity and flexibility. With modular parts like the head, neck, and four tentacles, and 2,496 possible tentacle combinations, it offers a rich variety of visual expressions and potential uses across different contexts.
Designing the mascot’s tentacles presented several challenges. The primary goal was to create a stable structure that kept all elements intact while allowing for various combinations. Striking the right balance between organic and geometric shapes was crucial to ensure the design was both modern and reflective of underwater life.
{{< figure class="with-js" src="/img/work/ooni-tentacles.svg" alt="OONI octopus tentacles" >}}
{{< figure class="no-js" src="/img/work/ooni-tentacles.webp" alt="OONI octopus tentacles" >}}
## Adding Emotional Depth
Adding an array of emotions brought the characters to life, infusing them with personality and expression. We designed a range of expressions, including neutral, bored, excited, shy, and angry, to cover the main emotions the character would need. This variety ensures that the mascot is vivid, lifelike, and approachable, making it easier to connect with and engage the audience.
{{< lottie src="https://lottie.host/79ec3797-da81-4e42-8260-cb907ddbfa67/TyiAKnh6SA.json" loop="true" autoplay="true" controls="true" >}}
## Expanding the OONIverse
Once the mascot was ready, we set out to build a world for them to explore. Using a similar modular approach, we developed two additional characters: the fish and the eel, each with its own set of components and variations. We also created scenes or ‘play sets’ featuring interchangeable elements like seaweed and various ‘underwater’ shapes, allowing for the creation of lively and dynamic scenes, including different color modes for different depths of the ocean.
{{< figure src="/img/work/ooni-ooniverse.webp" alt="OONIverse" >}}
## Managing Design Elements with Precision
To manage the various elements of each character and scene, we developed a comprehensive design system that organizes all the components and variations. A key aspect of this system is the use of Figma variables to create color options for each illustration component. This approach allows the creatures to change colors with just one click, offering six different color collections, each with 3-4 color modes.
{{< figure src="/img/work/ooni-precision.webp" alt="OONI design elements" >}}
## How we helped
**Custom Illustration System**: We designed a modular illustration system that allows both designers and non-designers to easily create custom visuals.
**Simplified Mascot**: We streamlined the mascot’s design for improved adaptability and introduced a range of emotions to keep it engaging and relatable across different scenarios.
**Enhanced Accessibility**: We created visuals that align with WCAG 2.2 guidelines, making OONI’s content more accessible for their diverse global audience.
**Cohesive Design System**: We built a cohesive design system that streamlines the creation process, offering flexibility and consistency for designers and non designers alike.

1
content/de/work/rallly.md

@ -1,6 +1,5 @@
+++
layout = "work/single"
featured = true
theme = "light"
background = "#F7F7FF"
accent = "#54298B"

95
content/en/work/ooni.md

@ -0,0 +1,95 @@
+++
layout = "work/single"
featured = true
theme = "light"
background = "#E7F5FF"
accent = "#005F9C"
text = "#212529"
title = "OONI Illustration System"
summary = "We created a modular illustration system for OONI making it easy for both designers and non-designers to craft their own illustrations, bringing the OONIverse to life."
date = "2024-12-16 12:00:00 +0200"
images = ["img/work/ooni-illustration-system.webp", "ooni-illustration-system"]
tags = []
categories = ["Illustrations"]
[links]
"OONI Website" = "https://ooni.org"
"Outreach Kit" = "https://ooni.org/support/ooni-outreach-kit/"
+++
{{< figure src="/img/work/ooni-cover.webp" alt="OONI Illustration System" >}}
## OONI’s Mission
OONI, or the Open Observatory of Network Interference, is an open source project dedicated to measuring internet censorship worldwide. Their work is vital for documenting and understanding the landscape of internet freedom, particularly in regions where access to information is controlled or restricted by governments or other entities.
## Our Approach and Objectives
OONI’s tools offer crucial insights into internet censorship, but the information can sometimes feel dense and hard to navigate. This can make it tough for users to fully engage and understand its implications.
To address this, we used illustrations to clarify the content, making it more engaging and dynamic. Our goal was to transform OONI’s website, tools, and promotional materials into resources that are both informative and inviting, helping their diverse, global audience understand and connect with the information more easily.
## Redesigning the Mascot
OONI’s original mascot played a vital role in their brand identity, but they needed a more streamlined design that could be easily adapted and used in different settings.
{{< ba-slider
before_light="/img/work/ooni-mascot-before.svg"
before_dark="/img/work/ooni-mascot-before-dark.svg"
before_nojs="/img/work/ooni-mascot-before.webp"
after_light="/img/work/ooni-mascot-after.svg"
after_dark="/img/work/ooni-mascot-after-dark.svg"
after_nojs="/img/work/ooni-mascot-after.webp"
>}}
## Refined Design
The new mascot features a simplified design with a balanced look, making it more adaptable to different environments.
**Streamlined Aesthetic**: We removed the suction cup details from the tentacles and adjusted their spread, ensuring a consistent appearance in every use.
**Modular Components**: The mascot includes a separate head and four movable tentacles that can be adjusted or rearranged independently.
**Versatility**: This modular design allows for a wide range of combinations and poses, making the mascot adaptable to many different scenarios.
{{< figure class="with-js d-lg-block d-none" src="/img/work/ooni-octopus-tentacles.svg" alt="OONI octopus tentacles" >}}
{{< figure class="with-js d-block d-lg-none" src="/img/work/ooni-octopus-tentacles-mobile.svg" alt="OONI octopus tentacles" >}}
{{< figure class="no-js d-lg-block d-none" src="/img/work/ooni-octopus-tentacles.webp" alt="OONI octopus tentacles" >}}
{{< figure class="no-js d-block d-lg-none" src="/img/work/ooni-octopus-tentacles-mobile.webp" alt="OONI octopus tentacles" >}}
## Refining the Tentacles
The octopus is the central figure in the OONIverse, notable for its complexity and flexibility. With modular parts like the head, neck, and four tentacles, and 2,496 possible tentacle combinations, it offers a rich variety of visual expressions and potential uses across different contexts.
Designing the mascot’s tentacles presented several challenges. The primary goal was to create a stable structure that kept all elements intact while allowing for various combinations. Striking the right balance between organic and geometric shapes was crucial to ensure the design was both modern and reflective of underwater life.
{{< figure class="with-js" src="/img/work/ooni-tentacles.svg" alt="OONI octopus tentacles" >}}
{{< figure class="no-js" src="/img/work/ooni-tentacles.webp" alt="OONI octopus tentacles" >}}
## Adding Emotional Depth
Adding an array of emotions brought the characters to life, infusing them with personality and expression. We designed a range of expressions, including neutral, bored, excited, shy, and angry, to cover the main emotions the character would need. This variety ensures that the mascot is vivid, lifelike, and approachable, making it easier to connect with and engage the audience.
{{< lottie src="https://lottie.host/79ec3797-da81-4e42-8260-cb907ddbfa67/TyiAKnh6SA.json" loop="true" autoplay="true" controls="true" >}}
## Expanding the OONIverse
Once the mascot was ready, we set out to build a world for them to explore. Using a similar modular approach, we developed two additional characters: the fish and the eel, each with its own set of components and variations. We also created scenes or ‘play sets’ featuring interchangeable elements like seaweed and various ‘underwater’ shapes, allowing for the creation of lively and dynamic scenes, including different color modes for different depths of the ocean.
{{< figure src="/img/work/ooni-ooniverse.webp" alt="OONIverse" >}}
## Managing Design Elements with Precision
To manage the various elements of each character and scene, we developed a comprehensive design system that organizes all the components and variations. A key aspect of this system is the use of Figma variables to create color options for each illustration component. This approach allows the creatures to change colors with just one click, offering six different color collections, each with 3-4 color modes.
{{< figure src="/img/work/ooni-precision.webp" alt="OONI design elements" >}}
## How we helped
**Custom Illustration System**: We designed a modular illustration system that allows both designers and non-designers to easily create custom visuals.
**Simplified Mascot**: We streamlined the mascot’s design for improved adaptability and introduced a range of emotions to keep it engaging and relatable across different scenarios.
**Enhanced Accessibility**: We created visuals that align with WCAG 2.2 guidelines, making OONI’s content more accessible for their diverse global audience.
**Cohesive Design System**: We built a cohesive design system that streamlines the creation process, offering flexibility and consistency for designers and non designers alike.

1
content/en/work/rallly.md

@ -1,6 +1,5 @@
+++
layout = "work/single"
featured = true
theme = "light"
background = "#F7F7FF"
accent = "#54298B"

211
layouts/shortcodes/ba-slider.html

@ -0,0 +1,211 @@
<div class="ba-slider-container">
<div class="ba-slider" style="--position: 0%;" role="region" aria-labelledby="slider">
<div class="image-container">
<div class="image-before slider-image">
<!-- Before Image (Desktop) -->
{{ if .Params.before_light_desktop }}
<figure class="with-js on-light d-lg-block d-none">
<img src="{{ .Params.before_light_desktop }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.before_light }}
<figure class="with-js on-light d-lg-block d-none">
<img src="{{ .Params.before_light }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.before_dark_desktop }}
<figure class="with-js on-dark d-lg-block d-none">
<img src="{{ .Params.before_dark_desktop }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.before_dark }}
<figure class="with-js on-dark d-lg-block d-none">
<img src="{{ .Params.before_dark }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.before_nojs_desktop }}
<figure class="no-js d-lg-block d-none">
<img src="{{ .Params.before_nojs_desktop }}" alt="Before Image">
</figure>
{{ else if .Params.before_nojs }}
<figure class="no-js d-lg-block d-none">
<img src="{{ .Params.before_nojs }}" alt="Before Image">
</figure>
{{ end }}
<!-- Before Image (Mobile) -->
{{ if .Params.before_light_mobile }}
<figure class="with-js on-light d-block d-lg-none">
<img src="{{ .Params.before_light_mobile }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.before_light }}
<figure class="with-js on-light d-block d-lg-none">
<img src="{{ .Params.before_light }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.before_dark_mobile }}
<figure class="with-js on-dark d-block d-lg-none">
<img src="{{ .Params.before_dark_mobile }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.before_dark }}
<figure class="with-js on-dark d-block d-lg-none">
<img src="{{ .Params.before_dark }}" alt="Before Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.before_nojs_mobile }}
<figure class="no-js d-block d-lg-none">
<img src="{{ .Params.before_nojs_mobile }}" alt="Before Image">
</figure>
{{ else if .Params.before_nojs }}
<figure class="no-js d-block d-lg-none">
<img src="{{ .Params.before_nojs }}" alt="Before Image">
</figure>
{{ end }}
</div>
<div class="image-after slider-image">
<!-- After Image (Desktop) -->
{{ if .Params.after_light_desktop }}
<figure class="with-js on-light d-lg-block d-none">
<img src="{{ .Params.after_light_desktop }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.after_light }}
<figure class="with-js on-light d-lg-block d-none">
<img src="{{ .Params.after_light }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.after_dark_desktop }}
<figure class="with-js on-dark d-lg-block d-none">
<img src="{{ .Params.after_dark_desktop }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.after_dark }}
<figure class="with-js on-dark d-lg-block d-none">
<img src="{{ .Params.after_dark }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.after_nojs_desktop }}
<figure class="no-js d-lg-block d-none">
<img src="{{ .Params.after_nojs_desktop }}" alt="After Image">
</figure>
{{ else if .Params.after_nojs }}
<figure class="no-js d-lg-block d-none">
<img src="{{ .Params.after_nojs }}" alt="After Image">
</figure>
{{ end }}
<!-- After Image (Mobile) -->
{{ if .Params.after_light_mobile }}
<figure class="with-js on-light d-block d-lg-none">
<img src="{{ .Params.after_light_mobile }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.after_light }}
<figure class="with-js on-light d-block d-lg-none">
<img src="{{ .Params.after_light }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.after_dark_mobile }}
<figure class="with-js on-dark d-block d-lg-none">
<img src="{{ .Params.after_dark_mobile }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ else if .Params.after_dark }}
<figure class="with-js on-dark d-block d-lg-none">
<img src="{{ .Params.after_dark }}" alt="After Image"
style="background-color: {{ .Page.Params.background }};">
</figure>
{{ end }}
{{ if .Params.after_nojs_mobile }}
<figure class="no-js d-block d-lg-none">
<img src="{{ .Params.after_nojs_mobile }}" alt="After Image">
</figure>
{{ else if .Params.after_nojs }}
<figure class="no-js d-block d-lg-none">
<img src="{{ .Params.after_nojs }}" alt="After Image">
</figure>
{{ end }}
</div>
</div>
<input id="slider" type="range" min="0" max="100" value="0" aria-label="Adjust before and after view slider"
class="slider" />
<div class="slider-line" aria-hidden="true"></div>
<div class="slider-button" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none"></rect>
<line x1="128" y1="40" x2="128" y2="216" fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="16"></line>
<line x1="96" y1="128" x2="16" y2="128" fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="16"></line>
<polyline points="48 160 16 128 48 96" fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="16"></polyline>
<line x1="160" y1="128" x2="240" y2="128" fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="16"></line>
<polyline points="208 96 240 128 208 160" fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="16"></polyline>
</svg>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const lottiePlayer = document.querySelector('lottie-player');
if (lottiePlayer) {
const shadowRoot = lottiePlayer.shadowRoot;
const toolbar = shadowRoot.querySelector('.toolbar');
if (toolbar) {
toolbar.style.justifyContent = 'center';
toolbar.style.gap = '0.5rem';
} else {
console.log('Toolbar not found inside shadow DOM.');
}
} else {
console.error('lottie-player element not found.');
}
});
</script>
<div class="d-flex justify-content-center align-items-center gap-2" role="group" aria-label="Toggle between Before and After views">
<span class="fw-semibold text-muted" id="before-text">Before</span>
<label class="form-switch position-relative d-inline-block">
<input
type="checkbox"
class="form-check-input visually-hidden"
id="view-toggle"
checked
aria-checked="true"
aria-labelledby="before-text after-text"
aria-describedby="toggle-description"
>
<span class="switch-slider rounded-pill"></span>
</label>
<span class="fw-semibold" id="after-text">After</span>
</div>
<p id="toggle-description" class="visually-hidden">
Use this toggle to switch between the Before and After views of the slider.
</p>
{{ $js := resources.Get "js/before-after-slider.js" | resources.Minify }}
<script src="{{ $js.RelPermalink }}"></script>

23
layouts/shortcodes/lottie.html

@ -0,0 +1,23 @@
{{ $lottieScript := resources.Get "js/lottie-player.js" | resources.Minify }}
<script src="{{ $lottieScript.RelPermalink }}"></script>
<lottie-player
src="{{ .Get "src" | relURL }}"
background="{{ .Get "background" | default "transparent" }}"
speed="{{ .Get "speed" | default "1" }}"
{{ if .Get "loop" }}loop{{ end }}
{{ if .Get "controls" }}controls{{ end }}
{{ if .Get "autoplay" }}autoplay{{ end }}>
</lottie-player>
<style>
lottie-player {
height: 300px;
max-width: 100%;
max-height: 100%;
--lottie-player-seeker-display: none;
--lottie-player-toolbar-icon-color: #999;
--lottie-player-toolbar-icon-hover-color: #555;
--lottie-player-toolbar-icon-active-color: #222;
}
</style>

1
static/img/animations/ooni-eyes.json

File diff suppressed because one or more lines are too long

BIN
static/img/work/ooni-cover.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

8
static/img/work/ooni-illustration-system.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/img/work/ooni-illustration-system.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

101
static/img/work/ooni-mascot-after-dark.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

101
static/img/work/ooni-mascot-after.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

BIN
static/img/work/ooni-mascot-after.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

110
static/img/work/ooni-mascot-before-dark.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 66 KiB

110
static/img/work/ooni-mascot-before.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 66 KiB

BIN
static/img/work/ooni-mascot-before.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

31
static/img/work/ooni-octopus-tentacles-mobile.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/img/work/ooni-octopus-tentacles-mobile.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

31
static/img/work/ooni-octopus-tentacles.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/img/work/ooni-octopus-tentacles.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
static/img/work/ooni-ooniverse.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
static/img/work/ooni-precision.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

21
static/img/work/ooni-tentacles.svg

@ -0,0 +1,21 @@
<svg width="748" height="230" viewBox="0 0 748 230" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M636.672 94.9703C618.252 94.9703 605.131 115.176 606.934 137.133C607.685 146.271 599.044 144.017 597.82 141.888H570.542C570.542 141.888 570.542 148.597 571.264 153.071C575.084 176.767 630.685 186.402 630.685 146.684C630.685 130.802 626.227 112.167 648.39 108.929C670.553 105.69 691 84.0652 686.589 68.0973C681.2 48.5925 660.324 32.6693 637.146 47.5639C637.124 47.5778 637.117 47.6071 637.13 47.6292C640.822 53.6825 648.222 56.3236 654.431 59.7466C659.345 62.4558 662.542 67.1746 662.15 71.8896C661.089 84.6636 657.124 94.9703 636.672 94.9703Z" fill="#0588CB"/>
<path d="M585.515 89.7905H585.515C580.125 89.7905 576.908 84.9827 576.908 77.1901C576.908 69.3974 580.125 64.5896 585.515 64.5896C585.545 64.5896 585.575 64.5898 585.605 64.5902L608.861 63.0808L602.02 66.2404L596.812 77.0842L602.02 88.1493L608.861 91.3005L585.515 89.7911V89.7905Z" fill="#343A40"/>
<path d="M589.2 90.219H589.2C583.81 90.219 579.441 85.2475 579.441 77.1896C579.441 69.1316 583.81 64.1601 589.2 64.1601C589.23 64.1601 589.26 64.1603 589.29 64.1607L612.162 62.6L605.321 65.8671L600.113 77.0801L605.321 88.5219L612.162 91.7804L589.2 90.2196V90.219Z" fill="#FAB005"/>
<path d="M608.894 92.7037H608.893C602.257 92.7037 598.296 86.7841 598.296 77.1894C598.296 67.5946 602.257 61.675 608.893 61.675C608.93 61.675 608.968 61.6752 609.005 61.6757L637.639 59.8173L629.215 63.7075L622.803 77.059L629.215 90.683L637.639 94.5629L608.894 92.7045V92.7037Z" fill="#343A40"/>
<path d="M613.431 93.2314H613.431C606.795 93.2314 601.415 87.1103 601.415 77.1889C601.415 67.2675 606.795 61.1463 613.431 61.1463C613.468 61.1463 613.505 61.1466 613.542 61.1471L641.703 59.2253L633.28 63.248L626.868 77.0541L633.28 91.1419L641.703 95.1539L613.431 93.2322V93.2314Z" fill="#FAB005"/>
<path d="M640.556 97.456H640.556C631.887 97.456 626.712 89.7229 626.712 77.1887C626.712 64.6546 631.887 56.9215 640.556 56.9215C640.604 56.9215 640.653 56.9218 640.701 56.9224L678.107 54.4946L667.103 59.5766L658.727 77.0184L667.103 94.8162L678.107 99.8848L640.556 97.457V97.456Z" fill="#343A40"/>
<path d="M646.482 98.146H646.482C637.813 98.146 630.786 90.1496 630.786 77.1888C630.786 64.2279 637.813 56.2315 646.482 56.2315C646.531 56.2315 646.579 56.2318 646.627 56.2325L683.416 53.722L672.412 58.9771L664.036 77.0127L672.412 95.4163L683.416 100.657L646.482 98.147V98.146Z" fill="#FAB005"/>
<path d="M675.359 102.969H675.359C666.69 102.969 659.663 93.1323 659.663 77.1887C659.663 61.2451 666.69 51.4085 675.359 51.4085C675.407 51.4085 675.456 51.4089 675.504 51.4097L712.293 48.3215L701.289 54.7859L692.912 76.9721L701.289 99.611L712.293 106.058L675.359 102.97V102.969Z" fill="#343A40"/>
<ellipse cx="20.1355" cy="28.8743" rx="20.1355" ry="28.8743" transform="matrix(-1 0 0 1 732.426 48.3158)" fill="#C9E8FF"/>
<path d="M694.785 77.1847C694.785 84.6866 696.91 91.3653 700.21 96.0978C703.517 100.839 707.83 103.431 712.294 103.431C716.758 103.431 721.071 100.839 724.378 96.0978C727.678 91.3653 729.803 84.6866 729.803 77.1847C729.803 69.6829 727.678 63.0042 724.378 58.2717C721.071 53.5302 716.758 50.9388 712.294 50.9388C707.83 50.9388 703.517 53.5302 700.21 58.2717C696.91 63.0042 694.785 69.6829 694.785 77.1847Z" stroke="#343A40" stroke-width="5.24579"/>
<path opacity="0.3" d="M701.075 77.7506C699.537 77.7506 698.279 76.5023 698.402 74.9698C698.578 72.7685 698.964 70.6089 699.55 68.5532C700.382 65.6373 701.6 62.9878 703.136 60.756C704.672 58.5243 706.496 56.754 708.503 55.5461C709.501 54.9457 710.534 54.4901 711.588 54.1837C713.333 53.6766 714.834 55.1896 714.834 57.0066C714.834 58.8236 713.296 60.2255 711.636 60.9644C706.912 63.067 704.63 69.7219 704.026 74.9753C703.851 76.5026 702.612 77.7506 701.075 77.7506Z" fill="white"/>
<path d="M623.049 65.9526L611.668 66.805" stroke="#FCC419" stroke-width="6.99438" stroke-linecap="round"/>
<path d="M594.018 69.3672L587.471 69.8362" stroke="#FCC419" stroke-width="6.99438" stroke-linecap="round"/>
<path opacity="0.3" d="M693.436 56.1827L673.398 57.6906" stroke="white" stroke-width="6.99438" stroke-linecap="round"/>
<path d="M656.686 61.5604L642.712 62.4128" stroke="#FCC419" stroke-width="6.99438" stroke-linecap="round"/>
<path d="M641.226 85.8518C641.879 86.3332 642.672 86.5559 643.484 86.5559C647.224 86.5559 649.217 81.887 647.196 78.7396C638.36 64.9799 645.276 55.3531 652.168 55.0992C654.911 54.1129 651.328 48.2871 651.421 46.0821C651.146 42.8605 642.24 41.5826 633.307 50.9549C620.782 64.095 633.75 80.3425 641.226 85.8518Z" fill="#0588CB"/>
<path d="M26.7671 68.6877C25.9683 71.1725 27.9087 73.622 30.5187 73.6219C32.1072 73.6219 33.5247 72.6806 34.3344 71.314C40.9293 60.1831 55.1451 56.1 64.4575 69.071C81.9254 93.4015 33.3001 137.337 70.409 168.495C97.4283 191.181 151.928 177.069 151.928 141.888H124.65C114.322 157.117 87.8605 154.965 79.8612 143.289C70.409 129.492 83.9673 102.743 86.1627 85.1748C90.5627 49.966 40.2848 26.6392 26.7671 68.6877Z" fill="#0588CB"/>
<path d="M211.542 190.626C211.429 193.055 209.472 195.046 207.04 195.046C205.007 195.046 203.223 193.631 202.803 191.641C193.331 146.759 220.701 140.619 248.024 128.509C268.18 119.576 273.282 85.4554 273.282 59.7042H300.559C303.008 90.4795 297.576 136.106 259.628 148.485C236.467 156.04 213.24 154.062 211.542 190.626Z" fill="#0588CB"/>
<path d="M485.858 170.879C486.581 168.376 484.586 166.009 481.98 166.009C480.327 166.009 478.859 167.002 477.948 168.382C470.408 179.804 448.796 185.193 443.244 168.641C437.313 150.959 462.806 135.943 462.806 115.135C462.806 92.0531 449.189 78.239 449.189 57.6072H421.911C417.015 91.0038 430.981 100.175 433.102 111.812C435.397 124.402 426.807 131.571 422.96 146.938C409.253 201.691 474.176 211.334 485.858 170.879Z" fill="#0588CB"/>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
static/img/work/ooni-tentacles.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Loading…
Cancel
Save