-
Detecting Content Blockers is a losing battle, but you can be smart and ethical when doing so...
There's been a bit of a cat and mouse game between adblockers/content blockers and advertisers/analytics/trackers. The short answer is you aren't going to defeat them single-handedly. Many of the libraries designed to detect them will fail as they're inevitably blocked once a content blocker is updated to detect them. As someone who once ran a website, that hit 150,000 unique visitors a month funded by advertising, I'm sympathetic the publisher's plight. As a content writer, I value analytics, I use google analytics on this site as it helps me understand what content resonates, what channels people use to find my content and how they consume it. As developer with a touch fo UX, logging and error tracking is extremely helpful. A service like loggly can help me find errors, and design better to catch edge cases that aren't on the "happy path" and make data-driven decisions about a product. However, the advertising industry has perniciously proven they are not to be trusted. There's a reason why as a user I surf with Ghostery/1blocker, block cross-origin cookies (on my desktop, kill all cookies), use a VPN, and disabled flash long before most people to dodge the dreaded forever flash cookie. Privacy matters.
This is my attempt create an ethical framework around content-blocking from the perspective of a developer/content create/publisher.
A quick list of observations
I've assembled a list of facts/observations about content blockers.
- Adblock/Adblock Plus focus on advertising but not analytics. This could change in the future.
- 1blocker and Ghostery are particularly good content blockers. Both will block
<script>tags from loading, or anyonerrorcodes at the src level - Content blockers are not fooled by appending
<script>tags via javascript to the DOM. - 1blocker and Ghostery will not be removed from the DOM, thus any checks to see if they exist will be true.
- 1blocker and Ghostery can detect anti-blockers popular scripts and prevent them.
- Browsers are more aggressively pushing privacy settings, FireFox leading the charge and Safari not far behind.
- If your website fails to work with one of the popular content blockers working, you are cutting out 20% of audience.
But I'm a special snowflake!
Using powers for goodSo as a developer/UX designer you're suddenly faced with a problem. Your website or web app has features that break when content blockers are enabled. You've already made sure that your core functionality isn't tied to anything that will be blocked by content blockers.
Likely your client or manager will ask "can't you just go around the content blocker?".
The short answer is "No". You will not forcibly defeat content blockers, and if you try, you're signing up for the unwinnable, all consuming, cat and mouse game. However, you can potentially detect content blockers, rather than defeat them. With a service like Loggly, you can easily check if the
_Ltrackervar has loaded.if (typeof _LTracker === 'undefined' || _LTracker === null) { //execute code }Suddenly we're at the ethical precipice as we can do a number of things with this information. I've assembled a list of the ethical paths.
Ethics of content blocking code
Most Ethical:
Website/WebApp's core features work any warnings until user reaches an ancillary feature that may be broken. User is able to complete core functions (consume content, use navigation, submit forms).
Example: Videos still work. User is able to place orders but 3rd party chat tech support may be broken. User is informed.
if (typeof _LTracker === 'undefined' || _LTracker === null) { //If and only if function on page requires service //inform user. }Fairly Ethical:
User receives warnings on every page, encouraging to whitelist site regardless if functionality is affected.
Example: User is pestered with a whitelist site message. User is still able perform operations. Videos still work. User is able to place orders. 3rd party live chat tech support may be broken. User is informed.
if (typeof _LTracker === 'undefined' || _LTracker === null) { //display global message. //Inform user that analytics are helpful for improving the service }Least Ethical:
User is blocked from consuming content until site is white listed regardless if functionality is affected.
if (typeof _LTracker === 'undefined' || _LTracker === null) { //display global message. //obfuscate content/block content/disable features when error is present. }No Ethical Stance: Site does not attempt to detect any blocked content. Site either functions or does not. This is the majority of websites.
This model isn't free of problems, its almost entirely from the lens of a non-advertisement supported website, like a campaign site / company site/ ecomm / SaaS. While these sites may contain advertising and tracking, all the aforementioned are either have revenue generated by sales (Sass/Ecomm) or lead generation (Campaign/Company). Websites that are dependent on ad-revenue adhere a different set of ethics and variables.
Other methods for checking for a script loaded.
Checking for variable existance is the most fail safe method to see if a script has loaded. While the
onerrorwill not work on an individual scrupt tag, you can write in scripts to the head with the following code. This though comes at a mild expense of code execution and may not work in all scenerios.
-
Google PageSpeed Insight lacks commonsense and is becoming irrelevant
This has been something that has irked me for some time now, and I haven't unloaded a good rant on development in some time. Yesterday I wrote about image bloat and decided to add a few negligible optimizations that I've meant to do for a year or two that resulted in about 8-10k reduction per page. After I enabled HTML and CSS minification on my blog, I skated over to PageSpeed, plugged my URL in and frowned. My newly optimized blog post scored a whopping 70/100. My page is 84.5k (or 68.5K without google analytics).

For reference, wired.com scores 73 out of 100 on mobile with the total page loading 5.1 megabytes and Newsweek.com scores a god damned 84 out of 100 and loads 7.1 MB!. This is utter and complete stupid bullshit.
Here in lies the rub: While Google PageSpeed always had a "reach for the stars" mentality but is woefully in out of touch when judging a page's real world performance. A 300k page, even poorly optimized one is going to beat the 3 MB page (average page size of major websites) in load times. In the era of a smart phone data plans: a customer could load 30 poorly optimized pages for one bloated highly optimized 3 MB beast of a page. It's telling that Google stopped developing its "PageSpeed" tool into Chrome and has since relegated to its annoying web-only interface. It's become a tool that would be SEO gurus/experts/snakeoil salespersons use when hired by clients use to hold over developers and provide "recommendations" in CMS websites that do not provide easy vectors for the more avant-garde optimizations like HTML minification (which incidentally tends to save less data than CSS/JS optimization, or less than using HTTP compression).
PageSpeed says nothing about image formats beyond image scaling (and seems to be mostly tone deaf) to responsive images in reasonable margins of error. You can plug in a 500k PNG that could be served by a 40k JPEG image only to have PageSpeed score not even budge. It won't even blink if you're making an effort to support avant-garde image formats like WebP and JPEG2000 to provide more bang per Kilobyte.
PageSpeed is also frighteningly javascript unaware. "Oh, you have a BitCoin mining javascript file? Is it minified? Is it uglified? Is it GZ compressed? Yes? THUMBS UP BUDDY! Also, good job on the 'Your flash is out of date malware javascript pop up.'" If you're tricky, write in an obfuscated javascript append script to say, the 460k uncompressed D3 library and Google PageSpeed won't even bother to check.
Other poor detects revolve around iframes to popular services like YouTube / Vimeo / SoundCloud / CodePen and suggest optimizations based on the iframe content, anathema to the entire principal of CORS.
There's also zero comment on total requests on the page other than suggesting to concatenate files and create image maps, it'll ding you hard for having multiple CSS imports for Google Fonts, but doesn't give a royal damn if you're making several hundred HTTP requests. (Note: most browsers are limited to 6 requests at a time per domain, and usually cap out at around 17 simultaneous. Each request must filled or 403/404ed to open another request. This says nothing about the limitations of the server either for max clients, more requests = more server stress.)
Want to measure rendering performance? Forget it. There's no discernable metric about time to paint, or continous painting. Feel free to go nuts with CSS filters and bring a lesser device to its knees, PageSpeed doesn't care as long as your CSS is minified.
Lastly, it can be wildly inaccuraate. My page is minified HTML and yet PageSpeed's wonderful insight is that I should minify my HTML. Wat. View source on any page on this blog if you don't believe me...
There's probably a reason why I didn't notice that PageSpeed Insights had been removed in Chrome, as its mostly useless to a savvy front end dev beyond a sanity check. It can be taken that Google Pagespeed isn't a metric of your site vs other websites but rather, you vs yourself. Even that rational falls apart as it doesn't give guidance on recommendations too many factors nor does it put any judgement on data use. Google clearly cares about data use, as its questionable Accelerated Mobile Mobile project (AMP) exists. PageSpeed Insights was a tool of genius, but now it feels like it's past its prime and/or in need of some TLC. Really, what I'm asking for is perspective, and Google Pagespeed Insights doesn't have it.
-
This article does not contain any images
At some point in the past several years, the millions of different possibilities of turning individual pixels into a website coalesced around a singularly recognizable and repeatable form: logo and menu, massive image, and page text distractingly split across columns or separated by even more images, subscription forms, or prompts to read more articles. The web has rapidly become a wholly unpleasant place to read. It isn’t the fault of any singular website, but a sort of collective failing to prioritize readers.
I don’t know about you, but I’ve become numb to the web’s noise. I know that I need to wait for every article I read to load fully before I click anywhere, lest anything move around as ads are pulled in through very slow scripts from ten different networks. I know that I need to wait a few seconds to cancel the autoplaying video at the top of the page, and a few more seconds to close the request for me to enter my email and receive spam. And I know that I’ll need to scroll down past that gigantic header image to read anything, especially on my phone, where that image probably cost me more to download than anything else on the page.
This blog post is a bit of a meta-reaction seeing as this is a response to Not Every Article Needs A Picture but it's pretty rare to see any blog or news source post an article without an image, and the ban lays squarely on the cult of the "hero" image. The Hero image was a late web 2.0 design, a celebration of bandwidth and the exploding opportunity in web design, and now is feeling trite, stale images and it's only exacerbated by the Medium.com, Kinjas and every news site imaginable.
Even the print guys fail this test, newspapers like NY Times do not even follow their own print standard and wedge photos into all their articles. As Wired famously wrote, "The Average Webpage Is Now the Size of the Original Doom" (ironically on a page surpasses the 2.3 MB mark at 3 MB* ), do we really need to tax users more? I feel bad cheating my favorite publishers out of ad-revenue, but even whitelisting sites has me running back to Ghostery as I watch my Mid 2015 MacBook slow down and go into leaf blower mode to simply surf the web. On my phone, I have 1blocker but find myself mostly using RSS to this day as its fast, quick and cuts through the unnecessary pictures. Admittedly, my blog index pages fail the Doom test but it's also loading 20 articles at time (this article viewed by itself is 103k), perhaps I may still yet sneak in another feature.
*With Ghostery Enabled, Wired.com's article is a much more palatable 937K.
*With Ghostery Enabled, this article is 97k instead of 102k.
-
Installing Composer, Drush 8 and Drupal Console globally via composer on macOS (OS X)
Install Composer
Before we install Drush, we need to install globally Composer. Composer is a PHP package manager akin to NPM or Bower.
curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composerNext we want to edit our .bash_profile. Go your home folder
cd ~/Create a new .bash_profile, (don't worry, if you have one, this won't overwrite it). We need to add a global entry for Composer.
touch .bash_profile nano .bash_profileAdd the following to your .bash_profile
$ export PATH="$HOME/.composer/vendor/bin:$PATH"Install Drush
Now that we have composer installed globally, we can install Drush via composer.
composer global require drush/drush:dev-masterFinally, we can select a specific version. For Drupal 8, we want Drush 8.
$ composer global require drush/drush:8.*
-
Setting up Jekyll Admin
I’ve finally gotten around to looking into Jekyll a bit more, and one of the more exciting projects is Jekyll Admin. The documentation is a bit loose (the developer documentation is quite good). I’m writing this under the assumption that you’re using OS X/Linux or such with Ruby preinstalled (OS X comes preinstalled).
Open, up the terminal.
Step 1: Install Jekyll-admin
gem install jekyll-adminI had a bit of trouble with the install on both my MacBook Pro and my Mac Pro. If it hangs, hit command period and run the command again. It should work second go around.
Step 2: Configure Jekyll
Open up your
_config.ymlin an editor.:ocate either
gemsorpluginsin your config (depending on your version) and add Jekyll admin. Right now Jekyll admin should run, but…. before you get too far ahead of yourself, you will want to add front matter defaults to your yml file.Step 3: Add front matter defaults.
You may already have configured front matter defaults, depending on your setup. If you do not, then every used meta-data field will have to be added by hand to every post. My blog almost 99% of its content exists in posts. Thus I only needed to add a configuration for
_poosts.Make sure you have front matter defaults set up for posts. For my blog, I do not make heavy use of front matter, my configuration I added the following so every post would have pre-filled for any post the categories, tags and layout.
defaults: - scope: path: "" type: posts values: layout: post categories: "" tags: ""Keep in mind yaml requires spaces and not tabs. Using tabs will not work for yaml.
Step 4: Run Jekyll.
Start up Jekyll as you normally would. Navigate to http://127.0.0.1:4000/admin/ after you’ve spun up Jekyll. Congrats. That’s it.
-
A mild blog update
I try to stay away from spending too much time under-the-hood for my blog. As developer and designer, I'm always prone to over-tweaking. The point of my blog is to write about development as opposed to developing. So against my own better judgment, I decided to finally unveil a new feature to my hyper-minimalist stylings that I've debated adding for a year now. All posts now can be viewed by topics.
-
Nuking SoundFlower.kext - Soundflower.kext can't be modified or deleted
You may want want to remove Soundflower by Rogue Amoeba for some reason or another (upgrade?). For me, I noticed my FocusRite Scarlett 6i6 seems to have a driver incompatibility with some versions of SoundFlower, as would not show up in macOS However, Soundflower (for some reason or another) is viewed as required by OS X/macOS, unlike many kext files. I found this surprisingly more difficult than expected.

You're here because you've tried everything to remove SoundFlower:
- You tried the official installer DMG, and the removal AppleScript failed.
- You manually went to /System/Library/Extensions and found that you received the error "Soundflower.kext" can't be modified or deleted because it's required by OS X.
- Tried sudo rm-ing the damned file to find out its a directory and sudo rm -r doesn't work either and returns an Operation not permitted.
- Tried an app zapping app
- You tried Kext signing disabling by plugging in boot args and the first three things still didn't work...
I do have a solution and its not as practical but boot your Mac on another volume OR boot your Mac into Target Disk mode
Launch OS X on your other drive or plug your Mac into your secondary computer
Locate the soundflower.kext in /System/Library/Extensions and drag it to the trash
Try deleting, if your Mac complains, do the following:
- Launch the terminal (its located under Applications/utilities)
- Type in the following:
Sudo rm -r
Note: the trailing space is important - Drag the icon of the kext into the terminal window, it should fill out the path to the kext file.
- Hit return, your Mac will prompt you for the admin password (this will be the admin password for the drive/computer you are currently booted from, not the password for the drive you are connected to)
- Hit return, it should delete now without any hitches
- Reboot your Mac as normal.
-
Setting up a Bootstrap subtheme for Drupal 8
There are a few directions for creating sub-themes for Bootstrap but none quite covered all the steps. For simplicity's sake, I'm going to use the name
customthemefor my theme's name and title. Feel free to use whatever makes sense for your site instead of customtheme.Step 1: Install the Boostrap theme
Download the latest version of the Bootstrap theme. Decompress the contents and drag the entire folder into
core/themes/. Check to see that installed properly under the admin appearance. It should be listed under uninstalled themes. Click the install button.Step 2: Pick a starter kit
Navigate to the newly created bootstrap folder in
core/themes/, and go into the starterkit directory. You should see three folders, CDN, LESS and SASS. Each of these are variants based on Bootstrap 3. Personally, I use Sass but for this example, it doesn't matter.Step 3: Copy your preferred setup into /themes in the root of your site.
Copy the selected folder into
/themesRename the directory to something that is acceptable for Drupal's theme naming conventions (No spaces etc).Step 4: Change file names
In your site you should have:
- /config
- /install
- THEMENAME.settings.yml
- /schema
- THEMENAME.schema.yml
- /install
- /images
- logo.svg
- README.md
- screenshot.png
- /scss (this is dependent on theme you selected)
- /templates
- THEMENAME.libraries.yml
- THEMENAME.starterkit.yml
- THEMENAME.theme
Change the names of the bolded files to your themename. Change the starterkit to info. It should look something like this:
- /config
- /install
- customtheme.settings.yml
- /schema
- customtheme.schema.yml
- /install
- /images
- logo.svg
- README.md
- screenshot.png
- /scss (this is dependent on theme you selected)
- /templates
- customtheme.libraries.yml
- customtheme.info.yml
- customtheme.theme
Step 5: Edit the yml files.
/config/schema/customtheme/customtheme.schema.ymlchange the instances of THEMNAME and title.
# Schema for the theme setting configuration file of the THEMETITLE theme. THEMENAME.settings: type: theme_settings label: 'THEMETITLE settings'Example:
# Schema for the theme setting configuration file of the customtheme . customtheme.settings: type: theme_settings label: 'customtheme settings'Next open up
customtheme.info.ymland change the the THEMENAME and THEMETITLEcore: 8.x type: theme base theme: bootstrap name: 'THEMETITLE' description: 'Uses the Bootstrap framework Sass source files and must be compiled (not for beginners).' package: 'Bootstrap' regions: navigation: 'Navigation' navigation_collapsible: 'Navigation (Collapsible)' header: 'Top Bar' highlighted: 'Highlighted' help: 'Help' content: 'Content' sidebar_first: 'Primary' sidebar_second: 'Secondary' footer: 'Footer' page_top: 'Page top' page_bottom: 'Page bottom' libraries: - 'THEMENAME/global-styling' - 'THEMENAME/bootstrap-scripts'Example
core: 8.x type: theme base theme: bootstrap name: 'customtheme' description: 'This is a custom theme.' package: 'Bootstrap' regions: navigation: 'Navigation' navigation_collapsible: 'Navigation (Collapsible)' header: 'Top Bar' highlighted: 'Highlighted' help: 'Help' content: 'Content' sidebar_first: 'Primary' sidebar_second: 'Secondary' footer: 'Footer' page_top: 'Page top' page_bottom: 'Page bottom' libraries: - 'customtheme/global-styling' - 'customtheme/bootstrap-scripts'Step 6: Go to appearance and install
Your theme is now ready to go; it should appear in your appearence.
Step 6.5: Go to appearance and install
If you're using the CSS (precompiled bersion) version and want to use their implimentation of Bootstrap, go getboostrap.com (3.3)
If you're using the Sass version and want to use their implimentation of Bootstrap, go to getboostrap.com (3.3)and download the Sass version, decompress the folder and rename it to
bootstrapand place in the root of your theme.Additional notes
From here, you'll most certainly want to turn off theme caching. I found this guide super helpful.
I'd suggest following the tutorial to create a local
local.services.ymlto enable debugging and thecachetofalseas well. Also, I recommend checking out, the article Drupal 8 fundamentals.
- /config
-
Goodbye FireBug

Today, Mozilla announced it was retiring FireBug. Inevitably native browser development tools eclipsed FireBug, but I can't help but say "Goodbye" as there are so very few singular pieces of software that have such a bearing on my life. When a friend of mine introduced me to FireBug, and I realized back in 2007 I could see in real-time the effects of CSS, little did I know it'd lead me down the path of web development.
-
Pure CSS (scss) Bootstrap compatible circular progress bars
First off, credit where credit is due. I found a pretty good start to a circle progress bar by Alimul Al Razy via a random google search.
I rather liked the approach and made my own modifications, pairing down the animation and step generation into two For loops, and making use of
data-attributes. You can see it on CodePen. It's quick and easy to style up and does not require bootstrap.$howManyStepscontrols how many levels of percentage needed, if you need increments of 5%, enter 20, increments of 2 would be 50, etc.See the Pen Pure CSS (SCSS) Bootstrap compatible circular progress bars by Greg Gant (@fuzzywalrus) on CodePen.
GitHub Gist
-
NearStory Launch
I don't make it habit of plugging products on my blog, but this one warrants it.
Longtime office mate, Giovanni Salimena, is officially launching his iOS application, NearStory. It's already up in the app store and its free to download and free to use. Near story aggregates news/audio content related to your location, so that you can learn the local history of the area around you.
-
Installing Provenance (OpenEmu) on iOS 11 without a jailbreak

Following up yesterday's post on how to install PPSSPP, I decided to add the instructions for how to install Provenance on iOS 11 without jailbreaking. I wrote in 2015 a guide on how to install emulators via Cydia and sideloading services which is worth checking out for more information on emulation on iOS. Provenance is THE go to emulator for retro iOS gaming, as its based on the wildly popular OpenEmu and boasts MFi gamepad support. Best of all, it is incredibly easy install compared to RetroArch or PPSSPP. Only RetroArch provides a wider range of support. Provenance boasts support for:
- Sega
- SG-1000
- Master System
- Genesis / Mega Drive
- MegaCD
- Game Gear
- 32X
- Nintendo
- NES (Nintendo Entertainment System)
- Famicom Disk System
- SNES (Super Nintendo)
- Gameboy / Gameboy Color
- Gameboy Advance
- Atari
- 2600
- 7800
Requirements
Building iOS applications requires installing Xcode, so if you haven't installed Xcode or updated to Xcode 9.0, download it. Once installed, launch Xcode and then launch a terminal session. Run the following to install the CLI utilities for Xcode.
xcode-select --installStep 1: Download Provenance
Either download the zip from https://github.com/jasarien/Provenance or via the terminal.
git clone https://github.com/jasarien/ProvenanceStep 2: Provenance.xcworkspace
Open the xcworkspace file (Not Provenance.xcodeproj)!

Step 3: Set up the xcworkspace
- Set up the project to the Provenance app
- Set the target to your iOS device (be sure you have it connected)
- Change the bundle identifier to something unique
- Set up your developer profile
Step 4: Build!
Hit build, you're good to go. Provenance is incredibly easy to set up. Go to the official wiki for full details on how to use of the emulator.
If you encounter the error, Untrusted Developer: "your device management setting do not allow using apps from developer... on this iPhone. You can allow using these apps in Settings."
Go to Settings -> General -> Profiles & Device Managment and under developer App, tap your profile to allow apps.
Congrats, you're now ready to use Provenance.
Step 5: Adding games to Provenance

Make sure you iOS device and computer are connected to the same access point. Tap on the Provenance icon on your iOS device. Click the + icon to start the webserver. Go to your computer and navigate to the URL in the message on the screen of your iOS device.
Leave your rom files in .zip file to save space. You can queue up as many games as you want using shift click from the file menu. Sub Folders do not appear to be work within the Roms folder.
Provenance is one of the better iOS apps. You'll probably want to pick up a MFI enabled controller to get the most out of it.
- Sega
-
Installing PPSSPP on iOS 11 without a jailbreak
Installing PPSSPP on iOS 11 isn't particularly hard but does require several steps which can be mildly daunting for non-developers or non-iOS developers. I based this guide off of the official guide but realized it provided scant details to troubleshoot any issues.
Important support disclaimer
PPSSPP, as of writing this is, not officially supported on iOS 11. iOS 11 was the reckoning for older apps, dropping 32-bit support. From a development standpoint, often this means replacing out-of-date libraries that require more than mere hours of work. Videos on youtube and articles claiming iOS 11 PSP emulation are generally posted around 2015 or 2016 running iOS9/iOS10, well before even the iOS 11 beta with updated titles to garner views. Some video and articles posted more recently, again, show complex sideloading claiming iOS 11 support but the project hasn't been updated. Websites like RedmondPie are distributing IPAs, they won't work. If you'd like to keep tabs on iOS 11 support, I'd suggest looking at Builds.io as they'll likely build the first stable iOS release for their service or better the official PPSSPP github. The emulator, Happy Chick, often featured in these YouTube videos and articles, also does not support iOS 11, which also uses the PPSSPP core. PPSSPP will launch on iOS 11 but it will be unable to load a game, this is due to 32 bit only support for dynamic recompilation as the emulator must on-the-fly recompile code meant for the PPSSPP. This guide should be accurate if/when PPSSPP gains 64 bit dynarec support and I plan to update this guide when that day comes. If you are interested in a fully functional emulator for iOS 11, I have a guide for installing Provenance, which supports a host of 8-bit and 16-bit era consoles (SNES, Genesis, Sega CD, NES, Master System, GameGear, GameBoy, etc).
These instructions should work for iOS 9 / iOS 10 devices as well.
For more information about Emulation on iOS, please see my very extensive guide iOS Emulation, gamepads, Cydia, Xcode, builds.io - A Tutorial for iOS emus. It's geared as primer for iOS emulation and the various ways emulators can be installed on iOS.

Requirements
Building iOS applications requires installing Xcode, so if you haven't installed Xcode or updated to Xcode 9.0, download it. Once installed, launch Xcode and then launch a terminal session. Run the following to install the CLI utilities for Xcode.
xcode-select --installIf you do not have an Apple developer ID, you will need to create one.
Step 1 Confirm MacPorts is installed
port versionIf you get an error message or about version matching or command not found, Go to MacPorts Releases and download the version of macports that matches your OS version.
Step 2
Navigate in your terminal to the directory you'd like install the project to.
Run in your terminal: (this may take a bit depending on your internet connection)
git clone https://github.com/hrydgard/ppsspp.gitAfter its installed navigate into your newly cloned repository,
cd ppssppNext from ppsspp directory run the following:
git submodule update --init --recursiveStep 4
Next we want to create the PPSSPP.xcodeproj and dependencies in an directory called build-ios. This will take a bit.
mkdir build-ios cd build-ios cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchains/ios.cmake -GXcode ..If you get an error:
Error: Current platform "darwin 16" does not match expected platform "darwin 15" Error: If you upgraded your OS, please follow the migration instructions: https://trac.macports.org/wiki/Migration OS platform mismatch while executingThe above error means you have the incorrect version of MacPorts. Go to the link listed above and download and install it.
Step 4
Open up PPSSPP.xcodeproj in the build-ios folder in Xcode.

Select in the menu, the PPSSPP app icon, instead of ALL_BUILD. If you do not do this, you will not successfully build the app. Next Under Project -> Build For, select running.
Plug in your iPhone or iPad into your computer and select the target as your device instead of a simulator.
If you hit build, mostly likely you will get a provisioning error. Assign it to your developer ID. If you haven't added your developer ID, go to Xcode -> Preferences -> Accounts, and add your developer profile. In the general tab of the PPSSPP project assign your developer profile to the Signing section.
Hit build. If Xcode errors out abt the bundle identifier, give a random string after the .org name.

Image: Properly configured PPSSPP project requires the fields to be set.
Step 5

You should now see PPSSPP on your iOS device.

If you encounter the error, Untrusted Developer: "your device management setting do not allow using apps from developer... on this iPhone. You can allow using these apps in Settings."
Go to Settings -> General -> Profiles & Device Managment and under developer App, tap your profile to allow apps.
Congrats, you're now ready to use PPSSPP. Note, as of writing this, PPSSPP's iOS 11 support is incomplete. PPSSPP will launch but freezes when gamesattempt to load due to the dynamic recompiler not being 64 bit.
If you'd like to build an iOS 11 compatible emulator, check out installing Provenance
2017-10-23: Added support disclaimer.
2017-11-20: Added further support disclaiming, PPSSPP still isn't iOS 11 compatible as it looks like youtubers are looking to cash in on the desires of would be gamers through questionable URLs. Why trust me? There aren't any ads here, I'm not getting paid to write this but there are ads on the YouTube vids and websites. Just sayin'
2017-11-21: Further disclaimer clarification, looks like RedmondPie is linking a busted IPA file. Until the PPSSPP github project is updated, there will be no iOS 11 PPSSPP support. It's that simple.
-
Creating an SVG Fill animation
Recently I was tasked with creating a fill animation on an SVG; a request has come up a few times recently even for my company's website. The animation as described would rise up to reach a certain predetermined point and stop, like a vertical progress bar. I didn't find any 100% useful guides but was able to piece together from previous SVG work, and a few good stack overflow finds the basics.

Creating an SVG fill animation requires some knowledge of a graphics program like Sketch or Illustrator. For this example, I'll be outlining what I did in Sketch to treat the graphic, but this is not Sketch specific. I'll do my best to make this novice accessible but some basic understanding.
Step 1: Treating your graphic
Creating a fill animation requires the right graphic. To pull off this animation, we need a polygon that's a solid color for the vertical progress bar effect. This particular animation will rise up to the 25% mark as outlined by the article.
Originally this graphic's green fill was a separate layer. While this a correct way to illustrate this, it's not easily animated. If we were to stretch the image, the effect would appear like the animation below.

Instead, a much simpler solution is to use a gradient fill. Due to the trickiness of SVGs and gradients, make sure the gradient points extend the entire length of the fill; otherwise, the start and end points can create problems. Sketch is a little picking about gradient points, so don't worry if you can see the gradient transition. We will correct this in the XML of the SVG after exporting. Make sure you name your SVG polygons as this will become very useful for CSS as these will become the IDs for each polygon.
Step 2: Export and paste
Note: A caveat of the SVG format is that it requires being inline on a page for CSS to be able to target the SVG nodes. If it's linked via SRC, CSS is then unable to target the XML in the SVG. We want CSS control as we will be using it to set the gradient.
Paste in the SVG into your HTML (feel free to remove any XML comments in the header). There are two things to observe: All the SVG gradients are declared
<defs>in the section of SVG and that the gradient is linked within the polygon.Step 3: Creating more gradients
To create our animation we're going to need three gradients:- Default Gradient - this will be our default unfilled state
- Animation Gradient - this will be our gradient that contains <animate> tags within our gradient
- Finished Gradient - this is the final animation state, this will be our simple bobbing animation that loops infinitely after the animation has completed
In the defs, I'm going to do three things: first name gradient and secondly set the second stops to the same endpoint to create the illusion of a solid line. Lastly, I need to make the light bulb "empty" so I'll set the offsets of the last two gradient stops to 100%.
<linearGradient x1="0%" y1="0%" x2="0%" y2="99.9334221%" id="bulbGradient-default"> <stop id="stop1" stop-color="#FFC809" offset="0%"></stop> <stop id="stop2" stop-color="#FFCF06" offset="100%"></stop> <stop id="stop3" stop-color="#6CB31D" offset="100%"></stop> </linearGradient>Copy and paste and rename the gradient to match this pattern. It'll take a bit of trial and error but set the final stop offset points.
<linearGradient x1="0%" y1="0%" x2="0%" y2="99.9334221%" id="bulbGradient-animate"> <stop id="stop1" stop-color="#FFC809" offset="0%"></stop> <stop id="stop2" stop-color="#FFCF06" offset="73.5%"></stop> <stop id="stop3" stop-color="#6CB31D" offset="73.5%"></stop> </linearGradient> <linearGradient x1="50%" y1="0%" x2="50%" y2="76.9334221%" id="bulbGradient-end"> <stop id="stop1" stop-color="#FFC809" offset="0%"></stop> <stop id="stop2" stop-color="#FFCF06" offset="73.5%"></stop> <stop id="stop3" stop-color="#6CB31D" offset="73.5%"></stop> </linearGradient>Step 4: Animation
We can't target the defs via CSS, but we do have another tool, SMIL animation. SMIL is depreciated, but it works for linear gradients. SVGs can contain animations. SMIL is supported in all browsers sans IE/Edge (more on that later). For this example, we're going to use
animateAnimate consists of the attributeName (the part we want to animate in our parent), duration, values and repeat count. Normally we'd use CSS animations as they're more well supported but as of writing this, I've yet to find any way to animate gradients without complex JS. Within our stop tags, we'll add the animate values. Fortunately, for both animations, the last two stops will contain the same animation to continue our solid line effect.If we do not declare a
beginproperty, the animation will automatically regardless if we can see it once the DOM is ready. To prevent this, we need to set the begin time asindefiniteotherwise our animation will begin to play. We may not even see our animation or see a strange jump. We also need to give each animate property an unique ID so we can target them.<linearGradient x1="0%" y1="0%" x2="0%" y2="99.9334221%" id="bulbGradient-animate"> <stop id="stop1" stop-color="#FFC809" offset="0%"></stop> <stop id="stop2" stop-color="#FFCF06" offset="73.5%"> <animate attributeName="offset" dur="2s" values="1; 0.735;" repeatCount="1" begin="indefinite" id="bulbGradient-animate-stop1"/> </stop> <stop id="stop3" stop-color="#6CB31D" offset="73.5%"> <animate attributeName="offset" dur="2s" values="1; 0.735;" repeatCount="1" begin="indefinite" id="bulbGradient-animate-stop2"/> </stop> </linearGradient> <linearGradient x1="50%" y1="0%" x2="50%" y2="76.9334221%" id="bulbGradient-end"> <stop id="stop1" stop-color="#FFC809" offset="0%"></stop> <stop id="stop2" stop-color="#FFCF06" offset="73.5%"> <animate attributeName="offset" dur="5s" values="0.995; 0.95; 0.995; 0.95; 0.995;" repeatCount="indefinite" begin="indefinite" /> </stop> <stop id="stop3" stop-color="#6CB31D" offset="73.5%"> <animate attributeName="offset" dur="5s" values="0.995; 0.95; 0.995; 0.95; 0.995;" repeatCount="indefinite" begin="indefinite" /> </stop> </linearGradient>Step 5: CSS
Finally, we need to set up our CSS so control our
linearGradient, each gradient being assigned to a CSS state. The following is written in scss.#bulb-icon { #Bulb { fill: url(#bulbGradient-default); } } #bulb-icon.animate { #Bulb { fill: url(#bulbGradient-animate); } } #bulb-icon.end { #Bulb { fill: url(#bulbGradient-end); } }What we have is a pre-animation state gradient, the actually animated gradient, and then the final state after the animation for the gradient.
Step 6: Javascript
First, we need to create objects from our
animatetags, this way we can access the methods available to them.var bulbstop1 = document.getElementById('bulbGradient-animate-stop1'); var bulbstop2 = document.getElementById('bulbGradient-animate-stop2');After that, it is time to write simple our JS. We want to create an animation based on time via JS using a simple
setTimeoutfunction to change the class after it is done. The animation is 5 seconds long, so I've made the setTimeout a few ms shorter than 5 seconds. To trigger the animation I need to use thebeginElement()method. This initializes the animate.function animate(){ $("#bulb-icon").attr("class", "animate");; setTimeout(function(){ $("#bulb-icon").attr("class", "end");; }, 4995); bulbstop1.beginElement(); bulbstop2.beginElement(); } animate();Note: I learned about the
beginElement()method from a great blog post at properdesign.rs which I highly recommend reading.IE Support
As mentioned previously, Internet Explorer and Edge do not support SMIL with no plans to support SMIL. However, we can add SMIL support with Fakesmile, an Internet Explorer shiv.
Our final Product!
See the Pen SVG Animation by Greg Gant (@fuzzywalrus) on CodePen.
Update 10/20/17: Added in more info about JS. Added the restart animation to CodePen. Added info about
beginElement
-
Total Eclipse Oregon
"Welcome back to daylight Portland. So that's it, the last solar eclipse to be seen on this continent in this century..
And as I said not until August 21st, 2017, will another eclipse be visible from North America. That's 38 years from now. May the shadow of the moon fall on a world at peace" - Frank Reynolds, ABC News
Such unbridled optimism... :(
Pictured: Hubbard, Oregon. Captured on my OM-D EM5
A total eclipse is something to behold as it touches more than the eyes. There are a few silly things I never considered going in which were all obvious in retrospect, the sudden temperature drop, the quiet as all traffic stopped and birds (mostly) stopped chirping, the distant cheering, and the 360-degree sunset. As fortunate as I was to witness it, friends in Salem and north of Corvallis reported being able to see the stars. I feel no need to place any more special significance on the experience than the beauty of nature and astrophysics. That alone should be enough to inspire...
The lead quote by Frank Reynolds can be found at 9:12.
Also, bonus, watch at eight as the commentators speculating as to what Oregon was like 360 years ago, the last time the path was nearly the same.
