Tumgik
#its the shift when i walk between the red and green halves that gives me an actual fucking headache and hurts my eyes
snorfbin · 5 months
Text
.
0 notes
collisiondiscourse · 3 years
Text
on the wonder duo (part 1)
(BNHA Analysis Post Ahead! This isn’t explicitly romantic, but it is an analysis of the relationship between the two most popular characters in BNHA--Katsuki Bakugou and Izuku Midoriya. Split into two posts because I realized that this was gonna be long as HELL)
yall ever think about the fact that the wonder duo is perfectly set up in so that bakugou and deku together are the better version of all might?
bc like. ive been thinking.
everyone knows the win to save and save to win parallel. How they are supposedly two halves of a whole perfect hero (which, previously, was defined as all might)
but ever since bakugou and deku started working as one—growing together to win AND save and continuously reminding each other that they shouldnt try to do things alone, ive realized that its BECAUSE theres two of them that they surpass all might. its not a case of deku and bakugou both being 50% of an ideal hero, but rather i think that they are 100% of what all might SHOULD HAVE BEEN from the very beginning.
as early as the AM v AFO battle in kamino, we see the effects of all mights flawed existence. the fact that he, the greatest and supposedly infallible symbol of peace, was destroyed—society had begun to collapse. there was suddenly no pillar to hold people together and the impacts were so severe that even in the latest chapters of mha it keeps on getting worse. the truth is, all mights biggest mistake was the burden he placed on his own shoulders
with bakugou and deku... its different.
its different for them because down to their attributions, they seem like two halves of a whole person.
i think that the wonder duo are going to surpass all might because of the fact that they work together.
@bakugoukatsuki-rising @svpercraigus @tybee​ @isaustraliaathing​
(batshit crazy and conspiratorial essay under the cut !)
1. Complementary Colors
I’d like to first preface literally everything I say by the fact that I am not an expert analyzer or literary major in any way. I am literally just some random fan on the internet who has wayyy too much time and looks wayyy too deep into things, but here we go!
A common thing we see when we talk about bakugou and deku is the way they are... sort of an inverse of one another.
Down to the design of their features and the way they move, Deku is the obviously softer of the two. There’s an intentional contrast between the two of them, in the way that Deku’s drawn with round shapes and curvy hair and the way Bakugou is literally all spikes and half-mast eyes and rough muscles. Bakugou’s movements too are languid and showy, with the way he leans when he walks and splays his legs and kicks open doors. Katsuki, in a casual sense, is loud and dramatic. 
Deku on the other hand s finicky. He jitters when he walks and he’s often fidgeting and mumbling. Comparatively, the aura he radiates is energetic and frenzied, even self-conscious to a point unlike Bakugou’s calm and confident movements.
Tumblr media Tumblr media
the point is, there’s a clear difference in how either of them are designed and what exactly they are supposed to represent. They utterly complement each other down to the way they behave and even their main colors (red-orange and blue-green) being literal complementary colors.
Now, moving to my more ungrounded points, this is quite a bit of a stretch so I’ll try as much as possible to make sense of these with hyperlinked sources because. yeah.
Down to their names, I think Deku and Bakugou both symbolize something deeper. I think that the way Hori expresses characters and what they’re meant to do is something that we have to pay close attention to when we talk about the Wonder Duo’s rise to success.
Izuku Midoriya (緑谷 出久), as some of us may know, does have an interesting meaning when broken up. According to a lovely fan translation of his name, ‘Izuku’--while not an actual name used commonly in real life--means to ‘Come out’ or ‘Long time’. ‘Midoriya’ on the other hand means (Midori) ‘Green’ and (ya) ‘valley’. The translator further pointed out that his first name ‘Izuku’ could be a reference to him being the first legendary hero to come out of the long-running All Might Era. (or, if you’ve been reading @/bakugoukatsuki-rising’s posts, the first significant anime protag in a long while to come out as queer, ppfft)
but that isn’t my focus right now.
We know that Hori LOVES telling stories with names, and more often than not in the BNHA universe, names alone tell us a lot of things about the characters. When referring to Izuku’s last name, Midoriya, it’s important I think to step back and realize that hey, maybe there’s something more to Green Valley than just the fact that his motif is all green.
After searching for a lil on the specifics of green valley, I’ve found out that across many cultures, the colour green and valleys in general tend to represent life. From dream analysts, to Christianity, and even old Taoist teachings, valleys are seen as areas of fertility and escape. They are seen as safe havens and often escapes for people to come to after running away from bad circumstances.
(Sound familiar?)
Deku, in essence represents life and peace. He represents being the “salvation” that the world in BNHA needed. To me, it sounds like Horikoshi is trying to say that he is the long-awaited hero in the sense. The one that people can feel will create a society that feels safe for everyone after years of All Might just saving people from themselves as a band-aid solution.
On the other hand, we have Katsuki Bakugou (爆豪 勝己), who’s name we commonly know means (Katsuki) Winner and (Bakugou) Explosion Master. He is essentially, the champion. The power. His name means success and power and all the things that make up winning.
When putting them side by side, it then becomes increasingly... interesting to me how their names almost perfectly slot into All Might’s save to win and win to save mantra, and how they are both quintessential parts to what made All Might as a hero.
2. Hero Too!
Now, I’m not even gonna really TOUCH much of what happens in canon. If you want me to do a step by step breakdown of their arcs in regards to the plot of manga and anime, feel free to send me a gratuitous ko-fi tip so I can pay for the headache I get after trying to organize my thoughts into word vomit.
What I WILL talk about on the other hand, is the subtle shift both of them slowly have in regards to how they look. Bakugou and Deku, while growing up, seem to have MANY many parallels--but before I elaborate on all of that, I wanna talk about something else.
Detour: Deku’s Red Shoes 
We all know the iconic symbol being Deku’s red shoes. For all his life, save for some outfits like his hero one, we see Deku more often than not wearing his signature red sneakers which have become a running joke in fandom.
But the funny thing is, in Japan, red shoes seem to have an interesting connotation.
In 1922, a popular Japanese nursery rhyme was written, called “Red Shoes”. The interesting part to me about this song was the symbolism that, in my tiny pea-sized brain, I could connect to the story of BNHA.
The story goes that there was a little girl with red shoes named ‘Kimi’. She was from Shizuoka prefecture (which, if you didn’t know, is most likely where Musutafu supposedly is) and was raised by a single mother. When she was young, her mother had to entrust her with a foreigner under the impression that they would give her a better life in America. The stranger is a man named Charles Hewitt (who was described to have blue eyes) and supposedly took her away. 
The singer of the song (supposedly the mother, but some argue it was written from the perspective of a childhood friend) believes that Kimi is happy and living a better life away from them, when the reality of the situation was much worse. The young girl with red shoes in actuality had Tuberculosis, and thus the foreigner whom she was entrusted to had left her to fend for herself and eventually left her to go to America while she died alone and orphaned.
“When I see red shoes, I think of her.”
A very interesting story with very interesting implications indeed.
-
Anyway, moving on to the more... “nuanced” and connected parts of this section, I have every reason to believe that Bakugou and Deku were simply MEANT to be working together down to how they dress. Now, I’d like to discuss their hero costumes.
At the start of their series, using these godawful pics for reference, it’s clear to see that neither of them seem alike in any way--reflecting the dissonance in their relationship at that point in canon.
Tumblr media Tumblr media
ough. deku why. (yes we know why its because you love your mom you stupid little bunny <3)
Anyway, we see an immediate gap in how the two of them are. Deku’s first costume is one that reflects how he treated his dream of being a hero. He was still in that childlike idolization phase, the one where his dreams and aspirations were hinged on pure feelings and inspiration from All Might. Katsuki on the other hand was a lot more tactical--professional to an extent. The gap between their respective development with their quirks is something that is clearly felt in every fashion decision they’d made.
(Notice how Deku’s green is a lot brighter and less like the green accents Katsuki has all over his costume.)
As time progressed however... their costumes changed. The colors, the silhouettes, the practical functions, most things.
Tumblr media Tumblr media
(Deku’s Gamma Costume and Bakugou’s Winter Costume used respectively)
we begin to notice a few similarities.
As the show goes on and we see more evolutions of their costumes, it almost seems like they begin to look like a matching pair. Deku’s green grows darker and almost teal in nature, while Bakugou’s orange is veering towards red territory. This is important to note because red-orange and blue-green as I said earlier were complementary colors as compared to simply orange and green. The minute shift is something I really wasn’t quite sure was intentional, but something I find interesting to pick up nonetheless as the colors they used to accent their costumes begin to match up.
Secondly, I think and important thing to note is silhouettes. The way that both Bakugou and Deku’s costumes are designed follow a lot of parallels that typically we don’t see with the rest of 1-A. For one, they both have a combination of tight long-sleeved tops with a bulkier set of bottoms. They also share the use of utility belts and metal pieces typically worn around their necks. Deku has his bunny-eared hood that mimics All Might’s hair, while Bakugou has his orange and black explosion ear-pieces that mimic his own quirk.
Tumblr media
i don’t think any other people in class 1-A match each other as subtly yet strongly as these two. Uraraka and Deku and Bakugou and Kirishima do come close however.
“But Codi, you fucking knob!” I hear you plea. “This is such a reach and tells us practically NOTHING!” And yes, I’m inclined to agree with you! You’d be sort of right in the idea that this is a reach. Maybe I am looking too much into this, and maybe it really isn’t that deep--but I do think that them subconsciously matching outfits means something quite brilliant.
In the way that their costumes are designed, each aspect of either outfits have a very logical explanation. The changes were strategic and made with their fighting styles vividly in mind, so what that tells me is that BECAUSE these costumes are so complementary or similar in nature (Bakugou’s reinforcing his arms while Deku reinforces his legs), these two are implicitly showing the audience that their combat styles are complementary as well. 
The evolution of their design choices and similarities tell us that even unknowingly, their minds line up in strategy on the battlefield--a clear exhibit for why they would be INCREDIBLY POWERFUL as a Hero Duo to begin with.
When I look at their hero costumes side by side, I see a mirror. I see the way that these two are reflections of each other and are strong where the other isn’t. The point I see in BNHA repeatedly is that EVERYONE HAS A WEAKNESS. Nothing is infallible, regardless of how hard you train or how powerful your quirk is. Everyone will always have a weakness, but the significant difference I see when fandom discusses the future of Pro-Hero Society is that the new generation is finally raising itself to be RELIANT on each other. 
Observing their fighting styles and the simple use of their quirks, its obvious that they are indeed two parts of a whole hero. Bakugou, who’s quirk emphasized his arms and hands and the power that comes from it, while Deku who’s quirk now emphasizes his legs and lower body and the way he’s always running to save people.
IN CONCLUSION:
As they become heroes, it is easy to assume that if nothing else, Bakugou and Deku will cover each other’s weak spots (especially when you consider the way Deku probably won’t be able to keep using his arms with the way both the anime and manga are going...) (also chapter 285, anyone?)
-
Part Two: Interactions, OfA
kofi || commission details
159 notes · View notes
chrysalizzm · 3 years
Note
Do you have fic recs or head canons? please ramble for paragraphs im bored and looking for something to read.
oh boy do i have some fic recs for you (and everyone who sees this), my friend! this one is quite long because there are a lot of fics i like and this isn’t all of them, so if you’d like more, you can check out my bookmarks page ^^
The Run and Go by Numanum 
“That’s not fair,” Bad protests. Dream raises an eyebrow at him and jerks his tied hands in emphasis, clearly saying that none of this is fair.
“Look, you keep running! Who runs if they’re not guilty?” Bad challenges, staring him down with obvious distrust from the generous distance of exactly five feet. It’s fair, as much as Dream hates to admit it; it’s not like he’s been the most honest hostage in the past, with all of his escaping and running and framing himself for his own murder, apparently.
“Only the good die young, and only the guilty run,” Technoblade chimes in, holding his own potato and sitting in the snow like it’s not cold at all.
A hot flash of irritation burns through him.
“Someone being chased?” he counters sarcastically, jerking his tied wrists up again to wave them in front of the group. Sapnap laughs so hard that he almost chokes on his potato, but it dies off when Dream gives him an icy stare.
Or: Dream is having a hard time, and the hunter just want to adopt him like a stray puppy that bites you at every opportunity.
multi-chapter, ongoing.
a manhunt with plot-style fic! exquisitely written, visceral in the emotions it evokes. it’s the kind of fic that makes me feel all shaky with anticipation, the kind that i have a physical reaction to; you can’t put it down.
pain. all-consuming pain. this one feels bad, man
and as he fell (you walked away) by Teahound
Once upon a time, there were three hunters.
They were good at what they did. If you wanted something-- or better yet, someone-- found, discovered, or destroyed, they were the people you asked. They didn’t have much to their name, besides a formidable reputation, but they were a team, and that was enough for them.
Once upon a time, there was a king in the forest.
He wore a mask, but it didn’t matter. That deep in the forest, in a hidden fortress, buried behind leaves and monsters and broken stone, no one could see his face anyway. He had been there a very long time, and he was alone.
Being a king can be a very lonely thing. So one day, the king left the fortress.
A Minecraft manhunt AU, with a fantasy twist. Dream is a cryptid, and Hunters are idiots.
multi-chapter (11), complete.
tea’s fic!! a manhunt-with-plot fic, featuring a forest spirit dream and circumstantial hunters and friendships that feel both intensely real and desperately melancholy because they can’t last.
or can they?
The Real World by Cinammonzoa and Fire_Fly464
"Ten, paces fire!"
Time stopped.
Tommy’s entire body went numb. He tried to open his mouth to say something, but his body was determined to keep him silent. His vision went dark, and he could no longer feel his headphones over his ears. The mouse in his hand. The slight breeze of his ceiling fan. For a few seconds, he couldn’t feel anything.
His senses came back to him all at once. The first thing Tommy noticed was the weight in his right hand -- a bow. His nostrils stung with the lingering scent of gunpowder. In front of him was a masked figure. Their right arm was bent, their elbow by their face. In their left hand was a bow, aiming directly at--
~~~
Aka Dream and Tommy get transported into the SMP world and have no idea what the fuck is happening
multi-chapter (23), complete
you’ve probably seen this one if you haunt the video blogging rpf/minecraft tags of ao3 often! an irl!dream and tommy replace their smp counterparts type of beat, very upbeat in dynamic and fun to keep pace with, great read.
staying alive (though the city is dead) by Alice_Not_In_Wonderland
"Damned if you do, damned if you don't," Schlatt smirks, his words lilting, almost song-like. His eyes seem to glow brighter. "Tell me, Dream, when did you realize that you could talk and talk and talk and no one would ever believe you?"
---
or: if dream's damned to be a villain in every story he's in, then he's going to show them exactly how much of one he can be
one-shot, complete.
the gratuitous greek mythology references are truly everything and this fic is such a good dissection of dream and schlatt’s motivations and how their goals intersect, and dream’s likening to cassandra really hits different 
Green & Gold by HognoseSnake
George’s legs ached.
His lungs felt tight and too small.
His breath was loud in his ears.
His pack bounced uncomfortably on his shoulders.
George, homeless and adrift, is an outlaw of the Mad King's reign. He'd spent the last two months being hunted across the wilderness at the fringe of society by a ruthless killer in a smiling mask and bright green coat. This, he understood.
What he didn't understand is why such a ruthless killer kept letting him go.
multi-chapter (8), complete. sequel ongoing.
a breathtaking pseudo-manhunt-with-plot fic, with george and dream running from a kingdom that wants them dead for perceived transgressions. this shit hurted, and the sequel hurts even worse ;-; snake please i beg
We’re Only Young series by ImperialKatwala
It's easy to forget amid the chaos and bloodshed how similar - and how young - Dream and Technoblade really are.
collection of both one-shots and ongoing multi-chapter fics.
((bangs on table)) please read this series it is dream and techno friendship fics that alternate between lighthearted and heartwrenchingly comforting and imperialkatwala’s characterisation of them and their respective groups of family and friends is so frickin’ good i read this series when i’m not having a good day and it never fails to make me crack a smile
kept promises and old ruins and names carved into stone by verecundiam
"Would you... would you want to stay here?" Bad wrings his hands, looking away. "Like, like actually stay? I know it's not, ah, not exactly comfortable, or all that homey, but I don't want you two to get hurt out there on your own, and I just... I think maybe you could stay? If you want?"
"That sounds nice," Sapnap says, because it does.
(Or: How four kids managed to build a family, against all odds.)
one-shot, complete.
muffinteers found family that makes me want to go to the smp writers and beg it to be made canon. unbelievably soft yet excellent at parsing out the younger counterparts of the four and creating backgrounds that feasibly form them into the people they grow up to be.
in the age of icons by BananasofThorns
“Yeah, keep digging,” Tommy crows.
The pickaxe hesitates on the downswing. The air shifts; Dream’s aura bursts into visibility, brilliant green and jagged. Ozone hums on Techno’s tongue and Bad stutters in the middle of his sentence. Up on the wall, silhouetted by the sun, Dream stands frozen and furious.
L'manberg messes with something it shouldn't. Techno watches the repercussions and tries not to laugh.
one-shot, complete.
i love deity aus (figures, i wrote one myself akjdfh), and this one hits. there’s something exquisitely delicate about how dream and the repercussions his godhood both on himself and on the people who are exposed to him in that moment of unbridled rage.
that's how we keep going (we make the best of things) by lieyuu
[ i can’t decide if this is heaven or hell. the walls keep closing in and we’re running out of space, but you’re pretty cute ]
“So, do you want to build a flower shop, a cottage, or a coffee shop?” Puffy asks, smiling like just Niki’s presence is enough to light up her world.
Niki looks at her, thinks, I want to bend nature to my will and weave tapestries in your name, says, “I think I might like the flower shop best.”
one-shot, complete.
a niki/puffy fic that crushed me in its hands in just six hundred words.  the delicate love and wonder and beauty of this fic killed me softly and i welcomed it. it’s girls in love rendered by lieyuu’s masterful hand, what more could you want
i need it to be known that as i was typing up my thoughts midnight love by girl in red started playing from my playlist if that’s not a shining endorsement i don’t know what is
did i ruin the moment? by itisjosh
Ranboo drags himself through the snow, burn wounds going up and down his body. His suit is crumpled, half of it discarded as he crawls along the ground. His eyes are firmly pressed shut, and he refuses to open them, just in case he sees him, Dream, again. Ranboo sobs as the snow melts on his skin, the water scalding him as it trickles down his arms and chest.
one-shot, complete.
it’s character death, i do need to put it out there because it felt like i was punched in the stomach at the end even though i knew. josh knows exactly how to drag his readers kicking and screaming into angst hell, as always - a ranboo is rescued by phil fic wherein ranboo ends up convincing himself that the only reason for his presence in the nearly-empty anarchist commune is because phil sees him as a placeholder for his sons ;-; pain
Frame The Halves, And Call Them Brothers by MusicallyActive
"Let's go!" Quackity roared. "Let's fucking go!"
The anvil dropped, and Techno reached for his totem of undying. This was going to hurt like a bitch.
Phil screamed something, and instantly a crushing force struck Technoblade's skull. It rattled him to the core, doused his vision in red, and then all he knew was black.
He gasped awake moments later to the sound of his communicator pinging softly at his bedside table, and when Technoblade opened his eyes, New L'manburg was nowhere in sight.
one-shot, complete.
a techno timeloop fic that shows off the unintentional cruelty of the children who run l’manberg and techno’s own inability to allow the people he tries so hard not to love to come to harm. techno’s rendered in painstaking detail; this one was cathartic in the best way.
on i go (move to move) by Aenqa
If you ask someone whether they’ve ever experienced real, severe physical pain, you’ll learn a lot from their response.
Techno knows what it means to be in pain. He’s accepted it as a necessary consequence of keeping his family safe. But when the pain he's experiencing starts to become too much to bear alone, it takes his family to show him what it might mean to feel better.
one-shot, complete.
chronic pain fic featuring sbi!! it’s really good - aenqa wrote chronic pain well, and incorporated respawn mechanics into it well, and the dynamic between sbi is impeccable.
Yellow and Blue and- by nic_takes_Ls (nic_L)
It’s another gorgeous day in New L’Manberg. Tubbo’s stilted streets of deep toned spruce and honey-touched oaks are warm under his feet from the sun, and a sign and a small banner proclaim the country’s name in front of his face. Wilbur is so happy to let the ‘L’ roll of his tongue as he says it, ‘Manberg’ was harsh and too guttural, but the two extra syllables make it something that could fit on a melody, a four-note beat he could set the pace of his unbeating heart to.
The citizens of New L’Manberg track him with cautious eyes at first, until Tubbo changes his eyes to slightly sad ones, listening along to Wilbur’s rambles, warming up to the truly soot-grey sight of his face and sunshine yellow of his ever-present sweater. The rest of the population soon follow, laughing at Wilbur’s strange innocence and telling him what he’s done with only a little bit of spite in a pitying mask and fixing their mouths in a line when he suddenly forgets what he’s doing or stares into space or laughs at nothing.
But all the people who get sad when Wilbur starts laughing after shock-still silence are dumb.
Because Wilbur’s not laughing at nothing.
one-shot, complete.
a ghostbur fic from quite early on! it includes references to wilbur and schlatt’s older videos/smp experiences and has a super interesting take on the nature of wilbur’s amnesia i enjoyed this fic a lot ^^
east of eden series by subwaywalls
Philza protects his home.
(An angel with a singing blade of fire guards the gates to paradise.)
two one-shots, one ongoing multi-chapter fic.
READ IT READ IT READ IT. the eoe series is exquisite in both content and presentation, centering around sbi and the powers they all respectively have but also bringing in people like grian and dream, and subwaywalls is a master of packaging her words ever so delicately to create an experience that is ethereal.
55 notes · View notes
artnerd1123 · 3 years
Text
Among Us: CR3WM8TS
Docked and Loaded ——————————————
The ship finally arrives at the newly established Sector G base on a new planet. Hopefully, start of work goes smoothly. Hopefully the logs are functioning well, too... 
Among Us archive/askblog Fic chapters post
——————————————
once again i am chopping a chapter in half to keep it from getting too long. anyway!!! more normal crewmate troubles n dorkiness with some ominous vibes for Flavor (tm).  hopefully the next part will come out sometime soon! enjoy this for now!
                                                 ===+===+===
Mission Log 10
Ship Model: SKELD D34-H120 Designation: SUPPLY TRANSPORT, EXPLORATION AND DOCUMENTATION OF SECTOR G PLANETS Crewmate Count: 9 Crewmate Colors: DARK GREEN, WHITE, PURPLE, DARK BLUE, YELLOW, RED, LIME, BLACK, PINK
Location: SECTOR G Ship Status: IN TRANSIT Course: PLANET 326-OCE-894 - SECTOR G Systems:
Navigation: COURSE INLAID / STABLE
Engines: UPPER - ONLINE, TANK 0.86 / LOWER - ONLINE, TANK 0.83 / OUTPUT ALIGNED
Reactor: ONLINE / FUNCTIONING OPTIMAL
O2: STABLE
Electrical: STABLE
Communications: ONLINE
Shields: ONLINE / FUNCTIONING OPTIMAL
Weapons: ONLINE / FUNCTIONING OPTIMAL
Security: CAMERAS ONLINE / ALL FUNCTIONAL
Administration: MAP ONLINE / CONNECTION SECURE / SHIP FILES UP TO DATE / ALL CREW ACCOUNTED FOR
Medbay: EQUIPMENT ONLINE / FUNCTIONAL / CREW FILES UP TO DATE
Supplies: [ONE] CRATES SHORT  
Storage Chutes: CLEAR
Vents: CLEAR
Notes: Updates by PINK still taking well to system. Travel into sector G uneventful. Crew bonding time worked into today’s schedule, all crew in attendance for at least an hour. Crew performing normal activities.
                                                ===+===+===
Mission Log 15
Ship Model: SKELD D34-H120 Designation: SUPPLY TRANSPORT, EXPLORATION AND DOCUMENTATION OF SECTOR G PLANETS Crewmate Count: 9 Crewmate Colors: DARK GREEN, WHITE, PURPLE, DARK BLUE, YELLOW, RED, LIME, BLACK, PINK
Location: SECTOR G Ship Status: DOCKED AT [PLANET 326-OCE-894 - SECTOR G] Course: N/A Systems:
Navigation: COURSE REACHED / STABLE
Engines: UPPER - ONLINE, TANK 0.72 / LOWER - ONLINE, TANK 0.68 / OUTPUT ALIGNED
Reactor: ONLINE / FUNCTIONING OPTIMAL
O2: STABLE
Electrical: STABLE
Communications: ONLINE
Shields: OFFLINE / FUNCTIONING N/A
Weapons: OFFLINE / FUNCTIONING N/A
Security: CAMERAS ONLINE / ALL FUNCTIONAL
Administration: MAP OFFLINE / CONNECTION SECURE / SHIP FILES UPDATING FOR MISSION / ALL CREW ACCOUNTED FOR
Medbay: EQUIPMENT ONLINE / FUNCTIONAL / CREW FILES UP TO DATE
Supplies: [ONE] CRATES SHORT  
Storage Chutes: CLEAR
Vents: CLEAR
Notes: [SKELD D34-H120] docked at [PLANET 326-OCE-894 - SECTOR G] successfully. No transport issues to report. All supplies for transport intact and in transfer process to base. Mission files currently integrating to ship system. Crew integrating to mission base to assist in tasks, exploration, and supply dropoff. Weapons and shields offline to align with docking procedures. Course will remain unlaid until HQ supplies further information.
                                               ===+===+===
