site-falcon/demo/index.html

1607 lines
57 KiB
HTML
Raw Normal View History

2024-06-08 10:48:56 +10:00
<!DOCTYPE html>
<html class="no-js" lang="">
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200");
</style>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>SaaSpal - SaaS and Software Landing Page Template</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="shortcut icon"
type="image/x-icon"
href="assets/img/favicon.png"
/>
<!-- Place favicon.ico in the root directory -->
<!-- ======== CSS here ======== -->
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
<link rel="stylesheet" href="assets/css/lineicons.css" />
<link rel="stylesheet" href="assets/css/animate.css" />
<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body>
<!--[if lte IE 9]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="https://browsehappy.com/">upgrade your browser</a> to improve
your experience and security.
</p>
<![endif]-->
<!-- ======== preloader start ======== -->
<div class="preloader">
<div class="loader">
<div class="spinner">
<div class="spinner-container">
<div class="spinner-rotator">
<div class="spinner-left">
<div class="spinner-circle"></div>
</div>
<div class="spinner-right">
<div class="spinner-circle"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- preloader end -->
<!-- ======== header start ======== -->
<header class="header">
<div class="navbar-area">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-12">
<nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="index.html">
<img src="assets/img/logo/logo.svg" alt="Logo" />
</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="toggler-icon"></span>
<span class="toggler-icon"></span>
<span class="toggler-icon"></span>
</button>
<div
class="collapse navbar-collapse sub-menu-bar"
id="navbarSupportedContent"
>
<ul id="nav" class="navbar-nav ms-auto">
<li class="nav-item">
<a class="page-scroll active" href="#home">Home</a>
</li>
<li class="nav-item">
<a class="page-scroll" href="#features">Features</a>
</li>
<li class="nav-item">
<a class="page-scroll" href="#about">About</a>
</li>
<li class="nav-item">
<a class="page-scroll" href="#why">Why</a>
</li>
<li class="nav-item">
<a href="javascript:void(0)">Pricing</a>
</li>
<li class="nav-item">
<a href="javascript:void(0)">Clients</a>
</li>
</ul>
</div>
<!-- navbar collapse -->
</nav>
<!-- navbar -->
</div>
</div>
<!-- row -->
</div>
<!-- container -->
</div>
<!-- navbar area -->
</header>
<!-- ======== header end ======== -->
<!-- ======== hero-section start ======== -->
<section id="home" class="hero-section">
<div class="container">
<div class="row align-items-center position-relative">
<div class="col-lg-6">
<div class="hero-content">
<h1 class="wow fadeInUp" data-wow-delay=".4s">
Your using free lite version
</h1>
<p class="wow fadeInUp" data-wow-delay=".6s">
Please, purchase full version to get all sections, features and
permission to remove footer credit.
</p>
<a
href="javascript:void(0)"
class="main-btn border-btn btn-hover wow fadeInUp"
data-wow-delay=".6s"
>Purchase Now</a
>
<a href="#features" class="scroll-bottom">
<i class="lni lni-arrow-down"></i
></a>
</div>
</div>
<div class="col-lg-6">
<div class="hero-img wow fadeInUp" data-wow-delay=".5s">
<img src="assets/img/hero/hero-img.png" alt="" />
</div>
</div>
</div>
</div>
</section>
<!-- ======== hero-section end ======== -->
<!-- ======== feature-section start ======== -->
<section id="features" class="feature-section pt-120">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-4 col-md-8 col-sm-10">
<div class="single-feature">
<div class="icon">
<i class="lni lni-bootstrap"></i>
</div>
<div class="content">
<h3>Bootstrap 5</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-8 col-sm-10">
<div class="single-feature">
<div class="icon">
<i class="lni lni-layout"></i>
</div>
<div class="content">
<h3>Clean Design</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-8 col-sm-10">
<div class="single-feature">
<div class="icon">
<i class="lni lni-coffee-cup"></i>
</div>
<div class="content">
<h3>Easy to Use</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ======== feature-section end ======== -->
<!-- ======== about-section start ======== -->
<section id="about" class="about-section pt-150">
<div class="container">
<div class="row align-items-center">
<div class="col-xl-6 col-lg-6">
<div class="about-img">
<img src="assets/img/about/about-1.png" alt="" class="w-100" />
<img
src="assets/img/about/about-left-shape.svg"
alt=""
class="shape shape-1"
/>
<img
src="assets/img/about/left-dots.svg"
alt=""
class="shape shape-2"
/>
</div>
</div>
<div class="col-xl-6 col-lg-6">
<div class="about-content">
<div class="section-title mb-30">
<h2 class="mb-25 wow fadeInUp" data-wow-delay=".2s">
Perfect Solution Thriving Online Business
</h2>
<p class="wow fadeInUp" data-wow-delay=".4s">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
dinonumy eirmod tempor invidunt ut labore et dolore magna
aliquyam erat, sed diam voluptua. At vero eos et accusam et
justo duo dolores et ea rebum. Stet clita kasd gubergren, no
sea takimata sanctus est Lorem.Lorem ipsum dolor sit amet.
</p>
</div>
<a
href="javascript:void(0)"
class="main-btn btn-hover border-btn wow fadeInUp"
data-wow-delay=".6s"
>Discover More</a
>
</div>
</div>
</div>
</div>
</section>
<!-- ======== about-section end ======== -->
<!-- ======== about2-section start ======== -->
<section id="about" class="about-section pt-150">
<div class="container">
<div class="row align-items-center">
<div class="col-xl-6 col-lg-6">
<div class="about-content">
<div class="section-title mb-30">
<h2 class="mb-25 wow fadeInUp" data-wow-delay=".2s">
Easy to Use with Tons of Awesome Features
</h2>
<p class="wow fadeInUp" data-wow-delay=".4s">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore et dolore magna
aliquyam erat, sed diam voluptua.
</p>
</div>
<ul>
<li>Quick Access</li>
<li>Easily to Manage</li>
<li>24/7 Support</li>
</ul>
<a
href="javascript:void(0)"
class="main-btn btn-hover border-btn wow fadeInUp"
data-wow-delay=".6s"
>Learn More</a
>
</div>
</div>
<div class="col-xl-6 col-lg-6 order-first order-lg-last">
<div class="about-img-2">
<img src="assets/img/about/about-2.png" alt="" class="w-100" />
<img
src="assets/img/about/about-right-shape.svg"
alt=""
class="shape shape-1"
/>
<img
src="assets/img/about/right-dots.svg"
alt=""
class="shape shape-2"
/>
</div>
</div>
</div>
</div>
</section>
<!-- ======== about2-section end ======== -->
<!-- ======== feature-section start ======== -->
<section id="why" class="feature-extended-section pt-100">
<div class="feature-extended-wrapper">
<div class="container">
<div class="row justify-content-center">
<div class="col-xxl-5 col-xl-6 col-lg-8 col-md-9">
<div class="section-title text-center mb-60">
<h2 class="mb-25 wow fadeInUp" data-wow-delay=".2s">
Why Choose SaaSpal
</h2>
<p class="wow fadeInUp" data-wow-delay=".4s">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore et dolore
</p>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-4 col-md-6">
<div class="single-feature-extended">
<div class="icon">
<i class="lni lni-display"></i>
</div>
<div class="content">
<h3>SaaS Focused</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="single-feature-extended">
<div class="icon">
<i class="lni lni-leaf"></i>
</div>
<div class="content">
<h3>Awesome Design</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="single-feature-extended">
<div class="icon">
<i class="lni lni-grid-alt"></i>
</div>
<div class="content">
<h3>Ready to Use</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="single-feature-extended">
<div class="icon">
<i class="lni lni-javascript"></i>
</div>
<div class="content">
<h3>Vanilla JS</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="single-feature-extended">
<div class="icon">
<i class="lni lni-layers"></i>
</div>
<div class="content">
<h3>Essential Sections</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="single-feature-extended">
<div class="icon">
<i class="lni lni-rocket"></i>
</div>
<div class="content">
<h3>Highly Optimized</h3>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ======== feature-section end ======== -->
<!-- ======== subscribe-section start ======== -->
<section id="contact" class="subscribe-section pt-120">
<div class="container">
<div class="subscribe-wrapper img-bg">
<div class="row align-items-center">
<div class="col-xl-6 col-lg-7">
<div class="section-title mb-15">
<h2 class="text-white mb-25">Subscribe Our Newsletter</h2>
<p class="text-white pr-5">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor
</p>
</div>
</div>
<div class="col-xl-6 col-lg-5">
<form action="#" class="subscribe-form">
<input
type="email"
name="subs-email"
id="subs-email"
placeholder="Your Email"
/>
<button type="submit" class="main-btn btn-hover">
Subscribe
</button>
</form>
</div>
</div>
</div>
</div>
</section>
<!-- ======== subscribe-section end ======== -->
<!-- ======== footer start ======== -->
<footer class="footer">
<div class="container">
<div class="widget-wrapper">
<div class="row">
<div class="col-xl-4 col-lg-4 col-md-6">
<div class="footer-widget">
<div class="logo mb-30">
<a href="index.html">
<img src="assets/img/logo/logo.svg" alt="" />
</a>
</div>
<p class="desc mb-30 text-white">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
dinonumy eirmod tempor invidunt.
</p>
<ul class="socials">
<li>
<a href="jvascript:void(0)">
<i class="lni lni-facebook-filled"></i>
</a>
</li>
<li>
<a href="jvascript:void(0)">
<i class="lni lni-twitter-filled"></i>
</a>
</li>
<li>
<a href="jvascript:void(0)">
<i class="lni lni-instagram-filled"></i>
</a>
</li>
<li>
<a href="jvascript:void(0)">
<i class="lni lni-linkedin-original"></i>
</a>
</li>
</ul>
</div>
</div>
<div class="col-xl-2 col-lg-2 col-md-6">
<div class="footer-widget">
<h3>About Us</h3>
<ul class="links">
<li><a href="javascript:void(0)">Home</a></li>
<li><a href="javascript:void(0)">Feature</a></li>
<li><a href="javascript:void(0)">About</a></li>
<li><a href="javascript:void(0)">Testimonials</a></li>
</ul>
</div>
</div>
<div class="col-xl-3 col-lg-3 col-md-6">
<div class="footer-widget">
<h3>Features</h3>
<ul class="links">
<li><a href="javascript:void(0)">How it works</a></li>
<li><a href="javascript:void(0)">Privacy policy</a></li>
<li><a href="javascript:void(0)">Terms of service</a></li>
<li><a href="javascript:void(0)">Refund policy</a></li>
</ul>
</div>
</div>
<div class="col-xl-3 col-lg-3 col-md-6">
<div class="footer-widget">
<h3>Other Products</h3>
<ul class="links">
<li><a href="jvascript:void(0)">Accounting Software</a></li>
<li><a href="jvascript:void(0)">Billing Software</a></li>
<li><a href="jvascript:void(0)">Booking System</a></li>
<li><a href="jvascript:void(0)">Tracking System</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</footer>
<!-- ======== footer end ======== -->
<!-- ======== scroll-top ======== -->
<a href="#" class="scroll-top btn-hover">
<i class="lni lni-chevron-up"></i>
</a>
<!-- ======== JS here ======== -->
<script src="assets/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/wow.min.js"></script>
<script src="assets/js/main.js"></script>
<template id="x-widget-template">
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-rc.5/dist/html2canvas.min.js"></script>
<style>
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200");
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200");
:host {
all: initial !important;
position: relative !important;
z-index: 9998 !important;
--unit-spacing: 8px;
--icon-default-dim: 60px;
--form-default-width: 450px;
--form-default-height: 580px;
--side-margin: calc(var(--unit-spacing) * 2);
--site-falcon-purple-1: #AE55E7;
--site-falcon-purple-2: #a544e3;
--site-falcon-blue: #0098FF;
--site-falcon-red: #FF4031;
--site-falcon-grey-1: #ececec;
--site-falcon-grey-2: #e0e0e0;
--site-falcon-grey-3: #d3d3d3;
--site-falcon-grey-4: #5A5A5A;
--form-element-border-size: 2px;
--form-border-radius: var(--unit-spacing);
}
* {
box-sizing: border-box;
}
#icon {
width: var(--icon-default-dim);
height: var(--icon-default-dim);
position: fixed;
visibility: visible;
opacity: 1;
transition: top 0.3s ease 0s, left 0.3s ease 0s, scale 0.2s ease, transform 0.5s ease, visibility 0s linear 0.3s, opacity 0.3s linear;
}
#icon.location-top-left {
left: var(--side-margin);
top: var(--side-margin);
}
#icon.location-top-right {
left: calc(100% - var(--icon-default-dim) - var(--side-margin));
top: var(--side-margin);
}
#icon.location-bottom-left {
left: var(--side-margin);
top: calc(100% - var(--icon-default-dim) - var(--side-margin));
}
#icon.location-bottom-right {
left: calc(100% - var(--icon-default-dim) - var(--side-margin));
top: calc(100% - var(--icon-default-dim) - var(--side-margin));
}
#icon:hover {
scale: 1.05;
}
#icon > img {
width: 100%;
height: 100%;
}
#form {
width: var(--form-default-width);
height: var(--form-default-height);
background-color: white;
border: 0px;
border-radius: var(--form-border-radius);
box-shadow: 0 calc(var(--unit-spacing) / 2) calc(var(--unit-spacing) * 2) rgb(0 0 0 / 25%);
position: fixed;
visibility: visible;
opacity: 1;
transition: transform 0.4s ease, visibility 0s linear 0.3s, opacity 0.3s linear;
display: flex;
flex-direction: column;
}
#form.location-top-left {
left: var(--side-margin);
top: var(--side-margin);
}
#form.location-top-right {
left: calc(100% - var(--form-default-width) - var(--side-margin));
top: var(--side-margin);
}
#form.location-bottom-left {
left: var(--side-margin);
top: calc(100% - var(--form-default-height) - var(--side-margin));
}
#form.location-bottom-right {
left: calc(100% - var(--form-default-width) - var(--side-margin));
top: calc(100% - var(--form-default-height) - var(--side-margin));
}
#form-header {
top: 0px;
width: 100%;
height: calc(var(--unit-spacing) * 8);
background-image: linear-gradient(var(--site-falcon-purple-2), var(--site-falcon-purple-1));
border-radius: var(--form-border-radius) var(--form-border-radius) 0px 0px;
border: 0px;
justify-content: center;
align-items: center;
display: flex;
}
.form-header-button {
cursor: pointer;
user-select: none;
color: white;
margin-left: calc(var(--unit-spacing) * 2);
font-size: 26px;
transition: scale 0.2s ease;
}
.form-header-button:hover {
scale: 1.1;
}
#form-button-help {
margin-left: calc(var(--unit-spacing) * 2);
}
#form-button-close {
margin-right: calc(var(--unit-spacing) * 2);
}
#form-title {
font-family: "Alexandria", sans-serif;
font-weight: 400;
font-size: 24px;
color: white;
text-align: center;
user-select: none;
flex-grow: 1;
}
#form-content-container {
width: 100%;
align-items: center;
padding-left: calc(var(--unit-spacing) * 4);
padding-right: calc(var(--unit-spacing) * 4);
padding: calc(var(--unit-spacing) * 4);
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
#input-name-container {
width: 100%;
position: relative;
}
.md-input {
width: 100%;
border: var(--form-element-border-size) solid var(--site-falcon-grey-3);
border-radius: calc(var(--unit-spacing) / 2);
padding: 16px;
}
#input-name {
height: calc(var(--unit-spacing) * 6);
}
.label-md-input {
position: absolute;
top: 0;
left: calc(var(--unit-spacing) * 2);
display: flex;
pointer-events: none;
}
#input-name + .label-md-input {
bottom: 0;
align-items: center;
}
#input-description + .label-md-input {
margin-top: calc(var(--unit-spacing) * 2);
}
#input-name:not(#input-name[value=""]), #input-description:not(#input-description[value=""]) {
font-size: 16px;
}
.md-input, .label-md-input > div {
font-family: "Alexandria", sans-serif;
font-weight: 300;
font-size: 16px;
}
.label-md-input > div {
transition: all 0.15s ease-out, visibility 0s;
color: grey;
}
.md-input:focus {
outline: none;
border: var(--form-element-border-size) solid var(--site-falcon-purple-1);
}
#input-name:focus + .label-md-input > div, #input-name:not(#input-name[value=""]) + .label-md-input > div {
font-size: 14px;
transform: translate(0, -150%);
background-color: white;
padding-left: calc(var(--unit-spacing) / 2);
padding-right: calc(var(--unit-spacing) / 2);
}
#input-description:focus + .label-md-input > div, #input-description:not(#input-description[value=""]) + .label-md-input > div {
font-size: 14px;
transform: translate(0, -150%);
background-color: white;
padding-left: calc(var(--unit-spacing) / 2);
padding-right: calc(var(--unit-spacing) / 2);
}
.md-input:focus + .label-md-input > div {
color: var(--site-falcon-purple-1);
}
#select-severity-container {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-column-gap: 0px;
justify-content: center;
align-items: center;
}
.severity-option {
margin: 0px;
height: calc(var(--unit-spacing) * 6);
background-color: var(--site-falcon-grey-1);
border: var(--form-element-border-size) solid var(--site-falcon-grey-3);
border-left: 0px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
cursor: pointer;
user-select: none;
transition: background-color 0.15s ease;
}
.severity-option .material-symbols-rounded {
color: var(--site-falcon-grey-4);
font-size: 26px;
}
.severity-option .material-symbols-rounded:not(:first-child) {
margin-left: -14px;
}
.severity-option:hover:not(.selected) {
background-color: var(--site-falcon-grey-2);
}
.severity-option.selected {
background-color: var(--site-falcon-grey-3);
}
#select-severity-container > :first-child {
border-left: var(--form-element-border-size) solid var(--site-falcon-grey-3);
border-radius: calc(var(--unit-spacing) / 2) 0px 0px calc(var(--unit-spacing) / 2);
}
#select-severity-container :last-child {
border-radius: 0px calc(var(--unit-spacing) / 2) calc(var(--unit-spacing) / 2) 0px;
}
#input-description-container {
width: 100%;
position: relative;
}
#input-description {
height: calc(var(--unit-spacing) * 19);
resize: none;
}
#icon:not(.hidden), #form:not(.hidden) {
transition-delay: 0s;
}
#icon.hidden, #form.hidden {
pointer-events: none;
visibility: hidden;
opacity: 0;
}
#icon.hidden.location-bottom-left, #form.hidden.location-bottom-left,
#icon.hidden.location-bottom-right, #form.hidden.location-bottom-right {
transform: translateY(100%);
}
#icon.hidden.location-top-left, #form.hidden.location-top-left,
#icon.hidden.location-top-right, #form.hidden.location-top-right {
transform: translateY(-100%);
}
#icon.hidden {
scale: 1.05;
}
#container-snapshot {
width: 100%;
display: grid;
grid-template-columns: 1fr;
justify-content: center;
align-items: center;
}
#container-snapshot.snapshot-taken {
grid-template-columns: 7fr 1fr 1fr;
}
#container-snapshot .material-symbols-rounded.snapshot-taken {
display: block;
user-select: none;
}
#container-snapshot .material-symbols-rounded:hover {
cursor: pointer;
}
#button-snapshot {
width: 100%;
height: calc(var(--unit-spacing) * 6);
border-radius: calc(var(--unit-spacing) / 2);
border: var(--form-element-border-size) dashed var(--site-falcon-grey-3);
background-color: var(--site-falcon-grey-1);
color: var(--site-falcon-grey-4);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
user-select: none;
transition: background-color 0.15s ease;
}
#button-snapshot .material-symbols-outlined {
font-size: 30px;
}
#button-snapshot:hover {
background-color: var(--site-falcon-grey-3);
}
#container-snapshot > .material-symbols-rounded {
display: none;
margin-left: auto;
font-size: 30px;
}
#button-view-snapshot {
color: var(--site-falcon-blue);
}
#button-delete-snapshot {
color: var(--site-falcon-red);
}
#footer-buttons-container {
width: 100%;
display: flex;
flex-direction: row;
}
#footer-buttons-container .form-footer-button {
width: 46%;
height: calc(var(--unit-spacing) * 6);
border-radius: calc(var(--unit-spacing) / 2);
font-family: "Alexandria", sans-serif;
font-weight: 400;
font-size: 20px;
border: 0px;
cursor: pointer;
user-select: none;
transition: background-color 0.15s ease;
}
#button-clear-form {
margin-right: auto;
color: var(--site-falcon-grey-4);
background-color: var(--site-falcon-grey-1);
}
#button-clear-form:hover {
background-color: var(--site-falcon-grey-2);
}
#button-submit {
margin-left: auto;
color: white;
background-color: var(--site-falcon-purple-1);
}
#button-submit:hover {
background-color: var(--site-falcon-purple-2);
}
#canvas-snapshot {
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
visibility: hidden;
cursor: crosshair;
}
#canvas-snapshot.visible {
visibility: visible;
}
</style>
<canvas id="canvas-snapshot">
</canvas>
<div id="icon">
<img src="icon_open.png">
</div>
<div id="form">
<div id="form-header">
<span title="View Help" id="form-button-help" class="material-symbols-rounded form-header-button">
help
</span>
<div id="form-title">
Add Feedback
</div>
<span id="form-button-close" class="material-symbols-rounded form-header-button">
close
</span>
</div>
<div id="form-content-container">
<div id="select-severity-container">
<div title="General Comment" class="severity-option selected" id="option-no-severity">
<span class="material-symbols-rounded">
info
</span>
</div>
<div title="Low Priority" class="severity-option" id="option-low-severity">
<span class="material-symbols-rounded">
priority_high
</span>
</div>
<div title="Medium Priority" class="severity-option" id="option-medium-severity">
<span class="material-symbols-rounded">
priority_high
</span>
<span class="material-symbols-rounded">
priority_high
</span>
</div>
<div title="High Priority" class="severity-option" id="option-high-severity">
<span class="material-symbols-rounded">
priority_high
</span>
<span class="material-symbols-rounded">
priority_high
</span>
<span class="material-symbols-rounded">
priority_high
</span>
</div>
</div>
<div id="input-name-container">
<input
type="text"
id="input-name"
class="md-input"
name="name"
value=""
aria-labelledby="label-input-name"
/>
<label for="name" class="label-md-input">
<div>Name</div>
</label>
</div>
<div id="input-description-container">
<textarea
type="text"
id="input-description"
class="md-input"
name="description"
value=""
aria-labelledby="label-input-description"
></textarea>
<label for="description" class="label-md-input">
<div>Description</div>
</label>
</div>
<div id="container-snapshot">
<button title="Take Snapshot" id="button-snapshot" class="taken">
<span class="material-symbols-outlined">
photo_camera
</span>
</button>
<span title="View Snapshot" id="button-view-snapshot" class="material-symbols-rounded">
image
</span>
<span title="Delete Snapshot" id="button-delete-snapshot" class="material-symbols-rounded">
delete
</span>
</div>
<div id="footer-buttons-container">
<button id="button-clear-form" class="form-footer-button">
Clear
</button>
<button id="button-submit" class="form-footer-button">
Submit
</button>
</div>
</div>
</div>
</template>
<script>
class XWidget extends HTMLElement {
#icon;
#form;
#canvas;
#canvasCtx;
#location;
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(document.getElementById("x-widget-template").content.cloneNode(true));
this.#icon = this.#createIcon();
this.#form = this.#createForm();
this.#canvas = this.#createCanvas();
this.#canvasCtx = this.#canvas.getContext("2d");
this.#location = "bottom-right";
}
connectedCallback() {
this.#renderInitial();
window.addEventListener("keydown", (e) => this.#windowKeyPressed(e));
}
#renderInitial() {
if (this.isConnected) {
this.#renderIcon({visibility: "visible"});
this.#renderForm({visibility: "hidden"});
}
}
#createIcon() {
let icon = this.shadowRoot.getElementById("icon");
icon.addEventListener("pointerdown", (e) => this.#onIconPointerDown(e));
icon.addEventListener("pointermove", (e) => this.#onIconPointerMove(e));
icon.addEventListener("pointerup", (e) => this.#onIconPointerUp(e));
icon.addEventListener("pointerover", (e) => this.#onIconPointerOver(e));
icon.addEventListener("pointerout", (e) => this.#onIconPointerOut(e));
icon.pointerDown = false;
icon.moving = false;
return icon;
}
#createCanvas() {
let canvas = this.shadowRoot.getElementById("canvas-snapshot");
canvas.addEventListener("pointerdown", (e) => this.#onCanvasPointerDown(e));
canvas.addEventListener("pointermove", (e) => this.#onCanvasPointerMove(e));
canvas.addEventListener("pointerup", async (e) => await this.#onCanvasPointerUpAsync(e));
canvas.pointerDown = null;
canvas.initialX = null;
canvas.initialY = null;
return canvas;
}
#createForm() {
let form = this.shadowRoot.getElementById("form");
let buttonHelp = this.shadowRoot.getElementById("form-button-help");
buttonHelp.addEventListener("click", (e) => this.#onFormHelpButtonClicked(e));
let buttonClose = this.shadowRoot.getElementById("form-button-close");
buttonClose.addEventListener("click", (e) => this.#onFormCloseButtonClicked(e));
let inputName = this.shadowRoot.getElementById("input-name");
inputName.addEventListener("input", (e) => this.#onNameInput(e));
let selectSeverityContainer = this.shadowRoot.getElementById("select-severity-container");
for (let i = 0; i < selectSeverityContainer.childElementCount; i++) {
let severityOption = selectSeverityContainer.children[i];
severityOption.addEventListener("click", (e) => this.#OnSeverityOptionClicked(e));
}
let inputDescription = this.shadowRoot.getElementById("input-description");
inputDescription.addEventListener("input", (e) => this.#onDescriptionInput(e));
let buttonSnapshot = this.shadowRoot.getElementById("button-snapshot");
buttonSnapshot.addEventListener("click", (e) => this.#onSnapshotButtonClicked(e));
let buttonViewSnapshot = this.shadowRoot.getElementById("button-view-snapshot");
buttonViewSnapshot.addEventListener("click", (e) => this.#onViewSnapshotButtonClicked(e));
let buttonDeleteSnapshot = this.shadowRoot.getElementById("button-delete-snapshot");
buttonDeleteSnapshot.addEventListener("click", (e) => this.#onDeleteSnapshotButtonClicked(e));
let buttonClearForm = this.shadowRoot.getElementById("button-clear-form");
buttonClearForm.addEventListener("click", (e) => this.#onClearFormButtonClicked(e));
let buttonSubmit = this.shadowRoot.getElementById("button-submit");
buttonSubmit.addEventListener("click", (e) => this.#onSubmitButtonClicked(e));
return form;
}
#renderIcon(parameters={}) {
if (this.#location == "top-left") {
removeClassByPrefix(this.#icon, "location");
this.#icon.classList.add("location-top-left");
}
else if (this.#location == "top-right") {
removeClassByPrefix(this.#icon, "location");
this.#icon.classList.add("location-top-right");
}
else if (this.#location == "bottom-left") {
removeClassByPrefix(this.#icon, "location");
this.#icon.classList.add("location-bottom-left");
}
else if (this.#location == "bottom-right") {
removeClassByPrefix(this.#icon, "location");
this.#icon.classList.add("location-bottom-right");
}
if (parameters["visibility"]) {
if (parameters["visibility"] == "visible") {
this.#icon.classList.remove("hidden");
}
else if (parameters["visibility"] == "hidden") {
this.#icon.classList.add("hidden");
}
}
}
#renderForm(parameters={}) {
if (this.#location == "top-left") {
removeClassByPrefix(this.#form, "location");
this.#form.classList.add("location-top-left");
} else if (this.#location == "top-right") {
removeClassByPrefix(this.#form, "location");
this.#form.classList.add("location-top-right");
} else if (this.#location == "bottom-left") {
removeClassByPrefix(this.#form, "location");
this.#form.classList.add("location-bottom-left");
} else if (this.#location == "bottom-right") {
removeClassByPrefix(this.#form, "location");
this.#form.classList.add("location-bottom-right");
}
if (parameters["visibility"]) {
if (parameters["visibility"] == "visible") {
this.#form.classList.remove("hidden");
}
else if (parameters["visibility"] == "hidden") {
this.#form.classList.add("hidden");
}
}
}
#onFormHelpButtonClicked(e) {
console.log("help pressed");
}
#onFormCloseButtonClicked(e) {
if (!this.#form.classList.contains("hidden")) {
this.#renderIcon({visibility: "visible"});
this.#renderForm({visibility: "hidden"});
}
}
#onNameInput(e) {
let inputName = this.shadowRoot.getElementById("input-name");
inputName.setAttribute('value', inputName.value);
}
#onDescriptionInput(e) {
let inputDescription = this.shadowRoot.getElementById("input-description");
inputDescription.setAttribute('value', inputDescription.value);
}
#OnSeverityOptionClicked(e) {
let selectedSeverityOption = e.currentTarget;
let selectSeverityContainer = this.shadowRoot.getElementById("select-severity-container");
for (let i = 0; i < selectSeverityContainer.childElementCount; i++) {
let severityOption = selectSeverityContainer.children[i];
if (severityOption == selectedSeverityOption) {
if (!severityOption.classList.contains("selected")) {
severityOption.classList.add("selected");
}
}
else {
severityOption.classList.remove("selected");
}
}
}
#onSnapshotButtonClicked(e) {
this.#renderIcon({visibility: "visible"});
this.#renderForm({visibility: "hidden"});
this.#setBlankCanvas();
this.#canvas.classList.add("visible");
}
#setBlankCanvas() {
this.#canvas.width = this.#canvas.clientWidth;
this.#canvas.height = this.#canvas.clientHeight;
this.#canvasCtx.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
this.#canvasCtx.globalAlpha = 0.5;
this.#canvasCtx.fillStyle = "white";
this.#canvasCtx.beginPath();
this.#canvasCtx.rect(0, 0, this.#canvas.width, this.#canvas.height);
this.#canvasCtx.fill();
this.#canvasCtx.closePath();
this.#setCanvasBrush(2, "#FF4031");
}
#setCanvasBrush(width, colour) {
this.#canvasCtx.globalAlpha = 1;
this.#canvasCtx.lineWidth = width;
this.#canvasCtx.strokeStyle = colour;
}
#onCanvasPointerDown(e) {
if (e.button !== 0) {
return;
}
e.preventDefault();
e.stopPropagation();
this.#canvas.initialX = e.offsetX;
this.#canvas.initialY = e.offsetY;
this.#canvas.pointerDown = true;
this.#renderIcon({visibility: "hidden"});
this.#canvas.setPointerCapture(e.pointerId);
}
#onCanvasPointerMove(e) {
e.preventDefault();
e.stopPropagation();
if (this.#canvas.pointerDown) {
let offsetX;
let offsetY;
if (e.offsetX < 0) {
offsetX = 0;
}
else if (e.offsetX > this.#canvas.width - 1) {
offsetX = this.#canvas.width - 1;
}
else {
offsetX = e.offsetX;
}
if (e.offsetY < 0) {
offsetY = 0;
}
else if (e.offsetY > this.#canvas.height - 1) {
offsetY = this.#canvas.height - 1;
}
else {
offsetY = e.offsetY;
}
let width = offsetX - this.#canvas.initialX;
let height = offsetY - this.#canvas.initialY;
this.#setBlankCanvas();
this.#canvasCtx.beginPath();
this.#canvasCtx.clearRect(this.#canvas.initialX, this.#canvas.initialY, width, height);
this.#canvasCtx.rect(this.#canvas.initialX, this.#canvas.initialY, width, height);
this.#canvasCtx.stroke();
this.#canvasCtx.closePath();
}
}
async #onCanvasPointerUpAsync(e) {
if (e.button !== 0) {
return;
}
e.preventDefault();
e.stopPropagation();
if (this.#canvas.pointerDown) {
let offsetX;
let offsetY;
if (e.offsetX < 0) {
offsetX = 0;
}
else if (e.offsetX > this.#canvas.width - 1) {
offsetX = this.#canvas.width - 1;
}
else {
offsetX = e.offsetX;
}
if (e.offsetY < 0) {
offsetY = 0;
}
else if (e.offsetY > this.#canvas.height - 1) {
offsetY = this.#canvas.height - 1;
}
else {
offsetY = e.offsetY;
}
if (Math.abs(offsetX - this.#canvas.initialX) > 10 && Math.abs(offsetY - this.#canvas.initialY) > 10) {
await this.#takeSnapshotAsync(offsetX, offsetY);
this.shadowRoot.getElementById("container-snapshot").classList.add("snapshot-taken");
this.shadowRoot.getElementById("button-view-snapshot").classList.add("snapshot-taken");
this.shadowRoot.getElementById("button-delete-snapshot").classList.add("snapshot-taken");
}
this.#canvas.initialX = null;
this.#canvas.initialY = null;
this.#canvas.pointerDown = false;
this.#canvas.classList.remove("visible");
this.#setBlankCanvas();
this.#renderForm({visibility: "visible"});
}
}
async #takeSnapshotAsync(offsetX, offsetY) {
let snapshotCanvas = await html2canvas(document.body, {
width: Math.abs(this.#canvas.initialX - offsetX),
height: Math.abs(this.#canvas.initialY - offsetY),
x: Math.min(this.#canvas.initialX, offsetX),
y: Math.min(this.#canvas.initialY, offsetY)
})
let base64image = snapshotCanvas.toDataURL("image/png");
this.#form.snapshotBase64 = base64image;
}
#onViewSnapshotButtonClicked(e) {
if (this.#form.snapshotBase64) {
let image = new Image();
image.src = this.#form.snapshotBase64;
let w = window.open("_blank");
w.document.write(image.outerHTML);
}
}
#onDeleteSnapshotButtonClicked(e) {
this.#deleteSnapshot();
}
#deleteSnapshot() {
this.#form.snapshot = null;
this.shadowRoot.getElementById("container-snapshot").classList.remove("snapshot-taken");
this.shadowRoot.getElementById("button-view-snapshot").classList.remove("snapshot-taken");
this.shadowRoot.getElementById("button-delete-snapshot").classList.remove("snapshot-taken");
}
#onClearFormButtonClicked(e){
let inputName = this.shadowRoot.getElementById("input-name");
inputName.value = null;
inputName.setAttribute("value", "");
this.#resetForm();
}
#onSubmitButtonClicked(e) {
this.#renderIcon({visibility: "visible"});
this.#renderForm({visibility: "hidden"});
this.#resetForm();
}
#resetForm() {
let selectSeverityContainer = this.shadowRoot.getElementById("select-severity-container");
selectSeverityContainer.children[0].classList.add("selected");
for (let i = 1; i < selectSeverityContainer.childElementCount; i++) {
let severityOption = selectSeverityContainer.children[i];
severityOption.classList.remove("selected");
}
let inputDescription = this.shadowRoot.getElementById("input-description");
inputDescription.value = null;
inputDescription.setAttribute("value", "");
this.#deleteSnapshot();
}
#onIconPointerOver(e) {
e.preventDefault();
e.stopPropagation();
}
#onIconPointerOut(e) {
e.preventDefault();
e.stopPropagation();
}
#onIconPointerDown(e) {
if (e.button !== 0) {
return;
}
e.preventDefault();
e.stopPropagation();
this.#icon.pointerDown = true;
this.#icon.setPointerCapture(e.pointerId);
}
#onIconPointerMove(e) {
e.preventDefault();
e.stopPropagation();
if (this.#icon.pointerDown) {
this.#icon.moving = true;
this.#icon.style.cursor = "grabbing";
this.#snapToCorner(e.clientX, e.clientY);
}
else {
this.#icon.style.cursor = "pointer";
}
}
#onIconPointerUp(e) {
if (e.button !== 0) {
return;
}
e.preventDefault();
e.stopPropagation();
this.#icon.style.cursor = "pointer";
this.#canvas.classList.remove("visible");
if (this.#icon.pointerDown && !this.#icon.moving) {
if (this.#form.classList.contains("hidden")) {
this.#renderIcon({visibility: "hidden"});
this.#renderForm({visibility: "visible"});
}
}
else {
this.#icon.moving = false;
}
this.#icon.pointerDown = false;
this.#icon.releasePointerCapture(e.pointerId);
}
#snapToCorner(anchorX, anchorY) {
let locationChanged = false;
if (anchorX < window.innerWidth / 2) {
if (this.#location != "top-left" && anchorY < window.innerHeight / 2) {
this.#location = "top-left";
locationChanged = true;
}
else if (this.#location != "bottom-left" && anchorY > window.innerHeight - window.innerHeight / 2) {
this.#location = "bottom-left";
locationChanged = true;
}
}
else if (anchorX > window.innerWidth - window.innerWidth / 2) {
if (this.#location != "top-right" && anchorY < window.innerHeight / 2) {
this.#location = "top-right";
locationChanged = true;
}
else if (this.#location != "bottom-right" && anchorY > window.innerHeight - window.innerHeight / 2) {
this.#location = "bottom-right";
locationChanged = true;
}
}
if (locationChanged) {
this.#renderIcon({visibility: "visible"});
this.#renderForm({visibility: "hidden"});
}
}
#windowKeyPressed(e) {
if (e.keyCode == 27 && this.#canvas.classList.contains("visible")) {
e.preventDefault();
e.stopPropagation();
this.#renderIcon({visibility: "hidden"});
this.#renderForm({visibility: "visible"});
this.#setBlankCanvas();
this.#canvas.classList.remove("visible");
this.#canvas.pointerDown = false;
}
else if (e.keyCode == 77 && e.ctrlKey) {
e.preventDefault();
e.stopPropagation();
this.#renderIcon({visibility: "visible"});
this.#renderForm({visibility: "hidden"});
this.#setBlankCanvas();
this.#canvas.classList.add("visible");
}
}
}
function removeClassByPrefix(el, prefix) {
if (!el.classList) {
return;
}
let filteredClassList = [];
for (let i = 0; i < el.classList.length; i++) {
if (!el.classList[i].startsWith(prefix)) {
filteredClassList.push(el.classList[i])
}
}
el.className = "";
for (let i = 0; i < filteredClassList.length; i++) {
el.classList.add(filteredClassList[i]);
}
}
customElements.define("x-widget", XWidget);
document.body.appendChild(document.createElement("x-widget"));
</script>
</body>
</html>