COMPONENTS
Note: Assume all CSS content have the following colour variables ie. rgb(var(--color))
This is to allow background transparency without enclosing container on elements eg. rgba(var(--color), 0.5)
:root { --primary: 27, 63, 255; --secondary: 96, 159, 255; --background: 244, 246, 255; --foreground: 0, 0, 0; --accent: 221, 221, 221; } .darked:root { --primary: 0, 228, 255; --secondary: 0, 171, 191; --background: 0, 17, 20; --foreground: 255, 255, 255; --accent: 69, 69, 69; }
Floating Action Buttons
Action menus on corners of screen with lists of buttons, responsive to content
On scroll and on mobile screens, menus will hide on scroll down and scrolled down certain level
Assume all menus fit on a min screen aspect-ratio of 1:1, max-width 680px
Create .action-menu class with location of menu (bottom-left/bottom-right) as class name, children .fab class
Note: FAB implementation is separate from menu design, only generic (.fab) and specific classes required
.action-menu { position: fixed; display: block; display: flex; flex-direction: column; gap: 8px; transition: opacity .2s linear, background .2s linear; z-index: 7; } .action-menu.bottom-left { bottom: 20px; left: 20px; } .action-menu.bottom-right { bottom: 20px; right: 20px; } .action-menu.bottom-left { left: calc(50% - 405px); transition: all 0.2s ease-out; } .action-menu.bottom-right { right: calc(50% - 400px); transition: all 0.2s ease-out; } .action-menu.bottom-left.hide { opacity: 0; z-index: -1; } .action-menu.bottom-right.hide { opacity: 0; z-index: -1; } a.material-icons { text-decoration: none; } span.small-icons { vertical-align: sub; } a.fab { border: 1px solid rgb(var(--foreground)); background-color: rgb(var(--background)); box-shadow: 3px 3px rgb(var(--foreground)); border-radius: 8px; color: rgb(var(--foreground)); padding: 8px 6px 6px 6px; cursor: pointer; line-height: 1; text-decoration: none; transition: background .2s linear; font-size: 36px; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } a.fab:hover, a.fab:focus-within { box-shadow: 1px 1px; transform: translate(2px, 2px); } /*MEDIA QUERIES - MOBILE SCREENS*/ @media only screen and (max-width: 880px) { .action-menu.bottom-left { left: 15px; transform: translateX(0); } .action-menu.bottom-right { right: 15px; transform: translateX(0); } .action-menu.bottom-left.hide { transform: translateX(-15em); } .action-menu.bottom-right.hide { transform: translateX(15em); } }
window.addEventListener('load', initFab); window.addEventListener('scroll', toggleActionsOnScroll); // initial load function initFab() { // if share feature unavailable, remove button if(!navigator.share) { document.querySelector('.fab.share')?.remove(); } } // scroll event function toggleActionsOnScroll() { // position of buttons on scroll let pageDown = document.body.scrollTop > 0.3 * document.documentElement.clientHeight || document.documentElement.scrollTop > 0.3 * document.documentElement.clientHeight; if (pageDown) { toggleActions(['.fab.share', '.fab.search', '.fab.top'], '.action-menu.bottom-right'); } else { toggleActions(['.fab.share', '.fab.search', '.fab.theme'], '.action-menu.bottom-right'); } // works with header, when scroll down/up, hide/show buttons let st = window.pageYOffset || document.documentElement.scrollTop; let diff = st - window['scroll-top']; let scrollDown = pageDown && diff <= 0.1; if(scrollDown || !pageDown) { document.querySelector('.action-menu.bottom-left')?.classList.remove('hide'); document.querySelector('.action-menu.bottom-right')?.classList.remove('hide'); } else if(window.innerWidth < 500) { document.querySelector('.action-menu.bottom-left')?.classList.add('hide'); document.querySelector('.action-menu.bottom-right')?.classList.add('hide'); } window['scroll-top'] = st; } // generic toggle FAB function based on residing menu function toggleActions(showElements, parentElement) { if(!showElements || !parentElement) return; if(typeof(parentElement) == 'string') parentElement = document.querySelector(parentElement); // hide all in parent element: assume has fab class children for(let fab of parentElement.querySelectorAll('.fab')) { fab.classList.add('hidden'); } // show button(s) specified if(Array.isArray(showElements)) { for(let elem of showElements) { if(typeof(elem) == 'string') elem = document.querySelector(elem); if(elem != null) elem.classList.remove('hidden'); } } else if(typeof(showElements) == 'string') { showElements = document.querySelector(showElements); if(showElements != null) showElements.classList.remove('hidden'); } else if(showElements != null) showElements.classList.remove('hidden'); }
<div class="action-menu bottom-left"> <div class="fab back">...</div> </div> <div class="action-menu bottom-right"> <div class="fab theme">...</div> </div>
Pop-Up Content
On right-click of external links, supported URLs will show embed content/iframe
.popup { display: inline; cursor: pointer; } .popup > .text { display: inline; } .popup > .text:hover { color: rgb(var(--foreground)); } .popup > .content { display: none; padding-top: 10px; left: 0; right: 0; margin: auto; text-align: center; max-width: 98%; animation: fade-in 0.2s ease-in 0.1s both; } .popup.show > .content { display: block; position: absolute; z-index: 9; } .overlay { background-color: rgb(var(--background), 0.5); transition: opacity .3s linear; position: fixed; width: 100%; height: 120%; bottom: 0; top: 0; left: 0; z-index: 8; cursor: pointer; } .overlay.hide { z-index: -1; opacity: 0; } @keyframes fade-in { from { opacity: 0; transform: translate3d(0, -5%, 0); } to { opacity: 1; transform: translate3d(0, 0, 0); } }
window.addEventListener('load', addPopupTriggers); function addPopupTriggers() { for (let a of document.querySelectorAll('a[target="_blank"]')) if(!a.classList.contains('ignore')) // for production remove ignore class a.addEventListener('mouseover', renderPopup); } function addPopupEvents() { for (let popup of document.querySelectorAll('.popup')) { popup.querySelector('.text').href = popup.getAttribute('data-url'); popup.addEventListener('contextmenu', togglePopup); let dataUrl = popup.getAttribute('data-url').toLowerCase(); if(dataUrl.includes('.jpg') || dataUrl.includes('.png') || dataUrl.includes('.gif') || dataUrl.includes('blogger.googleusercontent.com')) popup.addEventListener('click', togglePopup); else popup.querySelector('.content').addEventListener('click', closePopups); } } function closePopups() { // kill youtube videos playing for(let video of document.querySelectorAll('.yt-video')) video.contentWindow.postMessage('{"event":"command","func":"stopVideo","args":""}', '*'); // close all popups for (let popup of document.querySelectorAll('.popup.show')) { popup.classList.remove('show'); } // hide overlay hideOverlay(); } function renderPopup() { event.preventDefault(); let processLink = this.href; //exclusion for blogger images if ((processLink.includes('blogspot.com') || processLink.includes('blogger.googleusercontent.com')) && this.target == '') return; //exclusion for if target is not _blank if (this.target == '') return; //exclusion class, if any if (this.classList.contains('opt-out')) return; //if not compatible for any design let newContent = generatePopupContent(processLink); if (newContent == null) return; let thumbnail = document.createElement('div'); thumbnail.classList.add('popup'); let initial = document.createElement('a'); initial.classList.add('text'); initial.innerHTML = this.innerHTML; let focus = document.createElement('div'); focus.classList.add('content'); focus.classList.add('fade-in'); focus.innerHTML = newContent; focus.addEventListener('click', closePopups); thumbnail.appendChild(initial); thumbnail.appendChild(focus); thumbnail.setAttribute('data-url', processLink); renderEmbedProcess(); this.outerHTML = thumbnail.outerHTML; addPopupEvents(); return thumbnail; } function renderEmbedProcess() { try { if(document.querySelector('#tweet') == null) { let tweet = document.createElement('script'); tweet.id = 'tweet'; tweet.src = 'https://platform.twitter.com/widgets.js'; tweet.type = 'text/javascript'; tweet.charset = 'utf-8'; tweet.async = 'async'; document.head.appendChild(tweet); } if(document.querySelector('#insta') == null) { let insta = document.createElement('script'); insta.id = 'insta'; insta.src = 'https://www.instagram.com/embed.js'; insta.type = 'text/javascript'; insta.charset = 'utf-8'; insta.async = 'async'; document.head.appendChild(insta); } if(typeof twttr != 'undefined') twttr.widgets.load(); // to render twitter embed if(typeof instgrm != 'undefined') window.instgrm.Embeds.process(); // to render instagram embed return true; } catch(err) { console.error(err); return false; } } function generatePopupContent(url) { if (!url) return null; if (url.includes('.jpg') || url.includes('.png') || url.includes('.gif') || url.includes('blogger.googleusercontent.com')) { //process image return ''.replace('_URL_', url); } if (url.includes('music.apple.com')) { //process itunes embed let isTrack = url.includes('i='); return '' .replace('_HEIGHT_', isTrack ? 170 : 450) .replace('_WIDTH_' , isTrack ? 660 : 290) .replace('_URL_', url.replace('music.apple.com', 'embed.music.apple.com')); } if (url.includes('twitter.com') || url.includes('x.com')) { //process twitter embed, data-height for timeline is max width if(url.includes('/status/')) { return '' .replace('_data_theme_', document.querySelector('html').classList.contains('darked') ? 'data-theme="dark"' : '') .replace('_URL_', url.replace('x.com', 'twitter.com')) .replace('_DATA_HEIGHT_', 0.6*window.innerHeight); } } if (url.includes('youtube.com/watch') || url.includes('youtu.be/') || url.includes('youtube.com/shorts')) { //process youtube embed let id = url.substring(url.indexOf('?v=') + 3); let altDomain = url.includes('youtu.be/'); let isShorts = url.includes('youtube.com/shorts'); if(altDomain) id = url.substring(url.indexOf('youtu.be/') + 9); if(isShorts) id = url.substring(url.indexOf('youtube.com/shorts/') + 19, url.length); return '' .replace('_ID_', id) .replace('_STYLE_', isShorts ? 'min-height: 360px;' : 'max-height: 360px;'); } if (url.includes('instagram.com/p/') || url.includes('instagram.com/reel/')) { //process instagram embed return '' .replace('_STYLE_', window.innerWidth >= 576 ? 'width:550px;' : '') .replace('_URL_', url); } if (url.includes('jisho.org/search/')) { //process page as iframe return '' .replace('_URL_', url); } return null; } function togglePopup() { event.preventDefault(); if (this.classList.contains('show')) { //hide closePopups(); } else { //display closePopups(); this.classList.add('show'); showOverlay(); if(typeof fixExternalFrame == 'function') fixExternalFrame(this); renderEmbedProcess(); // if below threshold height scroll up, else open popup without scroll let thresholdHeight = 0.4*window.innerHeight; window.scrollTo({ top: document.querySelector('html').scrollTop + this.getBoundingClientRect().top - (this.getBoundingClientRect().top >= thresholdHeight ? thresholdHeight : this.getBoundingClientRect().top), behavior: 'smooth' }); } } function showOverlay() { let overlay = document.createElement('div'); overlay.className = 'overlay'; overlay.addEventListener('click', closePopups); overlay.addEventListener('contextmenu', function() { event.preventDefault(); }); document.body.appendChild(overlay); } function hideOverlay() { document.querySelector('.overlay')?.remove(); }
Displayed initially as normal link tag with target="_blank" (open in new window/tab)
This fallback and integration is as simple as adding a link like <a href="[LINK]" target="_blank"> [TEXT] </a> so it's inline.
"There's a thing called give and take, you know?" Yeah Isaki gave you a kiss so she can take your- All according to keikaku haha
When mouse hover, link will be transformed to class popup if URL is supported (click tag to close popup)
Container class "popup", first child class "text", other children class "content"
The link will transform into <div class="popup" data=url="[LINK]"> <div class="text">[TEXT]</div> <div class="content">[CONTENT]</div> </div> and behave the same on left click, just that right click will show popup, if available.
Jisho.org frame
"There's a thing called give and take, you know?" Yeah Isaki gave you a kiss so she can take your- All according to keikaku haha
Apple Music release/track embeds
I also took the chance to listen to some live albums including
wacci's live from 2021, and
SPYAIR's live from last year
featuring YOSUKE the new singer. Don't ask if I listened to all, I only pick
the
ones
I like. IKE-san still better though. *laughs* Alright, it's different, it's
like if someone shaved their beard away, you know?
Twitter/X posts, Instagram post/reel embeds
If not for IDOLY PRIDE's live tweets from
the lot
of them (ie. not MusicRay'n and their tiny images haha) I would have died from
anxiety. Instead I'm going to die of sensory overload on Instagram HAHA
just see
for yourself
dude
YouTube video/YouTube Shorts embeds
Okay, didn't expect YouTube to recommend me
Ogura Yui's halloween livestream
yesterday, it was pretty good for what it is, she just naturally gave us
fanservice I can't even. *giggles*
The new song's dance video
though. *laughs*
Blogger images (popup will also work with left click)
No, no overall assessment you can leave now- Koharu-chan. *laughs*
Carousel
Container class "carousel", all child items class "item", all but first child class "hide"
Click/tap container fast (<200ms) will turn off transition (for observing differences)
.carousel { display: block; position: relative; transition: height 0.2s; padding-top: 1em; } .carousel.fast { transition: none; } .carousel .item { position: absolute; left: 0; right: 0; opacity: 1; transition: opacity 0.4s, left 0.2s; } .carousel.fast .item { transition: none; } .carousel .item img, .carousel .item video { cursor: pointer; border: 2px solid rgb(var(--secondary)); transition: border 0.2s; border-bottom-width: 1em; } .carousel .item tr:hover img, .carousel .item tr:hover video { object-fit: cover; border-color: rgb(var(--primary)); } .carousel .item.hide { visibility: hidden; left: 80%; opacity: 0; } .carousel .item[data-index][data-length]::before { content: attr(data-index) " / " attr(data-length); position: absolute; top: -1em; left: 0; right: 0; font-size: 0.8em; text-align: center; } .carousel .item.text { color: rgb(var(--primary)); text-decoration: underline; } .carousel-dialog { background-color: rgb(var(--background)); color: rgb(var(--foreground)); }
window.addEventListener('load', setCarousel); function setCarousel() { if(Array.from(document.querySelectorAll('.carousel img')).filter(i => i.parentElement.tagName.toLowerCase() === 'a').length > 0) { console.error("Carousel has image with link: Will prevent carousel function"); return; } if(Array.from(document.querySelectorAll('.carousel')).filter(i => i.querySelectorAll('.item:not(.hide)').length == 1).length != document.querySelectorAll('.carousel').length) { console.error("Carousel initial view fail: Will prevent carousel function"); return; } for (let carousel of document.querySelectorAll('.carousel')) { carousel.style.height = carousel.querySelector('.item:not(.hide)')?.offsetHeight + 'px'; let counter = 1; let items = carousel.querySelectorAll('.item'); for (let item of items) { if(!item.hasAttribute('data-index')) { item.setAttribute('data-index', counter++); item.setAttribute('data-length', items.length); } for(let media of item.querySelectorAll('img, video')) { media.onclick = function() { showNextCarouselItem(event.target.closest('.carousel')); }; } } } } function showNextCarouselItem(tn) { // all children let tc = tn.querySelectorAll('.item'); // identify active child let active = Array.from(tc).findIndex(t => !t.classList.contains('hide')); if (active == null) return; // hide all for(let t of tc) { t.classList.add('hide'); } // set next child to display, or first if reach last child let nextActive = tc[active].nextElementSibling; if(!nextActive) nextActive = tn.firstElementChild; nextActive.classList.remove('hide'); // fast mode: transition disabled if click/tap fast enough if(new Date() - window['tn'] < 200) tn.classList.add('fast'); else window['tn'] = new Date(); // set height of container setTimeout(function() { tn.style.height = nextActive.offsetHeight + 'px'; }, tn.classList.contains('fast') ? 0 : 200); }
<div class="carousel"> <div class="item"></div> <div class="item hide"></div> <div class="item hide"></div> ... </div>
Even in a raincoat Isaki is still adorable af |
This week in violence we have: I gotta say Saki-chan you lost to Milika-san here this episode haha |
Table of Contents
Container class "agenda" contains inline style for grid customization (using variable or fallback, max-width)
Children class "item" for content (with link to section, see address bar after click)
Style variables: --columns to change no. of columns default 3, --aspect-ratio to if not 1:1
.agenda { margin-left: auto; margin-right: auto; } @supports (display: grid) { .agenda { /* if grid is supported */ display: grid; grid-template-columns: repeat(var(--columns, 3), 1fr); } } .agenda .item table.tr-caption-container { width: 100%; padding: 0; } .agenda .item a img { width: 100%; height: auto; object-fit: cover; transition: border 0.2s; border-color: rgb(var(--secondary)); border-width: 2px 2px 1em; aspect-ratio: var(--aspect-ratio, 1); } .agenda .item img:hover { border-color: rgb(var(--primary)); }
<div class="agenda" style="--aspect-ratio: [RATIO]; --columns: [COLS];"> <div class="item"></div> <div class="item"></div> <div class="item"></div> ... </div>
6 items, 3 columns, aspect ratio 2
2 items, 2 columns, default aspect ratio, inline style max width
Data Table
Responsive width table (not height responsive, no sticky rows) with horizontal borders
Use table generators (eg. DivTable.com) to render HTML and add container and inline styles (optional)
Add class "all-borders" to include vertical borders, "freeze-(left/right)" to freeze first/last column
.datatable { white-space: nowrap; overflow: auto; } .datatable table { border-collapse: collapse; } .datatable td, .datatable th { border-top: 1px solid rgb(var(--foreground)); border-bottom: 1px solid rgb(var(--foreground)); box-sizing: border-box; padding: 0 5px; } .datatable.center-text td { text-align: center; } .datatable.all-borders td, .datatable.all-borders th { border-left: 1px solid rgb(var(--foreground)); border-right: 1px solid rgb(var(--foreground)); } .datatable.freeze-left th:first-child, .datatable.freeze-left td:first-child { position: sticky; left: 0; max-width: 200px; overflow-x: hidden; text-overflow: ellipsis; background-color: rgb(var(--background)); } .datatable.freeze-right th:last-child, .datatable.freeze-right td:last-child { position: sticky; right: 0; max-width: 200px; overflow-x: hidden; text-overflow: ellipsis; background-color: rgb(var(--background)); }
<div class="datatable freeze-left freeze-right all-borders"> <table> <tbody> <tr> <th class="freeze"></th> <th></th> ... </tr> <tr> <td class="freeze" title='' ></td> <td></td> ... </tr> ... </tbody> </table> </div>
Freeze first column, inline smaller font
Item | From | Timeline | Qty | Price | Shipping | Purchased On | Arrived On | Condition |
---|---|---|---|---|---|---|---|---|
Kanno Mai "VOICE+ Vol.5" Print Tune Bromide Set | Mercari | 2023.02 | 2 | ¥999 | - | 2023.05.31 🟧 |
2023.06.09 🟧🟧 |
OK? |
Ueda Reina CD "Atrium" Animate Bonus | Y! | 2022.10 🟩 |
1 (+File) | ¥1000 | ¥210 | 2023.06.02 🟧 |
2023.06.05 🟧🟧 |
OK |
Waki Azumi "Kimi to no Mirai" Release Event Bromides | Y! | 2023.03 | 6 | ¥1000🔽 | ¥210 | 2023.06.02 🟧 |
2023.06.05 🟧🟧 |
Bent |
Nagae Rika "Re:color" Gamers Bonus | Mercari | 2023.02 | 2 | ¥1000 | - | 2023.06.02 🟧 |
2023.06.06 🟧🟧 |
OK |
Aizawa Saya "Kimi no Iro, Kimi no Koe wo Kikasete" Event Bromides | Mercari | 2022.05 🟩 |
2 | ¥444 | - | 2023.06.02 🟧 |
2023.06.08 🟧🟧 |
OK |
Koga Aoi "Seiyuu Animedia Aug 2020" Bonus | Mercari | 2020.07 🟩🟩🟩 |
1 | ¥50🔽 | ¥185 | 2023.06.02 🟧 |
2023.06.06 🟧🟧 |
OK |
Natsukawa Shiina "Yueni" Animate Bonus | Y! | 2023.05 | 1 | ¥700 | ¥210 | 2023.06.03 🟧 |
2023.06.07 🟧🟧 |
OK |
Natsukawa Shiina "Yueni" HMV Bonus | Mercari | 2023.05 | 1 | ¥300 | - | 2023.06.03 🟧 |
2023.06.08 🟧🟧 |
OK |
Kouno Marika "Gekkan Bushiroad Mar 2022" Gamers Bonus | Y! | 2022.02 🟩 |
1 | ¥400 | ¥185 | 2023.06.04 🟧 |
2023.06.07 🟧🟧 |
OK |
TrySail "Seiyuu Grand Prix Jul 2021" HMV Bonus | Y! | 2021.06 🟩🟩 |
1 | ¥700 | ¥210 | 2023.06.04 🟧 |
2023.06.09 🟧🟧 |
OK |
TrySail "Seiyuu Grand Prix Jul 2021" Animate Bonus | Y! | 2021.06 🟩🟩 |
1 | ¥300 | ¥185 | 2023.06.04 🟧 |
2023.06.07 🟧🟧 |
OK |
Takahashi Rie "Seiyuu Grand Prix Jul 2023" Bromide Set | Mercari | 2023.06 | 3 | ¥2555 | - | 2023.06.09 🟧🟧 |
2023.06.14 🟧🟧🟧 |
OK |
Takahashi Rie "TV Guide A Stars vol.02" Gamers Bonus | Y! | 2023.03 | 1 | ¥1100🔼 |
¥185 | 2023.06.09 🟧🟧 |
2023.06.13 🟧🟧🟧 |
OK |
Ueda Reina "Seiyuu Grand Prix Dec 2022" Animate Bonus | Mercari | 2022.11 🟩 |
1 | ¥399 | - | 2023.06.11 🟧🟧 |
2023.06.19 🟧🟧🟧🟧 |
OK |
TrySail "Karei One Turn / Follow You!" Set | Mercari | 2023.05 | 1 (+Card) | ¥1300 | - | 2023.06.12 🟧🟧🟧 |
2023.06.16 🟧🟧🟧 |
OK |
Kanno Mai "Kanno Mai to Issho." Bonus | Mercari | 2022.08 🟩 |
1 | ¥1500🔼 | - | 2023.06.12 🟧🟧🟧 |
2023.06.16 🟧🟧🟧 |
OK |
Amamiya Sora "10 miles to America" HMV Bonus | Mercari | 2023.06 | 2 | ¥1111 | - | 2023.06.14 🟧🟧🟧 |
2023.06.16 🟧🟧🟧 |
OK |
Asakura Momo "Seiyuu Grand Prix Apr 2022" Animate + Gamers Bonus | Mercari | 2022.03 🟩 |
2 | ¥599 | - | 2023.06.18 🟧🟧🟧 |
2023.06.21 🟧🟧🟧🟧 |
OK |
Accordion
Container "accordion" (optionally, with id) contains child class "header" to expand children "content" and optional "footer", latter to allow collapse
.accordion { cursor: pointer; } .accordion > .header { padding: 4px 8px; background-color: rgb(var(--accent)); } .accordion > .content { cursor: initial; border: 5px solid rgb(var(--accent)); border-top: 0; border-bottom: 0; } .accordion > .footer { background-color: rgb(var(--accent)); text-align: center; } .accordion > .content, .accordion > .footer { padding: 4px 8px; display: none; } .accordion.show { border-color: rgb(var(--foreground)); } .accordion.show > .content, .accordion.show > .footer { display: block; }
window.addEventListener('load', setExpander); function setExpander() { for(let accordion of document.querySelectorAll('.accordion')) { accordion.querySelector('.header')?.addEventListener('click', toggleExpander); accordion.querySelector('.footer')?.addEventListener('click', toggleExpander); } } function toggleExpander() { let parent = event.target.closest('.accordion'); parent.classList.toggle('show'); parent.querySelector('.header').scrollIntoView({ block: 'center' }); }
<div class="accordion"> <blockquote class="header">[TITLE]</blockquote> <div class="content">[CONTENT]</div> <hr> <blockquote class="footer">CLOSE</blockquote> </div>
100 QUESTIONS FOR HONDO KAEDE!!!
(Selected ones will be mentioned here in quote, above is full if you interested and can Japanese)
Q1: Origin of name
A1: Mother encountered people of fate who were named "Sakura" and
"Kaede" more than often especially more of the former, so it was a hard
decision between them
[Well thank goodness it isn't "Hondo Sakura" lol]
Q2: Nickname(s) as a kid
A2: Kae-chan, Ede-chan
[Kae-chan? Is that something I can call you after dating you twice?
Hehe]
Q3: Similarities to parents
A3: Partly similar to father, mostly mother
Q4: Earliest memory of your life
A4: Recollection of being in Higashiyama Zoo (Aichi Prefecture where
she was born) where she was trying to get a handshake with a giraffe
mascot who walked away, and (she) shouted "Wait up! If you leave I'll
throw this at you!!" while crying and running towards it
[LOL that sounded bad but the mascot with a long neck probably did not
notice her]
Q5: Things you were interested to learn as a kid
A5: Calligraphy for a year plus, swimming for around 5 years
A53: Without going overboard... hmm... shopping or sorts?
[Yeah fine I'll carry your bags for the stuff I paid haha]
Q55: What do you see in your partner
A55: Serious, honest, knowledgeable, nice, nice, nice, laughs a lot,
playful mind at heart and carries one's dreams and hopes moving forward
[Three nices? Well in her defence I already lost at being knowledgeable
so]
Q57: What you want to listen on a driving date?
A57: Whatever they like they can let it play! If it gets too loud I'll
get uncomfortable (though)
[Remind me to bring up like, Teshima Aoi on our drive lol]
Q58: Fetishes
A58: I wonder what they are...
[I wonder too, Ede-chan, you into female voice actresses?]
Q59: What tickles your fancy?
A59: I wonder what they are...
[Okay guys literally drill her with your kindness, she doesn't have that
moment so you gotta put in the effort]
Q61: Would you prefer to confess or be confessed to?
A61: I would like to confess from my side! Without hesitation!
[Well I look forward to the day I get confessed to by you-]
Q62: Own charm points
A62: Strong eyes and...
double tooth?
[Yes and YES OH MY FUCKING GOD- I thank our Lord and saviour for
stopping this girl from fixing her teeth]
Q100: Last word!
A100: Thanks for getting your hands on this! My favourite foods,
outfits, places, people... a lot of them were being compiled! This
photobook where being a voice actress but it was without the essence of
voice within, I gave the title of the book "MUTE". The stuff I worked a
lot on and I will continue to work on and enjoy them all back and forth,
so please continue to show your support!
[Ah, so THAT is how the title came about; She is right though]
Conversation
For selected lines of text with separator wrapped in container "conversation"
Content will be styled to have chat boxes, left and right aligned
Note: Uses innerText property which removes tags and only read per line in HTML (separator '\n'), do not style text
To set custom seaprator, define "data-separator" attribute, default colon ':'
To set who is right aligned, define "data-sender" attribute, else all left align
To allow enactment of lines, define "data-animate" attribute; Will auto scroll if container overflow
.conversation a { display: inline-block; padding: 12px 6px; font-size: 1.2em; } .conversation .messages { background-color: rgb(var(--secondary)); height: min(100dvh - 350px, 35em); overflow-y: auto; position: relative; resize: vertical; } .conversation .messages[data-running]::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .conversation .messages::-webkit-scrollbar { display: none; /* Chrome, Safari and Opera */ } .conversation .messages { -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } .conversation .message { display: flex; align-items: center; opacity: 1; padding: 0 8px 4px; } .conversation .message.hide { opacity: 0; } .conversation .message span { margin: 8px; padding: 6px; background-color: rgb(var(--accent)); border-radius: 10px 10px 10px 0; text-align: left; } .conversation .message[data-system] { justify-content: center; } .conversation .message[data-sender] { justify-content: flex-end; } .conversation .message[data-name]::before { content: attr(data-name); white-space: nowrap; } .conversation .message[data-sender] span { background-color: rgb(var(--secondary)); border-radius: 8px 8px 0 8px; } .conversation .message[data-system] span { background-color: rgb(var(--secondary)); } .conversation .message[data-url]:has(img) span { width: min(20em, 60%); line-height: 0; } .conversation .message[data-url] a { font-weight: normal; text-decoration: underline; font-size: small; overflow-wrap: anywhere; line-height: 0; } .conversation .message[data-url] a:hover { color: rgb(var(--background)); } .conversation .message[data-url] img { width: 100%; border-radius: 4px; } .conversation .footer { font-size: 2em; justify-content: center; cursor: pointer; }
window.addEventListener('load', processConversations); function processConversations() { for(let converse of document.querySelectorAll('.conversation')) { let separator = converse.getAttribute('data-separator') || ':'; let systemMessagePrefix = '==='; let lines = converse.innerText.split('\n'); if(lines.length < 2) { converse.innerHTML = 'Click on Editor to create a conversation list'; continue; } else converse.innerHTML = ''; let prevName = ''; for(let line of lines) { let isSystem = line.startsWith(systemMessagePrefix) && line.endsWith(systemMessagePrefix); let isUrl = line.startsWith('https://') || line.startsWith('http://'); let lineDiv = document.createElement('div'); lineDiv.classList.add('message'); let messageDiv = document.createElement('span'); if(!isSystem) // for non-system, if line has no sender, use previous lineDiv.setAttribute('data-name', !isUrl && line.includes(separator) ? line.trim().substring(0,line.indexOf(separator)).trim() : prevName); prevName = lineDiv.getAttribute('data-name'); if(converse.getAttribute('data-sender') != null) { if (isSystem) lineDiv.setAttribute('data-system', ''); else { if (isUrl) lineDiv.setAttribute('data-url', ''); if(converse.getAttribute('data-sender').toLowerCase() == lineDiv.getAttribute('data-name').toLowerCase()) lineDiv.setAttribute('data-sender', ''); else lineDiv.setAttribute('data-recipient', ''); } } messageDiv.innerText = line.trim().substring(line.indexOf(separator)+1).trim(); if(isSystem) messageDiv.innerText = line.trim(); if(isUrl) { messageDiv.innerHTML = ''; let messageLink = document.createElement('a'); messageLink.href = line.trim(); messageLink.innerText = line.trim(); messageDiv.appendChild(messageLink); let messageImg = document.createElement('img'); messageImg.src = line.trim(); messageImg.alt = ''; messageImg.setAttribute('onload', "event.target.previousElementSibling.remove();"); messageImg.setAttribute('onerror', "event.target.remove();"); messageImg.setAttribute('oncontextmenu', "return false;"); messageDiv.appendChild(messageImg); } lineDiv.appendChild(messageDiv); converse.appendChild(lineDiv); } if(converse.getAttribute('data-animate') != null) { let footer = document.createElement('div'); footer.className = 'footer message'; footer.innerText = '🔁'; footer.title = 'Replay Conversation'; footer.setAttribute('onclick', "animateConversation(this.closest('.conversation'))"); converse.appendChild(footer); } } } function animateConversation() { let conversation = event.target.closest('.conversation'); allowRunMessages(conversation); // read lines let lines = conversation.querySelectorAll('.message'); if(Array.from(lines).filter(l => l.classList.contains('hide')).length > 0) return; for(let line of lines) { line.classList.add('hide'); } // set timing to display and scroll for(let l = 0; l < lines.length; l++) { setTimeout(function() { if(conversation.getAttribute('data-running') != null) { if(window.ping && !lines[l].classList.contains('footer') && lines[l].getAttribute('data-system') == null) // play sound effect on each message sfxAudio.play(); if(lines[l].classList.contains('footer')) disableRunMessages(conversation); lines[l].classList.remove('hide'); let heightAboveItem = Array.from(lines).slice(0,l).reduce(function(total, current, index) { return total + current.getBoundingClientRect().height; }, 0); let currentHeight = lines[l].getBoundingClientRect().height; let diff = heightAboveItem + currentHeight - conversation.clientHeight; // delta to fix item height rounding // console.log(heightAboveItem + currentHeight, conversation.clientHeight); if(diff > 0) { if(diff < currentHeight) // newest message is not aligned to bottom of container conversation.scrollBy({ top: diff, behavior: 'smooth' }); else conversation.scrollBy({ top: currentHeight, behavior: 'smooth' }); } else // newest message is not at or beyond bottom of container conversation.scrollTo({ top: 0, behavior: 'smooth' }); } }, l*2000); } } function allowRunMessages(conversation) { // set status conversation.setAttribute('data-running', ''); // disable scroll capture conversation.setAttribute('onwheel', 'event.preventDefault()'); conversation.setAttribute('onmousewheel', 'event.preventDefault()'); conversation.setAttribute('ontouchstart', 'event.preventDefault()'); } function disableRunMessages(conversation) { // remove status conversation.removeAttribute('data-running'); // remove scroll capture conversation.removeAttribute('onwheel'); conversation.removeAttribute('onmousewheel'); conversation.removeAttribute('ontouchstart'); }
<div class="conversation" data-separator="[SEPARATOR_CHAR]" data-sender="[SENDER_NAME]" data-animate> [LINES_OF_CONTENT] </div>
TWO PARTY CONVERSATION, SENDER NOT DEFINED, NO INLINE STYLES
Nan | I saw you brought someone home in the afternoon, who was that?
Shouhei | Uh...
Nan | Is she your girlfriend? She is very cute.
Shouhei | Are you jealous?
Nan | W-what are you talking about? Of course not, I was just happy for onii-chan.
Shouhei | LOL
MORE THAN TWO PARTY CONVERSATION, SENDER DEFINED, ANIMATED
Miyu: Onii-chan, who is she?
Inori: I didn't know you have such an adorable sister!!
Shouhei: Uh...
Miyu: I-Is she your girlfriend??
Inori: Are you jealous I am hanging out with your brother? That's so cute!
Miyu: W-what are you talking about? Of course not!
Shouhei: LOL
Abbreviation
Extension of abbr element, to allow click/touch to show title
Uses new dialog HTML element to render popup
.dialog dialog { background-color: rgb(var(--background)); color: rgb(var(--foreground)); padding: 10px; max-width: 360px; font-family: Noto Sans; } .dialog dialog a { background-color: rgb(var(--background)); color: rgb(var(--primary)); }
window.addEventListener('load', showAbbrAsDialog); function showAbbrAsDialog() { for(let abbr of document.querySelectorAll('.abbreviation')) { abbr.addEventListener('click', function() { event.preventDefault(); popupText(event.target.title); }); } } function popupText(input) { let dialogDiv = document.querySelector('.dialog'); if(dialogDiv == null) { dialogDiv = document.createElement('div'); dialogDiv.classList.add('dialog'); document.body.appendChild(dialogDiv); } let dialog = createDialog(input); dialogDiv.innerHTML = ''; dialogDiv.appendChild(dialog); dialog.showModal(); } function createDialog(node) { // node in dialog will not have events! let dialog = document.createElement('dialog'); if(!dialog.classList.contains('box')) dialog.classList.add('box'); if(typeof node == 'string') dialog.innerHTML = node; if(typeof node == 'object') { let clonedNode = node.cloneNode(true); dialog.appendChild(clonedNode); } dialog.addEventListener('click', function() { this.remove(); }); dialog.addEventListener('keyup', function() { if (event.key === ' ' || event.key === 'Enter') this.remove(); }); return dialog; }
<abbr title="[TITLE HERE]"> [CONTENT] </abbr>TGIF
Omake
Wrapped element to show extra content
.omake { background-color: rgb(var(--accent)); padding: 1em; }
<div class="omake"> [CONTENT] </div>
WORTHY MENTION: COLLECTING CD
This technically doesn't fall in this category, since I wouldn't call collecting songs a thing, but hey, it's sort of like it. You like the songs, you buy them and they stack up. *laughs* Not going to do pricing on this one because... I spent a lot. Over the past decade alone, I've spent a lot. A good portion of my salary when I was in the Army went to those, and those were the first things I bought with my own money. So yeah, I'll update if I can count them all haha maybe I can do this
This technically doesn't fall in this category, since I wouldn't call collecting songs a thing, but hey, it's sort of like it. You like the songs, you buy them and they stack up. *laughs* Not going to do pricing on this one because... I spent a lot. Over the past decade alone, I've spent a lot. A good portion of my salary when I was in the Army went to those, and those were the first things I bought with my own money. So yeah, I'll update if I can count them all haha maybe I can do this