It had been a good long while since Bunbun worked on an exploration ship. She’d been stuck updating supply line SKELDs for awhile, and got bogged down repairing virus bound code in defensive SKELDs for longer. Exploration ships were like little breathers in the middle. It meant she just had to worry about one ship, once all the updates were done. She could sit and watch the stars. Make a friend, if she was lucky. And, on days like these, it meant she’d be helping out with something bigger. Planet 236-OCE-849 was a gorgeous world. A new one, too! Sector G was largely new to HQ. They had high hopes for the planets and inhabitants. This one in particular was the first they’d set up a base on. Crashing waves and strange colorful formations were the source of many rumors across the airwaves. Bun had been unable to see the planet’s surface as they descended, but the views before they made landing preparations were breathtaking. Most of it was enveloped by an ocean. The water bound world was like a painted marble, little bits of vibrant colors poking through. She’d never seen land so vivid. It made her all the more eager to get down to the surface.
The door of the ship opened with a loud hiss of pressurized air. Metal clanked against itself as the two halves pulled themselves apart, the groaning making everything in the vicinity go still. Of course, what happened afterwards was anything but. The gangplank was already lowered, so the cooped up crewmates came scrambling out. Captain Groud was first, their voice echoing back towards the ship as they called for the Mission Lead. Silk trailed after, one of xyr robots hot on xyr heels. Rose and Laser went off as a pair, aiming to tend to any wounded and check how defenses were going. Nanner emerged with supplies already loaded, a couple crewmates from the planet coming to help her transport them where needed. Lemon and Junior were off to check the base’s energy status, the son sticking anxiously close to his father. River, to no one’s surprise, sauntered off the ship to go find somewhere to lounge. Groud said he’d help when they needed him, but from the looks the rest of the crew gave, there was probably a fat chance. That left Bunbun walking down the gangplank last. She was glad now that she’d pulled down her solar visor. The light outside was nearly blinding compared to the ship’s lobby. Around her she could see miles and miles of open sea, the bluish purple waves almost hauntingly familiar. Like an echo of the blue green waters she had back at home… I wonder why the water here is more purple… I bet Rose might know. She only took her eyes off the ocean when something crunched under her boot. She drew back hastily, afraid she’d stepped on some planetary inhabitant- only to draw in a sharp breath from surprise. The ground she stood on was a bright pastel orange. Upon closer inspection, it looked to be made out of some sort of… finely webbed rock? Coral, she realized, eyes widening. Her gaze snapped up to the island they’d landed on. The whole thing looked like a fractured rainbow, colors radiating across the ground. It was all made of the same webbed rock- coral, as she’d remembered. The realization had her bouncing on her heels. I’m standing on an island made of coral!!! She took another hesitant step. It crunched again under her foot. She couldn’t keep herself from giggling as she kept moving, feet dancing over coral that shifted from orange to yellow to green to purple. A myriad of colors, and all so bright!!! She’d never laid eyes on anything quite like it. “Enjoying the scenery?” a voice called, snapping Bun out of her happy dance. She froze in place, instantly standing to attention and trying not to drop her tablet. Another voice laughed, the sound carrying closer as her face flushed. When Groud’s star patched green and Silk’s beat up purple came into view, she let herself breathe. Ok. So. She hadn’t entirely made a fool out of herself. These two, at least, knew who she was. “M-maybe a little, Captain, eheheh,” she called sheepishly. “I’ve never been on a planet with coral islands before.” “Well, it’s a treat for you then!” Groud laughed. “Hey, if you’re not busy, why don’t you go head down to the power room?” “Oh, are the base crewmates busy?” Bun blinked. “Yeah, a little,” Groud nodded. “There’s a lot going on. Lots to update and lots to move.” “That, and Lemon ‘n Junior said they might need some help,” Silk added. Between you and me, I think they just want someone they can rely on to help figure the energy out.” Bunbun’s face flushed again, the warmth from Groud’s voice and Silk’s visor-shadowed smile buoying her mood higher. She’d only been with this crew a few days, but they trusted her this much? … Honestly, she couldn’t tell if that was foolish or not. But things needed fixing, and her crewmates needed help. So, off she went.
The base layout was pretty standard. It was a bit compressed to fit the strange coral island, but they’d managed to fit everything in about the right place. Lucky for Bun, she didn’t have too far to go. The building to the gangplank’s right would give her access to Electrical’s courtyard. The crunching of multicolored polyps beneath her feet made her smile with giddiness, picking up the pace to get inside the base. The door was already left open- standard procedure for operating periods- so she strolled right through. Nobody seemed to be waiting for her in the building’s lobby. She’d guess they were all outside, if it weren’t for a few voices. She could hear a couple people down in the security area. River and some other security crewmates, if her auditory memory served. They were talking pretty boisterously. She hooked a hasty left out towards the courtyard, eager to move on before one of them noticed her. The courtyard was once again standard. Four generators, one control panel along the outside wall, and cagey wire fencing around the edges. Bunbun found herself wondering once again what that was meant to keep out. On a new planet like this, nobody really knew what to expect. It could be anything. Images of creeping beasts and flashing eyes started to bubble up, so she shoved the thought out of her mind. It was fine! The fence would do its job, surely. Just as she would do hers. It’s fine. Lemon and Junior were standing before the control panel, their confused chatter making Bun glad they enlisted her help. From the frustrated looks on their faces, they needed it. They should teach more cross system repair in the academy… oh dear… “You two need some help?” Bun called, striding over to the panel. The pair looked up, relief instantly appearing on their faces. “Bunbun, thank fuck,” Lemon sighed. He leaned against the wall, pulling his goggles back up over unruly red hair. “we’re really goin through the ringer here…” “Y-yeah, uh, any help would be really appreciated,” Junior added sheepishly. He fiddled with his tablet, nervous fingers closing out a couple HQ manual tabs. “Of course, guys,” Bunbun smiled shyly. She tilted her head at the open panel, a mess of wires greeting her. Well. That was. Not encouraging. Her brows furrowed the more she looked, eyes trying to trace input to output and back. “... ok. I uh. I see several problems here.” “That’s an understatement,” Lemon snorted, shaking his head. “I don’t even know where to start.” “Did you make sure to take the relevant systems offline?” Bun asked. “Already done,” Junior nodded. He held up his tablet, a couple tabs pulled up to show electrical systems operating on reserve. “We can’t keep it offline for too long, or else the lights are gonna go out. Which, um, wouldn’t be super fun.” “Right, we’ll have to move fast,” Bunbun replied. “I think I can manage that.” Rubbing her hands together, she eyed the mess of wires before them. She’d handled worse, right? This wouldn’t take too terribly long. It was only when she remembered the other two were watching that her confidence faltered. Was she supposed to help? Was it even ok to take over here??? “... Is, um, is it ok if I… uh… manage it…?” She chuckled timidly, scuffing a boot on the ground. “By all means, dear,” Lemon chuckled, giving her a little dramatic bow as he moved back. “I’ll give you all the space you need.” “Just remember we’re here to help if you need!” Junior offered, scooting back to his dad’s side. He smiled nervously, the sun flashing off his visor as he shifted a bit in place. Gathering courage, if his next little quip was anything to go by. “We might, uh, know the reactor better, but we still know our way around a pair of wire cutters! Eheheheh...” Bunbun smiled gratefully at them both. “Of course, thank you. I’m, um, gonna get on this as fast as I can.” With that, she fixed her attention on the wires once more. Whoever set this base up hadn’t been picky about organization. She could fix that easy enough. She’d just… start by sorting the colors. Taking a breath, the electrician hefted up her tablet, free hand reaching carefully for the nearest red wire. Yeah. This would be over in a sinch. And maybe later, she could go explore a bit more. The thought made her grin wide, fingers flying between wires even faster. Oh, yes. Seeing some more of this beautiful planet would be worth any circuitry headaches. She’d just have to remember to add this to the ship’s log later…
                                              ===+===+===
SHIP SYSTEM REALTIME LOG: System setting: DOCKED [STORAGE AND VENT MONITORING]
Location: SECTOR G Ship Status: DOCKED AT [PLANET 326-OCE-894 - SECTOR G] Course: N/A Systems: 
Supplies: [ONE] CRATES SHORT  
Storage Chutes: CLEAR
Vents: CLEAR
Supplies: [ONE] CRATES SHORT  
Storage Chutes: MATERIAL BUILDUP DETECTED  
Vents: CLEAR
Supplies: [ONE] CRATES SHORT  
Storage Chutes: CLEAR
Vents: CLEAR
13 notes · View notes
mythriteshah · 4 years
Text
A Spot of Revelry, Pt. 2
Beneath the Ruby Sea, the Regalia’s submersible was approaching the Dive.  Everyone was prepared to carry out their task, though Lilina was anxious to commence with the fun part of becoming a Summoner, clutching her new soul crystal in hand.
“This is Head Secretary Yuanji,” she whispered over the linkpearl.  “Miss Shiro and I have made off with one of their trinkets, and have commenced the diversion.  We’re already leading them southward, away from the Kojin’s vault.  You may begin insertion.”
“Take us up, little mammet – we’re here!” Sarielle ordered.  As they made their ascent, Rhaegos landed atop the rocks looming over the isle, while Himmeya found a small landing out of view from wandering eyes. She kept her griffin there and climbed the wall, looking up to Rhaegos, who was scanning the area: the Red Kojin were growling and barking orders at each other, weapons at the ready as they took paths around the isle to pursue the intruders.  The way was now clear save for a few sentries, and his first victim was an unwary swordsman. Jumping from his elevated position, Rhaegos would land on top of the Kojin sentry, knocking him out instantly.  He slowly made his way to the center, where Jaanavar’s Vision was beginning to surface.  Two more Red sentries armed with daggers attempted to ambush the Vizier, only to be met with a wind-imbued fist to the face of one, and a swift kick to the other, sending them both flying into a rock face and incapacitating them.
“Nice form, sir!” Himmeya grinned.
“Comes with the territory,” he coolly replied.  The submersible finally reached the surface, and out from the hatch jumped Meriri, Sarielle, Lilina, and Thiji.  The Jaanavar’s Vision then took back to the depths after closing the hatch to maintain stealth.
“Rhaegos, this show’s yours now,” Thiji spoke.
Tumblr media
“Right,” the Au Ra said as he cracked his knuckles.  “Miss Himmeya, you take care of anyone who gets past me.  Miss Sarielle’s got the heals and the power of time, so when they start to swarm, slow ‘em down.”
“Understood,” the green-haired Elezen acknowledged.
“Miss Meriri, disarm them with your arrows.  If they have any spellcasters, they’re priority targets,” he ordered.
“Aye!” Meriri stated. “These arrows are blunted, too, so a nice hit between the eyes’ll knock em on their shelled bottoms if need be!”
“Let Lord Thiji and Miss Lilina rest and conserve their strength for now; they’ll need it for the vault,” Rhaegos pointed out as he advanced.  With the plan now in motion and the diversionary tactic in place, it was finally time for the mission to begin in earnest.  Their push through the isle was relatively easy, occasionally taking fire from Red sentries who struck at them with volleys of water.  Though they were trying to prevent the Mythrite Sultan from engaging in combat, he had no time for holdups.  
The gems on his robes and turban were beginning to glow as he walked out of cover.  As the sentries prepared to conjure another water spell, the aether gathering within the gems shot out from his robe in bluish-white beams, freezing their weapons, and eventually their entire bodies.  The sight left everyone – including Rhaegos – utterly amazed and confused as they followed Thiji up the isle…
“How’d ye do that, My Sultan?” inquired Meriri.
“The Gemweave,” he answered.  “This particular robe was woven with materials conducive to aetherial manipulation; they travel through the robe like a conduit and is focused into the gems, allowing me to cast spells without somatic involvement.”
“Ye could’ve just said ‘Black Mage shenanigans’ and I’d still believe ye, milord, but I won’t question it!” Meriri commented.
Meanwhile, on the southern portion of the Kojin’s stronghold, Shiro and Yuanji were fleeing from the bulk of the Reds.  With quick hand gestures, Yuanji jumped over an incoming water blast and slapped the ground with her palm, disturbing the earth beneath her and turning it into a large patch of mud, slowing down the horde.
“Get back here, thieves…!” roared a Red marine, struggling to get through the disturbed earth.  “You will not get away with this!”
Tumblr media
“But we are, as you can see!” Yuanji yelled back as she stormed ahead.  With the enemy thus distracted, the main host was able to enjoy a simple push through the Air of the Opulent, taking down a few striped rays that were straggling. Yuanji and Shiro kept retreating until they reached a dead end, where they came upon a stockpile of goods and other miscellaneous trinkets.  “The dead end at the southernmost area, as Lord Thiji planned.  Shall we, old friend?”
Shiro nodded and handed a small glass orb to the Head Secretary before they hid themselves away from view.  Before too long, the Red Kojin would swarm the area, searching high and low for the intruders.
Tumblr media
“They’re here somewhere! The Mist Queen could not have gotten far!” spoke a Red Marine.
The preceding area now secure, it was time to begin the next phase.  Thiji and Lilina entered the vault and arrived within the Blessed Treasury. They immediately noticed the large altar on the far side of the area, lined with gold, trinkets, and all sorts of curiosities.  Submerged pedestals served as stepping stones leading to a platform in the center of the large pond; a faded scar could be seen, dividing the platform into two lengthwise halves.
“Welcome to the Pool of Tribute, Miss Lilina,” the Mythrite Sultan greeted.  “’Twas here I and several others encountered the Lord of the Revel, albeit brief.  And it is in this area full of water and lightning-aspected aether that you shall take your first steps into becoming a full-fledged Summoner.”
Tumblr media
“I am ready to receive your teachings, my lord!” Lilina anxiously replied with a bow.  “What must I do?”
“Simple: make your way to the center platform.  I will then commence the Austerities of Revelry and call forth a scintilla of Lord Susanoo’s power, more commonly known as an Egi.  You are to face this manifestation and conquer it.  Doing so will absorb and merge its aether with yours, allowing you to call it forth and bind it to your will via the soul crystal.”
“Understood!” Lilina acknowledged.  She slowly began to make her way towards the center, eager to get started.
“I will offer you guidance your battle unfolds.  I would aid you in defeating the Egi, but I do not wish to run the risk of collecting its energies,” he further explained.
“Not to worry, my lord! I’ve no intention of failing you or the Regalia!” she reassured.  Once she was in position, Thiji unveiled his own soul crystal, stretching out an arm as he focused inward, shifting his aetherial balance.  The crashing of water and dancing thunder coursed through his body; his soul crystal would begin to glow in response.  At this time, Lilina would summon her Topaz Carbuncle to aid her in the battle, giving it a gentle pat on the head as it appeared.
Tumblr media
After a short moment, something would begin to emerge in front of Lilina.  In a small flash, a bluish aetherial being wreathed in water and lightning energy appeared, brandishing a blade no longer than its form.
“The avatar of the Lord of the Revel has been drawn into the material plane, and the Austerities of Revelry is complete,” Thiji declared.  “The rest is up to you now, Miss Lilina: defeat the Egi and claim its power for your own.”
“Let’s do it, Carby!” cried Lilina as she gave the order to charge.  The Egi engaged her pet, and thus was battle joined.  Lilina’s Carbuncle leapt into the air to strike at the avatar, but the Egi showed its speed by moving to the right, avoiding the attack.  Its blade then began to crackle with electric energy… “Carby!  Incoming!”
The distinct sound of laughter came from Susanoo-Egi as it rose the blade in the air, calling down a column of thunder to strike at the Carbuncle.  It was able to dodge the incoming bolts by curling into a ball and rolling about the arena.  With the Egi’s attention thus diverted, Lilina readied her spell, using her quill to draw aetherial shapes in the air.
The Egi managed to catch Carbuncle with a lightning strike, temporarily immobilizing it.  However, enough time was bought so that Liliana could finish her spellcast, sending a large orb of unaspected aether towards her foe.
“Have some Ruinga!” she cried as her spell hit the Egi center mass.  While the Egi was still reeling from the attack, she sent a wave of restorative aether to Carbuncle, curing it of its paralysis as it fell back to her side.
“So far, so good…” the Mythrite Sultan said in his mind.  Meanwhile, outside the vault, Rhaegos and his crew had the perimeter secure.  While they set up posts around the Air of the Opulent, Meriri noticed what appeared to be smoke rising from the south.
“Oi, what do ye think that is?” she asked.
“That’s gotta be Miss Shiro and Miss Yuanji,” Himmeya replied.  “She’s called the ‘Mist Queen’ for a reason; getting out of her domain is difficult.  With her on our side, we shouldn’t have to worry overmuch.”
“Should we go and assist Lord Thiji and Miss Lilina, then?” Sarielle thought aloud.
“As good of an idea that sounds,” Rhaegos started, “we can’t interfere with the ritual.  All we can do now is stand watch.”
Back inside the treasure vault, the Egi had recovered from Lilina’s attack, and decided to change tactics. Raising its free hand, several dark clouds appeared along the outskirts of the arena, teeming with electricity.
“Whoa!  What’s this?!” Lilina gasped.  The clouds were beginning to charge up for a powerful attack, which gave the cue to get out of the way.  She dove to the left just as pillars of lightning shot across the pool in a cross-shaped pattern, while Carbuncle dodged in the opposite direction. After the attack, the clouds faded, and Susanoo-Egi returned to the fray with sword raised.  As Carbuncle charged their foe to draw its attention, Lilina watched the clouds part.  Small trails of water and lightning-aspected energies danced in the air, which caused her soul crystal to react.
“My Sultan!” she spoke. “The crystal is glowing!”
“It’s responding to the aethertrails left behind by the Lord of the Revel,” he explained. “Attune yourself to them and you will be one step closer to mastering Susanoo’s power.”
Without hesitation, she rushed over to the far side of the arena, stretching out a hand to attune herself to the aethertrail, but was stopped short by a wayward lightning strike.
“Whoa!  Carby, Glittering Topaz!” ordered Lilina.  Her pet nodded, its body glowing for a brief moment before channeling its power to the ruby on its head to erect an aetheric bulwark around Lilina’s form, absorbing the lightning strikes with ease as she successfully attuned herself to the first aethertrail.  “All right – two more to go!” she said to herself as she hurried to the opposite side.  The Egi took notice of what Lilina was attempting to do, and was making its best efforts to stop her, but Carbuncle was putting up an exceptional defensive effort, keeping it away from its master, and with the barrier still present, its lightning attacks proved ineffective as she was able to attune to the second aethertrail with no issues.  The excitement of mastering this power was beginning to get to Lilina, as she happily made her way to the final aethertrail, which laid behind the Egi on the far side of the arena, confident that the barrier would hold.
“Almost…!” Lilina uttered as she began to fuse her own aether with the final trail.  Just as it seemed like she would be victorious, her concentration would be broken as rocks began to rise from around her and her Carbuncle, encasing them in a stone gaol.
Tumblr media
“Ama-no-Iwato…” Thiji thought.  Three blue chains sprouted from Susanoo-Egi’s free hand, linking themselves to the earthen prisons that housed Lilina and Carbuncle, as well as the empty third prison.  It rotated them around the center of the arena in a triangle formation.  Lilina and her Carbuncle were helpless, unable to do anything to break out from within, as the stones began to glow, preparing to unleash The Sealed Gate.
Thiji, sensing trouble, quickly opened his Blissful Grimoire and prepared to retaliate.  He would prepare a spell powerful enough to shatter the Ama-no-Iwato in one strike.  The Mythrite Sultan would not enjoy this honor, however, as he heard footsteps approaching from the entrance.
Tumblr media
“Don’t waste your energy, My Sultan!  We got this!” shouted a voice.  Two Lalafell women zoomed past Lord Thiji in the blink of an eye – one with black hair, and one with strawberry-blonde hair and platinum-blonde highlights.  The black-haired Lalafell sprung into the air with her Mythrite pugil stick, while the other one hopped over the pedestals to enter the arena.  The strawberry-blonde blasted the center stone with a fire spell, destroying it and freeing Lilina, while the black-haired lass chucked her weapon straight through the gaol to the right, shattering it.  Unfortunately, it was the dummy.
“Sisters!  You came!” Lilina cheered.  Her older sisters, the strawberry-blonde Lelena, and the black-haired Luluma, had come to her side.
Tumblr media
“And you didn’t invite us?!” Lelena scolded.  “We would have loved to see our baby sister become a Summoner!”
“Miss Yuanji told us about the whole thing before she set off for the isle, so we took the Vigil to get over here after suiting up,” Luluma explained.  “Mamai is always watching over her Angels – may she know eternal peace in Nald’thal’s realm.”
As they spoke, the remaining stone prison exploded, and the Carbuncle was gone without a trace.
“Aww, there goes your Carbuncle…!” Lelena groaned.
“Hold on!” Lilina exclaimed.  She slapped the pages of her book and stretched out her palm as a bright sigil appeared around it for a brief moment.  Out from below, Lilina’s Topaz Carbuncle returned and struck at Susanoo-Egi from below!  “I was able to recall Carby before the gaol exploded!  Swiftcast is so handy!”
“Miss Lilina, the final aethertrail,” reminded Thiji.  Lilina nodded and hurried over, her sisters covering her back while she attuned to the final aethertrail.  Luluma dodging a few lightning bolts and then struck Susanoo-Egi, knocking it back towards the far end of the arena and briefly stunning it, while Carbuncle ran to the opposite end and curled into a ball, rolling across the pool to gather speed, the resulting acceleration causing aquaplaning to take effect. Lilina had finished preparing a fire spell to cast underneath the charging Carbuncle, causing a tiny explosion of steam to propel it upwards to smack the Egi with enough force to send it plummeting into the waters below.  The aethertrail was thus extracted, and Lilina’s attunement was complete. The sisters cheered and began conversing among themselves as they started to leave the arena.  Thiji gave a nod of approval and would begin to turn around and leave, but something caught his eye.  With a double take, he looked to the far side of the arena and noticed that the aether stored in blade was beginning to stir.  It began to levitate from its resting spot for a few brief moments before shooting into the heavens.  Just as Lilina and her sisters would step out of the arena, a large thud was heard that shook the earth, with enough magnitude for it to be felt from beyond the vault.
“What was that…?” Sarielle questioned.
“Oh, shit…” Rhaegos murmured as he turned to the door to the vault.
“Intruders have breached our treasure vault!” cried a Red marine in the distance, taking notice of the tremors.  “Alert our main force and protect Lord Susanoo!”
Tumblr media
Before he could get too far, an arrow would find its mark on the Red Kojin’s forehead, knocking him on his shelled back.
“Welp, there goes the neighborhood…” Meriri sighed.
“They still have yalms of mist to go through!” said Yuanji as she appeared behind them.  “Miss Shiro has stayed behind to maintain the ninjutsu.”
“Good, because until they leave the vault, our work here’s not done,” Rhaegos replied as he took to a combat stance.
Back at the Pool of Tribute, Lilina and her sisters turned around and immediately took notice of the large blade that had pierced the waters.  The surrounding atmosphere changed as well, with overcast clouds and rain suspended in midair.  The waters began to stir as the Egi had transformed into a larger variant of itself, towering over the arena by yalms.
“This one doesn’t seem as large as I remember…” Thiji pondered. “Two of the three sacred treasures are also missing…”
“How the heck are we going to stop that?!” Lelena shrieked.  The enlarged Egi took the blade in its hand and began to raise it over its head.  The Topaz Carbuncle ran over towards the shadow of the blade, turning back to Lilina and pointing upwards with a paw.
“Carby’s got our answer, I think!” Lilina commented.  “Sis, let’s do the Cryotic Feedback!”
“You want me to use Subzero now?!” she immediately questioned.
“Trust me!  We can’t let that sword touch the pool!” Lilina replied as she went over to Carbuncle’s side.  With grimoire in hand, she readied herself for Lelena’s ice attack.  Luluma attempted to stall for time with repeated strikes at the enhanced Egi, but it proved to give little effect as it shook off her strikes with relative ease.  Lelena finished conjuring enough ice energy to blast a large ray of frost at Lilina’s grimoire, which absorbed its energies into a barrier formed around the tome.  Luluma, who had did all she could to buy time, landed back onto the arena and sighed.
“Here it comes, girls,” she warned.  With the spell complete, Lilina rose her grimoire high in the air, meeting Susanoo-Egi’s blade head-on.  The sisters screamed in fright from the resounding clash, with Lilina’s plan actually proving to have some effect at holding back the sword.
“I am genuinely astounded and frightened right now!” Lilina informed everyone.  As she continued to hold back the blade, miniature lightning orbs formed around the arena, slowly making their way towards Lilina in an effort to shake her off.  Her Carbuncle took note of this, and began touching each one, which caused a small fulguration.  The resulting release of electric energy would take its toll on the Carbuncle, however, as it felt its strength waning.  “Come on… Come on…!” Lilina grunted with effort.  Once the barrier was broken from the sheer force of the clash, the energy stored from Lelena’s attack had shot out, causing a blast of ice-aspected energy sufficient enough to force back the blade.  This feedback sent Lilina skidding across the waters, her grimoire landing beside her.  Her sisters hurried to her side to get her on her feet as the Egi was recovering from the attack.
“Darnit!  That wasn’t enough to beat it?!” spoke Lelena.
“Lilina’s Carbuncle can’t hold out any longer, either…” Luluma spoke with concern.  With no other options, Lilina approached the shadow of the blade once more, but something about her was different.  Lelena pointed out the shift in aetheric balance within Lilina’s body, and her soul crystal had begun to respond to this change. Luluma watched in anticipation as the Egi begun to lower the blade back down again.  Just as it would finally hit home, a flash of lightning crashed down upon Lilina’s form, and holding the Egi’s blade back was another aetherial sword identical to it.  Lilina’s form had changed slightly as well, with the distinguishable helmet and plumage on her head.  She laughed victoriously as she held back the blade with ease. 
Tumblr media
“Attendeth me, my sisters!” she called.
“Wait, what does she mean by that?” Lelena asked.
“She want us to do another team attack,” answered Luluma.
“Then let’s invent a new one!” Lelena cheered as she prepared the Triplecast spell.  Luluma got into position as her older sister sent three thunder orbs at her, knocking them overhead as they spiraled into the side of the Egi’s sword, significantly weakening it.  Lilina used this opportunity to strike back, knocking the blade out of its hands and disarming the Egi.  Her own aetherial sword then touched the residual lightning energies from Lelena’s spell, wreathing the length of the blade with its power before chucking it at the reeling Egi, landing right in its torso before erupting in a pillar of bluish-violet energy.
The scintilla of Susanoo’s power had begun to lose its form, letting out a roar before finally dispersing back into the aetherial sea, the atmosphere returning to normal signifying its defeat.  Lelena and Luluma heaved a sigh of relief while Lilina fell on her bottom, exhausted from the effort.  She looked over to Carbuncle, who was slowly approaching its master.  With a smile, Lilina petted its head and stated that it will be missed dearly.  With an Egi of her own now obtained, there would be little reason to keep Carbuncle under her employ.  The Carbuncle nodded and gave Liliana a hug before being called back into the aether.
“Congratulations, Lilina,” said Thiji.  “You are now officially a Summoner, and a practitioner of Susanoo’s power – a Master of Revelry.”
“Thank you, My Sultan. I could not have done it without your guidance and the help of my sisters!” Lilina replied as she got back to her feet.
“A question, My Sultan,” Lelena interjected.  “What was that spike in power that Lilina had during the second clash?  Was that the Trance thing?”
“Correct,” Thiji confirmed. “In addition to bending eikons to their will, Summoners are also able to invoke the powers and personalities of said eikon by entering a Trance. When enough of an eikon’s aether is attuned, they are capable of assuming this form change for a brief period of time.  With practice, however, they can uphold this Trance longer and more deftly – and even call forth a simulacrum of the primal itself as a result.”
“Then as the first to obtain Lord Susanoo’s power, I shall dub it… the Reveler Trance!” declared Lilina.
“Now we just need to get out of the isle…” Luluma pondered.  “The Red Kojin are definitely not happy to learn that we’ve stolen into their vault, so they’ll send everything they’ve got at us.”
“No problem, Luluma!” Lelena assured.  “I think we’ll be just fine!”
“Indeed we shall, ladies.  Miss Lilina, pray lead the way,” said the Mythrite Sultan.  With an enthusiastic nod, she hopped and skipped her way out of the arena, exiting the vault.  Meanwhile, the Red Kojin’s main force had finally caught up with Rhaegos’s team.
“We fear not those who trespass upon our sacred vault!  Least of all you, Demon of the Steppe!” taunted the Red Kojin’s commander.  Rhaegos smirked and crossed his arms, taking a step forward.
“Be thankful we’re not here to kill you, or you’d soon learn to fear me,” the Au Ra coolly retorted.  “But if you’re so eager, then step on up.  I can’t promise anything, though.”
The commander gave the order to charge.  Everyone prepared for the Red’s onslaught as they drew closer and closer.  Just as battle was about to be joined, however, the doors to the vault burst open, and hearty laughter was heard from within. The Red Kojin coasted to a stop, murmuring amongst themselves…
“The Kami of the sacred treasures…?”
“Lord Susanoo…?”
“Has He awakened once more?”
Tumblr media
Himmeya and the others heaved a sigh of relief as Rhaegos turned his gaze toward the vault.  Out from the doors came Lilina, assuming the Reveler Trance once more, brandishing her aetherial weapon in the air with a wide grin on her face, with Susanoo-Egi in tow.  She was followed by her sisters and Thiji, who had approached the Red Kojin’s forces.
“What… what omen is this?” gasped the Commander.  Everyone on the isle was amazed at what they beheld.  With another spot of laughter, lightning danced around her being, frightening the Kojin and their pet rays.
“Behold, shelled ones!” boomed Lilina.  “These children of man hath come to pay tribute!  They sought not to abscond from ye who hath made us whole, but to offer respect to the banquet, and as thanks, have blessed them with a power none hath dared to hold!  Thus, the test is passed, and the feast hath ended!  Pray let these children of man free in the name of the divine!”
“If what you say is true, blessed one of Lord Susanoo,” began the Red Commander, “then we thank you for this test, and shall obey your words.”
Thiji beckoned the others over and fell in behind him, walking with Lilina out of the isle as the Kojin parted the way for them.
“So, does this mean that Miss Lilina’s gettin’ a promotion, milord?” Meriri thought aloud.  The only response she received was laughter from everyone but Thiji, who was shaking his head.
“Wait, wasn’t Miss Shiro with us?” Sarielle said as she looked around for any sign of the shinobi.
“Don’t concern yourself, Miss Sarielle,” Yuanji replied.  “We’ve already planned out for this; before this operation began, Miss Shiro went into the vault and took the Yasakani-no-Magatama and the Yata-no-Kagami, leaving the Ame-no-Murakumo alone.  She volunteered to stay behind and put them back in their proper resting place after our success!”
“That explains why the Lord of the Revel himself didn’t appear, and the Egi was considerably weaker when the sword awakened!” Lelena deducted.  “Wow, the Regalia’s just full of geniuses!  I may have to become a Summoner myself if we can have more exciting adventures like this!”
“I am certain we can strike a deal with the Immortal Flames when the time comes,” Thiji said.
“Well, I call dibs on Shiva-Egi!” Lelena decreed.  “And no one can say otherwise!”
With another bout of laughter, Thiji, Lelena, and her sisters took off on Mamai’s Vigil docked on the southern cliff face, while the remaining team boarded Jaanavar’s Vision, returning to their respective branches.  An Arcanist no more, Lilina Lina was ready to serve the Regalia with her newfound powers over the reveling chaos that was Lord Susanoo, setting her apart from the numerous other Summoners within Eorzea and beyond.
Tumblr media
“I am Lilina Lina, and in revelry do I rejoice!”
(Credit and thanks to @minstrels-ink​ and @tsubi-uru​ for the pictures!)
13 notes · View notes
hamlets-ghost-zaddy · 5 years
Text
queen of peace
Part 5/10
Shifty Powers x Reader
Tumblr media
“Mother, are you absolutely sure you don’t want to come? Margaret said I had to convince—” you call, taking the stairs two at a time, if only to hear the green chiffon of your skirt fluttering at your ankles. Yet, when your Mary-Janes plunk onto the thin carpet at the bottom, girlish delight is forgotten in favor of your eyes bulging, air stoppering in your throat, and stuttering out: “M-Mother, is that—? Is that the—?”
She perches on her armchair in the sitting room—the tapestry upholstery faded; it’s been in the cottage since Mother’s gran lived here as a little girl—her beaming smile shifting from the tea kettle in her hands to you. It’s the tea kettle; the robin-blue heavy ceramic one that’s been shown in the cookware shop’s place of pride since July. “Look, dear, I decided to spring for it,” she says, her voice floating with lightness, as if reveling in an indulgence long overdue. “Isn’t it the loveliest thing? And, you did mention you wanted something practical for your birthday in a few weeks, so I thought this could be for Christmas and your birthday.”
Yet, the justification doesn’t reach your ears; you’re deaf to the chattering praise of the kettle as Mother holds it to the weak electric light of the overhead chandelier, inviting you to admire it from all angles. Your imagination conjures the scene of Mother creeping to the stronghold box secreted in the workshop, illustrating how she took out your carefully stacked pound notes—freshly and hard-earned from the nurses’ orders—halving it and scurrying off to the cookware shop. Is this your fault for not confiding in her how desperately the money’s needed? She surely knows some it, but you’re so careful to hide the letters from the bank, the reminders on the loans and interest, and she had been so thinly pale and grayly sick in the years since London; you couldn’t risk a relapse. Where would you be, who would you have, if something happened to her?
The thought sobers you, allows you to plaster a smile on, and you offer: “It’s really quite lovely, Mother. It’s just the right dash of color our little house needs.” Admittedly, the old cottage with its threadbare carpets and worn upholstery would take much more ‘dashing’ than a blue tea kettle could offer, but your seeming-approval cheers Mother noticeably. “Are you coming to the Christmas Eve party? Margaret asked me that I positively badger you about it.”
Her smile shrinks marginally. “Oh, I don’t know, darling. Why don’t you go ahead, and I may catch you up in an hour or so?” Carefully, you keep a frown from pulling at your lips at Mother’s blatant lie. She hurries on: “Don’t forget your Christmas packages; I put the tin of cakes on top.” She gestures to a modest pile of boxes on the ottoman, an old tin stuffed with almond-butter cakes, dusted with real powdered sugar, crowning it. That white sugar, an absolute necessity in your family’s sacred holiday almond cake recipe, had cost you dearly. Smilingly, you allow her to load your arms up with packages, sacks, and tins, and shoo you out the door and into the early chill of nightfall. She sends you trudging through the flurries of snow and toward the bright bauble of Margaret’s house.
You try not to brood as you walk, your surly thoughts keeping the nip of the air at bay, but your thoughts revolve continuously back to the neat stack of bank letters folded into your jewelry box and how you’d politely word a begging request to extend the payment deadline—again.
Margaret shepherds you into the party with wide-flung arms, a bright grin that stretches her immaculately painted cherry-red lips, and any of her stress or harried anxiety from two days prior—during decorating—has entirely evaporated. She coos over your Christmas dress (the same one as last year, and the year before that, though she’s kind enough not to notice) as she carves a path through the seemingly uninterrupted mass of humanity cluttering her home. American sergeants laugh at the vicar’s jokes—he’s putting in a brief appearance before scampering off to other party invitations—Mrs. Pinchent, your dowdy widow-neighbor, giggles and flirts with a taciturn American colonel; Evie Lowell holds captive a slew of local and soldier boys alike; Mr. Jamison, the busybody bartender at one of Aldbourne’s two feuding pubs, hoots uproariously with a cluster of American captains. Couples attempt to dance in a narrow patch of carpet provided, youngsters dart between legs, and the elderly have claimed chairs to keep an amused eye on it all.
Whatever darkness heavying your mood, you leave behind, outside in the cold of the garden.
“We’re not doing any kind of formal gift-exchange,” Margaret informs. “The idea is you put your packages under the tree, and then you’re supposed to check every once and a while if someone has left something for you. It stretches out the fun and anticipation of the gift-giving!”
“Oh,” you mutter, glancing down at your packages, eyes catching on the card attached to the one at the very top: in your neatest cursive, you wrote, ‘To my dear friend, Shifty.’ Disappointment trickles into your chest; you’d never admit it, but you wanted to watch him open it. You’re not sure why it’s important to you all of a sudden.
After Margaret helps deposit your packages under the tree, merrily ripping into hers and exclaiming over the cape you knitted for her—a lovely, pure white lamb’s wool that you matched to her white muff—she whisks the almond-butter cakes away to put on the serving table. You watch her dissolve into the crowd, fidgeting with your velveteen sleeves as your eyes flick over the profiles and backs of the party-attendants nearest you. You don’t particularly want to mingle with Mrs. Pinchent or Mr. Jamison, but they seem to be your only options at the—
“Look!” exclaims George Luz—you instantly recognize that brash American accent of his, constantly pitched as if auditioning to announce for the Royal Ascot—and you find a delicately carved wooden squirrel under your nose. “He did carve me a squirrel!”
“Huh,” is all you can remark, gently plucking the figurine from George’s hands, inspecting its deep, chestnut color, honeyed and rich. The little squirrel even clutches a nut, its head cocked in inquiry at the viewer and fluffed tail held in trepidation. You manage: “It’s lovely, George.”
Accepting the squirrel back, George glances over it, too, trying mightily not to seem too pleased. “It’s alright; Shift’s talented, that’s for sure. The kid’s got, I don’t know, depth or something.”
As innocuously as possible, you ask, “Did Shifty give it to you just now?”
“Nah,” George replies, pocketing the squirrel. “He gave out all his gifts back at the barracks; said he didn’t want to deal with carrying anything here.” The drop of disappointment through your chest from before builds into a free-fall. “I swear, he’s got some imagination, too; he gave Skip an otter but the funny things, I kind of see why Skip’s an otter, you know?”
Before you can think of a response, before you can sort the slowly dawning horror creeping over you that you gave Shifty a gift, and he most assuredly didn’t give you one, Skip appears at your elbow. He shouts to be heard over the party’s rabble: “You’re here! Good, I’ve had to use every stalling tactic I can think of to get the guys to hold off on charades! Come on, you’re on our team; our secret weapon.”
Your eyebrows jump. He remembered; he was being genuine about the team, you think, befuddled.
Skip’s hand wraps around your elbow and he’s towing you—George Luz trails, snorting over the paper crown balanced precariously on Skip’s head, most likely from the Christmas poppers Margaret adores so. You’re helpless to being dragged away from the tree, and any hope you have of swiping up your gift to Shifty before he can see it; before he can open it and face the unmistakable truth that you’re horribly enamored with him. Before your friendship turns brittle and crumbles because of your own self-sabotaging.
First the kiss, now this. It’s like you don’t want to be happy.
(This, in tandem with the damnable kettle, you decide, might be warrant enough to label this the Worst Christmas Yet.)
You had your doubts, given that Skip seems someone inclined to comedic dramatics, but he hadn’t been hyperbolic when he proclaimed he, Penkala, and Malarkey were truly pitiable at charades. “What on Earth are you doing?” Malarkey bursts, exasperated, as Penkala skips around the cleared charades floor, flapping his arms and occasionally squawking. All the charades were—allegedly—Christmas themed, though you pulled a Clark Gable card your last round, and you’re fairly sure Clark Gable has nothing to do with the reason for the season.
“Chicken?” Skip guesses, Penkala shaking his head and squawking again, as if this time, it’d trigger the correct answer.
“A deranged goose?” you offer, Malarkey and Skip snorting, but Penkala waves his hands emphatically, pointing at you. “Oh, a goose?” you guess, when Penkala twiddles his fingers, meaning its part of the phrase. “Um, Christmas goose? Roasted goose? Goose and—”
“Time!” Margaret trumpets, popping to her feet and nearly upsetting the holly and garland crown she wears. Allen Vest had made a whole show of crowning her after the first round of charades ended in her team winning, declaring ‘peace unto the queen of Christmas.’ “How many points did they get, George?”
George had made scorekeeper when it became obvious he couldn’t keep his great trap shut, guessing for teams other than his own and giving out freebie points. “Uh, seven! Wow, Penkala, way to go! You didn’t embarrass yourself!”
Penkala takes a bow as all teams—four teams of four, all composed of Easy Company men, the company all your American friends (because you do suppose they’re your friends) belong to—clap and cheer, Malarkey and Skip whooping. As Penkala flops onto the couch next to you, Skip leans over to whisper in your ear: “Looks like you’re not the star player anymore.” He winks, curling grin mirroring yours, and you shake your head back. Without you, the team would have negative points, if any: you earned eleven points when it was your turn, and had guessed nearly all of the words when the boys were acting.
“Good,” you shoot back. “I was getting tired of carrying this team.”
Skip’s eyebrows quirk and he tilts his chin back to roar his laughter to the ceiling.
Basking in the glow of your joke, you swing your eyes away and around the room, your smile growing stale and then shriveling. In the crowd amassed as spectators to the game, you pick him out easily—looking older, more tired somehow, in his dress browns, despite the cheerful blue and white scarf wrapped, once, twice, four times around his neck, the scarf you knitted him—the sensible gray cap, your other gift, peeking from his trouser pocket. Yet, after the initial yank in your stomach, a yank that makes you feel you’ve been thrown into open space, you forget the gifts for his expression. An expression you don’t comprehend, can’t ascribe any logical reason to, because he’s envious? There’s melancholy written in his frown, confusion in the pinch of his brows as if baffled by his own reaction, but yet, despite himself, he looks envious.
His eyes find yours, across the lounge of jostling elbows and knocking knees, and your chest aches, your lips part, words building in your throat until you’re rendered completely mute. What is it about Shifty, about how he’s looking at you now, that fills you until you’re sure you’ll burst? that drains you until you’ll pop out of existence? that makes you burn and chilled, made significant and trivial—feeling every new contradiction on each inhale and exhale?
“C’mon, girlie, get on up there,” Skip says, close to your ear, nudging your shoulder, urging you from the coach—Shifty had made you forget where you were, what you were doing, and you blink at Skip to chase away the haze in your head—and to the cleared performance patch of the lounge’s carpet. “We’re five points away from winning! You’ve got to go bag this one for us!”
Malarkey, having taken Margaret’s invitation to ‘help yourself!’ to an extreme when it came to the ale keg, leans around Penkaka to plant a good-luck kiss on your cheek. “We’re counting on you, sweetheart; you can do it!”
When you collect yourself, when you dare to steal another glance into the crowd, Shifty has moved, is moving through the archway and out of the lounge, You crane to keep him in your sights. But, there are too many bodies, too many voices clogging the air and rooting you on. He melts into the party as Margaret calls: “Two minutes starts now—go!”
Bing Crosby’s new Christmas record, and the drunken rhapsodizing accompanying it, floats out of the kitchen when you slip into the back garden. Easing the door shut behind you and clutching your wool coat tightly to your body, your eyes sweep across the snow, winking and reflecting the lights on in the house. Your breath clouds and you plop down on the stoop next to Shifty—it took you nearly twenty minutes to locate him after the charades game devolved into George and Malarkey leading everyone in carols, another five minutes to track down your coat.
He blinks at you, the redness in his nose and cheeks—luminescent in the light reflected off the snow, a wash of the lamp’s yellow and the winking green and red of the fairy lights—softening him, easing that earlier maturity and tiredness you noticed in the lounge. “Oh, hey,” he offers, adjusting his scarf self-consciously and angling to square his shoulders toward you. “I like your, huh, crown.”
“Oh, thank you,” gusts from your lips as you touch your fingertips the holly and garland crown you wear, bestowed upon you by Margaret after your team won the final round of charades. “I was proclaimed the queen of peace, or something like that.”
Shifty nods, eyes skating over your cheekbones, along your nose, to your lips, and back to the crown. The intensity, the thoughts darkening his expression, remind you of looking into a fishing hole, falsely shallow, secreting hidden pockets inside its murky depth; it makes you fidget with contradictions again, makes your chest expand until it aches and your shoulders hunch, as if collapsing on yourself. You reach to pull the crown off, but he leans forward, hands on yours, stopping you. “No, please; keep it on. It…it suits you.”
Biting your lip and lowering your eyes as your fingers drop to twist in your lap—it suddenly seems far too much to look at him—you manage: “Oh, well, okay.” Pause, and you fuss at the bare fur still on your threadbare coat’s cuffs, trying to marshal your senses and recall why you wanted to come out here, what you had formulated saying. “Um, I was going to, um, make sure you’re all right. I noticed you slipped out and I wanted to make sure …”
You allow your words to trail off.
Shifty hums after a moment, leaning closer to you. You’re not sure if its conscious or not. “That’s kind of you to be worried; you’re a good friend,” he offers, his accent coaxing the words from him. “I…” he pauses and you can feel him choosing his next words: “It’s great seeing everyone so happy and enjoying themselves, but I keep thinking about my family playing charades and other games at home right now. It’s made me sad, I suppose. But…but, it’s more than that…I can’t help but think …”
You’re not sure when you thread his fingers with yours, but you offer a gentle squeeze when he stutters to a stop. You tilt your face so you can monitor his expression through your eyelashes and still hide just how desperately you want him to know you’re there for him; how much you desperately wish you could articulate how you care.
He tries again: “I can’t help thinking about what’s to come. The…the war. Who will get hurt, or, or not come back … who’s celebrating their last Christmas right now.” At your backs, a wave of laughter floats from inside, muffled by the brick walls and door, but you feel its weight slamming into your ears, pressing on your shoulders. You know Shifty does, too: you track how he winces. “I know I ought to enjoy the happiness while I can, but what if…what if it’s my—?”
“Don’t you dare finish that sentence, Darrell Powers,” falls from your mouth before you’re aware you spoke, gripping his hands urgently and entirely forgetting your carefully designed cover. You hold his eyes, hoping he sees the ferocity of your firm resolve, hoping he understands how greatly you feel and believe every syllable you say. “Don’t you dare talk or think like that. You’re going to come back, you have to—” because I’d be lost without your eyes in my life; your eyes looking at me like that, you think, but bite back. You can’t say it; you won’t. You can’t watch his face pale and widen in horror. Not again.
Yet his worry remains unchanged and you frown, placing a gentle hand on his cheek, trying again: “Shifty, I know you’re staring down the unknown and you’re scared, but it’s okay to be scared. I’d be worried if you weren’t scared honestly.” That earns you a faintly-cracked grin. “But this is one Christmas of many, many more to come, and whenever you want me to tell you that, let me know. I’ll keep saying it until I’m blue in the face, okay?”
His grin turns wobbly, his eyes glassy as you speak, and his nod is uncertain.
Huffing, you tease, “I’m going to need something more than a nod, Shift.”
“Sorry, ma’am,” he manages thickly. He sniffles, takes a choppy breath, and tries to smile. And, God, you want to kiss him; you want to fold yourself into him and be safe from the war, fate, and all that’s to come; you want to cry and laugh and feel multitudes with him. You want, you want, you want—but when has it ever mattered what I wanted?
Instead, you content yourself to wrapping your arm tightly around him, letting him tuck you under his chin, letting his scent of bonfires, boot polish, and summer rain wash over you, letting his arms brace firmly across your back. This is the most you can have from him, you know; this is the most you’d ever ask from him, because what does it matter what you want if you can at least be there for him. Hold him and whisper a thousand assurances, allowing yourself to pretend for a fleeting instant that he really is yours.
tag list: @gottapenny, @wexhappyxfew, @maiden-of-gondor, @mayhem24-7forever, @medievalfangirl
48 notes · View notes
cowgirlangel95 · 7 years
Text
Them Ol’ Songs
Tumblr media
1 | 2 | 3 
 Chapter 4: She’s Every Woman
