Selenium With Python Tutorial: Adding Extensions in Firefox for Testing

Modern browsers are equipped with all sorts of functionalities, i.e., from bookmarks to GPS location tracking, developer tools, and more. Almost all modern web browsers have immense capabilities – Firefox has built-in screenshot capabilities, Opera has a free VPN, and Edge comes with built-in support for displaying ebooks with the EPUB extension. Though browsers like Firefox, Chrome, and others are majorly used for web browsing, the fact is that Add Ons (or extensions) in Firefox provide additional power to the web browser! The same rule of thumb applies to other browsers like Chrome, Edge, etc.

Additional add-ons, plugins, and extensions are required to extend or customize browser functionalities, boost productivity, and suit the specific requirements of users and developers. Test automation engineers that use test automation can also benefit from browser extensions like normal end-users. In fact, testers often need to automate the testing of these extensions itself :slightly_smiling_face:

More Than “Slapping Paint on a Website”

I’m a sucker for anything about front-end job titles.

Anselm Hannemann:

CSS evolved and we’re beyond the point where everyone can just do it as a side interest. We all can learn it and build amazing stuff with it, but using it wisely and correctly in a large-scale context isn’t an easy job anymore. It deserves people whose work is to focus on that part of the code.

Anselm is partly in responding to Sacha Greif’s “Is There Too Much CSS Now?” and the overall sentiment that CSS has a much higher barrier to entry for those learning it today than it did, say, in the CSS3 days. Back then, there was a super direct path to see the magic of CSS. Rachel Andrew perfectly captures that magic feeling in a prescient post from 2019:

There is something remarkable about the fact that, with everything we have created in the past 20 years or so, I can still take a complete beginner and teach them to build a simple webpage with HTML and CSS, in a day. […] We just need a text editor and a few hours. This is how we make things show up on a webpage.

That’s the real entry point here […]

“HTML, CSS and our vanishing industry entry points”

Rachel is speaking to the abstraction of frameworks on top of vanilla CSS (and HTML) but you might as well tack big, shiny, and fairly new features on there, like CSS grid, flexbox, container queries, cascade layers, custom properties, and relational pseudo-classes, to name a few. Not that those are abstractions, of course. There’s just a lot to learn right now, whether you’ve been writing CSS for 20 days or 20 years.

But back to Anselm’s post. Do we need to think about CSS as more than just, you know, styling things? I often joke that my job is slapping paint on websites to make them pretty. But, honestly, I know it’s a lot more than that. We all know it’s more than that.

Maybe CSS is an industry in itself. Think of all the possible considerations that have to pass through your brain when writing CSS rules. Heck, Ahmad Shadeed recently shared all the things his brain processes just to style a Hero component. CSS touches so much of the overall user experience — responsiveness, accessibility, performance, cross-browser, etc. — that it clearly goes well beyond “slapping paint on websites”. So far beyond that each of those things could be someone’s full-time gig, depending on the project.

So, yes, CSS has reached a point where I could imagine seeing “CSS Engineer” on some job board. As Anselm said, “[CSS] deserves people whose work is to focus on that part of the code.” Seen that way, it’s not so hard to imagine front-end development as a whole evolving into areas of specialization, just like many other industries.


More Than “Slapping Paint on a Website” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Sketchy Pencil Effect with Three.js Post-Processing

In this tutorial, you’ll learn how to create a sketchy, pencil effect using Three.js post-processing. We’ll go through the steps for creating a custom post-processing render pass, implementing edge detection in WebGL, re-rendering the normal buffer to a render target, and tweaking the end result with generated and imported textures.

This is what the end result looks like, so let’s get started!

Post-processing in Three.js

Post-processing in Three.js is a way of applying effects to your rendered scene after it has been drawn. In addition to all the out-of-the-box post-processing effects provided by Three.js, it is also possible to add your own filters by creating custom render passes.

A custom render pass is essentially a function that takes in an image of the scene and returns a new image, with the desired effect applied. You can think of these render passes like layer effects in Photoshop—each applies a new filter based on the previous effect output. The resulting image is a combination of all the different effects.

Enabling post-processing in Three.js

To add post-processing to our scene, we need to set up our scene rendering to use an EffectComposer in addition to the WebGLRenderer. The effect composer stacks the post-processing effects one on top of the other, in the order in which they’re passed. If we want our rendered scene to be passed to the next effect, we need to add the RenderPass post-processing pass is passed first.

Then, inside the tick function which starts our render loop, we call composer.render() instead of renderer.render(scene, camera).

const renderer = new THREE.WebGLRenderer()
// ... settings for the renderer are available in the Codesandbox below

const composer = new EffectComposer(renderer)
const renderPass = new RenderPass(scene, camera)

composer.addPass(renderPass)

function tick() {
	requestAnimationFrame(tick)
	composer.render()
}

tick()

There are two ways of creating a custom post-processing effect:

  1. Creating a custom shader and passing it to a ShaderPass instance, or
  2. Creating a custom render pass by extending the Pass class.

Because we want our post-processing effect to get more information than just uniforms and attributes, we will be creating a custom render pass.

Creating a custom render pass

While there isn’t much documentation currently available on how to write your own custom post-processing pass in Three.js, there are plenty of examples to learn from already inside the library. A custom pass inherits from the Pass class and has three methods: setSize, render, and dispose. As you may have guessed, we’ll mostly be focusing on the render method.

First we’ll start by creating our own PencilLinesPass that extends the Pass class and will later implement our own rendering logic.

import { Pass, FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass'
import * as THREE from 'three'

export class PencilLinesPass extends Pass {
	constructor() {
		super()
	}

	render(
		renderer: THREE.WebGLRenderer,
		writeBuffer: THREE.WebGLRenderTarget,
		readBuffer: THREE.WebGLRenderTarget
	) {
		if (this.renderToScreen) {
			renderer.setRenderTarget(null)
		} else {
			renderer.setRenderTarget(writeBuffer)
			if (this.clear) renderer.clear()
		}
	}
}

As you can see, the render method takes in a WebGLRenderer and two WebGLRenderTargets, one for the write buffer and the other for the read buffer. In Three.js, render targets are basically textures to which we can render the scene, and they serve to send data between passes. The read buffer receives data in from the previous render pass, in our case that is the default RenderPass. The write buffer sends data out to the next render pass.

If renderToScreen is true, that means we want to send our buffer to the screen instead of to a render target. The renderer’s render target is set to null, so it defaults to the on-screen canvas.

At this point, we’re not actually rendering anything, not even the data coming in through the readBuffer. In order to get things rendered, we’ll need to create a FullscreenQuad and a shader material that will take care of rendering. The shader material gets rendered to the FullscreenQuad.

To test that everything is set up correctly, we can use the built-in CopyShader that will display whatever image we put into it. In this case, in this case the readBuffer‘s texture.

import { Pass, FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass'
import { CopyShader } from 'three/examples/jsm/shaders/CopyShader'
import * as THREE from 'three'

export class PencilLinesPass extends Pass {
	fsQuad: FullScreenQuad
	material: THREE.ShaderMaterial

	constructor() {
		super()

		this.material = new THREE.ShaderMaterial(CopyShader)
		this.fsQuad = new FullScreenQuad(this.material)
	}

	dispose() {
		this.material.dispose()
		this.fsQuad.dispose()
	}

	render(
		renderer: THREE.WebGLRenderer,
		writeBuffer: THREE.WebGLRenderTarget,
		readBuffer: THREE.WebGLRenderTarget
	) {
		this.material.uniforms['tDiffuse'].value = readBuffer.texture

		if (this.renderToScreen) {
			renderer.setRenderTarget(null)
			this.fsQuad.render(renderer)
		} else {
			renderer.setRenderTarget(writeBuffer)
			if (this.clear) renderer.clear()
			this.fsQuad.render(renderer)
		}
	}
}

Note: we’re passing the uniform tDiffuse to the shader material. The CopyShader already has this uniform built in and it represents the image to be displayed on the screen. If you’re writing your own ShaderPass, this uniform will be passed in to your shader automatically.

All that’s left is to hook the custom render pass into the scene by adding it to the EffectComposer—after the RenderPass of course!

const renderPass = new RenderPass(scene, camera)
const pencilLinesPass = new PencilLinesPass()

composer.addPass(renderPass)
composer.addPass(pencilLinesPass)

View the Codesandbox example

Scene with a custom render pass and the CopyShader

Now that we have everything set up, we can actually get started with creating our special effect!

Sobel operator for creating outlines

We need to be able to tell the computer to detect lines based on our input image, in this case the rendered scene. The kind of edge detection we’ll be using is called the Sobel operator, and it only consists of a few steps.

The Sobel operator does edge detection by looking at the gradient of a small section of the image—essentially how sharp the transition from one value to another is. The image is broken down into smaller “kernels”, or 3px by 3px squares where the central pixel is the one currently being processed. The image below shows what this might look like: the red square in the center represents the current pixel being evaluated and the rest of the squares are its neighbors.

3px by 3px kernel

The weighted value for each neighbor is then calculated by taking the pixels value (brightness) and multiplying it by a weight based on its position relative to the pixel being evaluated. This is done with weights biasing the gradient horizontally and vertically. The average of both values is taken, and if it passes a certain threshold, we consider the pixel to represent an edge.

The horizontal and vertical gradients for the Sobel operator

While the implementation for the Sobel operator follows the image representations above almost directly, it still takes time to grasp. Thankfully we don’t have to implement our own as Three.js already provides us with working code for one in the SobelOperatorShader. We’ll copy this code over into our shader material.

Implementing the Sobel operator

Instead of the CopyShader, we’ll now need to add our own ShaderMaterial so that we have control over the vertex and fragment shaders, as well as the uniforms sent to those shaders.

// PencilLinesMaterial.ts
export class PencilLinesMaterial extends THREE.ShaderMaterial {
	constructor() {
		super({
			uniforms: {
				// we'll keep the naming convention here since the CopyShader
				// also used a tDiffuse texture for the currently rendered scene.
				tDiffuse: { value: null },
				// we'll pass in the canvas size here later
				uResolution: {
					value: new THREE.Vector2(1, 1)
				}
			},
			fragmentShader, // to be imported from another file
			vertexShader // to be imported from another file
		})
	}
}

We’ll get to the fragment and vertex shaders soon, but first we need to use our new shader material in the scene. We do this by swapping out the CopyShader. Don’t forget to pass the resolution—the canvas size—as a the shader’s uniform. While outside the scope of this tutorial, it’s also important to update this uniform when the canvas resizes.

// PencilLinesPass.ts
export class PencilLinesPass extends Pass {
	fsQuad: FullScreenQuad
	material: PencilLinesMaterial

	constructor({ width, height }: { width: number; height: number }) {
		super()
		
		// change the material from to our new PencilLinesMaterial
		this.material = new PencilLinesMaterial() 
		this.fsQuad = new FullScreenQuad(this.material)

		// set the uResolution uniform with the current canvas's width and height
		this.material.uniforms.uResolution.value = new THREE.Vector2(width, height)
	}
}

Next, we can start on the vertex and fragment shaders.

The vertex shader doesn’t do much except set the gl_Position value and pass the uv attribute to the fragment shader. Because we’re rendering our image to a FullscreenQuad, the uv information corresponds to the position on the screen of any given fragment.

// vertex shader
varying vec2 vUv;

void main() {

    vUv = uv;

    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

The fragment shader is a fair bit more complicated, so let’s break it down line by line. First we want to implement the Sobel operator using the implementation already provided by Three.js. The only difference is that we want to control how we calculate the value at each pixel, since we’ll be introducing line detection of the normal buffer as well.

float combinedSobelValue() {
    // kernel definition (in glsl matrices are filled in column-major order)
    const mat3 Gx = mat3(-1, -2, -1, 0, 0, 0, 1, 2, 1);// x direction kernel
    const mat3 Gy = mat3(-1, 0, 1, -2, 0, 2, -1, 0, 1);// y direction kernel

    // fetch the 3x3 neighbourhood of a fragment

    // first column
    float tx0y0 = getValue(-1, -1);
    float tx0y1 = getValue(-1, 0);
    float tx0y2 = getValue(-1, 1);

    // second column
    float tx1y0 = getValue(0, -1);
    float tx1y1 = getValue(0, 0);
    float tx1y2 = getValue(0, 1);

    // third column
    float tx2y0 = getValue(1, -1);
    float tx2y1 = getValue(1, 0);
    float tx2y2 = getValue(1, 1);

    // gradient value in x direction
    float valueGx = Gx[0][0] * tx0y0 + Gx[1][0] * tx1y0 + Gx[2][0] * tx2y0 +
    Gx[0][1] * tx0y1 + Gx[1][1] * tx1y1 + Gx[2][1] * tx2y1 +
    Gx[0][2] * tx0y2 + Gx[1][2] * tx1y2 + Gx[2][2] * tx2y2;

    // gradient value in y direction
    float valueGy = Gy[0][0] * tx0y0 + Gy[1][0] * tx1y0 + Gy[2][0] * tx2y0 +
    Gy[0][1] * tx0y1 + Gy[1][1] * tx1y1 + Gy[2][1] * tx2y1 +
    Gy[0][2] * tx0y2 + Gy[1][2] * tx1y2 + Gy[2][2] * tx2y2;

    // magnitude of the total gradient
    float G = (valueGx * valueGx) + (valueGy * valueGy);
    return clamp(G, 0.0, 1.0);
}

To the getValue function we pass in the offset from the current pixel, thus identifying which pixel in the kernel we are looking at to get that value. For the time being, only the value of the diffuse buffer is being evaluated, we’ll add the normal buffer in the next step.

float valueAtPoint(sampler2D image, vec2 coord, vec2 texel, vec2 point) {
    vec3 luma = vec3(0.299, 0.587, 0.114);

    return dot(texture2D(image, coord + texel * point).xyz, luma);
}

float diffuseValue(int x, int y) {
    return valueAtPoint(tDiffuse, vUv, vec2(1.0 / uResolution.x, 1.0 / uResolution.y), vec2(x, y)) * 0.6;
}

float getValue(int x, int y) {
    return diffuseValue(x, y);
}

The valueAtPoint function takes any texture (diffuse or normal) and returns the grayscale value at a specified point. The luma vector is used to calculate the brightness of a color, hence turning the color into grayscale. The implementation comes from glsl-luma.

Because the getValue function only takes into account the diffuse buffer, this means that any edge in the scene will be detected, including edges created by both cast shadows and core shadows. This also means that edges which we would intuit, such as the outlines of objects, could get missed if they blend in too well with their surroundings. To capture those missing edges, we’ll add edge detection from the normal buffer next.

Finally, we call the Sobel operator in the main function like this:

void main() {
    float sobelValue = combinedSobelValue();
    sobelValue = smoothstep(0.01, 0.03, sobelValue);

    vec4 lineColor = vec4(0.32, 0.12, 0.2, 1.0);

    if (sobelValue > 0.1) {
        gl_FragColor = lineColor;
    } else {
        gl_FragColor = vec4(1.0);
    }
}

View the Codesandbox example

Rendered scene with edge detection using the Sobel operator

Creating a normal buffer rendering

For proper outlines, the Sobel operator is often applied to the normals of the scene and the depth buffer, so the outlines of objects are captured, but not lines within the object. Omar Shehata describes such a method in his excellent How to render outlines in WebGL tutorial. For the purposes of a sketchy pencil effect, we don’t need complete edge detection, but we do want to use normals for more complete edges and for sketchy shading effects later.

Since the normal is a vector that represents the direction of an object’s surface at each point, it often gets represented with a color to get an image with all the normal data from the scene. This image is the “normal buffer.”

In order to create a normal buffer, first we need a new render target in the PencilLinesPass constructor. We also need to create a single MeshNormalMaterial on the class, since we’ll be using this to override the scene’s default materials when rendering the normal buffer.

const normalBuffer = new THREE.WebGLRenderTarget(width, height)

normalBuffer.texture.format = THREE.RGBAFormat
normalBuffer.texture.type = THREE.HalfFloatType
normalBuffer.texture.minFilter = THREE.NearestFilter
normalBuffer.texture.magFilter = THREE.NearestFilter
normalBuffer.texture.generateMipmaps = false
normalBuffer.stencilBuffer = false
this.normalBuffer = normalBuffer

this.normalMaterial = new THREE.MeshNormalMaterial()

In order to render the scene inside the pass, the render pass actually needs a reference to the scene and to the camera. We’ll need to send those through the constructor of the render pass as well.

// PencilLinesPass.ts constructor
constructor({ ..., scene, camera}: { ...; scene: THREE.Scene; camera: THREE.Camera }) {
	super()
	this.scene = scene
	this.camera = camera
    ...
}

Inside the pass’s render method, we want to re-render the scene with the normal material overriding the default materials. We set the renderTarget to the normalBuffer and render the scene with the WebGLRenderer as we normally would. The only difference is that instead of rendering to the screen with the scene’s default materials, the renderer renders to our render target with the normal material. Then we pass the normalBuffer.texture to the shader material.

renderer.setRenderTarget(this.normalBuffer)
const overrideMaterialValue = this.scene.overrideMaterial

this.scene.overrideMaterial = this.normalMaterial
renderer.render(this.scene, this.camera)
this.scene.overrideMaterial = overrideMaterialValue

this.material.uniforms.uNormals.value = this.normalBuffer.texture
this.material.uniforms.tDiffuse.value = readBuffer.texture

If at this point you were to set the gl_FragColor to the value of the normal buffer with texture2D(uNormals, vUv); this would be the result:

Normal buffer of the current scene

Instead, inside the custom material’s fragment shader, we want to modify the getValue function to include the Sobel operator value from the normal buffer as well.

float normalValue(int x, int y) {
    return valueAtPoint(uNormals, vUv, vec2(1.0 / uResolution.x, 1.0 / uResolution.y), vec2(x, y)) * 0.3;
}

float getValue(int x, int y) {
    return diffuseValue(x, y) + normalValue(x, y);
}

The result will look similar to the previous step, but we will be able to add additional noise and sketchiness with this normal data in the next step.

View the Codesandbox example

Sobel operator applied to the diffuse and normal buffers

Adding generated and textured noise for shading and squiggles

There are two ways to bring noise into the post-processing effect at this point:

  1. By procedurally generating the noise within the shader, or
  2. By using an existing image with noise and applying it as a texture.

Both providing different levels of flexibility and control. For the noise function, I’ve gone with Inigo Quilez’s gradient noise implementation, since it provides nice uniformity in the noise when applying to the “shading” effect”.

This noise function is called when getting the value of the Sobel operator and is specifically applied to the normal value, so the getValue function in the fragment shader changes like so:

float getValue(int x, int y) {
    float noiseValue = noise(gl_FragCoord.xy);
    noiseValue = noiseValue * 2.0 - 1.0;
    noiseValue *= 10.0;

    return diffuseValue(x, y) + normalValue(x, y) * noiseValue;
}

The result is a textured pencil line and stippled effect on object curves where the normal vector values change. Notice that flat objects, like the plane, do not get these effects, since they don’t have any variation in normal values.

The next and final step of this effect is to add distortion to the lines. For this I used a texture file created in Photoshop using the Render Clouds effect.

Generated clouds texture created in Photoshop

The cloud texture is passed to the shader through a uniform, the same way that the diffuse and normal buffers are. Once the shader has access to the texture, we can sample the texture for each fragment and use it to offset the location we’re reading from in the buffer. Essentially, we get the squiggled line effect by distorting the image we’re reading from, not by drawing to a different place. Because the texture’s noise is smooth, the lines don’t come out jagged and irregular.

float normalValue(int x, int y) {
    float cutoff = 50.0;
    float offset = 0.5 / cutoff;
    float noiseValue = clamp(texture(uTexture, vUv).r, 0.0, cutoff) / cutoff - offset;

    return valueAtPoint(uNormals, vUv + noiseValue, vec2(1.0 / uResolution.x, 1.0 / uResolution.y), vec2(x, y)) * 0.3;
}

You can also play around with how the effect is applied each buffer individually. This can lead to lines being offset from one another, giving an even better hand-drawn effect.

View the Codesandbox example

Final effect including normal buffer based “shading” and line distortion

Conclusion

There are many techniques to create hand-drawn or sketchy effects in 3D, this tutorial lists just a few. From here, there are multiple ways to go forward. You could adjust the line thickness by modulating the threshold of what is considered an edge based on a noise texture. You could also apply the Sobel operator to the depth buffer, ignoring the diffuse buffer entirely, to get outlined objects without outlining shadows. You could add generated noise based on lighting information in the scene instead of based on the normals of the objects. The possibilities are endless, and I hope this tutorial has inspired you to pursue them!

Managing Dotfiles With Stow

As a programmer, much of our time at work is spent developing code (and attending meetings, of course. ), So much so that we usually spend a lot of time configuring the toolset that we use on a daily basis. We are not only talking about the evolution of the configuration but also about how to configure your new laptop, have several computers, or simply share a certain configuration of a certain tool with a colleague.

The great majority of applications (and more in UNIX systems) are configured through the well-known “dotfiles” (stored in the user’s $HOME), which are not more than files or folders that begin with a . to configure the applications (ex: .bashrc, .git/). The purpose of starting with a dot is that, by default operating systems treat them as hidden files and don’t show them unless you tell them to.

11 Typography Styles to Consider for Your Next Design

When it comes to typography, there’s a seemingly infinite number of styles to choose from. But which one is the right one for your next design?

That is the ultimate question when it comes to typography. And, unfortunately, there is no one-size-fits-all answer. It all depends on the specific project you’re working on and what kind of message you’re trying to communicate.

However, we can narrow it down to a few general categories.

Here are 11 popular typography styles to consider for your next project.

UNLIMITED DOWNLOADS: 400,000+ Fonts & Design Assets

Starting at only $16.50 per month!

1. Serif

01 - times new roman-Typography Styles

Serif fonts are the ones with the little feet (serifs) on the end of each letter. They are classic and elegant, and they have been around for centuries. Think of Times New Roman or Garamond – these are both serif fonts.

Serif fonts are generally seen as being more formal and traditional than other types of fonts. They are often used for headlines, logos, and other high-impact pieces.

2. Sans Serif

02 - sans serif-Typography Styles

Sans serif fonts are the exact opposite of serif fonts. They have no little feet on the end of the letters, hence the name “sans serif.”

Sans serif fonts are generally seen as being more modern and clean than serif fonts. They are often used for body copy, menus, and other pieces where readability is key.

3. Script

03 - adelia-Typography Styles

Script fonts are designed to look like they were written by hand. They are usually very flowing and cursive, and they can be difficult to read if they are used for large blocks of text.

Script fonts are best used for small pieces, such as headlines or logos. They can also be used for body copy, but only if the design is very simple and easy to read. A good example would be the Adelia Font.

4. Display

Display fonts are any type of font that is designed to be used at large sizes. They are often very bold and eye-catching, and they can be difficult to read at smaller sizes.

Display fonts are best used for headlines, logos, and other short pieces of text. They should not be used for body copy or any other type of long-form text. A good example that shows how bold these fonts can be is Arbutus.

5. Decorative

05 - space time - Typography Styles

Decorative fonts are just what they sound like – they are designed to be used for decorative purposes only. They are often very ornate and can be difficult to read.

Decorative fonts should only be used sparingly, if at all. They can be used for headlines or logos, but they should never be used for body copy. They are a lot of fun though. Just check out the Space Time font.

6. Blackletter

06- cloister black - Typography Styles

Blackletter fonts are a type of serif font that is designed to look like it was written in the Middle Ages. They are very ornate and can be difficult to read. It also goes by the name of gothic script or Old English.

Cloister Black is a great example of a blackletter font that encapsulates this old-fashioned style.

7. Handwritten

07 - autography

Handwritten fonts are designed to look like they were written by hand. They can be either serif or sans serif, but they usually have a more organic feel than other types of fonts.

Handwritten fonts are best used for small pieces, such as headlines or logos. The Autography Font illustrates this typography style well.

8. Slab Serif

08 - rosette

Slab serif fonts are a type of serif font that is designed to be used at large sizes. They are often very bold and eye-catching, and they can be difficult to read at smaller sizes.

Slab serif fonts are ideal for headlines, logos, and titles. They should not be used for body copy or any other type of long-form text though as the line weight is too thick for the confined spaces of paragraphs. The Rosette Font has a chunky look that serves as a good example of a slab serif.

9. Geometric

09 - geometric

Geometric fonts are designed to be very clean and simple. They often have straight lines and angles and rely on a geometric construction to achieve their letter shapes.

This kind of font is best used for headlines or logos, or any other spot where just a few words are needed. They can also be used for body copy, but only if the design is very simple, large, and easy to read.

10. Grotesque

10 - grotesque

Grotesque fonts are a type of sans serif font that is designed to be used at large sizes. Historically, they’re known for looking a bit awkward and unusual.

It is advisable to only use grotesque fonts for headlines, logos, and other brief pieces of text. They are not meant to be used for paragraphs or long stretches of text. Work Sans is a great example of a neo-grotesque style.

11. Humanistic

Humanistic fonts are sans serif fonts as well that are designed to look very natural and organic. They often have curved lines and softened edges.

Humanistic fonts are most successful when used for titles, headlines, or logos. They can also be readable if used sparingly in body copy with a simple design layout. You can look to the Centaur Font as a good example of this classic font style.

Let Typography Style Options Inspire You

There you have it! These are the 11 most common types of fonts that you’ll see used in graphic design. As you can see, each one has its own unique purpose and should be used accordingly.

When it comes to choosing the right font for your project, it’s important to think about the overall style you’re going for. Do you want something clean and modern? Or are you going for a more vintage or retro feel?

Once you have a general idea of the style you’re after, you can start browsing through different font options until you find one that fits your vision. Good luck!

How to Choose the Right Domain Service Provider

There’s more to choosing a domain provider than just the price of registering domains. Sure, the cost is something to consider; however, many factors come into play. This article shows you what to look for.

There are some questions you should consider when picking a domain provider. Like, do they offer bulk domain purchases for your agency? Or, can you quickly transfer a domain? What about customer support? And more…

So, how do you determine which domain provider to go with?

We have a breakdown in this post, along with some insight on what registering a domain entails, to help you make a good decision. Plus, we’ll show you a glimpse of why you should consider choosing WPMU DEV (wink, wink) for your domains!

We’ll be looking at domain-related topics, such as:

    1. Paid or Free Domain Protection
    2. Extension Options
    3. Simple Domain Transfer
    4. Purchasing Domains in Bulk
    5. Include Hosting with Domain
    6. Expired Domains Policy
    7. Support
    8. Reputation
    9. Registration Period
    10. Price
    11. Domain Managment Console

By the end of this article, you should have a good idea of what to look for before registering your domain(s) with a specific company — and feel good about your choice!

But, before we begin, let’s quickly touch on…

What a Domain Provider Does

In a nutshell, a Domain Provider is a company or business that handles the reservation of domain names and the assignment of IP addresses for those domain names.

They permit the purchase and registration of domain names that are accredited by ICANN (Internet Corporation for Assigned Names and Numbers). ICANN supports domains by helping companies apply for accreditation to become domain registrars and sell them to the public.

Domain name registration is allowed by ICANN to make adjustments to the domain name’s information in the database on your behalf.

There are over a thousand domain registrars available for this service.

Factors in Determining the Best Domain Provider

With all of the options for domain providers – how are you supposed to choose? There’s a lot to think about, so let’s break down some components that might help you make a clear decision.

Here are the main factors to consider:

1. Paid vs Free Domain Protection

There are precautions to take when purchasing a domain, so luckily, there is Domain Protection. And depending on your domain registrar, it’s either free or it comes at a price.

Just for context, signing up for a domain also includes providing specifics for a WHOIS directory– a resource database of all the registered domains in a country. It’s available to lookup users who have purchased a domain and/or created an IP address – including the contact info, name, and more.

information on icann on wpmudev.com
Domain information for WPMUDEV.com.

It’s so that the public can find information about any person with a domain name. So, spammers are big fans of this directory. It grants them easy access to different people all around the world.

With WHOIS Protection, the WHOIS data about your contact information will be hidden. However, it doesn’t hide any DNS records or IP addresses.

When determining the right domain provider, you may want to consider if there’s a fee for WHOIS Protection – or if it’s included. Many other providers have it set up automatically as well.

However, if it’s not included, expect to pay an extra $12-$15 a year for protection.

2. Extension (TLDs) Options

When it comes to domain name extensions (TLDs — Top Level Domains), like .info, .bike, .shop, etc. – make sure the company you’re with has plenty of options or specific ones you want to use.

A good domain provider should have ample extensions to choose from.

3. Simple Domain Transfer

Ensure that the domain company you decide on makes it easy to transfer domains.

It’s probable that you will transfer your domain (or many) at some point. Making sure that the domain company that you’re with has a clear path for domain transfer is vital.

Remember that transferring a domain can take a while, and very few domains don’t charge a transfer fee (about 5%). Though there’s usually a fee when you transfer a domain name, you get an extra year of registration along with the transfer.

4. Purchasing Domains in Bulk

Bulk purchasing domains are the norm if you own a web development company. Therefore, check and see if the domain company has bulk purchasing options. Also, see about a bulk discount.

If they do, make sure it’s simple to do. Plus, ensure there are no upsells or hidden fees.

5. Include Hosting with Domain

Many companies offer to host services with domains as bundles – or discounted rates.

Though getting a discount is tempting, be careful lumping the two together. When doing so, you lose some flexibility that you may want down the road. For example, if you ever want to change web hosts or domain registrants, you may encounter some complications.

However, there can also be advantages of having everything under one company, such as it’s easily manageable and accessible.

Be sure to know how simple it would be to transfer services (e.g. domains) to a new company if you ever decide to do so and how it would affect the price.

6. Expired Domain Policy

Considering domains are registered for a specific amount of time, be sure you know what happens when they expire with your company, and there’s a grace period.

In most cases, features like autorenewal can prevent expired domains, but look into the domain company’s expiration policy. Do they have an ample grace period?

Also, what’s their redemption period? The domain might be released to the general public when the redemption period is over.

Rules can differ for this, but this process of expired domains is generally the norm. So, make sure you know the guidelines on this to prevent any mishaps down the road.

7. Support

Does the company you purchase domains through offer good support? You shouldn’t need them in most cases (if transferring, implementing, purchasing, etc., is simple to do – which it should be); however, ensure that they are accessible if needed.

24/7 support is the best bet and shows a dedicated company’s seriousness about its domain business.

8. Reputable

Make sure that the company you’re with has a good, established reputation. Look for things, like Trustpilot, on their website. Or reviews from other sources. After all, you’re looking for a long-term relationship when it comes to domains, you want to ensure that you – and the domains – are in good hands.

9. Registration Period

Check and see what type of registration period the domain company offers. For example, do they have it, so you can register a domain for more than a year? Also, do they have convenience, like auto-renewal?

Most domains can be registered for up to 10 years at a time, so ensure that you have some flexibility when registering.

(And, if you didn’t know, you can’t permanently purchase a domain. Think of them as a lease – not permanent — unless you renew regularly.)

10. Price

Many companies offer a low price to get clients “hooked” into purchasing a domain and then become sticker shocked once up for renewal. Make sure you read the fine print and ensure the renewal price won’t break the bank.

All of this information should be easy to find in any good domain registrar. Plus, a good business model is not to hike up the prices tremendously (or at all) upon renewal.

11. Domain Management Console

Easily managing domains and having them under one roof in a domain management console is great for running your WordPress development agency.

Any management system that allows you to bill, renew, edit contact information and more can save you a ton of time and streamline the domain management process.

This is especially useful if you’re managing multiple domains for numerous clients as an agency.

What Are the Average Costs of a New Domain Name?

Now that you know what to look out for, it’s good to be aware of what a fair price to pay for a domain is as you decide which company to go with.

They all vary by companies and extensions, so there’s no exact figure for them; however, for a regular .com domain, prices tend to range from as cheap as $7 to $15 per year.

Also, some offer package deals, such as a free domain with hosting. Or discounts and promos are often available (especially for new customers). Additionally, discounts can be added if you pay annually instead of monthly.

All this being said, often, renewing a domain can cost more. Hence, if you know you want a domain for the long term, it’s usually a good idea to purchase for an extended length of time (e.g. ten years) right away.

A Note on Premium Domains…

If you purchase an existing domain, the owner can determine the price. This can become very expensive, depending on the domain and business. It’s different from buying a brand-new domain at a regular price.

Domain Extension Differences

We mentioned that it’s important to see what extensions a domain provider includes if you wish to use some specific ones other than .com. That being said – what’s the difference between them?

The most popular is .com. Then some other popular TLDs include .org, .net, .edu, and .gov. There are thousands of others to choose from.

A big difference between them all (as we touched on earlier) is the expense. Some extensions (such as the ever-growing .io and .co) are becoming more popular, thus, more expensive.

Also, some extensions are restricted, and you can’t purchase them unless you meet specific requirements. Several of these include .edu and .gov because they are related to education and government.

Probably the biggest reason for various extensions is to be unique to your site. Have a dance studio? You might want a .dance to fit in with your business.

An extension often won’t make or break a website, but that being said, they do make a difference and should be unique to your specifications.

Why WPMU DEV is a Good Choice for Domains

We’ve covered quite a bit of information in this article (whew!), and we’d be doing ourselves a disservice if we didn’t mention our domains here at WPMU DEV. Why? Because we check a lot of the boxes on what to look for when choosing a domain provider.

After all, our main goal IS to be the best domain provider around, so we want to make sure we have as much covered on our end, and make you aware of it, too.

Here’s a rundown of what we offer.

Paid or Free Domain Protection: Our domains automatically come with WHOIS protection, so it’s covered without additional costs.

Extension Options: We have over 120 extensions — and will be adding another 100-150 very soon.

Simple Domain Transfer: Coming soon, you’ll be able to quickly and easily transfer 3rd party domains to WPMU DEV, and if you need to transfer out of our platform, that will be simple, too. Stay tuned for updates on this…

Purchasing Domains in Bulk: Considering we sell our domains purely at cost, we don’t need a bulk domain discount. They’re already as low priced as they can get. You can purchase as many domains as you need.

Hosting & Domain: Though they are two different features (domains are not included with hosting), we have fully dedicated hosting and credits with our Agency plan.

Expired Domains Policy: An expired domain will enter into a 40-day grace period and can be renewed during that time by simply paying the renewal costs. Then, the domain enters a 30-day redemption period.

Support: We offer 24/7 support.

Reputation: We’ve been around for a while (since 2006!) and have extremely high marks from Trustpilot. Additionally, we’ve been a repeatedly “Top Choice” for web developers, won awards for our plugins, and have high reviews from over 5,000 places worldwide.

Various reviews for WPMU DEV.
Here’s a sample of what our reputation consists of.

Registration Period: Domains can be registered for 1-10 ten years (except for a few examples, like .co — which can only be registered for five). Read more about registration periods in our documentation.

Price: As mentioned earlier, with purchasing domains in bulk, we charge purely at cost and won’t sticker shock you on renewal. We offer domains exclusively through our Agency Plan, so we can keep them at a discounted rate. It’s strictly for our members’ benefit to have domains; we’re not in it for profit.

Domain Management Console: All domains are in one place in The Hub. From here, you can set up auto-renewal, edit information, set registration periods — and much more. Be sure to read our article about setting up domains from WPMU DEV.

For more about our domains, be sure to visit our domains page for more info — or to get started!

Providing You Info for the Right Domain Provider

Hopefully, this article answered some questions and helps you understand what to look for in a domain provider. Whether it’s price, the convenience of a domain console, or support — whatever it is, it’s important to choose the right company to go with. After all, there are over a thousand options out there, and it’s not always simple to decide on a single platform.

On top of that, you got a glimpse of what we can offer regarding domains here at WPMU DEV. We’re proud to be able to offer them at cost with our Agency plan and provide the best in many categories when it comes to domains. If you’re not a member yet, give us a trial run (for free!) today!

Make sure that whatever domain provider you go with, make sure it’s (dot)amazing!

Best Stacked Bar Charts For Data Visualization 2022

Data visualization is becoming more important as metrics are needed for everything from customer conversions to the current bounce rate. Data visualization allows for data to be organized in an easier to understand format. The wide variety of graphs and charting styles allows for any problem to be solved and any kind of data to be charted. […]

Creating Homebrew Formulas With GoReleaser

We chose to use GoReleaser at ObservIQ for our distro of the OpenTelemetry Collector to simplify how we build and support many operating systems and architectures. GoReleaser enables us to build targeting a matrix of GOOS and GOARCH  targets as well as automate creating a wide range of deliverables. The ones we have utilized are building tarballs, nfpm packages, docker images, and Homebrew formula.

For this article, the focus is on the Homebrew Taps capabilities in GoReleaser and our journey using it. Our goal was to make it easy for users to install our software on macOS so that they could easily try it out. We went with Homebrew as it’s familiar to many macOS users and would allow a user to try out our software and remove it just as easily when they are finished.

4 Key DevOps Metrics for Improved Efficiency and Performance

We’re seeing an increasing number of organizations renew their focus on adopting and improving their DevOps practices to help optimize their software development life cycle and improve their delivery velocity to reach markets and customers faster. Here’s all you need to know about the four key DevOps metrics and how teams can use these metrics to improve dev efficiency and performance to build better and faster products for their customers.  

What Are DevOps Metrics? 

DevOps metrics are the data points used to measure the performance and efficiency of a team’s DevOps software development process. Since DevOps integrates the functions of both development and operation, the metrics should be able to measure and optimize the performance of both the processes and people involved.

Learn With Practice: API Versioning Snags

I have finished watching a series of lessons on Designing and Versioning HTTP/REST APIs from well-known author Jeffrey Richter, who is an author of the best-sellers CLR via C# and Windows API via C++.

Lumberjack versioning graphic

For those who design back-end APIs, I do definitely recommend watching it (and reading the books as well, if you haven't), especially those who think that doing an API is simple (probably, impressed by several "Hello, World!" prototypes made during the weekends).

Process Debt Is Something You Should Care About

Intro and Problem Statement

Early this year, the book Software Architecture Metrics: Case Studies to Improve the Quality of Your Architecture was published.  

Christian Ciceri, co-founder and chief architect for Apiumhub, is one of the co-authors. He wrote a chapter that is particularly useful in contexts where the architecture and environment still have many opportunities for improvement. He emphasizes the importance of private builds in the development process, with the underlying principle of his reasoning being easily understandable — you should make use of private builds and run their tests on your local machine, in an environment as close as possible to production, to obtain feedback on your build as soon as possible. 

How to create sitemap for +150k links

iI was searching the online "free/cheap" generators and i found out that they are not so cheap...
They advertise the PRO plan for just $3 and when i came to the checkout page it was $1800
So is there other free tool for this job or how do i manually create the sitemap my self. I read on google that one sitemap file should contain no more than 50k links. So in this case i should create sitemap index file.

Any recommendations ?

Liquibase on Kubernetes

Context, Motivation, and Theory

We have a microservices environment with Kubernetes(k8s), in which we develop a service that requires a database. We decided to do the migration management of the database using Liquibase.

Eventually, we realized that some deploys left the database locked. After some research, we found the key. The k8s itself, if a deployment process takes longer than a certain time if the failureThreshold is exceeded combined with the initialDelaySeconds in the startupProbe, in our example, while applying migrations, it assumes that the Pod has been left in a corrupt and/or inconsistent state and kills the process.

O11y Guide: Bringing Monoliths Into the Cloud-Native World

This is the fifth article in the series covering my journey into the world of cloud-native observability. If you missed any of the previous articles, head on back to the introduction for a quick update.

After laying out the groundwork for this series in the initial article, I spent some time in the second article sharing who the observability players are. I also discussed the teams that these players are on in this world of cloud-native o11y. For the third article, I looked at the ongoing discussion around monitoring pillars versus phases. In the fourth article, I talked about keeping your options open with open-source standards.

Is Blogging Worth It in 2023?

is blogging worth itSetting up a blog is relatively easy. The difficult part is turning it into a profitable business. With the increasing number of bloggers worldwide, it can be challenging to make your content stand out. So, you might be wondering: is blogging worth it?