Lucas sat in the learning center, staring absentmindedly out the window.  The clouds were covering most of the stars that night, and the rest were masked by the reflection of the lights on the window.  But that didn’t matter to him right now – Lucas wasn’t focusing on the stars like he usually would, especially at this time of night when most of the students were finally making their way back to their dorms.  Or rather they were trying to, anyway as they stumbled along the sidewalk.
He pinched the bridge of his nose as he let out a frustrated breath. Sleep had eluded him ever since that night, and it was starting to show.  Lucas still couldn’t figure out what went wrong between him and Riley that night three weeks ago over break, and what she meant.  They had always gotten through hardships together.  Why was there a sudden change in heart?  A change so sudden he was still busy picking up the pieces of his shattered heart.
“Excuse me?” a voice asked, breaking the silence.
Lucas jumped at the soft voice, not expecting anyone to be in this area – except maybe the cleaning crew.  He looked up to see a petite woman about his age with shoulder length caramel hair, sun kissed skin, and deep brown eyes.  She looked vaguely familiar, but he couldn’t quite place where he may have seen her before.  She was wearing a forest green cardigan with a white tee underneath and jeans.  “Hi.  Can – can I help you with something”
“You’re Lucas, right?” she asked.
He nodded.  “Yeah? And you would be?”
She shook her head a little bit and sat down in the armchair beside his, curling her legs underneath her as she leaned on the arm.  “I’m sorry.  I’m Lauren – we’re in the same Calculus class.  You’ve seemed rather tired or preoccupied the past couple weeks. When I saw you here I figured I’d ask if you’re okay.  I have been meaning to ask, but you duck out of class really quickly.”
Calculus! his mind clicked.  She sat in the same row as him, over towards the window while he opted for the seat closest to the door.  “Sorry about that,” Lucas began, “I have practice on the other side of campus right after calc, so I have to get there as fast.”
“It’s okay.  So… are you okay?”
Lucas let out another sigh and ran a hand through his hair. “No… no I’m not.  Something rather unexpected happened about two weeks ago, and I’m not handling it too well.”
Lauren placed her hand on Lucas’, sending a chill up his spine. “I’m sorry.  Is there anything I can do?  Do you want to talk about it?”
“Thanks, but no.  I’d rather just try to get my mind off of it for now.” he smiled, albeit very sadly.
She nodded gently as she leaned back in her chair.  “Okay, but if you ever want to talk about it, I’m here to lend an ear.”
Lucas lightly bobbed his head from side to side.  “Maybe someday.”
The axe sang through the air as it easily slid through the chunk of wood and drove itself into the stump the wood was sitting on.  Lucas yanked the axe out of the tree stump and placed another piece of wood onto the stump.  He swung it again, sending the two halves flying in opposite directions and onto the ground.
He never did tell her.
Lucas wasn’t sure why he never told her, especially since they told each other most everything. However, when it came to Riley, Lucas never did say much – all Lauren knew was that they dated up until their freshman year of college.  Maybe it was because it was too painful, or maybe because this lovely mess he was in now would have happened sooner.  Lucas sighed as he pushed the memory from his mind and reached for his water bottle. Despite it being a bit cold for Texas in December, he was wearing only a white v neck tee and jeans – his red flannel shirt long forgotten about as it hung on a branch nearby.  He wiped the sweat that had formed on his brow before grabbing his axe again and starting back to work.
He was so frustrated with himself.  What in the world was I thinking?!  How did I get myself into this mess?  How could I let myself get into this situation?
If anything, not telling Lauren about what happened with Riley should have been a huge sign that maybe she wasn’t the one.  They should be able to tell each other everything over time, even if it was still rather raw.  But he thought for sure she was…
Lauren and Lucas sat by the fireplace in the student center after drinking a couple of coffees over the course of a few hours.  Most of the students were out celebrating the homecoming win, while the two were content on cuddling on the couch.
After a while, Lauren turned her head to make eye contact with Lucas. “I love you, you know that?” she asked him with a beautiful smile painted across her face.
“I love you, too,” he said as he wrapped her up in his arms and kissed the top of her head.  As he felt her melt into his side, Lucas closed his eyes and smiled.  He realized that he could easily stay in this moment forever.  It just felt right… like it was meant to be.  In that moment, Lucas realized that Lauren was –
He drove the axe right through another wood chunk as if it were butter, causing the blade to become lodged in the stump a few inches.  Lucas let out a frustrated groan as he tried to remove the axe, but it refused to budge. No matter how hard he tried it remained right where it was as if he wasn’t worthy to lift it.
“So, how ya doin’, Luke? You okay?” Pappy Joe asked.
Lucas turned to see his grandfather leaning up against one of the trees with his arms crossed and a certain look plastered across his face.  He knew that look.  Pappy Joe knew the answer, but he was setting him up.  It didn’t help that Lucas had a habit of retreating to his grandfather’s farm whenever he was rather frustrated or deeply confused.  There was something about the farm that helped Lucas clear his head… or rather, there was someone that helped him.  And that someone was looking him dead in the eye.
He shook his head as he decided to abandon the axe and wiped the sweat off of his brow, casting his gaze towards the ground.  “No… no, I’m not,” he replied as the other reason for his trip floated to the front of his mind.
Riley and Lucas were sitting on top of the roof of the Matthews’ apartment after Riley’s graduation party.  The last couple of people had just left a few moments ago, and Riley was in desperate need of some alone time.  Luckily for Lucas, she insisted that he should stay.
The two of them gazed up at the sky.  Dark clouds were rolling in, but there were still a few stars visible – enough to make Riley happy.
“Can you believe that we’ve graduated high school already?  It just doesn’t seem real,” Riley commented, her gaze still fixated on the stars.
Lucas shifted his gaze from the sky to his girlfriend sitting next to him.  A soft smile graced his lips as he stared in amazement at Riley’s awe.  He wrapped his arm around her shoulders and brought her closer to him before saying, “Yeah, it’s hard to believe.  Wasn’t it just yesterday we met on the subway?”
Even though Riley was fond of that memory, she found herself blushing.  “Time needs to slow down.”
Lucas nodded as Riley met his gaze.  “That it does,” he agreed as his eyes shifted down to her lips, and then back up to her.  As the two started to lean in towards each other, a drop of rain hit Riley right on the nose.  The two giggled as Lucas reached up and brushed it away.  
Quicker than they could blink, the clouds opened up and poured down on the two.  Sheets of rain were coming down on them as Lucas stood up.  “C’mon, we need to head inside before we get completely soaked!”
With a mischievous grin, Riley stood up, but didn’t make her way towards the door.  Instead, she darted towards the center of the floor and started twirling around.
“What are you doing?” Lucas called out to her.
Riley ran over to him, grabbed ahold of his hands, and dragged him back to where she was.  “Having fun! Come on, dance with me!  Live in the moment!”
Lucas chuckled as he twirled Riley around in circles.  He spun her back into his arms, and the two started to sway side to side.  As they moved across the floor, Riley melted into him and let out a pleasant sigh.  After a few more moments, the two looked deep into each other’s eyes.  Lucas leaned down and placed a gentle kiss on her lips, allowing the warmth from hers to seep into his heart.
Pappy Joe walked up to him, bringing Lucas back to reality and making him turn to face him. “Y’know, the last time I saw you like this was about three years ago or so.  Any correlation between the two?”
Again, Lucas sighed as he crossed his arms.  “Sort of…” he trailed.
“Boy, you’re going to have to give me more than that.”
“Sorry, Pappy Joe… they’re kind of related so to speak.  Lauren broke up with me… right before I was going to propose to her,” he started to explain as he took of his gloves and threw them onto the ground in frustration. “The reason why was because I ran into Riley on the subway on my way to meet Lauren, and I realized I still had all these feelings for her.  I tried to put them aside because I thought for sure Lauren was the one for me, but I couldn’t; I kept thinking about her when I should have been focusing on Lauren. Heck, I completely forgot about Lauren when Riley literally fell into my lap again.  According to Lauren, I was looking at Riley in a way that I’ve never looked at her… I’ve never felt so bad in my life,” he finished as a dull ache made its way through his heart.
Pappy Joe nodded as he crossed his arms.  “So, what are you going to do?”
Lucas threw up his hands, “I have no idea.  It felt so right with Lauren and I love her, but then Riley walked back into my life… a-and I’m just so confused!”
“Well, why did you allow Lauren to leave?”
Lucas raised a confused eyebrow.  “What do you mean?”
“What did you do when Lauren broke up with you?  You told me at one point you thought she was the one.  How did you fight for her?”
He thought back to that day two months ago and lowered his gaze.  “I didn’t… not really.”
“So, why did you allow Lauren to leave?”
“Because Riley… she’s something else.  Riley is anything but typical; she's unpredictable.  Even at her worst she’s not that bad.  She's as real as real can be.  Riley’s every lover that I've ever had, and she's every lover that I've never had. When I was with her again, it made perfect sense…” he trailed.
Pappy Joe shook his head as he placed a comforting hand on Lucas’ shoulder.  “Luke, you know I’ve never really believed in the ‘soulmate’ idea. I’ve always thought there’s more than one person out there that you could end up marrying.  But in this instance I feel that I may be wrong.  If you had met Lauren before you knew Riley, I think she may have been the one for you. However, Riley is something special. It’s clear that you still care for her, boy.  When it came to Lauren, you were confused.  But Riley… that was clear – despite all that’s happened.  You need to talk this through with her and sort out what happened that night… soon.”
A/N: I hope this was satisfying to those that were worried!  I had most of this chapter in mind when I made the decision to lengthen the story :)  
This song was based on Garth Brooks’ song She’s Every Woman.  Until next time!
14 notes · View notes
Note
River/Twelve "Good morning"-kiss PLEASE
She goes to bed alone.
With a brief touch to his elbow and tentative kiss to his cheek, she bids him goodnight and slips down the hall before he can think, before the tingling in his skin has faded and his breath has returned enough to say why or wait or can I—?
She's gone, and the console room is quiet except for the TARDIS’ disapproving hum, and he glares at the time rotor, easing her into the vortex, breaks off.
“Your daughter,” he grumbles, and swears she sniffs indignantly.
Your wife.
He cringes, hearts warring between the rejection heavy in his chest—she doesn't want you, you're not the same—and the look on her face hours earlier, the cracks in her voice, the vehemence, the Doctor does not, and has never, loved me.
He thinks of her cheek kisses and barely-there touched and feels a surge of self-loathing so acute he has to brace his hands on the console, let the cool metal take some of his weight and breathe and squeeze his eyes shut, try to think of anything but the pain in her eyes, stupid enough or sentimental enough and certainly not in love enough—
He was there, he reminds himself. He was there, at that perfect moment, with the perfect words and soft eyes full of love and he hopes it was a balm but he knows how easily he might not have been. Knows how it wasn't intentional. Knows she sought him out, not the other way around. Not to tell her he died. Not to tell her he lived. Not to simply say hello, wife, or goodbye, wife. Knows he had no intention of ever bringing her here. Knows he'd have let her go to her death without this memory, timelines be damned because he couldn't handle it. Because he was a coward.
Is a coward.
The TARDIS hums soothingly, her presence a warm wool blanket over his shoulders.
“What have I done?”
She has no answer.
Or maybe she does, maybe it's the niggling in the back of his head, maybe it's the shifting under his feet, maybe it's her or all him or a combination of both because he's suddenly determined, jaw set and shoulders back and fuck this echoing between his ears.
Fuck all of this.
Fuck cowardly and fuck cruel and fuck pretending.
Fuck shielding himself from pain, causing her pain, hiding the damage.
Fuck trying to be anything but horribly, desperately in love with her.
He lands the TARDIS a few miles away from the restaurant, double checks the monitors, and slips outside into the lobby of a tall, bright building.
A short, green man with a kind smile meets him at the desk.
“Ah, Doctor, right on time.”
He nods, makes a mental note to set up an appointment later, for before, and smiles.
“What have you got?”
The man—Thomas, he’ll learn later—opens a black leather folder. “I've narrowed it down to about ten properties,” he says, “all with your specifications.” He looks up and grins. “Certainly helps when money’s no object.”
The Doctor shrugs. “Anything for the wife.”
Thomas hums in agreement. “Always better that way,” he says, “Shall we get started?”
They visit each house, walk through empty foyers and back gardens. He checks every kitchen—breakfasts in bed are going to be a staple, and he's not cooking on an electric stove so help him—scrutinizes every master bedroom and every office.
“Her bookshelf won't fit.”
“She won't like the cut.”
“It's too big.”
“It's too small.”
“Not enough light.”
Thomas nods and makes notes and seems completely unperturbed by the Doctor’s growing frustration, calmly reassuring him this is just the first round and if there's nothing he likes not to worry. But he does worry, about River waking up and finding him gone, about the size of the swimming pool, about the neighbors, about the sun inching its way below the horizon.
They're eight houses down when they pull up to a stone cottage, an ivy-like plant that's blue in color winding itself around the porch railing and up the sides. It's two stories, set back a ways from the street, far enough from the neighbors to be secluded without being horribly antisocial. If he had it his way, they'd live in the country without a soul in sight but this is about what River wants, what she needs, and he can tell before he sets foot on the gravel path that it's right.
“This one,” he says.
Thomas doesn't even look surprised. “Want to see the inside?”
He nods, and takes the tour, more convinced with every step. There's a studio for him and an office for her and a master bedroom with an amazing shower and jet tub and walk in closet and there's a fancy kitchen and spacious living room and “the last couple used this room as a nursery” and the Doctor feels his gut clench with both desire and fear and he’s signing the forms and skipping forward and transferring funds and skipping forward and getting the keys and skipping forward and when he parks the TARDIS in the empty bedroom less than six hours have passed.
He knows she won't sleep much longer.
He hesitates, but she's in their room in their bed, curled into a ball facing the middle and he smiles. Toes off his shoes. He sheds his blazer and belt and slips under what covers she hasn't stolen, propped up a bit by all the pillows and she must feel his presence or the bed dip because it's only a moment before she’s closer, face buried in the crook of his shoulder and her arm slung over his chest.
“What times’it?” she mumbles.
He cards his fingers through her hair, so gently. “Still early. Go back to sleep.”
She mutters something else and falls silent, breathing even, weight heavy against him and he exhales slowly. Inhales, breathes her in.
He isn't sure when he falls asleep, but he wakes up to a pressure against his ribs and tickling against his ankle and when his eyes open, realizes she’s draped herself over him more intensely, leg wedged between his, cold toes under the leg of his trousers.
She blinks up at him slowly, smile sleepy. “‘Morning.”
He tucks a strand of hair behind her ear. “Morning.”
His voice is thick and gravelly where hers is low and soft but she's looking at him like he's the perfect gift, and he doesn't know how he got so lucky, so loved, and then her lips are over his, just a gentle brush, and his eyes sting.
She pulls back and he stares and her eyes open and she seems to register finally, seems to see him because she stiffens, cheeks flushing and expression twisted and he ignores the knife in his gut when she tries to disentangle herself, murmuring, oh and I'm sorry, and I didn't mean to—
He’s prepared this time, loops an arm around her waist and hauls her back, catches her wrist and brings their hands to settle on his chest.
“And where do you think you're going?”
River stumbles in her surprise, half sentences he finds adorable at the same time they break his hearts, her insecurity like a brand because she isn't awake enough to disguise it, to hide.
“I didn't know if you—”
He presses his forehead to hers. “Don't be stupid.”
It's hardy the declaration he'd intended, wanted, to give, but it seems to settle her a little, and she slowly sinks back into him.
“You didn't follow me.”
He kicks himself. “I didn't know you wanted me to.”
She snorts, not looking at him, but her fingers draw a pattern over his hand. “Now who’s stupid.”
“I'm always stupid,” he says, “What's your excuse?”
She shrugs, doesn't answer, and he crooks a finger under her chin to lift her face to his but there aren't any words, nothing that makes it past the knot in his throat so he kisses her again, purposeful, firm, but with his thumb tucked behind her ear and a tenderness he hopes she can feel.
River sighs, parts her lips just barely but it's all the permission he needs, hand sliding into her hair, weight shifting in his side so he can kiss her harder, longer, tongue in her mouth and they both have morning breath and fuzzy teeth and he doesn't give a damn because she's warm and soft and gripping his shoulder now, leg sliding over his hip to haul him closer and she's never done anything by halves and he loves it, loves her. He loves the way she still gasps when he nips at her lower lip and the way her body arches into his when he slides a hand to the base of her spine and the way her nails rake against his scalp and he loves and loves and loves until he can't breathe.
He can feel his hearts and her hearts pounding, feels her blanket-warmed skin under his palm, hand snuck up her shirt when he wasn't paying attention. River’s eyes are hooded and her lips red and she's smiling, smiling, smiling, almost giddy.
He frees a hand to frame her face, fingers half in her hair.
“I have a surprise for you.”
River tilts into his touch, body gravitating even  closer, and he delights in her shiver as his fingers draw patterns over her ribs.
“Later,” she says, part question, her hand curling around his shoulder. He frowns—what if she doesn't want it, like it, what if it's a mistake— “I want to stay here a while.”
Her tone is unsure, but he grins and it must be wide and stupid happy because she laughs softly, kisses him again and again and he knows the feeling, feels the years apart acutely and can't stand them, all that distance.
The movement is awkward and jerky and he thinks he elbows her in the stomach but then she's under him, his weight pressing her into the mattress but she doesn't complain, wraps both legs around his hips, arms wrapped around his neck, pulling him closer and closer where paper couldn't fit between them. He yanks the covers up over his shoulders, misjudges, covers them both and she laughs in the warm dark, open mouth against his.
“Twenty four years,” he breathes, of mornings and kisses and warm soft dark. “It’ll keep.”
83 notes · View notes
unstable-reality · 7 years
Text
The Space Between Echoes: Chapter Two
Chapter One [AO3]
Derso was not Derso. Or, at the very least, it was not the only Derso. Their planet, whose surface was now 1500 meters below them, was second from its star, and Cassian had informed Jyn that the fourth body in orbit, while equally habitable and equally named, was a subject of interest for traders of a certain type. There had been mention of smapp. Her memories of Coruscant were slight and vague, but there was enough there, enough to bring her parents to mind.
She’d asked him to stop talking. He had.
She liked that about him.
They sped over the jagged profile of a mountain range. The system’s star hung low, casting a glare, orange-white, on the window, offset by a toggleable overlay. Beneath them, the ground and flora had begun to glow. The day was newly born, on this part of the world. They flew into it.
Jyn leaned forward over the console, peered down. Derso 2 had a proper, multifaceted climate, with frozen poles and a band of warmth that hugged its equator, and they’d chosen to track a latitude along the northern edge of that band. Below, the red spines of the mountains fattened and then leveled, opening onto a wide, yellow-green valley, speckled by groves of trees and halved by an overfull river. Its rapids were white, frothy, violent; its banks were cluttered with clusters of tall grass.
She imagined a party, following the river. Ambushers, beyond the treeline, on either side. Flanking them. Drawing together, forming a loose semicircle, harrying them and pushing them up, up, away from the valley’s mouth, back toward where it narrowed, tapering into a solid wall of rock. Trapping them. Folding in on them.
There was a series of tones, short, close together, of medium pitch. Her heart jumped. Her head swung toward Cassian.
“Nothing to worry about,” he said. “Local fauna.” He flicked a series of switches, adjusting the transceiver. She frowned. She didn’t trust those things. She’d known them to be wrong, from time to time.
The valley continued to widen, pressing back against and siphoning ground off the mountains. Cassian dropped them down, angled the belly of the ship to match the slope of the land, hewed closer than Jyn would have dared. She knew how to fly. It was foolish not to learn. Those she’d known who’d never bothered had been locked in to the regions of their birth, at the mercy of environment and history and time, and she’d decided long ago not to be that brand of helpless. But he put her to shame. She supposed, thinking back, that he’d revealed a bit of his true skill when they’d escaped Jehda; she hadn’t really been in the frame of mind to pay attention at the time. Now, she couldn’t help but notice. And, given the givens, he was in a position to demonstrate.
She closed her eyes. It would be better, a damn lot better, if he wasn’t.
Her attention shifted back to the landscape. It had been nearly two hours since they’d broken atmosphere. Cassian must have thought this area looked promising, or he wouldn’t have closed in, but she still wondered how long it would take, whether they’d have to move to the next parallel. She wondered what she’d do if she got bored. She didn’t tend to make the best decisions, when she did. But, well, things had changed, hadn’t they?
“Wait.” They’d rounded the range and were headed toward the opposite side, passing over a section of rolling foothills. She pushed a foot against her seat, levered herself up.
“What?” Cassian cut the throttle. The craft shuddered. “Do you see something?”
“I think so.”
He banked to the right, turned them back. The sun glinted off the window.
“There,” she said. Amidst the foothills, abutting a ridgeline, there was a wide, U-shaped indentation, lightly forested. It was hard to tell from the sky, but it seemed as though there were extra points of egress along one arm of the U. “I like the looks of that.”
“Well, it’s a start, if nothing else.”
They touched down in the divot between a pair of hills, taxiing under a stand of trees. The transceiver hadn’t picked up any lifeforms since its last alert, but Cassian wasn’t keen to take any chances, and Jyn couldn’t blame him. They gave their weapons a once over. Adjusted their gear. He secured his blaster rifle, moved onto his pistol.
“Dammit.”
It refused to settle into its holster, slid halfway down, then resisted. He reached for the retention release. “It’s jammed.”
She watched him. Blinked. He could probably get it himself, but the angle was awkward, and depending on how bad the jam was, he might have to remove the belt altogether. Better to lend him an extra set of hands. Oh, bantha fodder, Jyn. “Here.” She took the blaster, handed it to him, slid her fingers into the holster, curled them toward the outside edge. Her knuckles pressed into his thigh; the flesh gave way, just a bit, just enough for her to detect it through the leather of his gear and the fabric of his trousers. He took a breath -- long, deep, slow. His exhale broke over her head. Curled around the tips of her ears. Her elbow nestled into his abdomen and shifted upward, and his muscles flexed in response, and her thoughts went spiraling.
Well. It wasn’t as if she’d expected, or wanted, anything less.
A click sounded, a bit of metal gave way, and the mechanism released. “All right.” She pulled her hand back. The rest of her stayed, right there, in his space, close enough that touch was incidental to movement, that the heat of his body was indistinguishable from the heat of her own. “Should be good to go.” She inclined her head and met his eyes. His face was no more than a decimeter from hers. “That ever happen before?”
“No. First time,” he said. His voice was low and full of gravel. “But it does mean I’ll have to get another, when I have the opportunity.” He glanced at the holster, leaning back just enough to retain his pistol. Looked down at her again. “Thank you.”
“Sure. Anytime.”
His fingertips grazed her hip, her waist. “Let’s go. We’re wasting time.”
A retort came to mind. She didn’t use it.
The air was cool, but not unpleasantly so -- 18° standard, light jacket weather. A nice change of pace, after spending half a week in the balmy environs of Yavin 4 and half a day in the tight climate control of a starship. The trees were strange, their bark a shade of orange she’d never seen before. Tiny white flowers bloomed within their leaves. There was a sweet smell, faint.
“You’d think someone would have settled here by now.”
“It’s not a bad place, no.” He walked a few paces behind her, his shoulders set in the wary relaxation of the ready and alert. “But it doesn’t have much in the way of resources -- not the sort that people are interested in, anyway. You’d never get rich off a planet like this.”
“Hasn’t stopped people from setting up on desert worlds.” Ice worlds, at least, had a tendency to be rich in ore and minerals. Like Fest. She tucked the thought away.
“Jyn, how much time have you spent in the outer rim?”
She opened her mouth, then closed it. He’d been the one to build and collate the file on her; he knew exactly how much time she’d spent out here. He was pretending, for her sake, that he didn’t. “This far out?” Even when she’d been at her most desperate, she’d never strayed too far beyond the mid. There’d always been a maximum distance, an invisible line that, for one reason or another, she’d never wanted to cross. “Not much.”
“It’s different out here. People need greater incentives to stay in a place.”
The terrain sloped sharply downward. They bent their knees, leaned back, palmed the earth. Their boots slid and kicked up small stones, clods of dirt. A worm-like creature, thick and grey and at least four or five decimeters long, shot up from the ground, screeched, wriggled away. Jyn gritted her teeth.
“But, with the Empire expanding, and with things the way they are…” He didn’t need to say it. “You may have a point. I wonder if…” He shook his head. “Well. It’ll be good enough for now.”
“Cassian.” They were on level ground again. She paused, waited for him to come up beside her. His arm pressed against hers. “Don’t get cagey with me. What is it?”
He sighed. “I’m wondering if ‘hospitable’ might actually be a detractor.”
The next couple of hours were spent investigating the space, a basin scooped out of the side of the mountains. Assisted by handhelds and electrobinoculars, they were able to estimate its width at just shy of a kilometer. Toward its mouth, it narrowed, giving Jyn the impression of swimming within the center of a glass of wine. And along the edges of the bowl, there was more than one exit; all but one presented opportunities for ambush and misdirection.
They could defend themselves here. The terrain favored them, gave them ground cover, provided relief valves should siege tactics come into play. And, what was more, the ridgeline bowed inward. Stretching back, far enough that their lights couldn’t reach its end, was a cave whose opening extended nearly the entire width of the bottom of the U.
Cassian shook his head. “Jyn.” He stepped closer to her. His features were warm, relaxed. He smiled. “I think this is it. Nicely done.”
She smiled back, pushed her tongue between her teeth. It was astounding, how much she had come to value his opinion.
“We’ll have to do some exploration in there before we send our report, of course, but...this is better than I could have hoped for.”
“Well, that’s saying something, considering how you feel about hope.”
He huffed. “And, what, you think you’re not the same way?”
She hadn’t been, when she’d first met him, when she’d first come into contact with the Alliance. If anything, she’d been the exact opposite, clinging to life largely out of habit. A lot had happened since then.
Kyber burned against her skin. I am one with the Force and the Force is with me.
Listlessness.
The change was barely detectable. Instincts honed over the course of long, hard, years vibrated, raised her hackles, made her shoulders roll back. She paused, widened her stance. Soil scraped beneath treads.
A susurration. Metallic whirring. Crescendo, air and current, the high-pitched wail of a blaster charge. He was between her and it. He was between her and it, and he was turning, but she had heard it first, seen it first, and she was slamming into him before she’d fully registered what she was doing.
It took three shots. They were on the ground, half of her on top of half of him, their legs intermingled in a way that might have made her stomach twist had adrenaline not been focusing her attention on something else altogether. Fifteen meters distant, black, an oval beset by protrusions, hemorrhaging sparks, there lay an Imperial probe. Cassian tilted back his head and took it in.
“Well, shit.”
Jyn was used to moving.
No fewer than three times, when she’d been too young to have a choice or a say, her parents had shuttled her from one planet to another. She had, out of necessity, let go of friends. She had, out of necessity, cultivated an expansive imagination, a refuge from her isolation. She had, out of necessity, thrown herself into her relationship with her mother and father. Made them her world.
Because they were her world. Because there was nothing and no one else.
And then they were gone.
Saw and his people were the next best thing, and, as is the wont of rebels, they were always on the march. There were periods, sometimes lengthy, of location-specific activity. But for the most part, they were a roving band, constantly searching and shifting, heading either where it seemed they were most needed or where it seemed they’d best be able to take cover. She’d latched onto the cadre because she’d been in desperate need of friends. She’d latched onto Saw because she’d been in desperate need of a parent.
And then, well, they were gone, too. After that, “rootlessness” had become a byword for “survival.” Transit was required for successful living in the margins of society. She had no true name, no true district, no true region, no true world. Suited her just fine.
So, she didn’t quite know how to take her disappointment.
They hadn’t selected Derso 2, not officially. Even if they had, it wasn’t foregone that Yavin 4 would need to be evacuated. And, beyond that, she didn’t get attached to places. It didn’t make any sense to.
Cassian had mentioned “greater incentives.” Ah, well.
She wondered if places might not always be places.
“Even if we don’t get anything from this, we need to resupply.” His voice was flat when he said it.
He was disappointed, too. That helped.
Zorii Outpost lay along the Hydian Way on the boundary of the mid and outer rims, a go-between that facilitated trade between the colonies and those worlds that lay beyond. Although Cassian had been chosen for the mission in part due to his network, he’d wanted to avoid tapping into it if possible; Rebellion-centric conversations always carried a risk with them. But one of the worlds on the list had been compromised, and that suggested they needed to update their intel. He’d jumped them to a different, irrelevant sector, sent a coded message. Gotten a response.
They moved down a broad road, past booths and storefronts and small, open-air restaurants, each of which seemed tailored to a specific culinary niche. It was better maintained than most such places she’d been to; the buildings sported the usual wear-and-tear, but they still looked as though they’d been cared for, and the street itself was relatively clean. She wasn’t sure whether that was a good or bad sign.
Cassian’s hand slid over her shoulder, onto her upper back. He bowed his head toward hers. “That’s our guy -- Telara.” His breath was hot on her cheek and neck. She followed his gaze, and took in a man of middle years, balding, leaning against a post with his arms folded and one foot crossed over the other. Even at a distance, she could make out the exaggerated rise and fall of his chest, could tell he was breathing hard.
“He looks…” she whispered. The man’s eyes darted. “...obvious.”
“Yes. He does.” Cassian took a step and turned to face her. His body wrapped around hers, the inside of his thigh pressing against her hip, his arm curling around her side, tugging her close. “We’ll have to be careful, I think. See what we can do to make this quick.” He glanced over his shoulder, back at Telara. “I may need to reach out to someone else instead.”
He lingered, just a moment, before pulling away. They fell into step beside one another. She struggled to concentrate.
“Oh, thank goodness.” The contact’s shoulders loosened and sagged, as if the sight of them had robbed him of tension. “I was starting to worry.” He pointed to a store across the way; there was a gaudy, oversized awning hanging over the entrance, making the door look tiny and meek. “That one’s mine,” he said. “You, uh, wanted to see it, didn’t you?”
They spared one another a glance, and then followed him inside.
Edric Telara, as it so happened, was a sympathizer from years back. Cassian had... inherited him. It was an odd, somewhat callous way to put it, but it was accurate: the officer who had initially cultivated the relationship had long since died. Cassian had mentioned, when they’d been discussing the meet-up, that Telara had initially had trouble accepting him as a replacement. It was understandable. Informants, as a matter of course, couldn’t afford to be careless with their trust.
And that was part of why Jyn knew, almost immediately, that something was wrong.
He offered his hand to her. “Good to meet you, uh…?”
“Kestrel,” she replied, accepting the handshake. It had been a while since she’d used that one.
“Used to know a girl named Kestrel. Wonder what happened to her…” He shook his head, began leading them through his shop. It was odds and ends, wiring and spare parts, mechanics’ tools, small sheets of durasteel and plasteel. Toward the back, there sat a cabinet, the split between its doors adorned with a very prominent lock. Jyn suspected it held weapons. “They usually don’t send him with a partner. Not that you being here’s a bad thing. Recruitment must be up, huh?”
“We’re not here to chit-chat.” Cassian’s face was impassive. “You know why I called you.”
“Sorry. Just trying to be polite.” Telara stopped, looked back toward the door. “There’s not, uh… There’s not a lot of news lately.”
“Don’t tell me you haven’t got anything.”
Jyn swallowed. It wouldn’t be a complete waste, coming here; they’d had the chance to top off their fuel reserves, buy some new gear (including a holster), stock up on food and water. But it would certainly be irritating -- and another sign that something wasn’t quite right.
“There have been rumors. Lots of interest in Wild Space these days, I guess. And Tolonda and Merel.”
Jyn and Cassian’s eyes met, briefly. One of their potential worlds was in Tolonda.
“Anything coming out of Sarnix, Varada, Wazta, Trilon?”
The skin between her shoulder blades crawled. None of those sectors was relevant to them. A couple of them may have bordered on those they’d be scouting; she wasn’t sure, having never been the type to memorize maps. She expected Cassian to lie while on the job. It was what he did. But the fact that he was being this evasive with a contact meant that he’d been picking up on exactly what she had, and that the angle of the meeting had changed.
Her fingers twitched toward her weapons.
“Wazta, maybe, but I don’t know about the others.” Telara shrugged. “Like I said, not much news.”
Old-style hinges creaked. Wood scraped against wood. Jyn turned, body going taut, readying itself for action. The door to the shop clattered against the wall. The man who walked in was young, younger even than her, dressed in starched linens and leather new enough to still retain its gloss. Rich kid. Looking for a little excitement, maybe, out here on the edge of things. He paused at the threshold, taking in the three of them. His larynx moved up, down.
“Oh, um.” He lowered his eyes and started walking, very pointedly, toward the side of the store opposite from where they stood.
Cassian clapped Telara on the back. He jumped. “Looks like you’ve got a customer. We’ll leave you to him.” He looked at Jyn, and she nodded. “Thank you for the tour, old friend.”
They started to leave.
“Wait!” Telara called after them. “Do you...do you have a place to stay?”
Jyn felt her senses sharpen, her heart rate increase.
“We have a room at the inn, on the next street over.”
The man half-smiled, bobbed his head. He was breathing hard, again. “Ah. Good.”
Cassian gave him a tight smile, then planted a hand on Jyn’s back and pushed her, gently, toward the door.
The first handful of steps they took, once they’d gotten outside, were casual, even, measured. They kept their gaits light. Glanced around. Cassian gestured in the direction of what she assumed was the inn he’d mentioned. It wasn’t until they’d rounded a corner, until they were sure that Telara couldn’t see them, that they began to hurry. Cassian’s hand hovered over his holster. Jyn was aware, very keenly, of the weight and shape of her truncheon, pressing into her side and upper thigh, cold and stiff and comforting.
“Well,” she said, “this mission is turning out to be a lot more...interesting than they made it sound.”
“It wouldn’t be us, if it wasn’t.”
“You have a point.” They moved past an alley. Her gaze swung toward it, eyes darting up walls, toward windows, toward roofs. “At least some of the information he gave us was useful.” And at least Cassian had thrown him off their trail.
“If any of it is even reliable.”
“He’d have no reason to lie, if it’s a set up.”
“Maybe, maybe not.”
It occurred to her that their conversation was veering into too-familiar territory. She decided to stop talking.
They had landed in a docking bay on the eastern side of the outpost, where the throb of commerce was abruptly halted and the primary streets opened up and emptied, like tributaries, into a wide, partially segmented space. They were nearly there. The steady drone of countless craft landing and taking off, of air being displaced, of engines firing and warming up and cooling down, of dock workers shouting and exclaiming, drifted back, wafted over them. Jyn could see some of the ships. She could see the lights.
A woman stepped out from the shadow of a doorway. She grinned.
“Hey, there!”
To her credit, she managed to raise her blaster nearly half of the way before Jyn’s truncheon connected with the side of her neck.
In the next instant, she and Cassian were running, sprinting toward the docking bay, and then skidding to a stop, scrambling to turn back as a group of Stormtroopers spilled out from a cross street. There was a high pitched screech near her ear, a hiss, an eruption of plaster. Cassian’s arm around her shoulders, pushing her head down. The strange, red-orange, barely-there glints of light accompanying blaster bolts. They dropped and all but threw themselves around a corner, down another street.
People began to scream. Vendors dove under their booths. Restaurant patrons rushed from open-air seating to the relative safety of the indoors. Most of them, anyway; some took up defensive positions, pulled out weapons of their own. Jyn wondered whether she should be grateful to them or annoyed at their foolishness.
Cassian spat. “Dammit!”
“What?”
They squeezed into a space between buildings that barely qualified as an alley, knees bent, backs to one of the walls, close together, side by side. Cassian was tugging at his pistol; he’d gone with it over the rifle, due to its lower profile.
“The retention release.”
She gaped at him. “Are you kidding me?”
“No.”
“Your new holster…”
“...is on the ship.”
“Andor, you are brilliant at what you do.” She paused. “But you’re also an idiot.”
“Yes, I know. You can insult me all you want for it later. But right now, I could use some help.”
Heavy footfalls sounded off to the right, in the street, joined by the distorted, tinny voices of men speaking through plasteel-encased microphones. They were moving closer, and quickly. Jyn’s thumb slid over Cassian’s, slipped under it.
“Maybe if we both…”
A blaster discharged, mere meters away. Someone cried out.
They pressed down on the release together, were rebuffed. “What ever did you do to this thing?”
The footsteps drew parallel to them. Jyn turned.
She had to twist the truncheon, pointing the end of it down, moving it upwards so that it wouldn’t strike the opposite wall. It came up under the trooper’s arm, smacking his elbow with a loud crack, causing his fist to pop open, his blaster to clatter to the ground. She took a step. Twisted the stick again. Struck his hip, swept it upwards, into his armpit, locking it behind his shoulder. Leveraged him forward. Brought his head into contact with her knee.
It hurt, like hell. But it hurt him quite a bit more.
He crumpled, and she hit him again, on the back of the neck.
And then looked up, into the barrel of another blaster.
“...no.”
She heard the shot. Winced. Felt her body flood with an emotion that she couldn’t quite name, an emotion she’d felt far more often over the course of her life than she likely should have. There was fear involved, but there was also a calm acquiescence, an acceptance of the fact that the end was on its way.
She watched as smoke began to rise from a hole in the Stormtrooper’s chest.
Two blaster bolts raced past her head, dropping another pair of unfriendlies. Cassian grabbed her arm, tugged her backwards.
His pistol was still at his hip; he’d picked up the one the first trooper had dropped.
“Let’s go,” he growled.
The alley was blessedly short, and they sidled down it as quickly as they could, and then broke into a run, heading in the general direction of the docking bay. Instead of connecting with the main road that they’d been on before, this street curved sharply to the east, taking them into a small residential area. Worry closed around Jyn’s heart. Behind them, she could hear the sounds of a firefight; those who’d chosen not to hide were fighting back, and if nothing else, they’d hold the Imperials off. But if they got through, and came this way…
Well, she very much hoped that they didn’t.
Cassian was right: she’d become a big fan of hope, in recent times.
After several moments, a roar filled the air, and her attention was drawn upward. A ship hovered, turning in midair, so near that she was surprised she couldn’t feel the backdraft from it. They were close. They were very, very close.
“This way!”
A street to the left, short. At its end, the main road.
“I’ve found them! Over here!”
Boots, stomping. Fewer than there had been, but still too many. Far enough back, though. Far enough back.
Most of the dock workers watched them with interest as they sped across the bay. A few, however, ran to intercept them. One managed to get himself between them and the ship, holding up his hands, shouting something about the name of the Empire. Cassian tucked his shoulder inward and bowled him over.
Once on board, they all but collapsed into their seats. Cassian’s fingers raced over the console; his knuckles went white around the throttle.
“What if they’ve tethered us?”
“We’ll break it.”
The engine’s song filled the interior of the ship. They lifted off the ground; Jyn’s nails dug into the underside of her seat. She waited for the sensation of arrested momentum.
It never came.
Some time later, when the rush of battle had worn off and they were cruising, silently, in hyperspace, Jyn left the cockpit and made her way back to the cabin. She walked past her own bunk, knelt down in front of Cassian’s. Had a thought. Went warm with it.
When she returned, he was sitting back, staring at the great, heaving mass of blue-white, expression unreadable. They were headed to one of the red herring sectors he’d mentioned; they’d spend a bit of time orbiting a world there, putting on as if it interested them, and then make the jump to one of their actual targets. There was no guarantee, if they were being followed, that their tail wouldn’t stay with them through the second jump. But it was better to try than to do nothing.
She stepped up behind him. Dropped something into his lap. He started.
“What…” It was the new holster. With a sigh, he swivelled in his seat and looked up at her. “I’m never going to hear the end of that, am I?”
“No. You’re not.”
“Well, thank you.” He reached out, touched her knee. “How’s this?”
She took a moment to consider. It did hurt, still, if she thought about it; she figured she’d have a hell of a faceplate-shaped bruise in a few hours. But she’d had much worse injuries, and endured much worse pain. “It’s all right.”
His hand stayed there. His fingers curled around the back of her leg. Their eyes locked.
At some point, there were things they were going to have to say. A lot of things.
“Good.”
10 notes · View notes
suzanneshannon · 6 years
Text
DRY Switching with CSS Variables: The Difference of One Declaration
This is the first post of a two-part series that looks into the way CSS variables can be used to make the code for complex layouts and interactions less difficult to write and a lot easier to maintain. This first installment walks through various use cases where this technique applies. The second post covers the use of fallbacks and invalid values to extend the technique to non-numeric values.
What if I told you a single CSS declaration makes the difference in the following image between the wide screen case (left) and the second one (right)? And what if I told you a single CSS declaration makes the difference between the odd and even items in the wide screen case?
Screenshot collage.
Or that a single CSS declaration makes the difference between the collapsed and expanded cases below?
Expanding search.
How is that even possible?
Well, as you may have guessed from the title, it's all in the power of CSS variables.
There are already plenty of articles out there on what CSS variables are and how to get started with them, so we won't be getting into that here.
Instead, we'll dive straight into why CSS variables are useful for achieving these cases and others, then we'll move on to a detailed explanation of the how for various cases. We'll code an actual example from scratch, step by step, and, finally, you'll be getting some eye candy in the form of a few more demos that use the same technique.
So let's get started!
Why CSS variables are useful
For me, the best thing about CSS variables is that they've opened the door for styling things in a logical, mathematical and effortless way.
One example of this is the CSS variable version of the yin and yang loader I coded last year. For this version, we create the two halves with the two pseudo-elements of the loader element.
Rotating ☯ symbol, with its two lobes increasing and decreasing in size.
We use the same background, border-color, transform-origin and animation-delay values for the two halves. These values all depend on a switch variable --i that's initially set to 0 on both halves (the pseudo-elements), but then we change it to 1 for the second half (the :after pseudo-element), thus dynamically modifying the computed values of all these properties.
Without CSS variables, we'd have to set all these properties (border-color, transform-origin, background, animation-delay) again on the :after pseudo-element and risk making some typo or even forgetting to set some of them.
How switching works in the general case
Switching between a zero and a non-zero value
In the particular case of the yin and yang loader, all the properties we change between the two halves (pseudo-elements) go from a zero value for one state of the switch and a non-zero value for the other state.
If we want our value to be zero when the switch is off (--i: 0) and non-zero when the switch is on (--i: 1), then we multiply it with the switch value (var(--i)). This way, if our non-zero value should be, let's say an angular value of 30deg, we have:
when the switch is off (--i: 0), calc(var(--i)*30deg) computes to 0*30deg = 0deg
when the switch is on (--i: 1), calc(var(--i)*30deg) computes to 1*30deg = 30deg
However, if we want our value to be non-zero when the switch is off (--i: 0) and zero when the switch is on (--i: 1), then we multiply it with the complementary of the switch value (1 - var(--i)). This way, for the same non-zero angular value of 30deg, we have:
when the switch is off (--i: 0), calc((1 - var(--i))*30deg) computes to (1 - 0)*30deg = 1*30deg = 30deg
when the switch is on (--i: 1), calc((1 - var(--i))*30deg) computes to (1 - 1)*30deg = 0*30deg = 0deg
You can see this concept illustrated below:
Switching between a zero and a non-zero value (live demo, no Edge support due to calc() not working for angle values)
For the particular case of the loader, we use HSL values for border-color and background-color. HSL stands for hue, saturation, lightness and can be best represented visually with the help of a bicone (which is made up of two cones with the bases glued together).
HSL bicone.
The hues go around the bicone, 0° being equivalent to 360° to give us a red in both cases.
Hue wheel.
The saturation goes from 0% on the vertical axis of the bicone to 100% on the bicone surface. When the saturation is 0% (on the vertical axis of the bicone), the hue doesn't matter anymore; we get the exact same grey for all hues in the same horizontal plane.
The "same horizontal plane" means having the same lightness, which increases along the vertical bicone axis, going from 0% at the black bicone vertex to 100% at the white bicone vertex. When the lightness is either 0% or 100%, neither the hue nor the saturation matter anymore - we always get black for a lightness value of 0% and white for a lightness value of 100%.
Since we only need black and white for our ☯ symbol, the hue and saturation are irrelevant, so we zero them and then switch between black and white by switching the lightness between 0% and 100%.
.yin-yang { /* other styles that are irrelevant here */ &:before, &:after { /* other styles that are irrelevant here */ --i: 0; /* lightness of border-color when * --i: 0 is (1 - 0)*100% = 1*100% = 100% (white) * --i: 1 is (1 - 1)*100% = 0*100% = 0% (black) */ border: solid $d/6 hsl(0, 0%, calc((1 - var(--i))*100%)); /* x coordinate of transform-origin when * --i: 0 is 0*100% = 0% (left) * --i: 1 is 1*100% = 100% (right) */ transform-origin: calc(var(--i)*100%) 50%; /* lightness of background-color when * --i: 0 is 0*100% = 0% (black) * --i: 1 is 1*100% = 100% (white) */ background: hsl(0, 0%, calc(var(--i)*100%)); /* animation-delay when * --i: 0 is 0*-$t = 0s * --i: 1 is 1*-$t = -$t */ animation: s $t ease-in-out calc(var(--i)*#{-$t}) infinite alternate; } &:after { --i: 1 } }
Note that this approach doesn't work in Edge due to the fact that Edge doesn't support calc() values for animation-delay.
But what if we want to have a non-zero value when the switch is off (--i: 0) and another different non-zero value when the switch is on (--i: 1)?
Switching between two non-zero values
Let's say we want an element to have a grey background (#ccc) when the switch is off (--i: 0) and an orange background (#f90) when the switch is on (--i: 1).
The first thing we do is switch from hex to a more manageable format such as rgb() or hsl().
We could do this manually either by using a tool such as Lea Verou's CSS Colors or via DevTools. If we have a background set on an element we can cycle through formats by keeping the Shift key pressed while clicking on the square (or circle) in front of the value in DevTools. This works in both Chrome and Firefox, though it doesn't appear to work in Edge.
Changing the format from DevTools.
Even better, if we're using Sass, we can extract the components with red()/ green()/ blue() or hue()/ saturation()/ lightness() functions.
While rgb() may be the better known format, I tend to prefer hsl() because I find it more intuitive and it's easier for me to get an idea about what to expect visually just by looking at the code.
So we extract the three components of the hsl() equivalents of our two values ($c0: #ccc when the switch is off and $c1: #f90 when the switch is on) using these functions:
$c0: #ccc; $c1: #f90; $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1))
Note that we've rounded the results of the hue(), saturation() and lightness() functions as they may return a lot of decimals and we want to keep our generated code clean. We've also divided the result of the hue() function by 1deg, as the returned value is a degree value in this case and Edge only supports unit-less values inside the CSS hsl() function. Normally, when using Sass, we can have degree values, not just unit-less ones for the hue inside the hsl() function because Sass treats it as the Sass hsl() function, which gets compiled into a CSS hsl() function with a unit-less hue. But here, we have a dynamic CSS variable inside, so Sass treats this function as the CSS hsl() function that doesn't get compiled into anything else, so, if the hue has a unit, this doesn't get removed from the generated CSS.
Now we have that:
if the switch is off (--i: 0), our background is hsl($h0, $s0, $l0)
if the switch is on (--i: 1), our background is hsl($h1, $s1, $l1)
We can write our two backgrounds as:
if the switch is off (--i: 0), hsl(1*$h0 + 0*$h1, 1*$s0 + 0*$s1, 1*$l0 + 1*$l1)
if the switch is on (--i: 1), hsl(0*$h0 + 1*$h1, 0*$s0 + 1*$s1, 0*$l0 + 1*$l1)
Using the switch variable --i, we can unify the two cases:
--j: calc(1 - var(--i)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1}))
Here, we've denoted by --j the complementary value of --i (when --i is 0, --j is 1 and when --i is 1, --j is 0).
Switching between two backgrounds (live demo)
The formula above works for switching in between any two HSL values. However, in this particular case, we can simplify it because we have a pure grey when the switch is off (--i: 0).
Purely grey values have equal red, green and blue values when taking into account the RGB model.
When taking into account the HSL model, the hue is irrelevant (our grey looks the same for all hues), the saturation is always 0% and only the lightness matters, determining how light or dark our grey is.
In this situation, we can always keep the hue of the non-grey value (the one we have for the "on" case, $h1).
Since the saturation of any grey value (the one we have for the "off" case, $s0) is always 0%, multiplying it with either 0 or 1 always gives us 0%. So, given the var(--j)*#{$s0} term in our formula is always 0%, we can just ditch it and our saturation formula reduces to the product between the saturation of the "on" case $s1 and the switch variable --i.
This leaves the lightness as the only component where we still need to apply the full formula.
--j: calc(1 - var(--i)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{d1l}))
The above can be tested in this demo.
Similarly, let's say we want the font-size of some text to be 2rem when our switch is off (--i: 0) and 10vw when the switch is on (--i: 1). Applying the same method, we have:
font-size: calc((1 - var(--i))*2rem + var(--i)*10vw)
Switching between two font sizes (live demo)
Alright, let's now move on to clearing another aspect of this: what is it exactly that causes the switch to flip from on to off or the other way around?
What triggers switching
We have a few options here.
Element-based switching
This means the switch is off for certain elements and on for other elements. For example, this can be determined by parity. Let's say we want all the even elements to be rotated and have an orange background instead of the initial grey one.
.box { --i: 0; --j: calc(1 - var(--i)); transform: rotate(calc(var(--i)*30deg)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); &:nth-child(2n) { --i: 1 } }
Switching triggered by item parity (live demo, not fully functional in Edge due to calc() not working for angle values)
In the parity case, we flip the switch on for every second item (:nth-child(2n)), but we can also flip it on for every seventh item (:nth-child(7n)), for the first two items (:nth-child(-n + 2)), for all items except the first and last two (:nth-child(n + 3):nth-last-child(n + 3)). We can also flip it on just for headings or just for elements that have a certain attribute.
State-based switching
This means the switch is off when the element itself (or a parent or one of its previous siblings) is in one state and off when it's another state. In the interactive examples from the previous section, the switch was flipped when a checkbox before our element got checked or unchecked.
We can also have something like a white link that scales up and turns orange when focused or hovered:
$c: #f90; $h: round(hue($c)/1deg); $s: round(saturation($c)); $l: round(lightness($c)); a { --i: 0; transform: scale(calc(1 + var(--i)*.25)); color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); &:focus, &:hover { --i: 1 } }
Since white is any hsl() value with a lightness of 100% (the hue and saturation are irrelevant), we can simplify things by always keeping the hue and saturation of the :focus/ :hover state and only changing the lightness.
Switching triggered by state change (live demo, not fully functional in Edge due to calc() values not being supported inside scale() functions)
Media query-based switching
Another possibility is that switching is triggered by a media query, for example, when the orientation changes or when going from one viewport range to another.
Let's say we have a white heading with a font-size of 1rem up to 320px, but then it turns orange ($c) and the font-size becomes 5vw and starts scaling with the viewport width.
h5 { --i: 0; color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); font-size: calc(var(--i)*5vw + (1 - var(--i))*1rem); @media (min-width: 320px) { --i: 1 } }
Switching triggered by viewport change (live demo)
Coding a more complex example from scratch
The example we dissect here is that of the expanding search shown at the beginning of this article, inspired by this Pen, which you should really check out because the code is pretty damn clever.
Expanding search.
Note that from a usability point of view, having such a search box on a website may not be the best idea as one would normally expect the button following the search box to trigger the search, not close the search bar, but it's still an interesting coding exercise, which is why I've chosen to dissect it here.
To begin with, my idea was to do it using only form elements. So, the HTML structure looks like this:
<input id='search-btn' type='checkbox'/> <label for='search-btn'>Show search bar</label> <input id='search-bar' type='text' placeholder='Search...'/>
What we do here is initially hide the text input and then reveal it when the checkbox before it gets checked — let's dive into how that works!
First off, we use a basic reset and set a flex layout on the container of our input and label elements. In our case, this container is the body, but it could be another element as well. We also absolutely position the checkbox and move it out of sight (outside the viewport).
*, :before, :after { box-sizing: border-box; margin: 0; padding: 0; font: inherit } html { overflow-x: hidden } body { display: flex; align-items: center; justify-content: center; margin: 0 auto; min-width: 400px; min-height: 100vh; background: #252525 } [id='search-btn'] { position: absolute; left: -100vh }
So far, so good...
See the Pen by thebabydino (@thebabydino) on CodePen.
So what? We have to admit it's not exciting at all, so let's move on to the next step!
We turn the checkbox label into a big round green button and move its text content out of sight using a big negative-valued text-indent and overflow: hidden.
$btn-d: 5em; /* same as before */ [for='search-btn'] { overflow: hidden; width: $btn-d; height: $btn-d; border-radius: 50%; box-shadow: 0 0 1.5em rgba(#000, .4); background: #d9eb52; text-indent: -100vw; cursor: pointer; }
See the Pen by thebabydino (@thebabydino) on CodePen.
Next, we polish the actual search bar by:
giving it explicit dimensions
providing a background for its normal state
defining a different background and a glow for its focused state
rounding the corners on the left side using a border-radius that equals half its height
Cleaning up the placeholder a bit
$btn-d: 5em; $bar-w: 4*$btn-d; $bar-h: .65*$btn-d; $bar-r: .5*$bar-h; $bar-c: #ffeacc; /* same as before */ [id='search-bar'] { border: none; padding: 0 1em; width: $bar-w; height: $bar-h; border-radius: $bar-r 0 0 $bar-r; background: #3f324d; color: #fff; font: 1em century gothic, verdana, arial, sans-serif; &::placeholder { opacity: .5; color: inherit; font-size: .875em; letter-spacing: 1px; text-shadow: 0 0 1px, 0 0 2px } &:focus { outline: none; box-shadow: 0 0 1.5em $bar-c, 0 1.25em 1.5em rgba(#000, .2); background: $bar-c; color: #000; } }
See the Pen by thebabydino (@thebabydino) on CodePen.
At this point, the right edge of the search bar coincides with the left edge of the button. However, we want a bit of overlap — let's say an overlap such that the right edge of the search bar coincides with the button's vertical midline. Given that we have a flexbox layout with align-items: center on the container (the body in our case), the assembly made up of our two items (the bar and the button) remains middle-aligned horizontally even if we set a margin on one or on the other or on both in between those items. (On the left of the leftmost item or on the right of the rightmost item is a different story, but we won't be getting into that now.)
Creating overlap, keeping alignment (live demo).
That's an overlap of .5*$btn-d minus half a button diameter, which is equivalent to the button's radius. We set this as a negative margin-right on the bar. We also adjust the padding on the right of the bar so that we compensate for the overlap:
$btn-d: 5em; $btn-r: .5*$btn-d; /* same as before */ [id='search-bar'] { /* same as before */ margin-right: -$btn-r; padding: 0 calc(#{$btn-r} + 1em) 0 1em; }
We now have the bar and the button in the positions for the expanded state:
See the Pen by thebabydino (@thebabydino) on CodePen.
Except the bar follows the button in DOM order, so it's placed on top of it, when we actually want the button on top. Fortunately, this has an easy fix (at least for now — it won't be enough later, but let's deal with one issue at a time).
[for='search-btn'] { /* same as before */ position: relative; }
Now that we've given the button a non-static position value, it's on top of the bar:
See the Pen by thebabydino (@thebabydino) on CodePen.
In this state, the total width of the bar and button assembly is the bar width $bar-w plus the button's radius $btn-r (which is half the button diameter $btn-d) because we have an overlap for half the button. In the collapsed state, the total width of the assembly is just the button diameter $btn-d.
Expanded vs. collapsed state (live).
Since we want to keep the same central axis when going from the expanded to the collapsed state, we need to shift the button to the left by half the assembly width in the expanded state (.5*($bar-w + $btn-r)) minus the button's radius ($btn-r).
We call this shift $x and we use it with minus on the button (since we shift the button to the left and left is the negative direction of the x axis). Since we want the bar to collapse into the button, we set the same shift $x on it, but in the positive direction (as we shift the bar to the right of the x axis).
We're in the collapsed state when the checkbox isn't checked and in the expanded state when it isn't. This means our bar and button are shifted with a CSS transform when the checkbox isn't checked and in the position we currently have them in (no transform) when the checkbox is checked.
In order to do this, we set a variable --i on the elements following our checkbox — the button (created with the label for the checkbox) and the search bar. This variable is 0 in the collapsed state (when both elements are shifted and the checkbox isn't checked) and 1 in the expanded state (when our bar and button are in the positions they currently occupy, no shift, and the checkbox is checked).
$x: .5*($bar-w + $btn-r) - $btn-r; [id='search-btn'] { position: absolute; left: -100vw; ~ * { --i: 0; --j: calc(1 - var(--i)) /* 1 when --i is 0, 0 when --i is 1 */ } &:checked ~ * { --i: 1 } } [for='search-btn'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is -$x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{-$x})); } [id='search-bar'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is $x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{$x})); }
And we now have something interactive! Clicking the button toggles the checkbox state (because the button has been created using the label of the checkbox).
See the Pen by thebabydino (@thebabydino) on CodePen.
Except now the button is a bit difficult to click since it's under the text input again (because we've set a transform on the bar and this establishes a stacking context). The fix is pretty straightforward — we need to add a z-index to the button and this moves it above the bar.
[for='search-btn'] { /* same as before */ z-index: 1; }
See the Pen by thebabydino (@thebabydino) on CodePen.
But we still have another bigger problem: we can see the bar coming out from under the button on the right side. In order to fix this, we set clip-path with an inset() value on the bar. This specifies a clipping rectangle with the help of the distances from the top, right, bottom and left edges of the element's border-box. Everything outside this clipping rectangle gets cut out and only what's inside is displayed.
How the inset() function works (live).
In the illustration above, each distance is going inward from the edges of the border-box. In this case, they're positive. But they can also go outwards, in which case they're negative and the corresponding edges of the clipping rectangle are outside the element's border-box.
At first, you may think we'd have no reason to ever do that, but in our particular case, we do!
We want the distances from the top (dt), bottom (db) and left (dl) to be negative and big enough to contain the box-shadow that extends outside the element's border-box in the :focus state as we don't want it to get clipped out. So the solution is to create a clipping rectangle with edges outside the element's border-box in these three directions.
The distance from the right (dr) is the full bar width $bar-w minus a button radius $btn-r in the collapsed case (checkbox not checked, --i: 0) and 0 in the expanded case (checkbox checked, --i: 1).
$out-d: -3em; [id='search-bar'] { /* same as before */ clip-path: inset($out-d calc(var(--j)*#{$bar-w - $btn-r}) $out-d $out-d); }
We now have a search bar and button assembly that expands and collapses on clicking the button.
See the Pen by thebabydino (@thebabydino) on CodePen.
Since we don't want an abrupt change in between the two states, we use a transition:
[id='search-btn'] { /* same as before */ ~ * { /* same as before */ transition: .65s; } }
We also want our button's background to be green in the collapsed case (checkbox not checked, --i: 0) and pink in the expanded case (checkbox checked, --i: 1). For this, we use the same technique as before:
[for='search-btn'] { /* same as before */ $c0: #d9eb52; // green for collapsed state $c1: #dd1d6a; // pink for expanded state $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); }
Now we're getting somewhere!
See the Pen by thebabydino (@thebabydino) on CodePen.
What we still need to do is create the icon that morphs between a magnifier in the collapsed state and an "x" in the expanded state to indicate a closing action. We do this with the :before and :after pseudo-elements. We begin by deciding on a diameter for the magnifier and how much of this diameter the width of the icon lines represent.
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d;
We absolutely position both pseudo-elements in the middle of the button taking their dimensions into account. We then make them inherit their parent's transition. We give the :before a background, as this will be the handle of our magnifier, make the :after round with border-radius and give it an inset box-shadow.
[for='search-btn'] { /* same as before */ &:before, &:after { position: absolute; top: 50%; left: 50%; margin: -.5*$ico-d; width: $ico-d; height: $ico-d; transition: inherit; content: '' } &:before { margin-top: -.4*$ico-w; height: $ico-w; background: currentColor } &:after { border-radius: 50%; box-shadow: 0 0 0 $ico-w currentColor } }
We can now see the magnifier components on the button:
See the Pen by thebabydino (@thebabydino) on CodePen.
In order to make our icon to look more like a magnifier, we translate both of its components outwards by a quarter of the magnifier's diameter. This means translating the handle to the right, in the positive direction of the x axis by .25*$ico-d and the main part to the left, in the negative direction of the x axis by the same .25*$ico-d.
We also scale the handle (the :before pseudo-element) horizontally to half its width with respect to its right edge (which means a transform-origin of 100% along the x axis).
We only want this to happen in the collapsed state (checkbox not checked, --i is 0 and, consequently --j is 1), so we multiply the translation amounts by --j and also use --j to condition the scaling factor:
[for='search-btn'] { /* same as before */ &:before { /* same as before */ height: $ico-w; transform: /* collapsed: not checked, --i is 0, --j is 1 * translation amount is 1*.25*$d = .25*$d * expanded: checked, --i is 1, --j is 0 * translation amount is 0*.25*$d = 0 */ translate(calc(var(--j)*#{.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 - 1*.5 = 1 - .5 = .5 * expanded: checked, --i is 1, --j is 0 * scaling factor is 1 - 0*.5 = 1 - 0 = 1 */ scalex(calc(1 - var(--j)*.5)) } &:after { /* same as before */ transform: translate(calc(var(--j)*#{-.25*$ico-d})) } }
We now have thew magnifier icon in the collapsed state:
See the Pen by thebabydino (@thebabydino) on CodePen.
Since we want both icon components to be rotated by 45deg, we add this rotation on the button itself:
[for='search-btn'] { /* same as before */ transform: translate(calc(var(--j)*#{-$x})) rotate(45deg); }
Now we have the look we want for the collapsed state:
See the Pen by thebabydino (@thebabydino) on CodePen.
This still leaves the expanded state, where we need to turn the round :after pseudo-element into a line. We do this by scaling it down along the x axis and bringing its border-radius from 50% to 0%. The scaling factor we use is the ratio between the width $ico-w of the line we want to get and the diameter $ico-d of the circle it forms in the collapsed state. We've called this ratio $ico-f.
Since we only want to do this in the expanded state, when the checkbox is checked and --i is 1, we make both the scaling factor and the border-radius depend on --i and --j:
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ &:after{ /* same as before */ /* collapsed: not checked, --i is 0, --j is 1 * border-radius is 1*50% = 50% * expanded: checked, --i is 1, --j is 0 * border-radius is 0*50% = 0 */ border-radius: calc(var(--j)*50%); transform: translate(calc(var(--j)*#{-.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 + 0*$ico-f = 1 * expanded: checked, --i is 1, --j is 0 * scaling factor is 0 + 1*$ico-f = $ico-f */ scalex(calc(1 - var(--j)*.5)) } }
See the Pen by thebabydino (@thebabydino) on CodePen.
Hmm, almost, but not quite. Scaling has also shrunk our inset box-shadow along the x axis, so let's fix that with a second inset shadow that we only get in the expanded state (when the checkbox is checked and --i is 1) and therefore, its spread and alpha depend on --i:
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ --hsl: 0, 0%, 0%; color: HSL(var(--hsl)); &:after{ /* same as before */ box-shadow: inset 0 0 0 $ico-w currentcolor, /* collapsed: not checked, --i is 0, --j is 1 * spread radius is 0*.5*$ico-d = 0 * alpha is 0 * expanded: checked, --i is 1, --j is 0 * spread radius is 1*.5*$ico-d = .5*$ico-d * alpha is 1 */ inset 0 0 0 calc(var(--i)*#{.5*$ico-d}) HSLA(var(--hsl), var(--i)) } }
This gives us our final result!
See the Pen by thebabydino (@thebabydino) on CodePen.
A few more quick examples
The following are a few more demos that use the same technique. We won't be building these from scratch — we'll merely go through the basic ideas behind them.
Responsive banners
Screenshot collage (live demo, not fully functional in Edge due to using a calc() value for font-size).
In this case, our actual elements are the smaller rectangles in front, while the number squares and the bigger rectangles in the back are created with the :before and :after pseudo-elements, respectively.
The backgrounds of the number squares are individual and set using a stop list variable --slist that's different for each item.
<p style='--slist: #51a9ad, #438c92'><!-- 1st paragraph text --></p> <p style='--slist: #ebb134, #c2912a'><!-- 2nd paragraph text --></p> <p style='--slist: #db4453, #a8343f'><!-- 3rd paragraph text --></p> <p style='--slist: #7eb138, #6d982d'><!-- 4th paragraph text --></p>
The things that influence the styles on the banners are the parity and whether we're in the wide, normal or narrow case. These give us our switch variables:
html { --narr: 0; --comp: calc(1 - var(--narr)); --wide: 1; @media (max-width: 36em) { --wide: 0 } @media (max-width: 20em) { --narr: 1 } } p { --parity: 0; &:nth-child(2n) { --parity: 1 } }
The number squares are absolutely positioned and their placement depends on parity. If the --parity switch is off (0), then they're on the left. If it's on (1), then they're on the right.
A value of left: 0% aligns with the left edge of the number square along the left edge of its parent, while a value of left: 100% aligns its left edge along the parent's right edge.
In order to have the right edge of the number square aligned with the right edge of its parent, we need to subtract its own width out of the previous 100% value. (Remember that % values in the case of offsets are relative to the parent's dimensions.)
left: calc(var(--parity)*(100% - #{$num-d}))
...where $num-d is the size of the numbering square.
In the wide screen case, we also push the numbering outwards by 1em — this means subtracting 1em from the offset we have so far for odd items (having the --parity switch off) and adding 1em to the offset we have so far for even items (having the --parity switch on).
Now the question here is... how do we switch the sign? The simplest way to do it is by using the powers of -1. Sadly, we don't have a power function (or a power operator) in CSS, even though it would be immensely useful in this case:
/* * for --parity: 0, we have pow(-1, 0) = +1 * for --parity: 1, we have pow(-1, 1) = -1 */ pow(-1, var(--parity))
This means we have to make it work with what we do have (addition, subtraction, multiplication and division) and that leads to a weird little formula... but, hey, it works!
/* * for --parity: 0, we have 1 - 2*0 = 1 - 0 = +1 * for --parity: 1, we have 1 - 2*1 = 1 - 2 = -1 */ --sign: calc(1 - 2*var(--parity))
This way, our final formula for the left offset, taking into account both the parity and whether we're in the wide case (--wide: 1) or not (--wide: 0), becomes:
left: calc(var(--parity)*(100% - #{$num-d}) - var(--wide)*var(--sign)*1em)
We also control the width of the paragraphs with these variables and max-width as we want it to have an upper limit and only fully cover its parent horizontally in the narrow case (--narr: 1):
width: calc(var(--comp)*80% + var(--narr)*100%); max-width: 35em;
The font-size also depends on whether we're in the narrow case (--narr: 1) or not (--narr: 0):
calc(.5rem + var(--comp)*.5rem + var(--narr)*2vw)
...and so do the horizontal offsets for the :after pseudo-element (the bigger rectangle in the back) as they're 0 in the narrow case (--narr: 1) and a non-zero offset $off-x otherwise (--narr: 0):
right: calc(var(--comp)*#{$off-x}); left: calc(var(--comp)*#{$off-x});
Hover and focus effects
Effect recording (live demo, not fully functional in Edge due to nested calc() bug).
This effect is created with a link element and its two pseudo-elements sliding diagonally on the :hover and :focus states. The link's dimensions are fixed and so are those of its pseudo-elements, set to the diagonal of their parent $btn-d (computed as the hypotenuse in the right triangle formed by a width and a height) horizontally and the parent's height vertically.
The :before is positioned such that its bottom left corner coincides to that of its parent, while the :after is positioned such that its top right corner coincides with that of its parent. Since both should have the same height as their parent, the vertical placement is resolved by setting top: 0 and bottom: 0. The horizontal placement is handled in the exact same way as in the previous example, using --i as the switch variable that changes value between the two pseudo-elements and --j, its complementary (calc(1 - var(--i))):
left: calc(var(--j)*(100% - #{$btn-d}))
We set the transform-origin of the :before to its left-bottom corner (0% 100%) and :after to its right-top corner (100% 0%), again, with the help of the switch --i and its complementary --j:
transform-origin: calc(var(--j)*100%) calc(var(--i)*100%)
We rotate both pseudo-elements to the angle between the diagonal and the horizontal $btn-a (also computed from the triangle formed by a height and a width, as the arctangent of the ratio between the two). With this rotation, the horizontal edges meet along the diagonal.
We then shift them outwards by their own width. This means we'll use a different sign for each of the two, again depending on the switch variable that changes value in between the :before and :after, just like in the previous example with the banners:
transform: rotate($btn-a) translate(calc((1 - 2*var(--i))*100%))
In the :hover and :focus states, this translation needs to go back to 0. This means we multiply the amount of the translation above by the complementary --q of the switch variable --p that's 0 in the normal state and 1 in the :hover or :focus state:
transform: rotate($btn-a) translate(calc(var(--q)*(1 - 2*var(--i))*100%))
In order to make the pseudo-elements slide out the other way (not back the way they came in) on mouse-out or being out of focus, we set the switch variable --i to the value of --p for :before and to the value of --q for :after, reverse the sign of the translation, and make sure we only transition the transform property.
Responsive infographic
Screenshot collage with the grid lines and gaps highlighted (live demo, no Edge support due to CSS variable and calc() bugs).
In this case, we have a three-row, two-column grid for each item (article element), with the third row collapsed in the wide screen scenario and the second column collapsed in the narrow screen scenario. In the wide screen scenario, the widths of the columns depend on the parity. In the narrow screen scenario, the first column spans the entire content-box of the element and the second one has width 0. We also have a gap in between the columns, but only in the wide screen scenario.
// formulas for the columns in the wide screen case, where // $col-a-wide is for second level heading + paragraph // $col-b-wide is for the first level heading $col-1-wide: calc(var(--q)*#{$col-a-wide} + var(--p)*#{$col-b-wide}); $col-2-wide: calc(var(--q)*#{$col-b-wide} + var(--p)*#{$col-a-wide}); // formulas for the general case, combining the wide and normal scenarios $row-1: calc(var(--i)*#{$row-1-wide} + var(--j)*#{$row-1-norm}); $row-2: calc(var(--i)*#{$row-2-wide} + var(--j)*#{$row-2-norm}); $row-3: minmax(0, auto); $col-1: calc(var(--i)*#{$col-1-wide} + var(--j)*#{$col-1-norm}); $col-2: calc(var(--i)*#{$col-2-wide}); $art-g: calc(var(--i)*#{$art-g-wide}); html { --i: var(--wide, 1); // 1 in the wide screen case --j: calc(1 - var(--i)); @media (max-width: $art-w-wide + 2rem) { --wide: 0 } } article { --p: var(--parity, 0); --q: calc(1 - var(--p)); --s: calc(1 - 2*var(--p)); display: grid; grid-template: #{$row-1} #{$row-2} #{$row-3}/ #{$col-1} #{$col-2}; grid-gap: 0 $art-g; grid-auto-flow: column dense; &:nth-child(2n) { --parity: 1 } }
Since we've set grid-auto-flow: column dense, we can get away with only setting the first level heading to cover an entire column (second one for odd items and first one for even items) in the wide screen case and let the second level heading and the paragraph text fill the first free available cells.
// wide case, odd items: --i is 1, --p is 0, --q is 1 // we're on column 1 + 1*1 = 2 // wide case, even items: --i is 1, --p is 1, --q is 0 // we're on column 1 + 1*0 = 1 // narrow case: --i is 0, so var(--i)*var(--q) is 0 and we're on column 1 + 0 = 1 grid-column: calc(1 + var(--i)*var(--q)); // always start from the first row // span 1 + 2*1 = 3 rows in the wide screen case (--i: 1) // span 1 + 2*0 = 1 row otherwise (--i: 0) grid-row: 1/ span calc(1 + 2*var(--i));
For each item, a few other properties depend on whether we're in the wide screen scenario or not.
The vertical margin, vertical and horizontal padding values, box-shadow offsets and blur are all bigger in the wide screen case:
$art-mv: calc(var(--i)*#{$art-mv-wide} + var(--j)*#{$art-mv-norm}); $art-pv: calc(var(--i)*#{$art-pv-wide} + var(--j)*#{$art-p-norm}); $art-ph: calc(var(--i)*#{$art-ph-wide} + var(--j)*#{$art-p-norm}); $art-sh: calc(var(--i)*#{$art-sh-wide} + var(--j)*#{$art-sh-norm}); article { /* other styles */ margin: $art-mv auto; padding: $art-pv $art-ph; box-shadow: $art-sh $art-sh calc(3*#{$art-sh}) rgba(#000, .5); }
We have a non-zero border-width and border-radius in the wide screen case:
$art-b: calc(var(--i)*#{$art-b-wide}); $art-r: calc(var(--i)*#{$art-r-wide}); article { /* other styles */ border: solid $art-b transparent; border-radius: $art-r; }
In the wide screen scenario, we limit the items' width, but let it be 100% otherwise.
$art-w: calc(var(--i)*#{$art-w-wide} + var(--j)*#{$art-w-norm}); article { /* other styles */ width: $art-w; }
The direction of the padding-box gradient also changes with the parity:
background: linear-gradient(calc(var(--s)*90deg), #e6e6e6, #ececec) padding-box, linear-gradient(to right bottom, #fff, #c8c8c8) border-box;
In a similar manner, margin, border-width, padding, width, border-radius, background gradient direction, font-size or line-height for the headings and the paragraph text also depend on whether we're in the wide screen scenario or not (and, in the case of the first level heading's border-radius or background gradient direction, also on the parity).
The post DRY Switching with CSS Variables: The Difference of One Declaration appeared first on CSS-Tricks.
DRY Switching with CSS Variables: The Difference of One Declaration published first on https://deskbysnafu.tumblr.com/
0 notes
siliconwebx · 6 years
Text
DRY Switching with CSS Variables: The Difference of One Declaration
This is the first post of a two-part series that looks into the way CSS variables can be used to make the code for complex layouts and interactions less difficult to write and a lot easier to maintain. This first installment walks through various use cases where this technique applies. The second post (coming tomorrow!) will cover the use of fallbacks and invalid values to extend the technique to non-numeric values.
What if I told you a single CSS declaration makes the difference in the following image between the wide screen case (left) and the second one (right)? And what if I told you a single CSS declaration makes the difference between the odd and even items in the wide screen case?
Screenshot collage.
Or that a single CSS declaration makes the difference between the collapsed and expanded cases below?
Expanding search.
How is that even possible?
Well, as you may have guessed from the title, it's all in the power of CSS variables.
There are already plenty of articles out there on what CSS variables are and how to get started with them, so we won't be getting into that here.
Instead, we'll dive straight into why CSS variables are useful for achieving these cases and others, then we'll move on to a detailed explanation of the how for various cases. We'll code an actual example from scratch, step by step, and, finally, you'll be getting some eye candy in the form of a few more demos that use the same technique.
So let's get started!
Why CSS variables are useful
For me, the best thing about CSS variables is that they've opened the door for styling things in a logical, mathematical and effortless way.
One example of this is the CSS variable version of the yin and yang loader I coded last year. For this version, we create the two halves with the two pseudo-elements of the loader element.
Rotating ☯ symbol, with its two lobes increasing and decreasing in size.
We use the same background, border-color, transform-origin and animation-delay values for the two halves. These values all depend on a switch variable --i that's initially set to 0 on both halves (the pseudo-elements), but then we change it to 1 for the second half (the :after pseudo-element), thus dynamically modifying the computed values of all these properties.
Without CSS variables, we'd have to set all these properties (border-color, transform-origin, background, animation-delay) again on the :after pseudo-element and risk making some typo or even forgetting to set some of them.
How switching works in the general case
Switching between a zero and a non-zero value
In the particular case of the yin and yang loader, all the properties we change between the two halves (pseudo-elements) go from a zero value for one state of the switch and a non-zero value for the other state.
If we want our value to be zero when the switch is off (--i: 0) and non-zero when the switch is on (--i: 1), then we multiply it with the switch value (var(--i)). This way, if our non-zero value should be, let's say an angular value of 30deg, we have:
when the switch is off (--i: 0), calc(var(--i)*30deg) computes to 0*30deg = 0deg
when the switch is on (--i: 1), calc(var(--i)*30deg) computes to 1*30deg = 30deg
However, if we want our value to be non-zero when the switch is off (--i: 0) and zero when the switch is on (--i: 1), then we multiply it with the complementary of the switch value (1 - var(--i)). This way, for the same non-zero angular value of 30deg, we have:
when the switch is off (--i: 0), calc((1 - var(--i))*30deg) computes to (1 - 0)*30deg = 1*30deg = 30deg
when the switch is on (--i: 1), calc((1 - var(--i))*30deg) computes to (1 - 1)*30deg = 0*30deg = 0deg
You can see this concept illustrated below:
Switching between a zero and a non-zero value (live demo, no Edge support due to calc() not working for angle values)
For the particular case of the loader, we use HSL values for border-color and background-color. HSL stands for hue, saturation, lightness and can be best represented visually with the help of a bicone (which is made up of two cones with the bases glued together).
HSL bicone.
The hues go around the bicone, 0° being equivalent to 360° to give us a red in both cases.
Hue wheel.
The saturation goes from 0% on the vertical axis of the bicone to 100% on the bicone surface. When the saturation is 0% (on the vertical axis of the bicone), the hue doesn't matter anymore; we get the exact same grey for all hues in the same horizontal plane.
The "same horizontal plane" means having the same lightness, which increases along the vertical bicone axis, going from 0% at the black bicone vertex to 100% at the white bicone vertex. When the lightness is either 0% or 100%, neither the hue nor the saturation matter anymore - we always get black for a lightness value of 0% and white for a lightness value of 100%.
Since we only need black and white for our ☯ symbol, the hue and saturation are irrelevant, so we zero them and then switch between black and white by switching the lightness between 0% and 100%.
.yin-yang { /* other styles that are irrelevant here */ &:before, &:after { /* other styles that are irrelevant here */ --i: 0; /* lightness of border-color when * --i: 0 is (1 - 0)*100% = 1*100% = 100% (white) * --i: 1 is (1 - 1)*100% = 0*100% = 0% (black) */ border: solid $d/6 hsl(0, 0%, calc((1 - var(--i))*100%)); /* x coordinate of transform-origin when * --i: 0 is 0*100% = 0% (left) * --i: 1 is 1*100% = 100% (right) */ transform-origin: calc(var(--i)*100%) 50%; /* lightness of background-color when * --i: 0 is 0*100% = 0% (black) * --i: 1 is 1*100% = 100% (white) */ background: hsl(0, 0%, calc(var(--i)*100%)); /* animation-delay when * --i: 0 is 0*-$t = 0s * --i: 1 is 1*-$t = -$t */ animation: s $t ease-in-out calc(var(--i)*#{-$t}) infinite alternate; } &:after { --i: 1 } }
Note that this approach doesn't work in Edge due to the fact that Edge doesn't support calc() values for animation-delay.
But what if we want to have a non-zero value when the switch is off (--i: 0) and another different non-zero value when the switch is on (--i: 1)?
Switching between two non-zero values
Let's say we want an element to have a grey background (#ccc) when the switch is off (--i: 0) and an orange background (#f90) when the switch is on (--i: 1).
The first thing we do is switch from hex to a more manageable format such as rgb() or hsl().
We could do this manually either by using a tool such as Lea Verou's CSS Colors or via DevTools. If we have a background set on an element we can cycle through formats by keeping the Shift key pressed while clicking on the square (or circle) in front of the value in DevTools. This works in both Chrome and Firefox, though it doesn't appear to work in Edge.
Changing the format from DevTools.
Even better, if we're using Sass, we can extract the components with red()/ green()/ blue() or hue()/ saturation()/ lightness() functions.
While rgb() may be the better known format, I tend to prefer hsl() because I find it more intuitive and it's easier for me to get an idea about what to expect visually just by looking at the code.
So we extract the three components of the hsl() equivalents of our two values ($c0: #ccc when the switch is off and $c1: #f90 when the switch is on) using these functions:
$c0: #ccc; $c1: #f90; $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1))
Note that we've rounded the results of the hue(), saturation() and lightness() functions as they may return a lot of decimals and we want to keep our generated code clean. We've also divided the result of the hue() function by 1deg, as the returned value is a degree value in this case and Edge only supports unit-less values inside the CSS hsl() function. Normally, when using Sass, we can have degree values, not just unit-less ones for the hue inside the hsl() function because Sass treats it as the Sass hsl() function, which gets compiled into a CSS hsl() function with a unit-less hue. But here, we have a dynamic CSS variable inside, so Sass treats this function as the CSS hsl() function that doesn't get compiled into anything else, so, if the hue has a unit, this doesn't get removed from the generated CSS.
Now we have that:
if the switch is off (--i: 0), our background is hsl($h0, $s0, $l0)
if the switch is on (--i: 1), our background is hsl($h1, $s1, $l1)
We can write our two backgrounds as:
if the switch is off (--i: 0), hsl(1*$h0 + 0*$h1, 1*$s0 + 0*$s1, 1*$l0 + 1*$l1)
if the switch is on (--i: 1), hsl(0*$h0 + 1*$h1, 0*$s0 + 1*$s1, 0*$l0 + 1*$l1)
Using the switch variable --i, we can unify the two cases:
--j: calc(1 - var(--i)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1}))
Here, we've denoted by --j the complementary value of --i (when --i is 0, --j is 1 and when --i is 1, --j is 0).
Switching between two backgrounds (live demo)
The formula above works for switching in between any two HSL values. However, in this particular case, we can simplify it because we have a pure grey when the switch is off (--i: 0).
Purely grey values have equal red, green and blue values when taking into account the RGB model.
When taking into account the HSL model, the hue is irrelevant (our grey looks the same for all hues), the saturation is always 0% and only the lightness matters, determining how light or dark our grey is.
In this situation, we can always keep the hue of the non-grey value (the one we have for the "on" case, $h1).
Since the saturation of any grey value (the one we have for the "off" case, $s0) is always 0%, multiplying it with either 0 or 1 always gives us 0%. So, given the var(--j)*#{$s0} term in our formula is always 0%, we can just ditch it and our saturation formula reduces to the product between the saturation of the "on" case $s1 and the switch variable --i.
This leaves the lightness as the only component where we still need to apply the full formula.
--j: calc(1 - var(--i)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{d1l}))
The above can be tested in this demo.
Similarly, let's say we want the font-size of some text to be 2rem when our switch is off (--i: 0) and 10vw when the switch is on (--i: 1). Applying the same method, we have:
font-size: calc((1 - var(--i))*2rem + var(--i)*10vw)
Switching between two font sizes (live demo)
Alright, let's now move on to clearing another aspect of this: what is it exactly that causes the switch to flip from on to off or the other way around?
What triggers switching
We have a few options here.
Element-based switching
This means the switch is off for certain elements and on for other elements. For example, this can be determined by parity. Let's say we want all the even elements to be rotated and have an orange background instead of the initial grey one.
.box { --i: 0; --j: calc(1 - var(--i)); transform: rotate(calc(var(--i)*30deg)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); &:nth-child(2n) { --i: 1 } }
Switching triggered by item parity (live demo, not fully functional in Edge due to calc() not working for angle values)
In the parity case, we flip the switch on for every second item (:nth-child(2n)), but we can also flip it on for every seventh item (:nth-child(7n)), for the first two items (:nth-child(-n + 2)), for all items except the first and last two (:nth-child(n + 3):nth-last-child(n + 3)). We can also flip it on just for headings or just for elements that have a certain attribute.
State-based switching
This means the switch is off when the element itself (or a parent or one of its previous siblings) is in one state and off when it's another state. In the interactive examples from the previous section, the switch was flipped when a checkbox before our element got checked or unchecked.
We can also have something like a white link that scales up and turns orange when focused or hovered:
$c: #f90; $h: round(hue($c)/1deg); $s: round(saturation($c)); $l: round(lightness($c)); a { --i: 0; transform: scale(calc(1 + var(--i)*.25)); color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); &:focus, &:hover { --i: 1 } }
Since white is any hsl() value with a lightness of 100% (the hue and saturation are irrelevant), we can simplify things by always keeping the hue and saturation of the :focus/ :hover state and only changing the lightness.
Switching triggered by state change (live demo, not fully functional in Edge due to calc() values not being supported inside scale() functions)
Media query-based switching
Another possibility is that switching is triggered by a media query, for example, when the orientation changes or when going from one viewport range to another.
Let's say we have a white heading with a font-size of 1rem up to 320px, but then it turns orange ($c) and the font-size becomes 5vw and starts scaling with the viewport width.
h5 { --i: 0; color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); font-size: calc(var(--i)*5vw + (1 - var(--i))*1rem); @media (min-width: 320px) { --i: 1 } }
Switching triggered by viewport change (live demo)
Coding a more complex example from scratch
The example we dissect here is that of the expanding search shown at the beginning of this article, inspired by this Pen, which you should really check out because the code is pretty damn clever.
Expanding search.
Note that from a usability point of view, having such a search box on a website may not be the best idea as one would normally expect the button following the search box to trigger the search, not close the search bar, but it's still an interesting coding exercise, which is why I've chosen to dissect it here.
To begin with, my idea was to do it using only form elements. So, the HTML structure looks like this:
<input id='search-btn' type='checkbox'/> <label for='search-btn'>Show search bar</label> <input id='search-bar' type='text' placeholder='Search...'/>
What we do here is initially hide the text input and then reveal it when the checkbox before it gets checked — let's dive into how that works!
First off, we use a basic reset and set a flex layout on the container of our input and label elements. In our case, this container is the body, but it could be another element as well. We also absolutely position the checkbox and move it out of sight (outside the viewport).
*, :before, :after { box-sizing: border-box; margin: 0; padding: 0; font: inherit } html { overflow-x: hidden } body { display: flex; align-items: center; justify-content: center; margin: 0 auto; min-width: 400px; min-height: 100vh; background: #252525 } [id='search-btn'] { position: absolute; left: -100vh }
So far, so good...
See the Pen by thebabydino (@thebabydino) on CodePen.
So what? We have to admit it's not exciting at all, so let's move on to the next step!
We turn the checkbox label into a big round green button and move its text content out of sight using a big negative-valued text-indent and overflow: hidden.
$btn-d: 5em; /* same as before */ [for='search-btn'] { overflow: hidden; width: $btn-d; height: $btn-d; border-radius: 50%; box-shadow: 0 0 1.5em rgba(#000, .4); background: #d9eb52; text-indent: -100vw; cursor: pointer; }
See the Pen by thebabydino (@thebabydino) on CodePen.
Next, we polish the actual search bar by:
giving it explicit dimensions
providing a background for its normal state
defining a different background and a glow for its focused state
rounding the corners on the left side using a border-radius that equals half its height
Cleaning up the placeholder a bit
$btn-d: 5em; $bar-w: 4*$btn-d; $bar-h: .65*$btn-d; $bar-r: .5*$bar-h; $bar-c: #ffeacc; /* same as before */ [id='search-bar'] { border: none; padding: 0 1em; width: $bar-w; height: $bar-h; border-radius: $bar-r 0 0 $bar-r; background: #3f324d; color: #fff; font: 1em century gothic, verdana, arial, sans-serif; &::placeholder { opacity: .5; color: inherit; font-size: .875em; letter-spacing: 1px; text-shadow: 0 0 1px, 0 0 2px } &:focus { outline: none; box-shadow: 0 0 1.5em $bar-c, 0 1.25em 1.5em rgba(#000, .2); background: $bar-c; color: #000; } }
See the Pen by thebabydino (@thebabydino) on CodePen.
At this point, the right edge of the search bar coincides with the left edge of the button. However, we want a bit of overlap — let's say an overlap such that the right edge of the search bar coincides with the button's vertical midline. Given that we have a flexbox layout with align-items: center on the container (the body in our case), the assembly made up of our two items (the bar and the button) remains middle-aligned horizontally even if we set a margin on one or on the other or on both in between those items. (On the left of the leftmost item or on the right of the rightmost item is a different story, but we won't be getting into that now.)
Creating overlap, keeping alignment (live demo).
That's an overlap of .5*$btn-d minus half a button diameter, which is equivalent to the button's radius. We set this as a negative margin-right on the bar. We also adjust the padding on the right of the bar so that we compensate for the overlap:
$btn-d: 5em; $btn-r: .5*$btn-d; /* same as before */ [id='search-bar'] { /* same as before */ margin-right: -$btn-r; padding: 0 calc(#{$btn-r} + 1em) 0 1em; }
We now have the bar and the button in the positions for the expanded state:
See the Pen by thebabydino (@thebabydino) on CodePen.
Except the bar follows the button in DOM order, so it's placed on top of it, when we actually want the button on top. Fortunately, this has an easy fix (at least for now — it won't be enough later, but let's deal with one issue at a time).
[for='search-btn'] { /* same as before */ position: relative; }
Now that we've given the button a non-static position value, it's on top of the bar:
See the Pen by thebabydino (@thebabydino) on CodePen.
In this state, the total width of the bar and button assembly is the bar width $bar-w plus the button's radius $btn-r (which is half the button diameter $btn-d) because we have an overlap for half the button. In the collapsed state, the total width of the assembly is just the button diameter $btn-d.
Expanded vs. collapsed state (live).
Since we want to keep the same central axis when going from the expanded to the collapsed state, we need to shift the button to the left by half the assembly width in the expanded state (.5*($bar-w + $btn-r)) minus the button's radius ($btn-r).
We call this shift $x and we use it with minus on the button (since we shift the button to the left and left is the negative direction of the x axis). Since we want the bar to collapse into the button, we set the same shift $x on it, but in the positive direction (as we shift the bar to the right of the x axis).
We're in the collapsed state when the checkbox isn't checked and in the expanded state when it isn't. This means our bar and button are shifted with a CSS transform when the checkbox isn't checked and in the position we currently have them in (no transform) when the checkbox is checked.
In order to do this, we set a variable --i on the elements following our checkbox — the button (created with the label for the checkbox) and the search bar. This variable is 0 in the collapsed state (when both elements are shifted and the checkbox isn't checked) and 1 in the expanded state (when our bar and button are in the positions they currently occupy, no shift, and the checkbox is checked).
$x: .5*($bar-w + $btn-r) - $btn-r; [id='search-btn'] { position: absolute; left: -100vw; ~ * { --i: 0; --j: calc(1 - var(--i)) /* 1 when --i is 0, 0 when --i is 1 */ } &:checked ~ * { --i: 1 } } [for='search-btn'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is -$x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{-$x})); } [id='search-bar'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is $x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{$x})); }
And we now have something interactive! Clicking the button toggles the checkbox state (because the button has been created using the label of the checkbox).
See the Pen by thebabydino (@thebabydino) on CodePen.
Except now the button is a bit difficult to click since it's under the text input again (because we've set a transform on the bar and this establishes a stacking context). The fix is pretty straightforward — we need to add a z-index to the button and this moves it above the bar.
[for='search-btn'] { /* same as before */ z-index: 1; }
See the Pen by thebabydino (@thebabydino) on CodePen.
But we still have another bigger problem: we can see the bar coming out from under the button on the right side. In order to fix this, we set clip-path with an inset() value on the bar. This specifies a clipping rectangle with the help of the distances from the top, right, bottom and left edges of the element's border-box. Everything outside this clipping rectangle gets cut out and only what's inside is displayed.
How the inset() function works (live).
In the illustration above, each distance is going inward from the edges of the border-box. In this case, they're positive. But they can also go outwards, in which case they're negative and the corresponding edges of the clipping rectangle are outside the element's border-box.
At first, you may think we'd have no reason to ever do that, but in our particular case, we do!
We want the distances from the top (dt), bottom (db) and left (dl) to be negative and big enough to contain the box-shadow that extends outside the element's border-box in the :focus state as we don't want it to get clipped out. So the solution is to create a clipping rectangle with edges outside the element's border-box in these three directions.
The distance from the right (dr) is the full bar width $bar-w minus a button radius $btn-r in the collapsed case (checkbox not checked, --i: 0) and 0 in the expanded case (checkbox checked, --i: 1).
$out-d: -3em; [id='search-bar'] { /* same as before */ clip-path: inset($out-d calc(var(--j)*#{$bar-w - $btn-r}) $out-d $out-d); }
We now have a search bar and button assembly that expands and collapses on clicking the button.
See the Pen by thebabydino (@thebabydino) on CodePen.
Since we don't want an abrupt change in between the two states, we use a transition:
[id='search-btn'] { /* same as before */ ~ * { /* same as before */ transition: .65s; } }
We also want our button's background to be green in the collapsed case (checkbox not checked, --i: 0) and pink in the expanded case (checkbox checked, --i: 1). For this, we use the same technique as before:
[for='search-btn'] { /* same as before */ $c0: #d9eb52; // green for collapsed state $c1: #dd1d6a; // pink for expanded state $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); }
Now we're getting somewhere!
See the Pen by thebabydino (@thebabydino) on CodePen.
What we still need to do is create the icon that morphs between a magnifier in the collapsed state and an "x" in the expanded state to indicate a closing action. We do this with the :before and :after pseudo-elements. We begin by deciding on a diameter for the magnifier and how much of this diameter the width of the icon lines represent.
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d;
We absolutely position both pseudo-elements in the middle of the button taking their dimensions into account. We then make them inherit their parent's transition. We give the :before a background, as this will be the handle of our magnifier, make the :after round with border-radius and give it an inset box-shadow.
[for='search-btn'] { /* same as before */ &:before, &:after { position: absolute; top: 50%; left: 50%; margin: -.5*$ico-d; width: $ico-d; height: $ico-d; transition: inherit; content: '' } &:before { margin-top: -.4*$ico-w; height: $ico-w; background: currentColor } &:after { border-radius: 50%; box-shadow: 0 0 0 $ico-w currentColor } }
We can now see the magnifier components on the button:
See the Pen by thebabydino (@thebabydino) on CodePen.
In order to make our icon to look more like a magnifier, we translate both of its components outwards by a quarter of the magnifier's diameter. This means translating the handle to the right, in the positive direction of the x axis by .25*$ico-d and the main part to the left, in the negative direction of the x axis by the same .25*$ico-d.
We also scale the handle (the :before pseudo-element) horizontally to half its width with respect to its right edge (which means a transform-origin of 100% along the x axis).
We only want this to happen in the collapsed state (checkbox not checked, --i is 0 and, consequently --j is 1), so we multiply the translation amounts by --j and also use --j to condition the scaling factor:
[for='search-btn'] { /* same as before */ &:before { /* same as before */ height: $ico-w; transform: /* collapsed: not checked, --i is 0, --j is 1 * translation amount is 1*.25*$d = .25*$d * expanded: checked, --i is 1, --j is 0 * translation amount is 0*.25*$d = 0 */ translate(calc(var(--j)*#{.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 - 1*.5 = 1 - .5 = .5 * expanded: checked, --i is 1, --j is 0 * scaling factor is 1 - 0*.5 = 1 - 0 = 1 */ scalex(calc(1 - var(--j)*.5)) } &:after { /* same as before */ transform: translate(calc(var(--j)*#{-.25*$ico-d})) } }
We now have thew magnifier icon in the collapsed state:
See the Pen by thebabydino (@thebabydino) on CodePen.
Since we want both icon components to be rotated by 45deg, we add this rotation on the button itself:
[for='search-btn'] { /* same as before */ transform: translate(calc(var(--j)*#{-$x})) rotate(45deg); }
Now we have the look we want for the collapsed state:
See the Pen by thebabydino (@thebabydino) on CodePen.
This still leaves the expanded state, where we need to turn the round :after pseudo-element into a line. We do this by scaling it down along the x axis and bringing its border-radius from 50% to 0%. The scaling factor we use is the ratio between the width $ico-w of the line we want to get and the diameter $ico-d of the circle it forms in the collapsed state. We've called this ratio $ico-f.
Since we only want to do this in the expanded state, when the checkbox is checked and --i is 1, we make both the scaling factor and the border-radius depend on --i and --j:
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ &:after{ /* same as before */ /* collapsed: not checked, --i is 0, --j is 1 * border-radius is 1*50% = 50% * expanded: checked, --i is 1, --j is 0 * border-radius is 0*50% = 0 */ border-radius: calc(var(--j)*50%); transform: translate(calc(var(--j)*#{-.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 + 0*$ico-f = 1 * expanded: checked, --i is 1, --j is 0 * scaling factor is 0 + 1*$ico-f = $ico-f */ scalex(calc(1 - var(--j)*.5)) } }
See the Pen by thebabydino (@thebabydino) on CodePen.
Hmm, almost, but not quite. Scaling has also shrunk our inset box-shadow along the x axis, so let's fix that with a second inset shadow that we only get in the expanded state (when the checkbox is checked and --i is 1) and therefore, its spread and alpha depend on --i:
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ --hsl: 0, 0%, 0%; color: HSL(var(--hsl)); &:after{ /* same as before */ box-shadow: inset 0 0 0 $ico-w currentcolor, /* collapsed: not checked, --i is 0, --j is 1 * spread radius is 0*.5*$ico-d = 0 * alpha is 0 * expanded: checked, --i is 1, --j is 0 * spread radius is 1*.5*$ico-d = .5*$ico-d * alpha is 1 */ inset 0 0 0 calc(var(--i)*#{.5*$ico-d}) HSLA(var(--hsl), var(--i)) } }
This gives us our final result!
See the Pen by thebabydino (@thebabydino) on CodePen.
A few more quick examples
The following are a few more demos that use the same technique. We won't be building these from scratch — we'll merely go through the basic ideas behind them.
Responsive banners
Screenshot collage (live demo, not fully functional in Edge due to using a calc() value for font-size).
In this case, our actual elements are the smaller rectangles in front, while the number squares and the bigger rectangles in the back are created with the :before and :after pseudo-elements, respectively.
The backgrounds of the number squares are individual and set using a stop list variable --slist that's different for each item.
<p style='--slist: #51a9ad, #438c92'><!-- 1st paragraph text --></p> <p style='--slist: #ebb134, #c2912a'><!-- 2nd paragraph text --></p> <p style='--slist: #db4453, #a8343f'><!-- 3rd paragraph text --></p> <p style='--slist: #7eb138, #6d982d'><!-- 4th paragraph text --></p>
The things that influence the styles on the banners are the parity and whether we're in the wide, normal or narrow case. These give us our switch variables:
html { --narr: 0; --comp: calc(1 - var(--narr)); --wide: 1; @media (max-width: 36em) { --wide: 0 } @media (max-width: 20em) { --narr: 1 } } p { --parity: 0; &:nth-child(2n) { --parity: 1 } }
The number squares are absolutely positioned and their placement depends on parity. If the --parity switch is off (0), then they're on the left. If it's on (1), then they're on the right.
A value of left: 0% aligns with the left edge of the number square along the left edge of its parent, while a value of left: 100% aligns its left edge along the parent's right edge.
In order to have the right edge of the number square aligned with the right edge of its parent, we need to subtract its own width out of the previous 100% value. (Remember that % values in the case of offsets are relative to the parent's dimensions.)
left: calc(var(--parity)*(100% - #{$num-d}))
...where $num-d is the size of the numbering square.
In the wide screen case, we also push the numbering outwards by 1em — this means subtracting 1em from the offset we have so far for odd items (having the --parity switch off) and adding 1em to the offset we have so far for even items (having the --parity switch on).
Now the question here is... how do we switch the sign? The simplest way to do it is by using the powers of -1. Sadly, we don't have a power function (or a power operator) in CSS, even though it would be immensely useful in this case:
/* * for --parity: 0, we have pow(-1, 0) = +1 * for --parity: 1, we have pow(-1, 1) = -1 */ pow(-1, var(--parity))
This means we have to make it work with what we do have (addition, subtraction, multiplication and division) and that leads to a weird little formula... but, hey, it works!
/* * for --parity: 0, we have 1 - 2*0 = 1 - 0 = +1 * for --parity: 1, we have 1 - 2*1 = 1 - 2 = -1 */ --sign: calc(1 - 2*var(--parity))
This way, our final formula for the left offset, taking into account both the parity and whether we're in the wide case (--wide: 1) or not (--wide: 0), becomes:
left: calc(var(--parity)*(100% - #{$num-d}) - var(--wide)*var(--sign)*1em)
We also control the width of the paragraphs with these variables and max-width as we want it to have an upper limit and only fully cover its parent horizontally in the narrow case (--narr: 1):
width: calc(var(--comp)*80% + var(--narr)*100%); max-width: 35em;
The font-size also depends on whether we're in the narrow case (--narr: 1) or not (--narr: 0):
calc(.5rem + var(--comp)*.5rem + var(--narr)*2vw)
...and so do the horizontal offsets for the :after pseudo-element (the bigger rectangle in the back) as they're 0 in the narrow case (--narr: 1) and a non-zero offset $off-x otherwise (--narr: 0):
right: calc(var(--comp)*#{$off-x}); left: calc(var(--comp)*#{$off-x});
Hover and focus effects
Effect recording (live demo, not fully functional in Edge due to nested calc() bug).
This effect is created with a link element and its two pseudo-elements sliding diagonally on the :hover and :focus states. The link's dimensions are fixed and so are those of its pseudo-elements, set to the diagonal of their parent $btn-d (computed as the hypotenuse in the right triangle formed by a width and a height) horizontally and the parent's height vertically.
The :before is positioned such that its bottom left corner coincides to that of its parent, while the :after is positioned such that its top right corner coincides with that of its parent. Since both should have the same height as their parent, the vertical placement is resolved by setting top: 0 and bottom: 0. The horizontal placement is handled in the exact same way as in the previous example, using --i as the switch variable that changes value between the two pseudo-elements and --j, its complementary (calc(1 - var(--i))):
left: calc(var(--j)*(100% - #{$btn-d}))
We set the transform-origin of the :before to its left-bottom corner (0% 100%) and :after to its right-top corner (100% 0%), again, with the help of the switch --i and its complementary --j:
transform-origin: calc(var(--j)*100%) calc(var(--i)*100%)
We rotate both pseudo-elements to the angle between the diagonal and the horizontal $btn-a (also computed from the triangle formed by a height and a width, as the arctangent of the ratio between the two). With this rotation, the horizontal edges meet along the diagonal.
We then shift them outwards by their own width. This means we'll use a different sign for each of the two, again depending on the switch variable that changes value in between the :before and :after, just like in the previous example with the banners:
transform: rotate($btn-a) translate(calc((1 - 2*var(--i))*100%))
In the :hover and :focus states, this translation needs to go back to 0. This means we multiply the amount of the translation above by the complementary --q of the switch variable --p that's 0 in the normal state and 1 in the :hover or :focus state:
transform: rotate($btn-a) translate(calc(var(--q)*(1 - 2*var(--i))*100%))
In order to make the pseudo-elements slide out the other way (not back the way they came in) on mouse-out or being out of focus, we set the switch variable --i to the value of --p for :before and to the value of --q for :after, reverse the sign of the translation, and make sure we only transition the transform property.
Responsive infographic
Screenshot collage with the grid lines and gaps highlighted (live demo, no Edge support due to CSS variable and calc() bugs).
In this case, we have a three-row, two-column grid for each item (article element), with the third row collapsed in the wide screen scenario and the second column collapsed in the narrow screen scenario. In the wide screen scenario, the widths of the columns depend on the parity. In the narrow screen scenario, the first column spans the entire content-box of the element and the second one has width 0. We also have a gap in between the columns, but only in the wide screen scenario.
// formulas for the columns in the wide screen case, where // $col-a-wide is for second level heading + paragraph // $col-b-wide is for the first level heading $col-1-wide: calc(var(--q)*#{$col-a-wide} + var(--p)*#{$col-b-wide}); $col-2-wide: calc(var(--q)*#{$col-b-wide} + var(--p)*#{$col-a-wide}); // formulas for the general case, combining the wide and normal scenarios $row-1: calc(var(--i)*#{$row-1-wide} + var(--j)*#{$row-1-norm}); $row-2: calc(var(--i)*#{$row-2-wide} + var(--j)*#{$row-2-norm}); $row-3: minmax(0, auto); $col-1: calc(var(--i)*#{$col-1-wide} + var(--j)*#{$col-1-norm}); $col-2: calc(var(--i)*#{$col-2-wide}); $art-g: calc(var(--i)*#{$art-g-wide}); html { --i: var(--wide, 1); // 1 in the wide screen case --j: calc(1 - var(--i)); @media (max-width: $art-w-wide + 2rem) { --wide: 0 } } article { --p: var(--parity, 0); --q: calc(1 - var(--p)); --s: calc(1 - 2*var(--p)); display: grid; grid-template: #{$row-1} #{$row-2} #{$row-3}/ #{$col-1} #{$col-2}; grid-gap: 0 $art-g; grid-auto-flow: column dense; &:nth-child(2n) { --parity: 1 } }
Since we've set grid-auto-flow: column dense, we can get away with only setting the first level heading to cover an entire column (second one for odd items and first one for even items) in the wide screen case and let the second level heading and the paragraph text fill the first free available cells.
// wide case, odd items: --i is 1, --p is 0, --q is 1 // we're on column 1 + 1*1 = 2 // wide case, even items: --i is 1, --p is 1, --q is 0 // we're on column 1 + 1*0 = 1 // narrow case: --i is 0, so var(--i)*var(--q) is 0 and we're on column 1 + 0 = 1 grid-column: calc(1 + var(--i)*var(--q)); // always start from the first row // span 1 + 2*1 = 3 rows in the wide screen case (--i: 1) // span 1 + 2*0 = 1 row otherwise (--i: 0) grid-row: 1/ span calc(1 + 2*var(--i));
For each item, a few other properties depend on whether we're in the wide screen scenario or not.
The vertical margin, vertical and horizontal padding values, box-shadow offsets and blur are all bigger in the wide screen case:
$art-mv: calc(var(--i)*#{$art-mv-wide} + var(--j)*#{$art-mv-norm}); $art-pv: calc(var(--i)*#{$art-pv-wide} + var(--j)*#{$art-p-norm}); $art-ph: calc(var(--i)*#{$art-ph-wide} + var(--j)*#{$art-p-norm}); $art-sh: calc(var(--i)*#{$art-sh-wide} + var(--j)*#{$art-sh-norm}); article { /* other styles */ margin: $art-mv auto; padding: $art-pv $art-ph; box-shadow: $art-sh $art-sh calc(3*#{$art-sh}) rgba(#000, .5); }
We have a non-zero border-width and border-radius in the wide screen case:
$art-b: calc(var(--i)*#{$art-b-wide}); $art-r: calc(var(--i)*#{$art-r-wide}); article { /* other styles */ border: solid $art-b transparent; border-radius: $art-r; }
In the wide screen scenario, we limit the items' width, but let it be 100% otherwise.
$art-w: calc(var(--i)*#{$art-w-wide} + var(--j)*#{$art-w-norm}); article { /* other styles */ width: $art-w; }
The direction of the padding-box gradient also changes with the parity:
background: linear-gradient(calc(var(--s)*90deg), #e6e6e6, #ececec) padding-box, linear-gradient(to right bottom, #fff, #c8c8c8) border-box;
In a similar manner, margin, border-width, padding, width, border-radius, background gradient direction, font-size or line-height for the headings and the paragraph text also depend on whether we're in the wide screen scenario or not (and, in the case of the first level heading's border-radius or background gradient direction, also on the parity).
The post DRY Switching with CSS Variables: The Difference of One Declaration appeared first on CSS-Tricks.
😉SiliconWebX | 🌐CSS-Tricks
0 notes
newsintodays-blog · 6 years
Text
Immigration and welfare fears merge as Sweden lurches to the right
New Post has been published on http://newsintoday.info/2018/09/05/immigration-and-welfare-fears-merge-as-sweden-lurches-to-the-right/
Immigration and welfare fears merge as Sweden lurches to the right
KOPPARBERG, Sweden (Reuters) – Those wondering why Swedish politics are set to lurch to the right in Sunday’s election need look no further than Ljusnarsberg, a tiny central county of dense pine forests and glistening lakes.
A woman rides a bike past supermarket in Kopparberg, Sweden August 22, 2018. REUTERS/Simon Christopher Johnson
Many inhabitants of this once-booming region are uneasy about asylum seekers after a large number arrived here in 2015. Some also feel that Sweden’s widely admired tax and welfare model has left them behind.
Fears over globalization’s effect on industrial jobs, the pressure of an aging population and a failure to integrate minorities have boosted right-wing and anti-establishment parties from Italy and Germany to Britain and the United States.
Polls indicating one in five voters in Sweden are likely to back a party with roots in the far-right fringe on Sept. 9 show that even seemingly successful political systems are vulnerable.
Several online surveys indicate the anti-immigration, anti-European Union Sweden Democrats could become the largest party, overtaking the Social Democrats, who have dominated politics for the last 100 years.
They are likely to do particularly well in Ljusnarsberg where they won a quarter of the vote in 2014, double their national score.
(Sweden Election graphic: tmsnrt.rs/2LmSZFD)
(Swedish economy snapshot: tmsnrt.rs/2bylYpf)
“I think people here want to see a change, they want society to be like it used to be,” said Mats Larsson, the Sweden Democrat’s top politician in Ljusnarsberg.
Most people in the county live in Kopparberg, where the 17th century church, with its blood-red, wooden facade and spires, hints at the region’s rich past, built on copper and iron mines.
For many years, the area was a heartland of the ruling Social Democrats. Its swing to the right highlights election themes of asylum and a split between poor rural or suburban areas home to immigrants and wealthy places like Stockholm.
Ljusnarsberg’s mines have gone – the last closed in the mid-1970s. Unemployment at the end of last year was nearly 13 percent, almost double the national level. Many live on sickness benefits, masking the figures of those relying on welfare.
As jobs have disappeared, so have people. The population has roughly halved in the last 50 years and many services have been centralized to Orebro, an hour’s drive south of Kopparberg.
“The 1970s and 1980s were a fantastic time to grow up here in this county. Now everything is falling to pieces,” said Leif Danielsson, 53, a businessman in Kopparberg, the county’s only sizable town.
“Houses are rotting, some places are overgrown with weeds. If you have any education or contacts, you leave.”
While Kopparberg retained its health clinic, it has been unable to recruit permanent doctors, with temporary staff filling the gap.
Decades of closures have left Kyrkbacks school in Kopparberg as the only school for 6-15 year-olds in the region. It has also had problems recruiting staff and was rated as one of Sweden’s worst by the teachers’ union, long before asylum numbers jumped.
STRETCHED SERVICES
When Sweden took in 163,000 asylum seekers in 2015 as hundreds of thousands fled war in Syria and Afghanistan, Ljusnarsberg was assigned around 1,200, the highest concentration compared with its population.
Many of the new arrivals were unaccompanied minors, and the influx stretched services to the limit.
Anne-Marie Hagglund, assistant headteacher of Kyrkbacks school, said families just showed up with their migration papers. “They came back day after day until we could take in their children,” she said.
All but 260 of the refugees have now gone – mostly assigned to other areas by the Migration Agency – yet the unease remains.
Town council property to house asylim seekers with closed shops are seen in Stalldalen, Sweden June 26, 2018. Picture taken June 26, 2018. REUTERS/Simon Christopher Johnson
Sitting in a cafe on the town green in Kopparberg, personal assistant Ulrika, 44, said that since the arrival of so many asylum seekers, women are afraid to walk the streets at night.
“There are lots of robberies. I think a lot of it is to do with immigration,” she said, declining to give her surname.
Police say the number of reported crimes fell in 2017 compared to the previous year, though they admit that many crimes go unreported. After cutbacks, the nearest police station is in Lindesberg, 40 km away.
“Of course, we should help people,” said Staffan Myrman, 53, who works at the Kopparberg brewery, one of the two major employers in the Ljusnarsberg region.
“But when 25-30 percent of the population are refugees, we need to be able to cope with that and we can’t.”
Sweden took in more asylum seekers than any other European country per capita in 2015. But while worries over immigration explain some of the Sweden Democrat’s gains, unease about economic and social change also plays a role.
“It is a target to point your anger at,” said Ljusnarsberg Liberal party politician Hendrik Bijloo. “Of course there are racists voting for the Sweden Democrats, but they are not even close to a majority.”
REFERENDUM ON WELFARE?
It is not just rural areas like Ljusnarsberg where the Sweden Democrats have thrived.
A spate of gang killings and car-burnings have sharpened concerns that authorities are losing control in poorer city suburbs where immigrants make up the majority of the population.
But welfare is also a big theme, despite that fact that Sweden is one of Europe’s richest countries, with strong growth and low unemployment.
“This election is a referendum on welfare or whether we have continued asylum immigration. I choose welfare,” Sweden Democrat leader Jimmie Akesson said in a televised election debate.
The center-left government and main opposition Moderate Party both plan to spend an extra 20 billion Swedish crowns ($2.19 billion) over the next four years.
Despite those plans, and already higher spending, many Swedes believe the welfare system is in crisis.
Sweden scores highly in the quality of healthcare – for example more Swedes are alive 30 days after a heart attack than in other European countries, according to a 2015 study.
But a growing and ageing population means waiting lists for operations have grown and half of health centers have to cover doctor shortages with temporary staff, according to a report by the Swedish Agency for Health and Care Services Analysis.
Since 2000, 16 percent of maternity units have closed, a Swedish television report showed. Many women travel more than 100 km (62 miles) to give birth, while schools need to recruit around 77,000 teachers over the next five years.
Inequality, measured by the Gini coefficient, has grown faster in recent years in Sweden than in any other industrialized nation, although the country remains among those where income is most evenly distributed.
This partly explains why mainstream parties’ shift to tougher immigration policies after the 2015 crisis has failed to win back disillusioned voters.
Many locals in Ljusnarsberg are resentful about what they see as preferential treatment for immigrants.
“They get a better deal, all of them, at the dentist, with the doctor, they are first in the queue always,” said 65-year-old pensioner Torbjorn Lundgren. “That makes me angry.”
Asylum seekers get subsidized welfare, housing and 71 crowns ($7.79) a day for food and other essentials, including healthcare. Healthcare costs are capped at 400 crowns over a 12 month period. For Swedish citizens the cap is 1,100 crowns.
“It’s not the immigrants fault, it’s the politicians,” said pensioner Torbjorn Lundgren, who backs the Sweden Democrats.
“I’m going to vote for them, then we’ll see if things change or not.”
Additional reporting by Johan Ahlander; editing by Niklas Pollard and Philippa Fletcher
Our Standards:The Thomson Reuters Trust Principles.
Source link
0 notes
suzanneshannon · 6 years
Text
DRY Switching with CSS Variables: The Difference of One Declaration
This is the first post of a two-part series that looks into the way CSS variables can be used to make the code for complex layouts and interactions less difficult to write and a lot easier to maintain. This first installment walks through various use cases where this technique applies. The second post (coming tomorrow!) will cover the use of fallbacks and invalid values to extend the technique to non-numeric values.
What if I told you a single CSS declaration makes the difference in the following image between the wide screen case (left) and the second one (right)? And what if I told you a single CSS declaration makes the difference between the odd and even items in the wide screen case?
Screenshot collage.
Or that a single CSS declaration makes the difference between the collapsed and expanded cases below?
Expanding search.
How is that even possible?
Well, as you may have guessed from the title, it's all in the power of CSS variables.
There are already plenty of articles out there on what CSS variables are and how to get started with them, so we won't be getting into that here.
Instead, we'll dive straight into why CSS variables are useful for achieving these cases and others, then we'll move on to a detailed explanation of the how for various cases. We'll code an actual example from scratch, step by step, and, finally, you'll be getting some eye candy in the form of a few more demos that use the same technique.
So let's get started!
Why CSS variables are useful
For me, the best thing about CSS variables is that they've opened the door for styling things in a logical, mathematical and effortless way.
One example of this is the CSS variable version of the yin and yang loader I coded last year. For this version, we create the two halves with the two pseudo-elements of the loader element.
Rotating ☯ symbol, with its two lobes increasing and decreasing in size.
We use the same background, border-color, transform-origin and animation-delay values for the two halves. These values all depend on a switch variable --i that's initially set to 0 on both halves (the pseudo-elements), but then we change it to 1 for the second half (the :after pseudo-element), thus dynamically modifying the computed values of all these properties.
Without CSS variables, we'd have to set all these properties (border-color, transform-origin, background, animation-delay) again on the :after pseudo-element and risk making some typo or even forgetting to set some of them.
How switching works in the general case
Switching between a zero and a non-zero value
In the particular case of the yin and yang loader, all the properties we change between the two halves (pseudo-elements) go from a zero value for one state of the switch and a non-zero value for the other state.
If we want our value to be zero when the switch is off (--i: 0) and non-zero when the switch is on (--i: 1), then we multiply it with the switch value (var(--i)). This way, if our non-zero value should be, let's say an angular value of 30deg, we have:
when the switch is off (--i: 0), calc(var(--i)*30deg) computes to 0*30deg = 0deg
when the switch is on (--i: 1), calc(var(--i)*30deg) computes to 1*30deg = 30deg
However, if we want our value to be non-zero when the switch is off (--i: 0) and zero when the switch is on (--i: 1), then we multiply it with the complementary of the switch value (1 - var(--i)). This way, for the same non-zero angular value of 30deg, we have:
when the switch is off (--i: 0), calc((1 - var(--i))*30deg) computes to (1 - 0)*30deg = 1*30deg = 30deg
when the switch is on (--i: 1), calc((1 - var(--i))*30deg) computes to (1 - 1)*30deg = 0*30deg = 0deg
You can see this concept illustrated below:
Switching between a zero and a non-zero value (live demo, no Edge support due to calc() not working for angle values)
For the particular case of the loader, we use HSL values for border-color and background-color. HSL stands for hue, saturation, lightness and can be best represented visually with the help of a bicone (which is made up of two cones with the bases glued together).
HSL bicone.
The hues go around the bicone, 0° being equivalent to 360° to give us a red in both cases.
Hue wheel.
The saturation goes from 0% on the vertical axis of the bicone to 100% on the bicone surface. When the saturation is 0% (on the vertical axis of the bicone), the hue doesn't matter anymore; we get the exact same grey for all hues in the same horizontal plane.
The "same horizontal plane" means having the same lightness, which increases along the vertical bicone axis, going from 0% at the black bicone vertex to 100% at the white bicone vertex. When the lightness is either 0% or 100%, neither the hue nor the saturation matter anymore - we always get black for a lightness value of 0% and white for a lightness value of 100%.
Since we only need black and white for our ☯ symbol, the hue and saturation are irrelevant, so we zero them and then switch between black and white by switching the lightness between 0% and 100%.
.yin-yang { /* other styles that are irrelevant here */ &:before, &:after { /* other styles that are irrelevant here */ --i: 0; /* lightness of border-color when * --i: 0 is (1 - 0)*100% = 1*100% = 100% (white) * --i: 1 is (1 - 1)*100% = 0*100% = 0% (black) */ border: solid $d/6 hsl(0, 0%, calc((1 - var(--i))*100%)); /* x coordinate of transform-origin when * --i: 0 is 0*100% = 0% (left) * --i: 1 is 1*100% = 100% (right) */ transform-origin: calc(var(--i)*100%) 50%; /* lightness of background-color when * --i: 0 is 0*100% = 0% (black) * --i: 1 is 1*100% = 100% (white) */ background: hsl(0, 0%, calc(var(--i)*100%)); /* animation-delay when * --i: 0 is 0*-$t = 0s * --i: 1 is 1*-$t = -$t */ animation: s $t ease-in-out calc(var(--i)*#{-$t}) infinite alternate; } &:after { --i: 1 } }
Note that this approach doesn't work in Edge due to the fact that Edge doesn't support calc() values for animation-delay.
But what if we want to have a non-zero value when the switch is off (--i: 0) and another different non-zero value when the switch is on (--i: 1)?
Switching between two non-zero values
Let's say we want an element to have a grey background (#ccc) when the switch is off (--i: 0) and an orange background (#f90) when the switch is on (--i: 1).
The first thing we do is switch from hex to a more manageable format such as rgb() or hsl().
We could do this manually either by using a tool such as Lea Verou's CSS Colors or via DevTools. If we have a background set on an element we can cycle through formats by keeping the Shift key pressed while clicking on the square (or circle) in front of the value in DevTools. This works in both Chrome and Firefox, though it doesn't appear to work in Edge.
Changing the format from DevTools.
Even better, if we're using Sass, we can extract the components with red()/ green()/ blue() or hue()/ saturation()/ lightness() functions.
While rgb() may be the better known format, I tend to prefer hsl() because I find it more intuitive and it's easier for me to get an idea about what to expect visually just by looking at the code.
So we extract the three components of the hsl() equivalents of our two values ($c0: #ccc when the switch is off and $c1: #f90 when the switch is on) using these functions:
$c0: #ccc; $c1: #f90; $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1))
Note that we've rounded the results of the hue(), saturation() and lightness() functions as they may return a lot of decimals and we want to keep our generated code clean. We've also divided the result of the hue() function by 1deg, as the returned value is a degree value in this case and Edge only supports unit-less values inside the CSS hsl() function. Normally, when using Sass, we can have degree values, not just unit-less ones for the hue inside the hsl() function because Sass treats it as the Sass hsl() function, which gets compiled into a CSS hsl() function with a unit-less hue. But here, we have a dynamic CSS variable inside, so Sass treats this function as the CSS hsl() function that doesn't get compiled into anything else, so, if the hue has a unit, this doesn't get removed from the generated CSS.
Now we have that:
if the switch is off (--i: 0), our background is hsl($h0, $s0, $l0)
if the switch is on (--i: 1), our background is hsl($h1, $s1, $l1)
We can write our two backgrounds as:
if the switch is off (--i: 0), hsl(1*$h0 + 0*$h1, 1*$s0 + 0*$s1, 1*$l0 + 1*$l1)
if the switch is on (--i: 1), hsl(0*$h0 + 1*$h1, 0*$s0 + 1*$s1, 0*$l0 + 1*$l1)
Using the switch variable --i, we can unify the two cases:
--j: calc(1 - var(--i)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1}))
Here, we've denoted by --j the complementary value of --i (when --i is 0, --j is 1 and when --i is 1, --j is 0).
Switching between two backgrounds (live demo)
The formula above works for switching in between any two HSL values. However, in this particular case, we can simplify it because we have a pure grey when the switch is off (--i: 0).
Purely grey values have equal red, green and blue values when taking into account the RGB model.
When taking into account the HSL model, the hue is irrelevant (our grey looks the same for all hues), the saturation is always 0% and only the lightness matters, determining how light or dark our grey is.
In this situation, we can always keep the hue of the non-grey value (the one we have for the "on" case, $h1).
Since the saturation of any grey value (the one we have for the "off" case, $s0) is always 0%, multiplying it with either 0 or 1 always gives us 0%. So, given the var(--j)*#{$s0} term in our formula is always 0%, we can just ditch it and our saturation formula reduces to the product between the saturation of the "on" case $s1 and the switch variable --i.
This leaves the lightness as the only component where we still need to apply the full formula.
--j: calc(1 - var(--i)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{d1l}))
The above can be tested in this demo.
Similarly, let's say we want the font-size of some text to be 2rem when our switch is off (--i: 0) and 10vw when the switch is on (--i: 1). Applying the same method, we have:
font-size: calc((1 - var(--i))*2rem + var(--i)*10vw)
Switching between two font sizes (live demo)
Alright, let's now move on to clearing another aspect of this: what is it exactly that causes the switch to flip from on to off or the other way around?
What triggers switching
We have a few options here.
Element-based switching
This means the switch is off for certain elements and on for other elements. For example, this can be determined by parity. Let's say we want all the even elements to be rotated and have an orange background instead of the initial grey one.
.box { --i: 0; --j: calc(1 - var(--i)); transform: rotate(calc(var(--i)*30deg)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); &:nth-child(2n) { --i: 1 } }
Switching triggered by item parity (live demo, not fully functional in Edge due to calc() not working for angle values)
In the parity case, we flip the switch on for every second item (:nth-child(2n)), but we can also flip it on for every seventh item (:nth-child(7n)), for the first two items (:nth-child(-n + 2)), for all items except the first and last two (:nth-child(n + 3):nth-last-child(n + 3)). We can also flip it on just for headings or just for elements that have a certain attribute.
State-based switching
This means the switch is off when the element itself (or a parent or one of its previous siblings) is in one state and off when it's another state. In the interactive examples from the previous section, the switch was flipped when a checkbox before our element got checked or unchecked.
We can also have something like a white link that scales up and turns orange when focused or hovered:
$c: #f90; $h: round(hue($c)/1deg); $s: round(saturation($c)); $l: round(lightness($c)); a { --i: 0; transform: scale(calc(1 + var(--i)*.25)); color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); &:focus, &:hover { --i: 1 } }
Since white is any hsl() value with a lightness of 100% (the hue and saturation are irrelevant), we can simplify things by always keeping the hue and saturation of the :focus/ :hover state and only changing the lightness.
Switching triggered by state change (live demo, not fully functional in Edge due to calc() values not being supported inside scale() functions)
Media query-based switching
Another possibility is that switching is triggered by a media query, for example, when the orientation changes or when going from one viewport range to another.
Let's say we have a white heading with a font-size of 1rem up to 320px, but then it turns orange ($c) and the font-size becomes 5vw and starts scaling with the viewport width.
h5 { --i: 0; color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); font-size: calc(var(--i)*5vw + (1 - var(--i))*1rem); @media (min-width: 320px) { --i: 1 } }
Switching triggered by viewport change (live demo)
Coding a more complex example from scratch
The example we dissect here is that of the expanding search shown at the beginning of this article, inspired by this Pen, which you should really check out because the code is pretty damn clever.
Expanding search.
Note that from a usability point of view, having such a search box on a website may not be the best idea as one would normally expect the button following the search box to trigger the search, not close the search bar, but it's still an interesting coding exercise, which is why I've chosen to dissect it here.
To begin with, my idea was to do it using only form elements. So, the HTML structure looks like this:
<input id='search-btn' type='checkbox'/> <label for='search-btn'>Show search bar</label> <input id='search-bar' type='text' placeholder='Search...'/>
What we do here is initially hide the text input and then reveal it when the checkbox before it gets checked — let's dive into how that works!
First off, we use a basic reset and set a flex layout on the container of our input and label elements. In our case, this container is the body, but it could be another element as well. We also absolutely position the checkbox and move it out of sight (outside the viewport).
*, :before, :after { box-sizing: border-box; margin: 0; padding: 0; font: inherit } html { overflow-x: hidden } body { display: flex; align-items: center; justify-content: center; margin: 0 auto; min-width: 400px; min-height: 100vh; background: #252525 } [id='search-btn'] { position: absolute; left: -100vh }
So far, so good...
See the Pen by thebabydino (@thebabydino) on CodePen.
So what? We have to admit it's not exciting at all, so let's move on to the next step!
We turn the checkbox label into a big round green button and move its text content out of sight using a big negative-valued text-indent and overflow: hidden.
$btn-d: 5em; /* same as before */ [for='search-btn'] { overflow: hidden; width: $btn-d; height: $btn-d; border-radius: 50%; box-shadow: 0 0 1.5em rgba(#000, .4); background: #d9eb52; text-indent: -100vw; cursor: pointer; }
See the Pen by thebabydino (@thebabydino) on CodePen.
Next, we polish the actual search bar by:
giving it explicit dimensions
providing a background for its normal state
defining a different background and a glow for its focused state
rounding the corners on the left side using a border-radius that equals half its height
Cleaning up the placeholder a bit
$btn-d: 5em; $bar-w: 4*$btn-d; $bar-h: .65*$btn-d; $bar-r: .5*$bar-h; $bar-c: #ffeacc; /* same as before */ [id='search-bar'] { border: none; padding: 0 1em; width: $bar-w; height: $bar-h; border-radius: $bar-r 0 0 $bar-r; background: #3f324d; color: #fff; font: 1em century gothic, verdana, arial, sans-serif; &::placeholder { opacity: .5; color: inherit; font-size: .875em; letter-spacing: 1px; text-shadow: 0 0 1px, 0 0 2px } &:focus { outline: none; box-shadow: 0 0 1.5em $bar-c, 0 1.25em 1.5em rgba(#000, .2); background: $bar-c; color: #000; } }
See the Pen by thebabydino (@thebabydino) on CodePen.
At this point, the right edge of the search bar coincides with the left edge of the button. However, we want a bit of overlap — let's say an overlap such that the right edge of the search bar coincides with the button's vertical midline. Given that we have a flexbox layout with align-items: center on the container (the body in our case), the assembly made up of our two items (the bar and the button) remains middle-aligned horizontally even if we set a margin on one or on the other or on both in between those items. (On the left of the leftmost item or on the right of the rightmost item is a different story, but we won't be getting into that now.)
Creating overlap, keeping alignment (live demo).
That's an overlap of .5*$btn-d minus half a button diameter, which is equivalent to the button's radius. We set this as a negative margin-right on the bar. We also adjust the padding on the right of the bar so that we compensate for the overlap:
$btn-d: 5em; $btn-r: .5*$btn-d; /* same as before */ [id='search-bar'] { /* same as before */ margin-right: -$btn-r; padding: 0 calc(#{$btn-r} + 1em) 0 1em; }
We now have the bar and the button in the positions for the expanded state:
See the Pen by thebabydino (@thebabydino) on CodePen.
Except the bar follows the button in DOM order, so it's placed on top of it, when we actually want the button on top. Fortunately, this has an easy fix (at least for now — it won't be enough later, but let's deal with one issue at a time).
[for='search-btn'] { /* same as before */ position: relative; }
Now that we've given the button a non-static position value, it's on top of the bar:
See the Pen by thebabydino (@thebabydino) on CodePen.
In this state, the total width of the bar and button assembly is the bar width $bar-w plus the button's radius $btn-r (which is half the button diameter $btn-d) because we have an overlap for half the button. In the collapsed state, the total width of the assembly is just the button diameter $btn-d.
Expanded vs. collapsed state (live).
Since we want to keep the same central axis when going from the expanded to the collapsed state, we need to shift the button to the left by half the assembly width in the expanded state (.5*($bar-w + $btn-r)) minus the button's radius ($btn-r).
We call this shift $x and we use it with minus on the button (since we shift the button to the left and left is the negative direction of the x axis). Since we want the bar to collapse into the button, we set the same shift $x on it, but in the positive direction (as we shift the bar to the right of the x axis).
We're in the collapsed state when the checkbox isn't checked and in the expanded state when it isn't. This means our bar and button are shifted with a CSS transform when the checkbox isn't checked and in the position we currently have them in (no transform) when the checkbox is checked.
In order to do this, we set a variable --i on the elements following our checkbox — the button (created with the label for the checkbox) and the search bar. This variable is 0 in the collapsed state (when both elements are shifted and the checkbox isn't checked) and 1 in the expanded state (when our bar and button are in the positions they currently occupy, no shift, and the checkbox is checked).
$x: .5*($bar-w + $btn-r) - $btn-r; [id='search-btn'] { position: absolute; left: -100vw; ~ * { --i: 0; --j: calc(1 - var(--i)) /* 1 when --i is 0, 0 when --i is 1 */ } &:checked ~ * { --i: 1 } } [for='search-btn'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is -$x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{-$x})); } [id='search-bar'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is $x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{$x})); }
And we now have something interactive! Clicking the button toggles the checkbox state (because the button has been created using the label of the checkbox).
See the Pen by thebabydino (@thebabydino) on CodePen.
Except now the button is a bit difficult to click since it's under the text input again (because we've set a transform on the bar and this establishes a stacking context). The fix is pretty straightforward — we need to add a z-index to the button and this moves it above the bar.
[for='search-btn'] { /* same as before */ z-index: 1; }
See the Pen by thebabydino (@thebabydino) on CodePen.
But we still have another bigger problem: we can see the bar coming out from under the button on the right side. In order to fix this, we set clip-path with an inset() value on the bar. This specifies a clipping rectangle with the help of the distances from the top, right, bottom and left edges of the element's border-box. Everything outside this clipping rectangle gets cut out and only what's inside is displayed.
How the inset() function works (live).
In the illustration above, each distance is going inward from the edges of the border-box. In this case, they're positive. But they can also go outwards, in which case they're negative and the corresponding edges of the clipping rectangle are outside the element's border-box.
At first, you may think we'd have no reason to ever do that, but in our particular case, we do!
We want the distances from the top (dt), bottom (db) and left (dl) to be negative and big enough to contain the box-shadow that extends outside the element's border-box in the :focus state as we don't want it to get clipped out. So the solution is to create a clipping rectangle with edges outside the element's border-box in these three directions.
The distance from the right (dr) is the full bar width $bar-w minus a button radius $btn-r in the collapsed case (checkbox not checked, --i: 0) and 0 in the expanded case (checkbox checked, --i: 1).
$out-d: -3em; [id='search-bar'] { /* same as before */ clip-path: inset($out-d calc(var(--j)*#{$bar-w - $btn-r}) $out-d $out-d); }
We now have a search bar and button assembly that expands and collapses on clicking the button.
See the Pen by thebabydino (@thebabydino) on CodePen.
Since we don't want an abrupt change in between the two states, we use a transition:
[id='search-btn'] { /* same as before */ ~ * { /* same as before */ transition: .65s; } }
We also want our button's background to be green in the collapsed case (checkbox not checked, --i: 0) and pink in the expanded case (checkbox checked, --i: 1). For this, we use the same technique as before:
[for='search-btn'] { /* same as before */ $c0: #d9eb52; // green for collapsed state $c1: #dd1d6a; // pink for expanded state $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); }
Now we're getting somewhere!
See the Pen by thebabydino (@thebabydino) on CodePen.
What we still need to do is create the icon that morphs between a magnifier in the collapsed state and an "x" in the expanded state to indicate a closing action. We do this with the :before and :after pseudo-elements. We begin by deciding on a diameter for the magnifier and how much of this diameter the width of the icon lines represent.
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d;
We absolutely position both pseudo-elements in the middle of the button taking their dimensions into account. We then make them inherit their parent's transition. We give the :before a background, as this will be the handle of our magnifier, make the :after round with border-radius and give it an inset box-shadow.
[for='search-btn'] { /* same as before */ &:before, &:after { position: absolute; top: 50%; left: 50%; margin: -.5*$ico-d; width: $ico-d; height: $ico-d; transition: inherit; content: '' } &:before { margin-top: -.4*$ico-w; height: $ico-w; background: currentColor } &:after { border-radius: 50%; box-shadow: 0 0 0 $ico-w currentColor } }
We can now see the magnifier components on the button:
See the Pen by thebabydino (@thebabydino) on CodePen.
In order to make our icon to look more like a magnifier, we translate both of its components outwards by a quarter of the magnifier's diameter. This means translating the handle to the right, in the positive direction of the x axis by .25*$ico-d and the main part to the left, in the negative direction of the x axis by the same .25*$ico-d.
We also scale the handle (the :before pseudo-element) horizontally to half its width with respect to its right edge (which means a transform-origin of 100% along the x axis).
We only want this to happen in the collapsed state (checkbox not checked, --i is 0 and, consequently --j is 1), so we multiply the translation amounts by --j and also use --j to condition the scaling factor:
[for='search-btn'] { /* same as before */ &:before { /* same as before */ height: $ico-w; transform: /* collapsed: not checked, --i is 0, --j is 1 * translation amount is 1*.25*$d = .25*$d * expanded: checked, --i is 1, --j is 0 * translation amount is 0*.25*$d = 0 */ translate(calc(var(--j)*#{.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 - 1*.5 = 1 - .5 = .5 * expanded: checked, --i is 1, --j is 0 * scaling factor is 1 - 0*.5 = 1 - 0 = 1 */ scalex(calc(1 - var(--j)*.5)) } &:after { /* same as before */ transform: translate(calc(var(--j)*#{-.25*$ico-d})) } }
We now have thew magnifier icon in the collapsed state:
See the Pen by thebabydino (@thebabydino) on CodePen.
Since we want both icon components to be rotated by 45deg, we add this rotation on the button itself:
[for='search-btn'] { /* same as before */ transform: translate(calc(var(--j)*#{-$x})) rotate(45deg); }
Now we have the look we want for the collapsed state:
See the Pen by thebabydino (@thebabydino) on CodePen.
This still leaves the expanded state, where we need to turn the round :after pseudo-element into a line. We do this by scaling it down along the x axis and bringing its border-radius from 50% to 0%. The scaling factor we use is the ratio between the width $ico-w of the line we want to get and the diameter $ico-d of the circle it forms in the collapsed state. We've called this ratio $ico-f.
Since we only want to do this in the expanded state, when the checkbox is checked and --i is 1, we make both the scaling factor and the border-radius depend on --i and --j:
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ &:after{ /* same as before */ /* collapsed: not checked, --i is 0, --j is 1 * border-radius is 1*50% = 50% * expanded: checked, --i is 1, --j is 0 * border-radius is 0*50% = 0 */ border-radius: calc(var(--j)*50%); transform: translate(calc(var(--j)*#{-.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 + 0*$ico-f = 1 * expanded: checked, --i is 1, --j is 0 * scaling factor is 0 + 1*$ico-f = $ico-f */ scalex(calc(1 - var(--j)*.5)) } }
See the Pen by thebabydino (@thebabydino) on CodePen.
Hmm, almost, but not quite. Scaling has also shrunk our inset box-shadow along the x axis, so let's fix that with a second inset shadow that we only get in the expanded state (when the checkbox is checked and --i is 1) and therefore, its spread and alpha depend on --i:
$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ --hsl: 0, 0%, 0%; color: HSL(var(--hsl)); &:after{ /* same as before */ box-shadow: inset 0 0 0 $ico-w currentcolor, /* collapsed: not checked, --i is 0, --j is 1 * spread radius is 0*.5*$ico-d = 0 * alpha is 0 * expanded: checked, --i is 1, --j is 0 * spread radius is 1*.5*$ico-d = .5*$ico-d * alpha is 1 */ inset 0 0 0 calc(var(--i)*#{.5*$ico-d}) HSLA(var(--hsl), var(--i)) } }
This gives us our final result!
See the Pen by thebabydino (@thebabydino) on CodePen.
A few more quick examples
The following are a few more demos that use the same technique. We won't be building these from scratch — we'll merely go through the basic ideas behind them.
Responsive banners
Screenshot collage (live demo, not fully functional in Edge due to using a calc() value for font-size).
In this case, our actual elements are the smaller rectangles in front, while the number squares and the bigger rectangles in the back are created with the :before and :after pseudo-elements, respectively.
The backgrounds of the number squares are individual and set using a stop list variable --slist that's different for each item.
<p style='--slist: #51a9ad, #438c92'><!-- 1st paragraph text --></p> <p style='--slist: #ebb134, #c2912a'><!-- 2nd paragraph text --></p> <p style='--slist: #db4453, #a8343f'><!-- 3rd paragraph text --></p> <p style='--slist: #7eb138, #6d982d'><!-- 4th paragraph text --></p>
The things that influence the styles on the banners are the parity and whether we're in the wide, normal or narrow case. These give us our switch variables:
html { --narr: 0; --comp: calc(1 - var(--narr)); --wide: 1; @media (max-width: 36em) { --wide: 0 } @media (max-width: 20em) { --narr: 1 } } p { --parity: 0; &:nth-child(2n) { --parity: 1 } }
The number squares are absolutely positioned and their placement depends on parity. If the --parity switch is off (0), then they're on the left. If it's on (1), then they're on the right.
A value of left: 0% aligns with the left edge of the number square along the left edge of its parent, while a value of left: 100% aligns its left edge along the parent's right edge.
In order to have the right edge of the number square aligned with the right edge of its parent, we need to subtract its own width out of the previous 100% value. (Remember that % values in the case of offsets are relative to the parent's dimensions.)
left: calc(var(--parity)*(100% - #{$num-d}))
...where $num-d is the size of the numbering square.
In the wide screen case, we also push the numbering outwards by 1em — this means subtracting 1em from the offset we have so far for odd items (having the --parity switch off) and adding 1em to the offset we have so far for even items (having the --parity switch on).
Now the question here is... how do we switch the sign? The simplest way to do it is by using the powers of -1. Sadly, we don't have a power function (or a power operator) in CSS, even though it would be immensely useful in this case:
/* * for --parity: 0, we have pow(-1, 0) = +1 * for --parity: 1, we have pow(-1, 1) = -1 */ pow(-1, var(--parity))
This means we have to make it work with what we do have (addition, subtraction, multiplication and division) and that leads to a weird little formula... but, hey, it works!
/* * for --parity: 0, we have 1 - 2*0 = 1 - 0 = +1 * for --parity: 1, we have 1 - 2*1 = 1 - 2 = -1 */ --sign: calc(1 - 2*var(--parity))
This way, our final formula for the left offset, taking into account both the parity and whether we're in the wide case (--wide: 1) or not (--wide: 0), becomes:
left: calc(var(--parity)*(100% - #{$num-d}) - var(--wide)*var(--sign)*1em)
We also control the width of the paragraphs with these variables and max-width as we want it to have an upper limit and only fully cover its parent horizontally in the narrow case (--narr: 1):
width: calc(var(--comp)*80% + var(--narr)*100%); max-width: 35em;
The font-size also depends on whether we're in the narrow case (--narr: 1) or not (--narr: 0):
calc(.5rem + var(--comp)*.5rem + var(--narr)*2vw)
...and so do the horizontal offsets for the :after pseudo-element (the bigger rectangle in the back) as they're 0 in the narrow case (--narr: 1) and a non-zero offset $off-x otherwise (--narr: 0):
right: calc(var(--comp)*#{$off-x}); left: calc(var(--comp)*#{$off-x});
Hover and focus effects
Effect recording (live demo, not fully functional in Edge due to nested calc() bug).
This effect is created with a link element and its two pseudo-elements sliding diagonally on the :hover and :focus states. The link's dimensions are fixed and so are those of its pseudo-elements, set to the diagonal of their parent $btn-d (computed as the hypotenuse in the right triangle formed by a width and a height) horizontally and the parent's height vertically.
The :before is positioned such that its bottom left corner coincides to that of its parent, while the :after is positioned such that its top right corner coincides with that of its parent. Since both should have the same height as their parent, the vertical placement is resolved by setting top: 0 and bottom: 0. The horizontal placement is handled in the exact same way as in the previous example, using --i as the switch variable that changes value between the two pseudo-elements and --j, its complementary (calc(1 - var(--i))):
left: calc(var(--j)*(100% - #{$btn-d}))
We set the transform-origin of the :before to its left-bottom corner (0% 100%) and :after to its right-top corner (100% 0%), again, with the help of the switch --i and its complementary --j:
transform-origin: calc(var(--j)*100%) calc(var(--i)*100%)
We rotate both pseudo-elements to the angle between the diagonal and the horizontal $btn-a (also computed from the triangle formed by a height and a width, as the arctangent of the ratio between the two). With this rotation, the horizontal edges meet along the diagonal.
We then shift them outwards by their own width. This means we'll use a different sign for each of the two, again depending on the switch variable that changes value in between the :before and :after, just like in the previous example with the banners:
transform: rotate($btn-a) translate(calc((1 - 2*var(--i))*100%))
In the :hover and :focus states, this translation needs to go back to 0. This means we multiply the amount of the translation above by the complementary --q of the switch variable --p that's 0 in the normal state and 1 in the :hover or :focus state:
transform: rotate($btn-a) translate(calc(var(--q)*(1 - 2*var(--i))*100%))
In order to make the pseudo-elements slide out the other way (not back the way they came in) on mouse-out or being out of focus, we set the switch variable --i to the value of --p for :before and to the value of --q for :after, reverse the sign of the translation, and make sure we only transition the transform property.
Responsive infographic
Screenshot collage with the grid lines and gaps highlighted (live demo, no Edge support due to CSS variable and calc() bugs).
In this case, we have a three-row, two-column grid for each item (article element), with the third row collapsed in the wide screen scenario and the second column collapsed in the narrow screen scenario. In the wide screen scenario, the widths of the columns depend on the parity. In the narrow screen scenario, the first column spans the entire content-box of the element and the second one has width 0. We also have a gap in between the columns, but only in the wide screen scenario.
// formulas for the columns in the wide screen case, where // $col-a-wide is for second level heading + paragraph // $col-b-wide is for the first level heading $col-1-wide: calc(var(--q)*#{$col-a-wide} + var(--p)*#{$col-b-wide}); $col-2-wide: calc(var(--q)*#{$col-b-wide} + var(--p)*#{$col-a-wide}); // formulas for the general case, combining the wide and normal scenarios $row-1: calc(var(--i)*#{$row-1-wide} + var(--j)*#{$row-1-norm}); $row-2: calc(var(--i)*#{$row-2-wide} + var(--j)*#{$row-2-norm}); $row-3: minmax(0, auto); $col-1: calc(var(--i)*#{$col-1-wide} + var(--j)*#{$col-1-norm}); $col-2: calc(var(--i)*#{$col-2-wide}); $art-g: calc(var(--i)*#{$art-g-wide}); html { --i: var(--wide, 1); // 1 in the wide screen case --j: calc(1 - var(--i)); @media (max-width: $art-w-wide + 2rem) { --wide: 0 } } article { --p: var(--parity, 0); --q: calc(1 - var(--p)); --s: calc(1 - 2*var(--p)); display: grid; grid-template: #{$row-1} #{$row-2} #{$row-3}/ #{$col-1} #{$col-2}; grid-gap: 0 $art-g; grid-auto-flow: column dense; &:nth-child(2n) { --parity: 1 } }
Since we've set grid-auto-flow: column dense, we can get away with only setting the first level heading to cover an entire column (second one for odd items and first one for even items) in the wide screen case and let the second level heading and the paragraph text fill the first free available cells.
// wide case, odd items: --i is 1, --p is 0, --q is 1 // we're on column 1 + 1*1 = 2 // wide case, even items: --i is 1, --p is 1, --q is 0 // we're on column 1 + 1*0 = 1 // narrow case: --i is 0, so var(--i)*var(--q) is 0 and we're on column 1 + 0 = 1 grid-column: calc(1 + var(--i)*var(--q)); // always start from the first row // span 1 + 2*1 = 3 rows in the wide screen case (--i: 1) // span 1 + 2*0 = 1 row otherwise (--i: 0) grid-row: 1/ span calc(1 + 2*var(--i));
For each item, a few other properties depend on whether we're in the wide screen scenario or not.
The vertical margin, vertical and horizontal padding values, box-shadow offsets and blur are all bigger in the wide screen case:
$art-mv: calc(var(--i)*#{$art-mv-wide} + var(--j)*#{$art-mv-norm}); $art-pv: calc(var(--i)*#{$art-pv-wide} + var(--j)*#{$art-p-norm}); $art-ph: calc(var(--i)*#{$art-ph-wide} + var(--j)*#{$art-p-norm}); $art-sh: calc(var(--i)*#{$art-sh-wide} + var(--j)*#{$art-sh-norm}); article { /* other styles */ margin: $art-mv auto; padding: $art-pv $art-ph; box-shadow: $art-sh $art-sh calc(3*#{$art-sh}) rgba(#000, .5); }
We have a non-zero border-width and border-radius in the wide screen case:
$art-b: calc(var(--i)*#{$art-b-wide}); $art-r: calc(var(--i)*#{$art-r-wide}); article { /* other styles */ border: solid $art-b transparent; border-radius: $art-r; }
In the wide screen scenario, we limit the items' width, but let it be 100% otherwise.
$art-w: calc(var(--i)*#{$art-w-wide} + var(--j)*#{$art-w-norm}); article { /* other styles */ width: $art-w; }
The direction of the padding-box gradient also changes with the parity:
background: linear-gradient(calc(var(--s)*90deg), #e6e6e6, #ececec) padding-box, linear-gradient(to right bottom, #fff, #c8c8c8) border-box;
In a similar manner, margin, border-width, padding, width, border-radius, background gradient direction, font-size or line-height for the headings and the paragraph text also depend on whether we're in the wide screen scenario or not (and, in the case of the first level heading's border-radius or background gradient direction, also on the parity).
The post DRY Switching with CSS Variables: The Difference of One Declaration appeared first on CSS-Tricks.
DRY Switching with CSS Variables: The Difference of One Declaration published first on https://deskbysnafu.tumblr.com/
0 notes