diff --git a/README.md b/README.md
index 42105f7..0ba5475 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,106 @@
# keymanweb.com
-Home page for online web keyboard with support for over 2000 languages
+This is the source for the website https://keymanweb.com/, which hosts the
+KeymanWeb online keyboard supporting over 2500 different languages. This site
+runs on Apache in a Docker container.
+
+## Other Keyman websites
+
+* **[api.keyman.com]** - database backend for Keyman websites
+* **[help.keyman.com]** - documentation home for Keyman
+* **[keyman.com]** - Keyman home
+* **[keymanweb.com]** - KeymanWeb online keyboard
+* **[s.keyman.com]** - static Javascript, font, and related resources
+* **[website-local-proxy]** - run all Keyman sites on localhost on the same port
+
+## How to run keymanweb.com locally
+
+When run locally, this site can be accessed at http://localhost:8057 or
+http://web.keyman.com.localhost:8057. Note that the site is internally called
+web.keyman.com, but the public site is at keymanweb.com; web.keyman.com
+automatically forwards to keymanweb.com.
+
+**Recommended:** Use [website-local-proxy] to run multiple keyman.com sites
+all from the same port (default port 80).
+
+**Recommended:** Use [shared-sites] to control startup and shutdown of all
+keyman.com sites together.
+
+### Prerequisites
+
+The host machine needs the following apps installed:
+* [Git]
+* Bash 5.x (on Windows, you can use Git Bash that comes with [Git])
+* [Docker Desktop]
+
+
+ Configuration of Docker on Windows
+
+ On Windows machines, you can setup Docker in two different ways, either of
+ which should work:
+ * [Enable Hyper-V on Windows 11](https://techcommunity.microsoft.com/t5/educator-developer-blog/step-by-step-enabling-hyper-v-for-use-on-windows-11/ba-p/3745905)
+ * [WSL2](https://ubuntu.com/tutorials/install-ubuntu-on-wsl2-on-windows-10#1-overview)
+
+
+
+### Actions
+
+#### Build the Docker image
+
+The first time you want to start up the site, or if there have been Docker
+configuration changes, you will need to rebuild the Docker images. Start a bash
+shell, and from this folder, run:
+
+```sh
+./build.sh build
+```
+
+#### Start the Docker container
+
+To start up the website, in bash, run:
+
+```sh
+./build.sh start --debug
+```
+
+Once the container starts, you can access the web.keyman.com site at
+http://localhost:8057 or http://web.keyman.com.localhost:8057. Note that the
+site is internally called web.keyman.com, but the public site is at
+keymanweb.com; web.keyman.com automatically forwards to keymanweb.com.
+
+#### Stop the Docker container
+
+In bash, run:
+
+```sh
+./build.sh stop
+```
+
+#### Remove the Docker container and image
+
+In bash, run:
+
+```sh
+./build.sh clean
+```
+
+#### Running tests
+
+To check for broken links and .php file conformance, when the site is running,
+in bash, run:
+
+```sh
+./build.sh test
+```
+
+[Git]: https://git-scm.com/downloads
+[Docker Desktop]: https://docs.docker.com/get-docker/
+[api.keyman.com]: https://github.com/keymanapp/api.keyman.com
+[help.keyman.com]: https://github.com/keymanapp/help.keyman.com
+[keyman.com]: https://github.com/keymanapp/keyman.com
+[keymanweb.com]: https://github.com/keymanapp/keymanweb.com
+[s.keyman.com]: https://github.com/keymanapp/s.keyman.com
+[website-local-proxy]: https://github.com/keymanapp/website-local-proxy
+[shared-sites]: https://github.com/keymanapp/shared-sites
+[enable Hyper-V]: https://techcommunity.microsoft.com/t5/educator-developer-blog/step-by-step-enabling-hyper-v-for-use-on-windows-11/ba-p/3745905
+[enable WSL2]: https://ubuntu.com/tutorials/install-ubuntu-on-wsl2-on-windows-10#1-overview
diff --git a/build.sh b/build.sh
index 04c255d..19ea186 100755
--- a/build.sh
+++ b/build.sh
@@ -2,7 +2,7 @@
## START STANDARD SITE BUILD SCRIPT INCLUDE
readonly THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
readonly BOOTSTRAP="$(dirname "$THIS_SCRIPT")/resources/bootstrap.inc.sh"
-readonly BOOTSTRAP_VERSION=v1.0.6
+readonly BOOTSTRAP_VERSION=v1.08
[ -f "$BOOTSTRAP" ] && source "$BOOTSTRAP" || source <(curl -H "Cache-Control: no-cache" -fs https://raw.githubusercontent.com/keymanapp/shared-sites/$BOOTSTRAP_VERSION/bootstrap.inc.sh)
## END STANDARD SITE BUILD SCRIPT INCLUDE
diff --git a/cdn/dev/css/footer.css b/cdn/dev/css/footer.css
deleted file mode 100644
index e69de29..0000000
diff --git a/cdn/dev/css/kmw-body.css b/cdn/dev/css/kmw-body.css
new file mode 100644
index 0000000..d447622
--- /dev/null
+++ b/cdn/dev/css/kmw-body.css
@@ -0,0 +1,139 @@
+/*
+ Name: KeymanWeb's Body Styles
+ Copyright: Copyright (C) 2025 SIL Global
+ Create Date: 5 November 2024
+
+ Modified Date:
+ Authors: Meng-Heng
+
+ History:
+
+ Description: All of the styles for KMW's Body
+*/
+/*
+=========
+==Icons==
+=========
+*/
+.fa-grip-lines:hover {
+ cursor: grab;
+}
+/*
+================
+==Section Tag==
+================
+*/
+.container-flex {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+}
+/*
+==============
+==Text area==
+==============
+*/
+.textarea-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: var(--textarea-width);
+ height: var(--textarea-height);
+}
+
+.text-area {
+ padding: 10px;
+ resize: none;
+ width: 100%;
+ height: 100% !important;
+ box-shadow: var(--textarea-box);
+ font-size: 16px;
+ border: none;
+ padding: 10px;
+ line-height: 1.6em;
+ font-family:SindhiWeb,Verdana,GeezWeb,LaoWeb,TibetanWeb,MyanmarWeb,SinhalaWeb,TamilWeb,KhmerWeb,LatinWeb,OriyaWeb,EgyptianWeb !important;
+}
+/*
+============
+==Divider==
+============
+*/
+.divider-container i {
+ opacity: 50%;
+}
+
+.divider-container i:hover {
+ color: var(--keyman-blue);
+ opacity: 100%;
+}
+/*
+=====================================
+==Items in the middle of the divider==
+=====================================
+*/
+.middle-divider {
+ grid-area: middle;
+ text-align: center;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+}
+/*
+=============
+==Keyboard==
+=============
+*/
+.keyboard-container {
+ width: 70vw;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.keyboard-area {
+ user-select: none;
+ flex: 1;
+ width: 70vw;
+ height: 100%;
+ border: var(--keyman-blue) 1px solid !important;
+}
+
+.phone.kmw-osk-frame, .tablet.kmw-osk-frame {
+ display: block !important;
+ position: relative !important;
+ height: 100% !important;
+ width: 100% !important;
+ max-width: 100vw !important;
+ max-height: 50vh !important;
+ z-index: 0 !important;
+}
+/*
+=============
+==Examples==
+=============
+*/
+.example-box {
+ background-color: var(--keyman-blue);
+ color: white;
+ width: 70vw;
+ text-align: center;
+ padding: 5px;
+ border-bottom: 1px solid rgb(159, 159, 159);
+}
+
+#exampleBox {
+ visibility: visible !important;
+}
+
+.example-box p {
+ margin: 10px;
+}
+
+#example {
+ visibility: visible !important;
+}
+
+/* End of Section Tag */
diff --git a/cdn/dev/css/kmw-desktop.css b/cdn/dev/css/kmw-desktop.css
index 68a0620..0cb3f68 100644
--- a/cdn/dev/css/kmw-desktop.css
+++ b/cdn/dev/css/kmw-desktop.css
@@ -6,677 +6,181 @@
======================
*/
-html {
- overflow-y: auto;
- overflow-x: hidden;
-}
-
-body {
- width:100%;
- font-family: Cabin, Arial, Helvetica, sans-serif;
- color:#2D2C2C;
- margin:0px;
- }
-
-a {
- text-decoration:none;
- color: #2EADD3;
- }
-
-a:hover {
- text-decoration:underline;
- }
-
-h3 {
- font-size:18px;
- }
-
-p {
- font-size:13px;
- line-height:1.5em;
- }
-
-header, #content, footer {
- /* width:960px; */
- margin:0px auto;
- clear: both;
- }
-
-.box {
- -webkit-border-radius: 8px;-moz-border-radius: 8px; border-radius: 8px;
+#kmw_controls{
+ border-top-left-radius: 8px;
+ border-top-right-radius: 8px;
-moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
- }
-
-/*
-===================
-==Header Elements==
-===================
-*/
-header {
- margin-top:0px;
- height: 96px;
- }
-
-header > div > img {
- width: 100%;
- display: block;
- height: 8px;
}
-#headerLeft img {
- display: block; /* default (inline) display inserts an unwanted bottom border */
- margin-top:0px;
- margin-left:28px;
- height:88px;
- /*width: 660px;*/
- /*-webkit-border-radius: 0px 0px 8px 8px; -moz-border-radius: 0px 0px 8px 8px; border-radius: 0px 0px 8px 8px;*/
- }
-#headerRight {
- display: block;
- float: right;
- text-align: right;
- }
-
-#headerRight-beta {
- display: block;
- font-weight: bold;
- color: red;
- font-size: 16pt;
- padding: 20px 12px 0;
+#kmw_btn_osk{
+ border-top-right-radius: 10px;
}
-#headerRight-link {
- padding: 0 12px 0;
+#kmw_controls_start{
+ border-top-left-radius: 10px;
}
+
/*
-====================
-==Content Elements==
-====================
+======================
+==Mobile Tool Box==
+======================
*/
-#content {
- min-height:375px;
- }
-
-
-/* KMW Main App */
-#app {
- float:left;
- /*height:330px;*/
- width:710px;
- margin: 30px 15px 15px;
- }
-#KeymanWebControl {
- width:710px;
- height: 35px;
- display:block;
- -webkit-border-radius: 8px 8px 0px 0px; -moz-border-radius: 8px 8px 0px 0px; border-radius: 8px 8px 0px 0px;
- }
-
- #kmw_controls{
- border-top-left-radius: 8px;
- border-top-right-radius: 8px;
- -moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
- }
-
- #kmw_btn_osk{
- border-top-right-radius: 10px;
- }
-
- #kmw_controls_start{
- border-top-left-radius: 10px;
- }
-
-#example,#help {
- margin:8px 10px;
- background-color:#95C6D6;
- border:1px solid #2EADD3;
- padding:2px;
- text-align:center;
- }
-
-#example {
- visibility: hidden;
- width:685px;
- min-height: 45px;
- display: flex;
- justify-content: center;
- align-items: center;
- padding-top: 5px;
-}
-
-#example a{
- padding-left: 10px;
+.tool-menu {
+ display: none;
}
-textarea {
- height:260px;
- width:554px;
- float:left;
- margin: 0px 10px 10px;
- color:black;
- font-size:16px;
- font-family:SindhiWeb,Verdana,GeezWeb,LaoWeb,TibetanWeb,MyanmarWeb,SinhalaWeb,TamilWeb,KhmerWeb,LatinWeb,OriyaWeb,EgyptianWeb !important;
- }
-
form {
padding-right:10px;
float:left;
- }
-#messageContainer {
- float:left;
- width:80%;
- }
-
-/* KMW App Buttons */
-
-#instructions {
- margin:-1px 0px 2px;
- }
-
-#buttons {
- float:right;
- padding-right:10px;
- }
-
-#buttons div {
- width:120px;
- -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px;
- background-color: #eeeeee;
- border: 1px solid #888888;
- margin: 0px 10px;
-/* -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 4px;
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- color: white;
- font: bold 14px Helvetica, Verdana, Arial, sans-serif;
- text-shadow: 0px -1px 1px #1E2D4D; */
- text-align:left;
- margin: 0 auto 6px;
- padding: 3px;
- padding-top: 9px;
- height: 26px;
- background-repeat: no-repeat;
- background-position: center left;
- }
-#buttons div:disabled {
- cursor: default !important;
- color: #eeeeee !important;
- /* -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important; */
-}
-
-#buttons p{
- margin-left: 41px;
-}
-
-#search{
- background-image:url('../img/btn_google_small.png');
-
-}
-
-#search.linksOff{
- background-image:url('../img/btn_google_small_white.png');
-}
-
-#copy{
- background-image:url('../img/btn_copy_small.png');
-
-}
-
-#copy.linksOff{
- background-image:url('../img/btn_copy_small_white.png');
}
-#font{
- display: none;
-}
-
-#mobile-font{
+/*
+==================
+==Mobile Divider==
+==================
+*/
+.left-divider {
display: none;
}
-#font-size{
- float: right;
- width: 120px;
- margin-right: 10px;
- margin-bottom: 8px;
- padding: 0px 4px;
- display: table;
-}
-
-#font-small{
- font-size: 10pt;
- padding-top: 7px;
- text-align: left;
-}
-
-#font-large{
- font-size: 16pt;
- text-align: right;
-}
-
-.ui-slider-handle{
- width: 10px !important;
- height: 10px !important;
- cursor: pointer !important;
- outline: none !important;
-}
-
-#slider{
- height: 4px;
- width: 68px;
- float: left;
- position: relative;
- top: 14px;
-}
-
-.font-letter{
- float: left;
- width: 25px;
- text-align: center;
- display: table-cell;
-}
-
-#buttons div * {
- display:inline;
- }
-
-div.links {
- cursor:pointer;
- }
-
-div.linksOff {
- cursor:default !important;
- border: 1px solid #cccccc !important;
- }
-
-div.linksOff * {
- color: gray;
- -moz-user-select:none;
- }
-
-hr {
- margin: 6px 4px 6px 4px;
- }
-
-
-/* Aside Elements */
-aside {
- float:right;
- width:200px;
- margin-right:15px;
- }
-aside h3 {
- background-color: rgba(181, 177, 176, 0.2);
- padding:6px 10px 7px;
- margin:0px;
- -webkit-border-radius: 8px 8px 0px 0px; -moz-border-radius: 8px 8px 0px 0px; border-radius: 8px 8px 0px 0px;
- -moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
- }
-aside > div {
- margin:30px 0px 0px;
- }
-aside p {
- padding:10px;
- margin:0px;
- }
-aside #learn {
- font-size:13px;
- text-align:center;
- }
-
-#keymandesktop, #bookmarklet{
+.right-divider {
display: none;
}
/*
-===================
-==Footer Elements==
-===================
+==================
+==Tool Container==
+==================
*/
-
-
-.button {
- float: left;
- width: 200px;
- height: 40px;
- line-height: 40px;
- border-radius: 6px;
- text-align: center;
- margin: 20px;
- cursor: pointer;
- box-shadow: 1px 1px 2px #3e3e3e;
-}
-
-.footer{
- float: left;
- text-align: center;
- width: 100%;
- background: #A4A8AB;
- line-height: 1;
-}
-
-.footer .wrapper {
- width: auto;
- display: inline-block;
- margin: 0;
- text-align: left;
-}
-
-.footer-third{
- float: left;
- width: 320px;
- margin-left: 4px;
- margin-right: 4px;
-}
-
-.footer-third-title{
- margin-top: 30px;
- text-align: center;
- font-size: 18pt;
- font-weight: normal;
- color: #fff;
- margin-bottom: 25px;
+.tool-container{
+ background-color: var(--bs-body-bg);
+ border-radius: 15px;
+ border: var(--bs-border-width) solid var(--bs-border-color);
+ width: 400px;
+ height: 50px;
+ padding: 10px;
}
-.footer-third form{
- width: 320px;
+.small-icon-tools, .large-icon-tools {
+ height: 40px;
+ padding: 0 10px;
}
-.footer-third form input{
- -webkit-appearance: none;
- padding: 0px;
- float: left;
- height: 38px;
- width: 200px;
- padding-left: 10px;
- font-size: 14pt;
- border-radius: 6px;
- border-bottom-right-radius: 0px;
- border-top-right-radius: 0px;
- box-shadow: none;
- border: 1px solid #FFFFFF;
- margin-left: 10px;
+.large-icon-tools {
+ width: 50%;
}
-.footer-third form input:focus{
- outline: 0;
+.small-icon-tools {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ width: 50%;
+ height: 40px;
+ border-left: 0.5px solid gray;
}
-#footer-mailchimp br {
- clear: both;
+.small-icon-tools > * {
+ width: max-content;
+ height: max-content;
+ padding: 10px;
+ margin: 0 5px;
}
-
/*
- social-logos font from https://github.com/Automattic/social-logos, GPLv2
- */
-@font-face {
- font-family: "social-logos";
- src: url("../../fonts/social-logos.ttf");
+======================
+==Keyboard Examples==
+======================
+*/
+.highlightKeys kbd {
+ border: solid 1px #808080;
+ border-radius: 4px;
+ background: #cccccf;
+ color: black;
+ font-family: Verdana;
font-weight: normal;
- font-style: normal;
-}
-
-[data-icon]:before {
- font-family: "social-logos" !important;
- content: attr(data-icon);
- font-style: normal !important;
- font-weight: normal !important;
- font-variant: normal !important;
- text-transform: none !important;
- line-height: 1;
- speak: none;
- padding-right: 4px;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale; }
-
-#footer-social {
- text-align: center;
-}
-
-#footer-social a {
- display: block;
- text-decoration: none;
- color: #666666;
- font-size: 14pt;
- padding: 4px;
-}
-
-#footer-social a:hover {
- color: #444444;
-}
-
-#privacy-policy {
- text-align: center;
- margin-top: 16px;
-}
-
-#privacy-policy a {
- display: block;
- text-decoration: none;
- color: white;
- font-size: 12pt;
-}
-
-#privacy-policy a:hover {
- color: #444444;
-}
-
-#version {
- color: #666666;
- font-size: 10pt;
- margin-top: 46px;
- text-align: center;
-}
-
-#mobile-footer {
- display: none;
-}
-
-#footer-social div {
+ min-width: 16px;
display: inline-block;
- text-align: left;
- margin-bottom: 24px;
+ box-shadow: 2px 2px 1px rgba(128, 128, 128, 0.3);
+ margin-right: 4px;
}
-#footer-social a#footer-community {
- padding-left: 27px;
+.highlightKeys kbd.space {
+ min-width: 64px;
}
-.subscribe{
- width: 90px;
- margin: 0px;
- border-top-left-radius: 0px;
- border-bottom-left-radius: 0px;
- background: #666666;
- box-shadow: none;
+/*
+======================
+==Keyboard Styling==
+======================
+*/
+.kmw-osk-frame {
+ display: block !important;
+ position: static !important;
+ width: 100% !important;
+ height: 100% !important;
+ max-width: 100% !important;
+ max-height: 100% !important;
+ font-size: 21px !important;
+ border-radius: 0;
}
-.subscribe:hover{
- background: #888888;
+.kmw-help-osk-frame {
+ height: 40vh !important;
+ scroll-behavior: smooth !important;
+ scrollbar-width: thin;
+ overflow-x: hidden !important;
+ overflow-y: scroll !important;
+ text-align: center !important;
}
-.subscribe h2{
- font-size: 16px;
- margin: 0px;
+.kmw-osk-static {
+ height: fit-content !important;
+ text-align: center !important;
+ border: none !important;
}
-.sil-logo a, .sil-logo a:visited {
- color: white;
- text-decoration: none;
+.kmw-osk-static table {
+ display: flex !important;
+ flex-direction: row !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: 100% !important;
}
-.sil-logo a:hover {
- color: white;
- text-decoration: underline;
+.kmw-osk-inner-frame {
+ font-size: 21px !important;
+ width: 100% !important;
+ height: 100% !important;
+ max-width: 100% !important;
+ max-height: 100% !important;
}
-.sil-logo {
- text-align: center;
- margin-top: 20px;
+#kmw-osk-help-page > table > tbody > tr > td > .row {
+ --bs-gutter-x: 0 !important;
}
-.sil-logo p{
- color: #fff;
- font-size: 12pt;
- margin-top: 10px;
+.kmw-title-bar {
+ background-color: #2EADD3 !important;
+ border-radius: 0 !important;
+ height: fit-content !important;
}
-/*
-===================
-==Extras==
-===================
-*/
-
-#fblike{
-position: absolute;
-left: 50%;
-margin-left: 320px;
- }
-
-nav ul {
- list-style: none outside none;
- width:90%;
- margin:0px auto;
- padding:15px 0px;
- }
-
-nav li {
- display:inline;
- padding:20px 39px;
- }
-
-nav a {
- color:#eee;
- font-family:Tahoma;
- font-weight:bold;
- font-size:10.7px;
- }
-
-.messageBox {
- -webkit-border-radius: 8px;-moz-border-radius: 8px; border-radius: 8px;
- -moz-box-shadow: 3px 3px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 3px 3px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 3px 3px 3px 0px rgba(0, 0, 0, 0.5);
- background: #c4c4c4;
- position: fixed;
- left: 50%;
- top: 25%;
- z-index: 10002;
- margin-left: -250px;
- margin-top: -50px;
- width: 500px;
- height: 200px;
+#keyboard_europeanlatin_help p, .kmw-osk-static p {
+ display: flex !important;
+ justify-content: end !important;
+ position: relative !important;
+ right: 0 !important;
+ top: 0 !important;
}
-.messageBox div {
- width: 464px;
- height: 134px;
- left: 10px;
- top: 10px;
- padding: 8px;
- font-size: 13px;
- position: absolute;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 3px;
- background: white;
+.kmw-key-square {
+ z-index: 0 !important;
}
-.messageBox button {
- width:83px;
- position: absolute;
- right: 10px;
- bottom: 4px;
- background: #414141;
- border-top: 1px solid #383838;
- border-right: 1px solid #1F1F1F;
- border-bottom: 1px solid #151515;
- border-left: 1px solid #1F1F1F;
- -webkit-border-radius: 8px;-moz-border-radius: 8px; border-radius: 8px;
- -moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
- color: white;
- font: bold 12px Helvetica, Verdana, Arial, sans-serif;
- text-shadow: 0px -1px 1px #1E2D4D;
- text-align:center;
- margin: 0 auto 6px;
- padding: 3px;
- }
-
-.messageBox button:disabled {
- cursor: default !important;
+.kmw-spacebar-caption {
color: gray !important;
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
-}
-
-.messageBox button:hover {
--webkit-border-radius: 8px;-moz-border-radius: 8px; border-radius: 8px;
- -moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
- cursor: pointer;
}
-.messageBox button:active {
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
-}
-
-.progressBox {
--webkit-border-radius: 8px;-moz-border-radius: 8px; border-radius: 8px;
- -moz-box-shadow: 3px 3px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 3px 3px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 3px 3px 3px 0px rgba(0, 0, 0, 0.5);
- background: #c4c4c4;
- position: fixed;
- left: 50%;
- top: 25%;
- margin-left: -200px;
- width: 400px;
- height: 54px;
-}
-
-.progressBox div {
- width: 364px;
- height: 18px;
- left: 10px;
- top: 10px;
- padding: 8px;
- background: url('../img/wait.gif') no-repeat 354px 8px white;
- font-size: 13px;
- position: absolute;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 3px;
-}
-
-#footer-logo{
- margin-top: 10px;
-}
-
-#content {width: 960px;}
-
-/*
-======================
-==Keyboard Styling==
-======================
-*/
-
.desktop .kmw-osk-frame, .desktop .kmw-osk-inner-frame, .desktop .kmw-key-layer-group {
background-color: #95C6D6 !important;
}
@@ -710,7 +214,7 @@ nav a {
.desktop .kmw-key-label { top: 5%; }
.desktop .kmw-key-label { left: 10%; }
.desktop .kmw-key-default .kmw-key-text { top: 25%; }
- .desktop .kmw-footer, .desktop .kmw-title-bar { background: #2EADD3 !important; }
+ .desktop .kmw-footer, .desktop .kmw-title-bar { background-color: #2EADD3 !important; display: none;}
.desktop .kmw-title-bar div{min-height: 100%;}
.desktop .kmw-footer-caption a { color: white !important; }
.kmw-osk-frame, .kmw-osk-inner-frame { border-color: #2EADD3 !important; }
@@ -733,58 +237,6 @@ nav a {
text-align: center;
}
-#keyman-desktop-download {
- display: block;
- text-align: center;
- margin-top: 8px;
-}
-
-#free-open-source {
- color: #606060;
- font-size: 8pt;
- font-weight: normal;
- display: block;
- text-align: center;
-
-}
-
-#close {
- float: right;
- cursor: pointer;
- position: relative;
- top: -10px;
-}
-
-#bookmarklet.hidden {
- display: none;
-}
-
-#bookmarklet div {
- text-align: center;
- margin: 16px 0 2px;
-}
-
-#bookmarklet p {
- font-size: 9px;
- text-align: justify;
-}
-
-#learn div {
- margin: 0;
- padding-bottom: 12px;
-}
-
-@media all and (min-width: 1320px) {
- ins.adsbygoogle {
- display: inline-block !important;
- height: 600px;
- position: absolute;
- right: 16px;
- top: 120px;
- width: 160px;
- }
-}
-
.desktop .kmw-keyboard-dari_clra .kmw-key-default .kmw-key-text {
top: -33%;
}
diff --git a/cdn/dev/css/kmw.css b/cdn/dev/css/kmw-global.css
similarity index 58%
rename from cdn/dev/css/kmw.css
rename to cdn/dev/css/kmw-global.css
index 02c9c07..50c9ca1 100644
--- a/cdn/dev/css/kmw.css
+++ b/cdn/dev/css/kmw-global.css
@@ -1,8 +1,33 @@
/*
=============
-==Reset CSS==
+==Reset & Global CSS==
=============
*/
+:root {
+ --textarea-box: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgb(209, 213, 219) 0px 0px 0px 1px inset;
+ --textarea-width: 100vw;
+ --textarea-height: calc(100vh - 55vh);
+ --divider-height: 5vh;
+ --keyboard-height: calc(97vh - (var(--textarea-height) + var(--divider-height)));
+ --box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
+ --keyman-orange: #FC7200;
+ --keyman-red: #B92034;
+ --keyman-blue: #2EADD3;
+ --tablet--kb-bg-color: #666;
+ --mobile-font-size: 12px;
+ --mobile-search-max-width: 100%;
+}
+
+* {
+ box-sizing: border-box;
+ letter-spacing: .05rem;
+ font-weight: 400;
+}
+
+body {
+ overflow: hidden;
+}
+
html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
@@ -24,11 +49,12 @@ time, mark, audio, video {
}
a {
- margin:0;
- padding:0;
+ margin:0 !important;
+ padding:0 !important;
vertical-align:baseline;
background:transparent;
- }
+ text-decoration: none !important;
+}
article,aside,details,figcaption,figure,
section,header,footer,hgroup,menu,nav {
@@ -65,6 +91,7 @@ hr {
border:0;
border-top:1px solid #cccccc;
padding:0;
+ margin: 0;
}
button, input, select {
@@ -76,3 +103,12 @@ button, input, select {
ins.adsbygoogle {
display: none !important;
}
+
+.hidden {
+ display: none !important;
+}
+
+/* Icons */
+i:hover {
+ transition: all 0.3s ease;
+}
diff --git a/cdn/dev/css/kmw-header.css b/cdn/dev/css/kmw-header.css
new file mode 100644
index 0000000..8dbd919
--- /dev/null
+++ b/cdn/dev/css/kmw-header.css
@@ -0,0 +1,920 @@
+/*
+ Name: KeymanWeb's Header Styles
+ Copyright: Copyright (C) 2025 SIL Global
+ Documentation:
+ Description: All of the styles for KMW's Header
+ Create Date: 5 November 2024
+
+ Modified Date:
+ Authors: Meng-Heng
+
+ History:
+*/
+
+header > div > img {
+ width: 100%;
+ display: block;
+ height: 5px;
+}
+
+.main-header {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ height: 75px;
+ padding: 0 10px;
+ box-shadow: var(--box-shadow);
+}
+/*
+=============================================================
+==Left side of Header: Search Box, Keyboard Selection Menu==
+=============================================================
+*/
+
+#searchDropdownMenu {
+ padding: 10px;
+ margin: 0;
+}
+
+.left-header {
+ display: flex;
+ align-items: center;
+ width: 70vw;
+}
+
+.left-header img {
+ margin-right: 10px;
+}
+
+/*
+==============
+==Search Box==
+==============
+*/
+#searchBar {
+ position: relative;
+ width: 550px;
+ margin-right: 10px;
+}
+
+#searchBar #searchInput {
+ height: 50px;
+ width: 100%;
+ text-indent: 10px;
+ border-radius: 15px;
+ text-align: left;
+ font-family: inherit !important;
+ box-shadow: none;
+}
+
+#searchInput:hover, #searchInput:focus {
+ border: 1px solid var(--keyman-orange);
+}
+
+#searchIcons {
+ position: absolute;
+ right: 5px;
+ top: 12px;
+ border-left: 1px solid gray;
+ background-color: white;
+}
+
+#searchIcons > * {
+ margin-right: 10px;
+ padding-left: 15px;
+ opacity: 50%;
+}
+
+#searchIcons > *:hover {
+ padding-left: 15px;
+ opacity: 100%;
+}
+
+#magnifyingGlassIcon {
+ display: inline;
+}
+
+#clearSearchIcon {
+ display: none;
+}
+
+#searchDropdownMenu {
+ width: 100%;
+ max-height: 600px;
+ overflow: auto;
+ scrollbar-width: thin;
+}
+
+#searchDropdownMenu hr {
+ margin: 15px 0;
+}
+/*
+============================
+==Instruction on dropdown==
+============================
+*/
+.instruction-title {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+}
+
+.instruction-title, .keyboard-title {
+ width: 100%;
+}
+
+.instruction-title p, .keyboard-title {
+ font-weight: 500;
+ font-size: 18px;
+ text-align: center;
+}
+
+#closeInstruction {
+ position: absolute;
+ right: 20px;
+ top: 10px;
+ border: 1px solid black;
+ border-radius: 20px;
+ text-align: center;
+ width: 30px;
+ cursor: pointer;
+}
+
+/*
+================
+==Search cards==
+================
+*/
+.card-wrap {
+ box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;;
+ padding: 10px;
+ border-radius: 10px;
+ letter-spacing: .05rem;
+ overflow: hidden;
+ margin-bottom: 5px;
+}
+
+.card-wrap.disabled {
+ opacity: 0.5;
+ pointer-events: none;
+ position: relative;
+}
+.card-wrap .monthly-download {
+ opacity: 75%;
+}
+
+.card-header {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.card-header > * {
+ margin-right: 0 10px 0 0;
+}
+
+.card-header .card-header-title {
+ display: flex;
+ flex-direction: row;
+ justify-content: left;
+ align-items: center;
+}
+
+.card-header h4 {
+ color: var(--keyman-orange);
+ font-weight: 400;
+ font-size: large;
+ margin: 0 5px;
+}
+
+.card-header h4 span {
+ color: black;
+}
+
+.card-header p {
+ position: relative;
+ float: right;
+ font-weight: 200;
+ font-size: medium;
+ margin: 0;
+}
+
+.card-header p, .card-header .help-icon-span {
+ color: var(--keyman-red);
+ cursor: pointer;
+}
+
+.card-wrap.disabled .kb-icon-plus {
+ pointer-events: auto;
+ opacity: 1;
+ position: absolute;
+ right: 15px;
+ z-index: 1;
+}
+
+.kb-icon-plus {
+ transition: 0.5s ease;
+}
+
+.kb-icon-plus.animate {
+ color: green;
+}
+
+.keyboard-id {
+ font-size: 12px;
+ margin-bottom: 10px;
+ color: #006622;
+}
+
+.card-wrap h6 {
+ opacity: 75%;
+ font-size: 12px;
+ margin: 0 0 0.35rem;
+}
+
+.keyboard-description {
+ color: gray;
+}
+
+.keyboard-specs {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.keyboard-specs .platform {
+ display: flex;
+ flex-direction: row;
+}
+
+.keyboard-specs .platform span,
+.keyboard-content .keyboard-platforms .platform span {
+ font-size: 0px;
+ width: 15px;
+ height: 20px;
+ margin: 0;
+}
+/*
+==============
+==Pagination==
+==============
+*/
+#searchDropdownMenu .bottom-row-search {
+ background-color: white;
+ align-items: center;
+ justify-content: center;
+ width: fit-content;
+ border-radius: 10px;
+ width: 100%;
+ bottom: 0;
+ text-align: center;
+ box-shadow: rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px;
+}
+
+.bottom-row-search > * {
+ margin: 10px;
+ cursor: pointer;
+}
+
+#paginationControls {
+ display: none;
+}
+/*
+===========================
+==Selected Keyboard Menu==
+===========================
+*/
+#keyboardSelectionButton {
+ z-index: 3;
+}
+
+.keyboard-selection-menu {
+ width: 350px;
+ height: 35px;
+ position: relative;
+}
+
+#keyboardSelection {
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ width: 0;
+ height: 100%;
+ background-color: white;
+ color: black;
+ box-shadow: var(--box-shadow);
+ overflow-y: scroll;
+ scrollbar-width: none;
+ margin-left: -10px;
+ border-radius: 10px;
+ z-index: 0;
+ white-space: nowrap;
+ text-indent: 15px;
+}
+
+#keyboardSelection.open {
+ position: relative;
+ width: 400px;
+ height: fit-content;
+ padding: 0 0 10px;
+ z-index: 2;
+}
+
+#keyboardSelection p {
+ margin: 0;
+ height: 25px;
+ width: 100%;
+}
+
+.kb-selection.open {
+ display: block;
+}
+
+.kb-selection {
+ display: none;
+}
+
+.kb-item-header, .kb-item-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 30px;
+ font-weight: 500;
+ margin: 5px 0;
+}
+
+.kb-item-header {
+ border-bottom: 1px solid gray;
+}
+
+.kb-item-footer {
+ border-top: 1px solid gray;
+ cursor: pointer;
+}
+
+.kb-item-keyboard {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ height: fit-content;
+ padding: 5px;
+ opacity: 50%;
+}
+
+.kb-item-keyboard:hover {
+ cursor: pointer;
+ color: black;
+ padding: 5px;
+ opacity: 100%;
+}
+
+.kb-item-keyboard span {
+ width: 50%;
+ height: 30px;
+ text-align: center;
+ overflow-x: scroll;
+ scrollbar-width: thin;
+ margin: 5px;
+}
+
+.kb-item-keyboard span:hover {
+ text-decoration: underline;
+}
+
+.keyboard-configs {
+ height: fit-content;
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ border-left: 1px solid black;
+ align-self: center;
+ justify-content: center;
+ list-style-type: none;
+ padding-left: 15px;
+ margin: 5px;
+}
+
+.keyboard-configs > li {
+ opacity: 50%;
+ margin-right: 5px;
+}
+
+.keyboard-configs > li:hover {
+ opacity: 100%;
+ cursor: pointer !important;
+ font-size: 20px;
+}
+
+.keyboard-configs li > i {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ width: 35px;
+ height: 35px;
+ margin-right: 3px;
+}
+
+::-webkit-scrollbar:horizontal {
+ height: 5px;
+}
+
+::-webkit-scrollbar-thumb:horizontal {
+ background: #2c7be5;
+ border-radius: 6px;
+}
+
+::-webkit-scrollbar-track:horizontal {
+ background: #f1f1f1;
+}
+
+.btn-keyman-orange {
+ background-color: var(--keyman-orange) !important;
+ color: white !important;
+}
+
+.keyboard-details {
+ position: fixed;
+ width: 400px;
+ height: max-content;
+ background-color: white;
+ color: black;
+ box-shadow: var(--box-shadow);
+ border-radius: 20px;
+ padding: 10px;
+ z-index: 2;
+ top: 100px;
+}
+
+.keyboard-content-wrapper {
+ margin-top: 10px;
+}
+
+.keyboard-content {
+ width: max-content;
+ list-style-type: none;
+ padding: 0;
+ text-align: left;
+ display: flex;
+ flex-direction: column;
+}
+
+.keyboard-content > * {
+ margin: 5px 0px;
+}
+
+.keyboard-content .keyboard-platforms .platform {
+ display: flex;
+ flex-direction: row;
+}
+
+.keyboard-content li .row .col:nth-child(2),
+.keyboard-content li .row .col:nth-child(2) * {
+ text-align: center;
+ white-space: normal;
+ word-break: break-word;
+ width: 200px;
+ overflow: hidden;
+}
+
+.keyboard-content li .row .col:nth-child(1) {
+ width: 200px;
+}
+
+#removeLanguage {
+ cursor: pointer;
+}
+
+.platform span {
+ background-repeat: no-repeat;
+ padding-left: 24px;
+ background-size: 18px;
+ display: block;
+ margin: 4px 0;
+}
+
+.platform span:hover, .keyboard-configs li:hover{
+ cursor: help;
+}
+
+.platform .platform-windows {
+ background-image: url('/cdn/dev/img/platforms/icon-windows.png');
+}
+
+.platform .platform-macos {
+ background-image: url('/cdn/dev/img/platforms/icon-macos.png');
+}
+
+.platform .platform-linux {
+ background-image: url('/cdn/dev/img/platforms/icon-linux.png');
+}
+
+.platform .platform-android {
+ background-image: url('/cdn/dev/img/platforms/icon-android.png');
+}
+
+.platform .platform-ios {
+ background-image: url('/cdn/dev/img/platforms/icon-ios.png');
+}
+
+.platform .platform-desktopweb {
+ background-image: url('/cdn/dev/img/platforms/icon-desktopWeb.png');
+}
+
+.platform .platform-mobileweb {
+ background-image: url('/cdn/dev/img/platforms/icon-mobileWeb.png');
+}
+/*
+=========================================
+==Font size slider + Hide/show keyboard==
+=========================================
+*/
+.tool-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+}
+
+.font-size-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 5px 5px;
+ height: fit-content;
+}
+
+.font-size-slider {
+ display: flex;
+ flex-direction: column;
+ justify-content: end;
+ align-items: center;
+ height: 60px;
+}
+
+#fontSizeRange[type=range] {
+ -webkit-appearance: none;
+ margin: 10px 0;
+ width: 100%;
+}
+
+.font-large {
+ font-size: 20px;
+}
+
+input[type="range"] {
+ width: 100px;
+ background-color: transparent;
+}
+
+input[type="range"]::-webkit-slider-runnable-track {
+ height: 3px;
+ background: rgb(107, 107, 107);
+ border-radius: 10px;
+}
+
+input[type="range"]::-webkit-slider-thumb {
+ border-radius: 50%;
+ height: 20px;
+ width: 20px;
+ position: relative;
+ bottom: 6px;
+ background-size: 50%;
+ box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.4);
+ cursor: grab;
+}
+
+#fontSizeRange[type=range]::-webkit-slider-runnable-track {
+ width: 100%;
+ cursor: pointer;
+ transform: 0.2s;
+ box-shadow: var(--box-shadow);
+ background: gray;
+ border-radius: 25px;
+}
+
+#fontSizeRange[type=range]::-webkit-slider-thumb {
+ height: 10px;
+ width: 20px;
+ border-radius: 7px;
+ background: var(--keyman-blue);
+ cursor: pointer;
+ -webkit-appearance: none;
+ box-shadow: 0px 0px 5px rgb(143, 143, 143);
+ margin-top: 3px;
+}
+
+/*
+===================================================================================
+==Right side of Header: Font Resizer, Hide keyboard + Hamburger menu==
+===================================================================================
+*/
+.right-header {
+ display: grid;
+ grid-template-columns: 80% 20%;
+ width: 100%;
+ justify-items: right;
+ align-items: center;
+ margin-right: 0;
+}
+
+.right-header i {
+ font-size: 24px;
+ opacity: 50%;
+}
+
+.right-header i:hover {
+ opacity: 100%;
+ cursor: pointer;
+ color: var(--keyman-blue);
+}
+
+.burger-menu-top-section > div {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ align-items: center;
+ border-bottom: 0.5px solid gray;
+ padding: 15px;
+}
+
+.burger-menu-top-section .instruction-status .title p {
+ font-size: 16px;
+ margin: 0;
+}
+
+#burgerMenu #burgerDropDownMenu {
+ box-shadow: var(--box-shadow);
+}
+
+#burgerMenu #burgerDropDownMenu:hover {
+ cursor: pointer;
+}
+
+#burgerMenu #burgerDropDownMenu .burger-menu-middle-section ul li {
+ align-items: center;
+ width: 300px;
+ border-bottom: 1px solid rgb(196, 196, 196);
+ margin-top: 10px;
+ margin-left: 10px;
+}
+
+#burgerMenu #burgerDropDownMenu .burger-menu-middle-section ul li::after {
+ background-color: orange;
+}
+
+#burgerMenu #burgerDropDownMenu .burger-menu-middle-section ul li p:hover {
+ opacity: 100%;
+}
+
+#burgerMenu #burgerDropDownMenu .burger-menu-middle-section ul li p {
+ width: 80%;
+ white-space: normal;
+ position: relative;
+ left: 40px;
+ font-size: 14px;
+ opacity: 50%;
+ margin-top: 10px;
+}
+
+#burgerMenu #burgerDropDownMenu .burger-menu-middle-section ul li a {
+ text-decoration: none;
+ color: black;
+ font-style: bold;
+ font-size: 16px;
+}
+
+#burgerMenu #burgerDropDownMenu .burger-menu-middle-section ul li i, #burgerMenu #burgerDropDownMenu .burger-menu-middle-section ul li img {
+ margin: 0 10px;
+ font-size: 16px;
+ width: 18px;
+ opacity: 100%;
+ color: var(--keyman-orange)
+}
+
+#burgerMenu button i:hover {
+ cursor: pointer;
+}
+
+.burger-menu-external-links {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: 1fr;
+ padding: 0;
+ list-style-type: none;
+}
+
+.external-links-item:active {
+ background-color: transparent !important;
+}
+
+.kmw-socials, .kmw-version, .sil-logo {
+ font-size: 14px;
+}
+
+.kmw-socials {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding-bottom: 15px;
+ line-height: 30em;
+ margin-bottom: 10px;
+ border-bottom: 0.5px solid gray;
+}
+
+.kmw-socials h5 {
+ line-height: 1.5em;
+ font-size: 25px;
+}
+
+.kmw-socials-icons {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ margin: 10px;
+}
+
+.kmw-socials-icons > * {
+ line-height: 1.5em;
+}
+
+.kmw-socials-icons > a {
+ display: flex;
+ text-decoration: none;
+ color: black;
+ opacity: 50%;
+ font-size: 16px;
+ position: relative;
+}
+
+.kmw-socials-icons > a:hover {
+ opacity: 100%;
+}
+
+@font-face {
+ font-family: "social-logos";
+ src: url("../../fonts/social-logos.ttf");
+ font-weight: normal;
+ font-style: normal;
+}
+
+[data-icon]:before {
+ font-family: "social-logos" !important;
+ content: attr(data-icon);
+ padding-right: 4px;
+ font-size: 16px;
+}
+
+.kmw-version {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+
+.sil-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 15px;
+}
+/*
+================================
+==Limits of Keyboard Selection==
+================================
+*/
+.warning-container {
+ background: rgba(0, 0, 0, 0.25);
+ position: fixed;
+ height: 100vh;
+ width: 100%;
+ z-index: 1000;
+ top: 0;
+ left: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.warning-content {
+ width: 500px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-color: white;
+ color: black;
+ text-align: center;
+ padding: 30px 30px 70px;
+ border-radius: 20px;
+ box-shadow: var(--box-shadow);
+}
+
+.warning-cancel-btn {
+ width: 30px;
+ font-size: 20px;
+ opacity: 50%;
+ align-self: flex-end;
+ background-color: transparent;
+ border: none;
+ margin-bottom: 10px;
+}
+
+.warning-keyman-image {
+ width: 82px;
+ margin-bottom: 15px;
+}
+
+.warning-text {
+ margin-bottom: 40px;
+ font-size: 18px;
+}
+
+.warning-accept-btn {
+ background-color: var(--keyman-orange);
+ border: none;
+ border-radius: 5px;
+ width: 200px;
+ padding: 14px;
+ color: white;
+ box-shadow: var(--box-shadow);
+}
+/*
+===============================
+==DELETE: Confirmation Dialog==
+===============================
+*/
+.dialog-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.25);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+}
+
+.dialog-box {
+ background: #fff;
+ border-radius: 12px;
+ padding: 2rem;
+ width: 90%;
+ max-width: 400px;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.1);
+ text-align: center;
+}
+
+.dialog-icon {
+ font-size: 2rem;
+ color: #e74c3c;
+ margin-bottom: 1rem;
+}
+
+.dialog-title {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin: 0.5rem 0;
+}
+
+.dialog-message {
+ font-size: 0.95rem;
+ color: #555;
+ margin-bottom: 1.5rem;
+}
+
+.dialog-buttons {
+ display: flex;
+ justify-content: space-around;
+ gap: 1rem;
+}
+
+.btn-cancel, .btn-delete {
+ flex: 1;
+ padding: 0.75rem;
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ font-size: 1rem;
+}
+
+.btn-cancel {
+ background: #f4f4f4;
+ color: #333;
+}
+
+.btn-delete {
+ background: var(--keyman-red);
+ color: white;
+}
+
+.btn-cancel:hover {
+ background: #eaeaea;
+}
+
+.btn-delete:hover {
+ background: #d54335;
+}
diff --git a/cdn/dev/css/kmw-interaction.css b/cdn/dev/css/kmw-interaction.css
new file mode 100644
index 0000000..d50a438
--- /dev/null
+++ b/cdn/dev/css/kmw-interaction.css
@@ -0,0 +1,62 @@
+/*
+ Name: KeymanWeb Interaction Styles
+ Copyright: Copyright (C) 2025 SIL Global
+ Documentation:
+ Description: Animation & Styles of every interactable components.
+ Create Date: 19 Nov 2025
+
+ Modified Date:
+ Authors: Meng-Heng
+
+ History:
+*/
+
+/* Warning before delete */
+.warning-content {
+ animation: fadeIn 0.2s ease;
+}
+
+.dialog-box {
+ animation: fadeIn 0.2s ease;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: scale(0.9); }
+ to { opacity: 1; transform: scale(1); }
+}
+
+/* Enable keyboard for Keyboard Selection Animation */
+.kb-icon-plus-animate {
+ animation: plustoTickToMinus 2s forwards;
+}
+
+@keyframes plustoTickToMinus {
+ 0% {
+ content: "+";
+ transform: scale(1);
+ }
+ 30% {
+ color: lime;
+ content: "✓";
+ transform: scale(1.5);
+ }
+ 60% {
+ color: lime;
+ content: "✓";
+ transform: scale(1.3);
+ }
+ 100% {
+ content: "-";
+ transform: scale(1);
+ }
+}
+
+/* Textarea */
+.textarea-animation {
+ animation: highlight 3s ease;
+}
+
+@keyframes highlight {
+ from {box-shadow:#2EADD3 0px 0px 0px 3px;}
+ to {box-shadow: var(--textarea-box);}
+}
\ No newline at end of file
diff --git a/cdn/dev/css/kmw-mobile.css b/cdn/dev/css/kmw-mobile.css
index 95ce082..82666a5 100644
--- a/cdn/dev/css/kmw-mobile.css
+++ b/cdn/dev/css/kmw-mobile.css
@@ -6,23 +6,20 @@
======================
*/
-html {
- overflow-y: scroll;
- overflow-x: hidden;
-}
-
body {
- width:100%;height:100%;
+ width:100%;
+ height:100%;
font-family: Verdana, Arial, Helvetica, sans-serif;
color:#2D2C2C;
background-color: #eeeeee;
margin:0px;
- }
+ overflow: none;
+}
a {
text-decoration:none;
color: #AD4A28;
- }
+}
a:hover {
text-decoration:underline;
@@ -37,368 +34,177 @@ p {
line-height:1.5em;
}
/*
-header, #content, footer {
- margin:0px auto;
- clear: both;
- }
-*/
-
-hr {
- display: none;
- }
-
-/*
-.box {
- -webkit-border-radius: 8px;-moz-border-radius: 8px; border-radius: 8px;
- -moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
- }
-*/
-/*
-===================
-==Header Elements==
-===================
+==========
+==Header==
+==========
*/
-header img, #headerLeft {
- display: none;
-}
-header {
- position: absolute;
- top: 190px;
- background: #776666;
- font-size: 9pt;
- padding: 6px 0;
- color: white;
+.main-header {
width: 100%;
- text-align: center;
-}
-header a {
- color: #c0c0ff;
- text-decoration: underline;
+ grid-template-columns: 2fr 1fr;
}
-/*
-====================
-==Content Elements==
-====================
-*/
-#content {
- width: 100%; /* specific for mobile and tablet */
- margin: 0px auto;
- background-color: #eeeeee;
- /* min-height:375px; */
- }
-
-
-/* KMW Main App */
-#app {
- width:100%;
- margin: 0px;
- }
-#KeymanWebControl {
+.left-header img {
display: none;
- }
-
-#exampleBox,#example {
- display:none;
- }
-
-#messageContainer {
- width: 80%;
- float:left;
- height: 50%;
- }
-
-#message {
- height:144px;
- width: 100%;/*554px;*/
- float:left;
- margin: 4px;
- color:black;
- font-size:17px;
- font-family:SindhiWeb,Verdana,GeezWeb,LaoWeb,TibetanWeb,MyanmarWeb,SinhalaWeb,TamilWeb,KhmerWeb,LatinWeb,OriyaWeb,EgyptianWeb !important;
- }
-
- #font-size{
- display: none;
- }
-
-/* KMW App Buttons */
+}
-#buttons {
- float: right;
- width: 40px;
- margin: 8px 15px 8px 0px;
- border: none;
+.right-header {
+ grid-template-columns: 50% 50%;
+ padding: 10px;
}
-#buttons div {
- /*width:50px;*/
- -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px;
- background: #eeeeee;
- border: none;
- margin: 0px 0px 4px 10px;
- height:40px;
- width: 41px;
- padding: 0px;
- padding-top: 2px;
- background-repeat: no-repeat;
- background-position: center center;
- background-size: 34px 34px;
- }
+.tool-container {
+ margin: 10px;
+}
-#font{
- text-align: center;
+.hamburger-menu-container {
+ overflow: scroll;
+ scroll-behavior: smooth;
+ height: 70vh;
}
-#font p{
- display: none;
+#burgereMenu ul {
+ width: 80vw;
+ scroll-behavior: smooth;
+ flex-direction: column;
+ height: 60vh;
+ overflow: hidden;
}
-#buttons div:disabled {
- cursor: default !important;
- color: gray !important;
- /*border-color: gray !important; */
- }
+.burger-menu-external-links{
+ display: flex !important;
+ flex-direction: column !important;
+}
-#search,#search.links{
- background-image:url('../img/btn_google.png');
+.kmw-socials-icons {
+ display: flex;
+ flex-direction: column;
+}
+/*
+=======================
+==Language Search box==
+=======================
+*/
+#searchBar {
+ width: 50%;
+}
+#searchInput::placeholder {
+ opacity: 0;
}
-#search.linksOff{
- background-image:url('../img/btn_google_white.png');
+#searchIcons {
+ border: 0;
+ background-color: transparent;
+ z-index: 0;
+ user-select: none;
}
-#copy,#copy.links{
- background-image:url('../img/btn_copy.png');
+#searchInput:focus, #keyboardSelection:active {
+ width: var(--mobile-search-max-width) !important;
+}
+#searchDropdownMenu {
+ width: 95vw !important;
}
-#copy.linksOff{
- background-image:url('../img/btn_copy_white.png');
+.textarea-container {
+ height: 100%;
}
-#buttons div * {
- display:inline;
- }
+.mobile-font-size-container {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ right: 50px;
+ top: 80px;
+ width: 150px;
+ background: white;
+ border: 1px solid black;
+ padding: 10px;
+ border-radius: 10px;
+}
-#buttons p { /* buttons have no text label on mobiles */
- display:none;
- }
+.large-icon-tools, .small-icon-tools {
+ display: none;
+}
-div.links {
- cursor:pointer;
- }
+#keyboardSelection.open {
+ width: 70vw !important;
+}
+/*
+====================
+==Content Elements==
+====================
+*/
-div.linksOff {
- cursor:default !important;
- border:none;
- /*border: 1px solid #cccccc !important; */
+/* KMW Main App */
+#app {
+ width: 100%;
+ margin: 0px;
}
-div.linksOff * {
- color: gray;
- -webkit-user-select:none;
+#KeymanWebControl {
+ display: none;
}
-#mobile-font{
+.kmw-tools {
display: none;
- width: 40px;
- border-radius: 4px;
- position: absolute;
- left: 100%;
- top: 60px;
- margin-left: -45px;
- text-align: center;
- font-size: 24pt;
- background: #fff;
}
-#mobile-increase{
- height: 40px;
- line-height: 40px;
- border-bottom: solid 1px #000;
+/* Divider */
+.divider-container {
+ cursor: pointer;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-template-rows: 100%;
+ align-items: center;
+ grid-template-areas: "left middle right";
+ width: 100%;
+ height: 5vh;
+ padding: 10px;
+ border-radius: 5px;
+ box-shadow: var(--box-shadow);
}
-#mobile-decrease{
- height: 40px;
- line-height: 40px;
+.left-divider {
+ display: flex;
+ flex-direction: row;
+ justify-content: start;
+ align-items: center;
+ height: 100%;
}
-/* Aside Elements */
-aside, aside div {
- display:none;
- }
-
-/*
-===================
-==Footer Elements==
-===================
-*/
-.footer,footer {
- display:none;
- }
-
-#mobile-footer {
- position: fixed;
- bottom: 0;
+.left-divider > * {
+ margin: 0 10px;
+}
+/* Keyboard */
+#exampleBox {
+ background-color: gray;
+ height: 10vh;
width: 100%;
}
-#mobile-footer #mobile-version {
- font-size: 7pt;
- text-align: center;
+.keyboard-container {
width: 100%;
+ height: 100% !important;
}
-.messageBox {
- box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -o-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -moz-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -khtml-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- background: #c4c4c4;
- margin-left: 10%;
- margin-top: 10%;
- width: 80%;
- height: 150px;
- position: fixed;
-}
-
-.messageBox div {
- width: 85%;
- height: 84px;
- left: 5%;
- top: 10px;
- padding: 8px;
- font-size: 13px;
- position: absolute;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 3px;
- background: white;
+.desktop-keyboard {
+ display: none;
}
-.messageBox button {
- width:83px;
- position: absolute;
- right: 10px;
- bottom: 4px;
- background: #414141;
- border-top: 1px solid #383838;
- border-right: 1px solid #1F1F1F;
- border-bottom: 1px solid #151515;
- border-left: 1px solid #1F1F1F;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 4px;
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- color: white;
- font: bold 12px Helvetica, Verdana, Arial, sans-serif;
- text-shadow: 0px -1px 1px #1E2D4D;
- text-align:center;
- margin: 0 auto 6px;
- padding: 3px;
- }
-
-.messageBox button:disabled {
- cursor: default !important;
- color: gray !important;
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
-}
-
-.messageBox button:hover {
- -webkit-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -moz-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -ms-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -o-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- cursor: pointer;
-}
-
-.messageBox button:active {
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
-}
-
-.progressBox {
- box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -o-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -moz-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -khtml-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- background: #c4c4c4;
- top: 10%;
- margin-left: 10%;
- width: 80%;
- height: 70px;
- position: fixed;
-}
-
-.progressBox div {
- width: 85%;
- height: 34px;
- left: 5%;
- top: 10px;
- padding: 8px;
- background: url('/img/wait.gif') no-repeat 354px 8px white;
- font-size: 13px;
- position: absolute;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 3px;
+.phone-keyboard {
+ width: 100%;
+ display: block;
}
- /* Additional rules for portrait orientation */
-@media only screen and (orientation: portrait) {
-
- #message {
- height: 170px;
- }
-
- #messageContainer {
- float: left;
- width: 80%;
- height: 40%;
- }
+.keyboard-download-box {
+ display: none;
}
-
- /* Additional rules for landscape orientation */
-@media only screen and (orientation: landscape) {
-
- #messageContainer {
- float: left;
- width: 85%;
- height: 50%;
- }
- #message {
- height: 50%;
- }
- #buttons {
- margin: 4px 15px 4px 0;
- }
-
- #font{
- display: none;
- }
+.spacing-purpose {
+ display: none;
}
-
/*
======================
==Keyboard Styling==
@@ -413,8 +219,68 @@ aside, aside div {
background-color: gray !important;
}
-
.phone .kmw-keyboard-dari_clra .kmw-key-default .kmw-key-text {
top: -33%;
font-size: 0.8em !important;
}
+
+.keyboard-area {
+ border: none;
+}
+
+/*
+============
+==Viewport==
+============
+*/
+/* Additional rules for portrait orientation */
+@media only screen and (orientation: portrait) {
+ .keyboard-container > * {
+ width: 100%;
+ height: 100%;
+ }
+}
+
+ /* Additional rules for landscape orientation */
+@media only screen and (orientation: landscape) {
+ body {
+ overflow: auto;
+ }
+
+ .main-header {
+ height: 65px;
+ }
+
+ .textarea-container {
+ height: 15vh;
+ margin-bottom: 10px;
+ }
+
+ .divider-container {
+ height: 10vh;
+ }
+
+ .middle-divider {
+ align-content: center;
+ font-size: 20px;
+ }
+
+ #example {
+ font-size: 10px;
+ }
+
+ .phone.android.kmw-osk-frame > * {
+ height: 100% !important;
+ }
+
+ .keyboard-container {
+ width: 100vw;
+ height: 100% !important;
+ }
+
+ .keyboard-area {
+ border: none;
+ width: 100%;
+ height: 100%;
+ }
+}
diff --git a/cdn/dev/css/kmw-screen.css b/cdn/dev/css/kmw-screen.css
new file mode 100644
index 0000000..61ee41d
--- /dev/null
+++ b/cdn/dev/css/kmw-screen.css
@@ -0,0 +1,71 @@
+/*
+ Name: KeymanWeb Media Screen Styles
+ Copyright: Copyright (C) 2025 SIL Global
+ Documentation:
+ Description: Different media screens to allow components to display uniquely from the index.php.
+ Create Date: 19 Nov 2025
+
+ Modified Date:
+ Authors: Meng-Heng
+
+ History:
+*/
+@media only screen and (max-width: 632px) {
+ /* Smaller Search Box */
+ #searchBar #searchInput {
+ min-width: 50px;
+ }
+}
+
+@media only screen and (max-width: 456px) {
+ /* Smaller Keyboard list in Selection Menu */
+ .kb-item-keyboard {
+ min-height: fit-content;
+ }
+
+ .kb-item-keyboard span {
+ min-width: 112px;
+ }
+
+ .kb-item-keyboard {
+ overflow-x: scroll;
+ }
+}
+
+@media screen and (width < 565px) {
+ /* Show Mobile's font resizer */
+ .font-size-mobile {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ border-radius: 24px;
+ opacity: 50%;
+ }
+
+ .font-size-mobile button {
+ border: 0;
+ background-color: #fff;
+ border-style: none;
+ }
+
+ .font-size-mobile > *{
+ margin: 0 5px;
+ }
+}
+
+@media screen and (max-width: 400px) {
+ .example-box p {
+ font-size: 10px;
+ }
+}
+
+@media all and (min-width: 1320px) {
+ ins.adsbygoogle {
+ display: inline-block !important;
+ height: 600px;
+ position: absolute;
+ right: 16px;
+ top: 120px;
+ width: 160px;
+ }
+}
diff --git a/cdn/dev/css/kmw-tablet.css b/cdn/dev/css/kmw-tablet.css
index d48202d..a97b534 100644
--- a/cdn/dev/css/kmw-tablet.css
+++ b/cdn/dev/css/kmw-tablet.css
@@ -6,27 +6,23 @@
======================
*/
-html {
- overflow-y: scroll;
- overflow-x: hidden;
-}
-
body {
width:100%;
+ height:100%;
font-family: Verdana, Arial, Helvetica, sans-serif;
color:#2D2C2C;
background-color: #eeeeee;
margin:0px;
- }
+}
a {
text-decoration:none;
color: #AD4A28;
- }
+}
a:hover {
text-decoration:underline;
- }
+}
h3 {
font-size:18px;
@@ -43,398 +39,149 @@ header, footer {
/*clear: both;*/
}
-hr {
- display: none;
- }
-
.box {
-webkit-border-radius: 8px;-moz-border-radius: 8px; border-radius: 8px;
-moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
}
-/*
-===================
-==Header Elements==
-===================
-*/
-header {
- margin-top:0px;
- }
-
-#headerLeft img {
- display: block; /* default (inline) display inserts an unwanted bottom border */
- margin-top:0px;
- height:60px;
- /*-webkit-border-radius: 0px 0px 8px 8px; -moz-border-radius: 0px 0px 8px 8px; border-radius: 0px 0px 8px 8px;*/
- }
-
-header > div > img {
- display: block;
- width: 100%;
- height: 8px;
+.container-flex {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
}
/*
-====================
-==Content Elements==
-====================
+==========
+==Header==
+==========
*/
-#content {
- width: 100%; /* specific for tablet and mobile */
- margin: 0px auto;
- background-color: #eeeeee;
- /*min-height:375px;*/
- }
-
-
-/* KMW Main App */
-#app {
- /*float:left;*/
- /*height:330px;*/
- width:100%; /*710px; */
- margin: 0px;/*30px 15px 15px;*/
- }
-
-#KeymanWebControl {
- display: none;
- }
-
-#exampleBox, #example {
- display: none;
- }
-
-#messageContainer {
- width: 80%;
- float:left;
- }
-
-#message {
- height:195px;
- width: 100%;/*554px;*/
- float:left;
- margin: 10px;
- color:black;
- font-size:17px;
- font-family:SindhiWeb,Verdana,GeezWeb,LaoWeb,TibetanWeb,MyanmarWeb,SinhalaWeb,TamilWeb,KhmerWeb,LatinWeb,OriyaWeb,EgyptianWeb !important;
- }
-
-/* KMW App Buttons */
-
-#buttons {
- float: right;
- width:15%;
- margin: 0px 20px;
- margin-top: -6px;
- }
-
-#buttons div {
- width:140px;
- -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px;
- background: #fff;
- border: 1px solid #888888;
- margin: 0px 10px;
- padding: 3px;
- padding-top: 9px;
- height: 41px;
- line-height: 36px;
- background-repeat: no-repeat;
- background-position: center left;
- }
-
-#buttons div:disabled {
- cursor: default !important;
- color: gray !important;
- border-color: gray !important;
- }
-
-#buttons p{
- margin-left: 41px;
-}
-
-#search.links, #search{
- background-image:url('../img/btn_google_small.png');
-
-}
-
-#search.linksOff{
- background-image:url('../img/btn_google_small_white.png');
+.main-header {
+ width: 100%;
+ grid-template-columns: 3fr 1fr;
}
-#copy.links, #copy{
- background-image:url('../img/btn_copy_small.png');
-
+.right-header {
+ grid-template-columns: 70% 30%;
}
-#copy.linksOff{
- background-image:url('../img/btn_copy_small_white.png');
+#searchDropdownMenu {
+ width: 50vw;
}
-#buttons div * {
- display:inline;
- }
-
-div.links {
- cursor:pointer;
- }
-
-div.linksOff {
- cursor:default !important;
- border: 1px solid #cccccc !important;
- }
-
-div.linksOff * {
- color: gray;
- -webkit-user-select:none;
- }
-
-/* Aside Elements */
-
-aside {
- display:none;
- }
-
-#font-size{
- display: none;
+.burger-menu-external-links {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: 1fr;
+ padding: 0;
+ list-style-type: none;
}
/*
-
-#font,
-===================
-==Footer Elements==
-===================
+==================
+==Tool Container==
+==================
*/
-.footer, footer {
- display: none;
- background-color: #6A2915;
- height:50px;
- left:0px;
- bottom:0px;
- position: fixed;
- /* -webkit-border-radius: 8px 8px 0px 0px; -moz-border-radius: 8px 8px 0px 0px; border-radius: 8px 8px 0px 0px; */
- }
-
-nav ul {
- list-style: none outside none;
- width:90%;
- margin:0px auto;
- padding:15px 0px;
- }
-
-nav li {
- display:inline;
- padding:20px 39px;
- }
-
-nav a {
- color:#eee;
- font-family:Tahoma;
- font-weight:bold;
- font-size:10.7px;
- }
-
-.messageBox {
- box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -o-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -moz-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -khtml-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- background: #c4c4c4;
- position: fixed;
- left: 50%;
- top: 20%;
- z-index: 999 !important;
- margin-left: -250px;
- margin-top: -100px;
- width: 500px;
- height: 200px;
+.tool-menu button {
+ width: 120px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ border-radius: 10px;
+ border: 1px solid gray;
+ opacity: 50%;
+ padding: 0 10px;
}
-.messageBox div {
- width: 464px;
- height: 134px;
- left: 10px;
- top: 10px;
- padding: 8px;
- font-size: 13px;
- position: absolute;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 3px;
- background: white;
+.tool-menu button:hover {
+ opacity: 100%;
}
-.messageBox button {
- width:83px;
- position: absolute;
- right: 10px;
- bottom: 4px;
- background: #414141;
- border-top: 1px solid #383838;
- border-right: 1px solid #1F1F1F;
- border-bottom: 1px solid #151515;
- border-left: 1px solid #1F1F1F;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 4px;
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- color: white;
- font: bold 12px Helvetica, Verdana, Arial, sans-serif;
- text-shadow: 0px -1px 1px #1E2D4D;
- text-align:center;
- margin: 0 auto 6px;
- padding: 3px;
- }
-
-.messageBox button:disabled {
- cursor: default !important;
- color: gray !important;
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
- box-shadow: 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111 !important;
+.tool-menu .dropdown-menu {
+ width: 25vw;
+ right: 30px;
}
-.messageBox button:hover {
- -webkit-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -moz-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -ms-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- -o-box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- box-shadow: inset 0 0px 20px 1px #878787, 0px 1px 0 #1D1D1D, 0 2px 0px #1F1F1F, 0 2px 4px 1px #111;
- cursor: pointer;
+.tool-menu .dropdown-menu > * {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ width: 100%;
+ height: 50px;
+ padding: 10px;
}
-.messageBox button:active {
- -webkit-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -moz-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -ms-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- -o-box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
- box-shadow: 0px 1px 0 #1D1D1D, 0 0px 0px #1F1F1F, 0 1px 4px 1px #111;
+.tool-menu .dropdown-menu > div:hover {
+ color: var(--keyman-blue);
+ cursor: pointer;
}
-.progressBox {
- box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -o-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -moz-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- -khtml-box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.5);
- background: #c4c4c4;
- position: fixed;
- left: 50%;
- top: 20%;
- z-index: 999;
- margin-left: -200px;
- margin-top: -54px;
- width: 400px;
- height: 54px;
+#copyTool {
+ top: 110px;
}
-.progressBox div {
- width: 364px;
- height: 18px;
- left: 10px;
- top: 10px;
- padding: 8px;
- background: url('../img/wait.gif') no-repeat 354px 8px white;
- font-size: 13px;
- position: absolute;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- -ms-border-radius: 4px;
- -o-border-radius: 4px;
- border-radius: 3px;
+.copy-container span, .eraser-container span, .expand-container span {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ align-items: center;
}
-/* Elements modified for tablet */
-#headerBackground {
- width: 100%;
- background-color:white;
+.copy-container span p, .eraser-container span p, .expand-container span p {
+ margin: 0;
}
-
-#headerLeft {
- float: left;
- margin-left: 28px;
+/*
+=========================
+==Section/Body Elements==
+=========================
+*/
+.example-box {
+ display: block;
+ width: 100vw;
+ height: 7vh;
+ user-select: none;
+ align-items: center;
+ position: sticky;
+ background-color: var(--tablet--kb-bg-color);
}
-#headerRight {
- text-align: right;
- height: 40px;
- float: right;
- padding-bottom: 33px;
- padding-right: 10px;
+.left-divider {
+ display: none;
}
-#headerRight-beta {
- display: block;
- font-weight: bold;
- color: red;
- font-size: 16pt;
- padding: 16px 12px 0;
+.right-divider {
+ display: none;
}
-#headerRight-link {
- padding: 0 12px 0;
+.tablet.kmw-osk-frame {
+ display: block !important;
+ position: absolute !important;
+ z-index: 1 !important;
+ height: 100% !important;
+ width: 100% !important;
}
-
-#instructions {
- margin:10px;
- font:12pt Helvetica,Verdana,Arial,sans-serif;
- color: white;
+/* Keyboard */
+.keyboard-container {
+ display: flex;
+ width: 100%;
}
-/* Additional rules for portrait orientation */
-@media only screen and (orientation: landscape) {
- #buttons{
- padding-top: 11px;
- }
- #buttons div {
- margin: 4px 20px 10px 0px;
- padding: 0px;
- }
- #font span:first-child{
- margin-left: 3px;
- }
- #font p{
- margin-left: 8px;
- position: relative;
- top: -5px;
- }
- #mobile-font{
- display: none;
- width: 136px;
- border-radius: 4px;
- position: absolute;
- left: 100%;
- top: 136px;
- margin-left: -170px;
- text-align: center;
- font-size: 24pt;
- background: #fff;
- }
-
- #mobile-increase{
- height: 40px;
- line-height: 40px;
- border-bottom: solid 1px #000;
- }
+.keyboard-area {
+ border: none;
+ width: 100%;
+ height: 100%;
+ overflow: hidden !important;
+ transition: transform 0.2s ease-out !important;
+ transform: translateY(0) !important;
+}
- #mobile-decrease{
- height: 40px;
- line-height: 40px;
- }
+.keyboard-area.hidden {
+ transform: translateY(100%) !important;
}
/* Additional rules for portrait orientation */
@media only screen and (orientation: portrait) {
-
#app {
width: 97%;
}
@@ -507,6 +254,12 @@ nav a {
}
}
+@media only screen and (orientation: landscape) {
+ body {
+ overflow: auto;
+ }
+}
+
/*
======================
==Keyboard Styling==
diff --git a/cdn/dev/img/keymanweb-mini-logo-88 copy.png b/cdn/dev/img/keymanweb-mini-logo-88 copy.png
new file mode 100644
index 0000000..c1b274e
Binary files /dev/null and b/cdn/dev/img/keymanweb-mini-logo-88 copy.png differ
diff --git a/cdn/dev/img/keymanweb-mini-logo-88.png b/cdn/dev/img/keymanweb-mini-logo-88.png
new file mode 100644
index 0000000..c1b274e
Binary files /dev/null and b/cdn/dev/img/keymanweb-mini-logo-88.png differ
diff --git a/cdn/dev/img/platforms/icon-android.png b/cdn/dev/img/platforms/icon-android.png
new file mode 100644
index 0000000..7cdf8b4
Binary files /dev/null and b/cdn/dev/img/platforms/icon-android.png differ
diff --git a/cdn/dev/img/platforms/icon-desktopWeb.png b/cdn/dev/img/platforms/icon-desktopWeb.png
new file mode 100644
index 0000000..457804b
Binary files /dev/null and b/cdn/dev/img/platforms/icon-desktopWeb.png differ
diff --git a/cdn/dev/img/platforms/icon-ios.png b/cdn/dev/img/platforms/icon-ios.png
new file mode 100644
index 0000000..817edd6
Binary files /dev/null and b/cdn/dev/img/platforms/icon-ios.png differ
diff --git a/cdn/dev/img/platforms/icon-linux.png b/cdn/dev/img/platforms/icon-linux.png
new file mode 100644
index 0000000..1776e7f
Binary files /dev/null and b/cdn/dev/img/platforms/icon-linux.png differ
diff --git a/cdn/dev/img/platforms/icon-macos.png b/cdn/dev/img/platforms/icon-macos.png
new file mode 100644
index 0000000..12bd6f6
Binary files /dev/null and b/cdn/dev/img/platforms/icon-macos.png differ
diff --git a/cdn/dev/img/platforms/icon-mobileWeb.png b/cdn/dev/img/platforms/icon-mobileWeb.png
new file mode 100644
index 0000000..16e6b54
Binary files /dev/null and b/cdn/dev/img/platforms/icon-mobileWeb.png differ
diff --git a/cdn/dev/img/platforms/icon-windows.png b/cdn/dev/img/platforms/icon-windows.png
new file mode 100644
index 0000000..97d76a5
Binary files /dev/null and b/cdn/dev/img/platforms/icon-windows.png differ
diff --git a/cdn/dev/js/api.js b/cdn/dev/js/api.js
new file mode 100644
index 0000000..e7b968f
--- /dev/null
+++ b/cdn/dev/js/api.js
@@ -0,0 +1,6 @@
+export function getKeymanWeb() {
+ if(window.tavultesoft) {
+ return window.tavultesoft.keymanweb;
+ }
+ return window.keyman;
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/copy.js b/cdn/dev/js/feature/copy.js
new file mode 100644
index 0000000..9e13c71
--- /dev/null
+++ b/cdn/dev/js/feature/copy.js
@@ -0,0 +1,28 @@
+export function copyTool() {
+ // Copy tool
+ const copyDiv = document.querySelector('#copyTool')
+ const copyBtn = copyDiv.children[0]
+ copyDiv?.addEventListener('click', async function() {
+ let textToCopy = textArea.value.trim()
+
+ // Show X icon when there's no text to copy
+ if(!textToCopy) {
+ copyBtn.classList.replace('fa-copy', 'fa-xmark')
+ setTimeout(() => {
+ copyBtn.classList.replace('fa-xmark', 'fa-copy')
+ copyBtn.textContent = ''
+ }, 3000)
+ return;
+ }
+
+ await navigator.clipboard.writeText(textToCopy)
+
+ // Once copied, the icon shows a check mark
+ copyBtn.classList.replace('fa-copy', 'fa-check');
+
+ // After copied, the icon goes back to its default
+ setTimeout(() => {
+ copyBtn.classList.replace('fa-check', 'fa-copy');
+ }, 1000);
+ })
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/eraser.js b/cdn/dev/js/feature/eraser.js
new file mode 100644
index 0000000..b4a1c87
--- /dev/null
+++ b/cdn/dev/js/feature/eraser.js
@@ -0,0 +1,89 @@
+export function eraserTool() {
+ const eraseDiv = document.querySelector('#eraseTool')
+ const eraseBtn = eraseDiv.children[0]
+
+ eraseDiv?.addEventListener('click', function() {
+ let value = textArea.value.trim();
+
+ // Show X icon when there's no text to erase
+ if(!value) {
+ eraseBtn.classList.replace('fa-eraser', 'fa-xmark');
+ setTimeout(() => {
+ eraseBtn.classList.replace('fa-xmark', 'fa-eraser');
+ }, 3000);
+ return;
+ } else {
+ confirmAndClearText(() => {
+ // This function is passed as onConfirmErase
+
+ textArea.value = '';
+ // Once erased, the icon shows a check mark
+ eraseBtn.classList.replace('fa-eraser', 'fa-check');
+ // After erased, the icon goes back to its default
+ setTimeout(() => {
+ eraseBtn.classList.replace('fa-check', 'fa-eraser');
+ }, 1000);
+ });
+ }
+ })
+
+ // Check to erase all text
+ function confirmAndClearText(onConfirmErase) {
+ if (typeof onConfirmErase !== 'function') {
+ console.log("Expected onConfirmErase a function, got:", typeof onConfirmErase);
+ alert("Click on the keyboard again to enable.");
+ return;
+ }
+
+ const overlay = eraseTextUI(() => {
+ onConfirmErase();
+ });
+
+ document.body.appendChild(overlay);
+ }
+
+ // UI for Keyboard Selected Limitation
+ function eraseTextUI(onDelete) {
+ const overlay = document.createElement('div');
+ overlay.classList.add('dialog-overlay');
+
+ // Dialog box
+ const dialog = document.createElement('div');
+ dialog.classList.add('dialog-box');
+
+ const icon = document.createElement('div');
+ icon.classList.add('dialog-icon');
+ icon.textContent = "⚠️"
+
+ const title = document.createElement('h2');
+ title.classList.add('dialog-title');
+ title.textContent = 'Clear all text?';
+
+ const message = document.createElement('p');
+ message.classList.add('dialog-message');
+ message.textContent = 'Are you sure? This action cannot be undone.';
+
+ const btnContainer = document.createElement('div');
+ btnContainer.classList.add('dialog-buttons');
+
+ const cancelBtn = document.createElement('button');
+ cancelBtn.classList.add('btn-cancel');
+ cancelBtn.textContent = 'Cancel';
+ cancelBtn.onclick = () => overlay.remove();
+
+ const deleteBtn = document.createElement('button');
+ deleteBtn.classList.add('btn-delete');
+ deleteBtn.textContent = 'Erase';
+ deleteBtn.onclick = () => {
+ deleteBtn.disabled = true
+ overlay.remove();
+ onDelete?.();
+ };
+
+ btnContainer.append(cancelBtn, deleteBtn);
+ dialog.append(icon, title, message, btnContainer);
+ overlay.append(dialog);
+
+ return overlay;
+ }
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/expand-textarea.js b/cdn/dev/js/feature/expand-textarea.js
new file mode 100644
index 0000000..59a9106
--- /dev/null
+++ b/cdn/dev/js/feature/expand-textarea.js
@@ -0,0 +1,14 @@
+import { textAreaState } from "../state/appState.js"
+import { defaultSize, fullScreenSize } from "../operation/resizeTextArea.js"
+
+// Identical to hide-keyboard.js but this is for Tablet & Mobile screen size
+export function expandTool() {
+ const expandTool = document.querySelector('#expandTool')
+ expandTool?.addEventListener('click', () => {
+ if (textAreaState.isTextAreaFullHeight) {
+ defaultSize()
+ } else {
+ fullScreenSize()
+ }
+ })
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/font-slider.js b/cdn/dev/js/feature/font-slider.js
new file mode 100644
index 0000000..8f1fbf8
--- /dev/null
+++ b/cdn/dev/js/feature/font-slider.js
@@ -0,0 +1,7 @@
+export function fontSliderTool() {
+ const textArea = document.querySelector('#textArea')
+ const fontSliderBtn = document.querySelector('#fontSizeRange')
+ fontSliderBtn.addEventListener('input', function() {
+ textArea.style.fontSize = `${this.value}px`
+ })
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/hide-keyboard.js b/cdn/dev/js/feature/hide-keyboard.js
new file mode 100644
index 0000000..475d047
--- /dev/null
+++ b/cdn/dev/js/feature/hide-keyboard.js
@@ -0,0 +1,14 @@
+import { textAreaState } from "../state/appState.js"
+import { defaultSize, fullScreenSize } from "../operation/resizeTextArea.js"
+
+// Identical to hide-keyboard.js but this is for Desktop screen size
+export function hideKeyboard() {
+ const hideKeyboardBtn = document.querySelector('#hideKeyboard')
+ hideKeyboardBtn?.addEventListener('click', () => {
+ if (textAreaState.isTextAreaFullHeight) {
+ defaultSize()
+ } else {
+ fullScreenSize()
+ }
+ })
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/kb-selection-menu.js b/cdn/dev/js/feature/kb-selection-menu.js
new file mode 100644
index 0000000..bfaec5d
--- /dev/null
+++ b/cdn/dev/js/feature/kb-selection-menu.js
@@ -0,0 +1,366 @@
+import { selectedKbList } from "../state/appState.js"
+import { platformSupport } from "../operation/platformSupport.js"
+import { setKeyboard } from "../operation/handleKeyboardData.js"
+import { removeKbSelected, setKbHelpDocHamburger } from "../operation/selectedKb.js"
+import { checkKbCardUI } from "./search.js"
+import { setKeyboardToType } from "../operation/keyboard.js"
+import { confirmAndAddKb } from "../operation/confirmKeyboard.js"
+import { bundleKbDataforSelectionMenu } from "../operation/bundleKbSelectionMenu.js"
+import { kbConfigMenu } from "../operation/keyboardDetails.js"
+
+export function addKbToSelectionMenu(kbIconPTag, element, kb, data) {
+ const keyboardSelectionButton = document.getElementById('keyboardSelectionButton')
+
+ let isSelected = selectedKbList.some(selected => selected.id == kb.id) // Check which keyboard is already in the keyboard selection menu
+
+ // Remove keyboard
+ if (isSelected) {
+ removeKbSelected(kb.id)
+ kbIconPTag.textContent = '+'
+ generateKbUI(selectedKbList) // Generate Keyboard selection menu UI
+ checkKbCardUI(kbIconPTag, element, kb) // Disable or Enable Keyboard Search card UI
+
+ return
+ }
+
+ if(selectedKbList.length < 5) { // if keyboard selection menu has less than 5 keyboards
+ bundleKbDataforSelectionMenu(kb) // continue to add it to the menu
+ generateKbUI(selectedKbList)
+ kbIconPTag.textContent = '✓'
+ kbIconPTag.classList.add('kb-icon-plus-animate')
+
+ setTimeout(() => {
+ kbIconPTag.textContent = '-'
+ kbIconPTag.classList.remove('kb-icon-plus-animate')
+ keyboardSelectionButton.classList.remove('btn-secondary')
+ keyboardSelectionButton.classList.add('btn-keyman-orange')
+ }, 800)
+
+ checkKbCardUI(kbIconPTag, element, kb)
+ return
+ }
+ // if the keyboards in the selection menu are over 5, there's a confirmation needed before adding the keyboard
+ confirmAndAddKb(() => {
+ bundleKbDataforSelectionMenu(kb)
+ generateKbUI(selectedKbList)
+
+ kbIconPTag.textContent = '✓'
+ kbIconPTag.classList.add('kb-icon-plus-animate')
+
+ setTimeout(() => {
+ kbIconPTag.textContent = '-'
+ kbIconPTag.classList.remove('kb-icon-plus-animate')
+ keyboardSelectionButton.classList.remove('btn-secondary')
+ keyboardSelectionButton.classList.add('btn-keyman-orange')
+ }, 800)
+ checkKbCardUI(kbIconPTag, element, kb)
+ setKeyboardToType()
+ textArea.focus()
+ })
+}
+
+// UI for keyboard selection menu
+export function generateKbUI(selectedKbList) {
+ const textArea = document.querySelector('#textArea')
+ const keyboardSelection = document.getElementById("keyboardSelection")
+ keyboardSelection.innerHTML = ''
+
+ // Check if keyboard selection menu has less than 1 keyboard
+ if(selectedKbList.length < 1) {
+ triggerKbCount(selectedKbList)
+ resetKbSelectionMenu()
+ return
+ }
+
+ const kbDivHeader = document.createElement('div')
+ kbDivHeader.textContent = "Keyboard Selection menu"
+ kbDivHeader.classList.add('kb-item-header')
+
+ const kbDivFoot = document.createElement('div')
+ kbDivFoot.textContent = "US Basic Keyboard"
+ kbDivFoot.classList.add('kb-item-footer')
+
+ keyboardSelection.appendChild(kbDivHeader)
+
+ selectedKbList.forEach(data => {
+ const kbDiv = document.createElement('div')
+ kbDiv.classList.add("kb-item-keyboard")
+ kbDiv.setAttribute('id', `${data.id}`)
+
+ const kbName = document.createElement('span')
+ kbName.textContent = data.name
+
+ const kbConfigList = document.createElement('ul')
+ kbConfigList.classList.add('keyboard-configs')
+ kbConfigList.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+
+ const kbDetails = displayKbDetails(data)
+
+ kbDiv.appendChild(kbName)
+ kbDiv.appendChild(kbConfigList)
+ kbDiv.appendChild(kbDetails)
+ keyboardSelection.appendChild(kbDiv)
+ keyboardSelection.append(kbDivFoot)
+
+ let kbdId = data.id
+ let langCode = Object.keys(data.supportedLanguage)[0] || "en"
+ let kbdName = data.name
+
+ highlightKbSelected(kbdId)
+
+ setKeyboard(kbdId, langCode, kbdName) // Set the data to display the keyboard
+ // Each tools on each of the keyboard in the selection menu
+ kbConfigList.addEventListener('click', (e) => {
+ const target = e.target
+ if (target.tagName == 'I' && target.dataset.action) {
+ const action = target.dataset.action
+ const id = target.dataset.id
+ const helpLink = target.dataset.helplink
+ kbConfigMenu(action, id, helpLink)
+ }
+ })
+
+ // Click on the keyboard will enable the keyboard for typing
+ kbName.addEventListener('click', () => {
+ kbdId = data.id
+ langCode = Object.keys(data.supportedLanguage)[0] || "en"
+ setKeyboard(kbdId, langCode, kbdName)
+ setKeyboardToType()
+ setKbHelpDocHamburger(kbdId, kbdName)
+ highlightKbSelected(kbdId)
+ keyboardSelection.classList.remove('open')
+ textArea.focus()
+ })
+
+ // Default English keyboard
+ kbDivFoot.onclick = () => {
+ setKeyboard('basic_kbdus', 'en', 'US Basic')
+ setKeyboardToType()
+ }
+ })
+ triggerKbCount(selectedKbList)
+}
+
+// Keyboard count for Keyboard selection menu
+export function triggerKbCount(selectedKbList) {
+ const kbSelectedLength = selectedKbList.length
+ const keyboardSelectionButton = document.querySelector('#keyboardSelectionButton')
+ const keyboardCount = document.querySelector('#kbCount')
+
+ if (kbSelectedLength >= 1) {
+ keyboardCount.classList.remove('fa-caret-right')
+ keyboardCount.textContent = `${kbSelectedLength}` // Show the number of keyboard in the menu
+ keyboardSelectionButton.classList.add('btn-keyman-orange')
+ keyboardSelectionButton.classList.remove('btn-secondary')
+ } else {
+ keyboardCount.classList.add('fa-caret-right')
+ keyboardCount.textContent = ''
+ keyboardSelectionButton.classList.remove('btn-keyman-orange')
+ keyboardSelectionButton.classList.add('btn-secondary')
+ }
+}
+
+// Set the keyboard selection menu to its default UI
+function resetKbSelectionMenu() {
+ const keyboardSelection = document.querySelector("#keyboardSelection")
+
+ const kbDivHeader = document.createElement('div')
+ kbDivHeader.textContent = "Keyboard Selection menu"
+ kbDivHeader.classList.add('kb-item-header')
+
+ const kbItem = document.createElement('div')
+ kbItem.classList.add('.kb-item-keyboard')
+
+ const pTag = document.createElement('p')
+ pTag.textContent = "Open Search to get your keyboard"
+
+ const kbDivFoot = document.createElement('div')
+ kbDivFoot.textContent = "US Basic Keyboard"
+ kbDivFoot.classList.add('kb-item-footer')
+
+ kbItem.appendChild(pTag)
+
+ keyboardSelection.appendChild(kbDivHeader)
+ keyboardSelection.appendChild(kbItem)
+ keyboardSelection.appendChild(kbDivFoot)
+}
+
+// UI for Keyboard Selected Limitation
+export function limitKbSelectionUI(onAccept) {
+ const dialogDiv = document.createElement('div')
+ dialogDiv.classList.add('warning-container')
+
+ const dialogContentDiv = document.createElement('div')
+ dialogContentDiv.classList.add('warning-content')
+
+ const dialogCancel = document.createElement('button')
+ dialogCancel.classList.add('warning-cancel-btn')
+ dialogCancel.setAttribute('id', 'cancelWarningBtn')
+ dialogCancel.textContent = '✖'
+ dialogCancel.onclick = () => {
+ dialogDiv.remove()
+ generateKbUI(selectedKbList)
+ }
+
+ const dialogImgTag = document.createElement('img')
+ dialogImgTag.classList.add('warning-keyman-image')
+ dialogImgTag.src = `/cdn/dev/img/keymanweb-mini-logo-88.png`
+ dialogImgTag.alt = "Keyman Logo"
+
+ const firstKeyboard = selectedKbList[0].name
+
+ const dialogPTag = document.createElement('p')
+ dialogPTag.classList.add('warning-text')
+ dialogPTag.innerHTML = `The keyboards selected exceeds the limitation of 5 keyboards. Click 'Allow' to remove the
${firstKeyboard} keyboard.`
+
+ const dialogUlTag = document.createElement('ol')
+ dialogUlTag.textContent = "Your keyboards selection:"
+ selectedKbList.forEach((kb, index) => {
+ const dialogLiTag = document.createElement('li')
+ if (index == 0) {
+ const markKeyboard = document.createElement('mark')
+ markKeyboard.textContent = kb.name
+ dialogLiTag.appendChild(markKeyboard)
+ } else {
+ dialogLiTag.textContent = kb.name
+ }
+ dialogUlTag.appendChild(dialogLiTag)
+
+ })
+
+ const dialogAccept = document.createElement('button')
+ dialogAccept.classList.add('warning-accept-btn')
+ dialogAccept.setAttribute('id', 'acceptWarningBtn')
+ dialogAccept.textContent = "Allow"
+ dialogAccept.onclick = () => {
+ dialogDiv.remove()
+ onAccept()
+ }
+
+ dialogContentDiv.appendChild(dialogCancel)
+ dialogContentDiv.appendChild(dialogImgTag)
+ dialogContentDiv.appendChild(dialogPTag)
+ dialogContentDiv.appendChild(dialogUlTag)
+ dialogContentDiv.appendChild(dialogAccept)
+
+ dialogDiv.appendChild(dialogContentDiv)
+
+ return dialogDiv
+}
+
+// Keyboard details for Keyboard selection menu
+function displayKbDetails(data) {
+ const item = selectedKbList.find(kb => kb.id == data.id)
+ if (!item) return;
+
+ const kbDetails = document.createElement('div')
+ kbDetails.classList.add('keyboard-details')
+ kbDetails.setAttribute('id', `keyboard-${data.id}-details`)
+ kbDetails.classList.add('hidden')
+
+ const kbDetailsHead = document.createElement('div')
+ kbDetailsHead.textContent = 'Keyboard Details'
+ kbDetailsHead.classList.add('kb-item-header')
+
+ const kbDetailsContent = document.createElement('div')
+ kbDetailsContent.classList.add('keyboard-content-wrapper')
+ kbDetailsContent.innerHTML = `
+
+ -
+
+
Name
+
${item.name || "N/A"}
+
+
+ -
+
+
Keyboard ID
+
${item.id || "N/A"}
+
+
+ -
+
+
Version
+
${item.version || "N/A"}
+
+
+ -
+
+
Total downloads
+
${item.totalDownloads || 0}
+
+
+ -
+
+
+ -
+
+
Supported Platforms
+
+ ${platformSupport(item.platformSupport) || 'No platforms supported'}
+
+
+
+ -
+
+
Last Updated
+
${getOnlyDates(item.lastUpdated) || 0}
+
+
+
+ `
+ kbDetails.appendChild(kbDetailsHead)
+ kbDetails.appendChild(kbDetailsContent)
+
+ return kbDetails
+}
+
+// Get only the YYYY-mm-dd format
+function getOnlyDates(dateData) {
+ const finalDateData = dateData.match(/^\d{4}-\d{2}-\d{2}/)
+ return finalDateData
+}
+
+// Highlight an enabled keyboard in the Keyboard Selection Menu
+export function highlightKbSelected(id) {
+ document.querySelectorAll('.kb-item-keyboard').forEach(el => {
+ el.style.backgroundColor = ''
+ el.style.color = ''
+ el.style.opacity = ''
+ })
+
+ const kbSelectedItem = document.getElementById(id)
+ if (kbSelectedItem) {
+ kbSelectedItem.style.backgroundColor = 'var(--keyman-orange)'
+ kbSelectedItem.style.color = 'white'
+ kbSelectedItem.style.opacity = '100%'
+ }
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/pagination.js b/cdn/dev/js/feature/pagination.js
new file mode 100644
index 0000000..457cbee
--- /dev/null
+++ b/cdn/dev/js/feature/pagination.js
@@ -0,0 +1,43 @@
+// UI for Pagination
+import { getKeyboardList } from "../operation/searchAPI.js"
+import { searchState } from "../state/appState.js"
+import { searchKeyboard } from "../operation/searchLogic.js"
+
+/* Pagination */
+const prevBtn = document.getElementById('prevPage')
+const nextBtn = document.getElementById('nextPage')
+const pageInfo = document.getElementById('pageInfo')
+
+export function updatePaginationCtrl() {
+ pageInfo.textContent = `${searchState.currentPage} of ${searchState.totalPage}` // Number of Total page
+ nextBtn.disabled = searchState.currentPage >= searchState.totalPage // Check to disable next page
+ prevBtn.disabled = searchState.currentPage <= 1 // Check to disable previous page
+}
+
+export function goPrevPage() {
+ if (searchState.currentPage > 1) {
+ searchState.currentPage--
+ searchKeyboard(searchState.searchQuery, searchState.currentPage)
+ }
+}
+
+export function goNextPage() {
+ if (searchState.currentPage < searchState.totalPage) {
+ searchState.currentPage++
+ searchKeyboard(searchState.searchQuery, searchState.currentPage)
+ }
+}
+
+// Load keyboard search info (pages)
+export async function loadPageInfo(defaultQuery = "p:popular") {
+ const data = await getKeyboardList(defaultQuery) // depending on the query
+
+ if (data?.context) {
+ searchState.totalPage = data.context.totalPages || 1;
+ searchState.currentPage = data.context.pageNumber || 1;
+ } else {
+ searchState.totalPage = Math.ceil(data.keyboards.length / searchState.itemPerPage);
+ }
+
+ updatePaginationCtrl()
+}
diff --git a/cdn/dev/js/feature/search.js b/cdn/dev/js/feature/search.js
new file mode 100644
index 0000000..a64fe77
--- /dev/null
+++ b/cdn/dev/js/feature/search.js
@@ -0,0 +1,180 @@
+// UI for Search
+import { searchState, selectedKbList } from "../state/appState.js";
+import { highlightSearchContext, truncateDesc, getMarkedContext, showMarkedContext } from "../operation/searchCardContent.js";
+import { addKbToSelectionMenu } from "./kb-selection-menu.js";
+import { platformSupport } from "../operation/platformSupport.js";
+import { validateURL } from "../operation/validURL.js";
+import { setKeyboardToType } from "../operation/keyboard.js";
+import { setKbHelpDocHamburger } from "../operation/selectedKb.js";
+import { loadPageInfo } from "./pagination.js";
+
+/* Search */
+const kbSearchCard = document.getElementById('kbSearchCardUI')
+const searchResultCount = document.getElementById('resultCount')
+const paginationCtrl = document.getElementById('paginationControls')
+const magnifying = document.querySelector('#magnifyingGlassIcon')
+
+export function updateSearchIcon(value) {
+ // Base on the value given, it will display one of the icon below
+ magnifying.style.display = value ? 'none' : 'inline'
+ clearSearchIcon.style.display = value ? 'inline' : 'none'
+}
+
+/* Display items return from search */
+export function displaySearch(keyboards, total = 0, query = '') {
+ const searchInstruction = document.querySelector('.search-instruction')
+ const kbHrTitle = document.querySelector('.keyboard-title')
+
+ kbSearchCard.innerHTML = '';
+
+ // Validate the keyboards data
+ if (!keyboards || keyboards.length == 0) {
+ kbSearchCard.innerHTML = `Well, no keyboards found for ${query}.`
+ searchInstruction.classList.add("hidden")
+ kbHrTitle.textContent = "Results"
+ paginationCtrl.style.display = 'none'
+ return
+ }
+
+ if (total) {
+ searchResultCount.innerHTML = `${total} results`
+ searchResultCount.classList.remove('hidden')
+ }
+
+ // Getting searched Word ready for highlight
+ const marked = getMarkedContext(query)
+
+ if (!query) {
+ // Most Download UI
+ searchInstruction.classList.remove("hidden")
+ kbHrTitle.textContent = "Most Downloads"
+ paginationCtrl.style.display = 'none'
+ } else if (total < 2) {
+ // Total results < 2 UI
+ searchInstruction.classList.add("hidden")
+ kbHrTitle.textContent = "Results"
+ paginationCtrl.style.display = 'none'
+ } else {
+ // Result of search query
+ searchInstruction.classList.add("hidden")
+ kbHrTitle.textContent = "Results"
+ paginationCtrl.style.display = 'flex'
+ }
+
+ // UI for each keyboards
+ keyboards.forEach(kb => {
+ const card = searchKbCardUI(kb, marked, selectedKbList, keyboards)
+ kbSearchCard.appendChild(card)
+ })
+}
+
+// Search card UI
+function searchKbCardUI(kb, marked = '', selectedKbList, data) {
+ const searchInput = document.querySelector('#searchInput')
+ const searchDropdown = new bootstrap.Dropdown(searchInput)
+ const textArea = document.querySelector('#textArea')
+
+ const kbFoundInList = selectedKbList.some(selected => selected.id == kb.id) // find a match between the keyboard from selection menu & search
+ // Keyboard card container
+ let cardWrap = document.createElement('div')
+ cardWrap.classList.add('card-wrap')
+ cardWrap.setAttribute('id', 'keyboardCardWrap')
+
+ // Keyboard header container
+ let cardHeader = document.createElement('div')
+ cardHeader.classList.add('card-header')
+
+ const {matchFound, matchField, matchValue} = highlightSearchContext(kb, marked) // Highlight search query
+
+ const kbNameHeading = matchFound ? showMarkedContext(kb, matchField, matchValue) // Highlight search query UI
+ : (() => {
+ const heading = document.createElement('h4')
+ heading.innerHTML = kb.name
+ return heading
+ })()
+
+ // Keyboard Plus (+) icon
+ const kbIconPTag = document.createElement('p')
+ kbIconPTag.textContent = kbFoundInList ? "-" : "+"
+ kbIconPTag.style.fontSize = '20px'
+ kbIconPTag.style.cursor = 'pointer'
+ kbIconPTag.classList.add('kb-icon-plus')
+
+ // Keyboard Help (?) icon
+ const kbHelpIconSpan = document.createElement('span')
+ kbHelpIconSpan.classList.add('help-icon-span')
+ const kbHelpIcon = document.createElement('i')
+ kbHelpIcon.classList.add('fa-solid', 'fa-question')
+ kbHelpIconSpan.appendChild(kbHelpIcon)
+
+ const kbHeaderTitle = document.createElement('div')
+ kbHeaderTitle.classList.add('card-header-title')
+
+ // Must check if the keyboard is in the selection menu
+ checkKbCardUI(kbIconPTag, cardHeader, kb)
+
+ // Keyboard ID
+ const kbIdPTag = document.createElement('p')
+ kbIdPTag.classList.add('keyboard-id')
+ kbIdPTag.textContent = kb.id
+
+ // Keyboard Description
+ const kbDescHeading = truncateDesc(kb, matchField, marked)
+
+ // Keyboard monthly downloads and platform support
+ const kbSpecs = document.createElement('div')
+ kbSpecs.classList.add('keyboard-specs')
+
+ const kbDownloadHeading = document.createElement('h6')
+ kbDownloadHeading.textContent = `${kb.match.downloads} monthly downloads`
+ kbDownloadHeading.classList.add('monthly-download')
+
+ const kbPlatformSupport = document.createElement('div')
+ kbPlatformSupport.classList.add('platform')
+ kbPlatformSupport.innerHTML = platformSupport(kb.platformSupport) // Get icons for platform
+
+ // Append children
+ kbHeaderTitle.appendChild(kbIconPTag)
+ kbHeaderTitle.appendChild(kbNameHeading)
+ cardHeader.appendChild(kbHeaderTitle)
+ cardHeader.appendChild(kbHelpIconSpan)
+
+ kbSpecs.appendChild(kbDownloadHeading)
+ kbSpecs.appendChild(kbPlatformSupport)
+
+ cardWrap.appendChild(cardHeader)
+ cardWrap.appendChild(kbIdPTag)
+ cardWrap.appendChild(kbDescHeading)
+ cardWrap.appendChild(kbSpecs)
+
+ // Click on a keyboard name to enable the keyboard & add it into selection menu
+ kbHeaderTitle.onclick = (e) => {
+ e.stopPropagation()
+ addKbToSelectionMenu(kbIconPTag, cardHeader, kb, data)
+ setKeyboardToType()
+ setKbHelpDocHamburger(kb.id, kb.name)
+ searchDropdown.hide()
+ textArea.focus()
+ }
+
+ // Click on the help icon on the search card to get to the keyboard help documentation
+ kbHelpIcon.addEventListener('click', () => {
+ const checkedURL = validateURL(`https://help.keyman.com/keyboard/`)
+ const newURL = checkedURL + kb.id
+ window.open(newURL, '_blank')
+ })
+
+ return cardWrap
+}
+
+// Disable or Enable UI with opacity
+export function checkKbCardUI(kbIconPTag, element, kb) {
+ // For checking if the keyboard exists in to respond to UI
+ let kbFoundInList = selectedKbList.some(selected => selected.id == kb.id)
+
+ if (kbFoundInList) {
+ element.style.opacity = '50%'
+ } else {
+ element.style.opacity = '100%'
+ }
+}
\ No newline at end of file
diff --git a/cdn/dev/js/feature/tool-tray.js b/cdn/dev/js/feature/tool-tray.js
new file mode 100644
index 0000000..c908153
--- /dev/null
+++ b/cdn/dev/js/feature/tool-tray.js
@@ -0,0 +1,95 @@
+const tabletToolElements = `
+
+ `
+
+const desktopToolElements = `
+
+
+ `
+
+const phoneToolElements = `
+
+
+
+
+
+
+
+
+
+
+ `
+
+const phoneExpandElement = `
+
+ `
+
+const calcScreenSize = Math.min(screen.width, screen.height)
+
+const toolContainer = document.querySelector('.tool-container')
+const divider = document.querySelector('.divider-container')
+const resizeGrip = document.querySelector('#resizeGrip')
+
+export function renderToolsTray() {
+ if (calcScreenSize < 900 && calcScreenSize >= 720) { // Tablet
+ toolContainer.innerHTML = tabletToolElements
+ divider.style.display = 'none'
+ } else if (calcScreenSize < 720) { // Phone
+ divider.innerHTML = phoneToolElements
+ toolContainer.innerHTML = phoneExpandElement
+ resizeGrip.style.display = 'none'
+ } else { // Desktop
+ toolContainer.innerHTML = desktopToolElements
+ }
+}
diff --git a/cdn/dev/js/kmwlive.js b/cdn/dev/js/kmwlive.js
index 54051b4..46d806b 100644
--- a/cdn/dev/js/kmwlive.js
+++ b/cdn/dev/js/kmwlive.js
@@ -1,471 +1,471 @@
-$.urlParam = function(name){
- var results = new RegExp('[\\?&]' + name + '=([^]*)').exec(window.location.href);
- if (!results) { return 0; }
- return decodeURIComponent(results[1]).replace(/\+/g, ' ') || 0;
-}
-
-function getKeymanWeb() {
- if(window.tavultesoft) {
- return window.tavultesoft.keymanweb;
- }
- return window.keyman;
-}
-
-//
-// If we detect a rewrite of /go//, remap that to the new #-based model.
-// This will force a reload so the /return/ statement isn't really necessary...
-//
-
-function setCookie(c_name,value,exdays){
- var exdate=new Date();
- exdate.setDate(exdate.getDate() + exdays);
- var c_value=escape(value) + ((exdays==null) ? "" : "; expires="+exdate.toUTCString());
- document.cookie=c_name + "=" + c_value;
-}
-
-var locationmatch = location.pathname.match(/^\/go\/([^/]+)\/([^/]+)/);
-if(locationmatch) {
-
- location.href = '/#'+locationmatch[1]+',Keyboard_'+locationmatch[2];
-}
-
-// Perform all page-load init that is NOT dependent on KeymanWeb.
-// That script may not load in time for this method.
-$(document).ready(function() {
- // Font size function
- $( "#slider" ).slider({
- value:16,
- min: 10,
- max: 132,
- step: 2,
- slide: function( event, ui ) {
- $('#message').css('font-size',ui.value);
- $('#message').focus();
- }
- });
-
- var font = document.getElementById('font');
- var fontIncrease = document.getElementById('mobile-increase');
- var fontDecrease = document.getElementById('mobile-decrease');
-
- font.addEventListener('touchstart', function(event) {
- $('#mobile-font').show();
- $('#search').css('visibility','hidden');
- var r = setTimeout(function(){
- $('#mobile-font').hide();
- setTimeout(function(){
- $('#search').css('visibility','visible');
- },1000);
- },3000);
- var size = Number($('#mobile-font-size1').val());
-
- fontIncrease.addEventListener('touchstart', function(event) {
- clearInterval(r);
- size = size * 1.2;
- $('#mobile-font-size1').val(size);
- $('textarea').css('font-size',size);
- $('.keymanweb-input').css('font-size',size).focus();
- r = setTimeout(function(){
- $('#mobile-font').hide();
- setTimeout(function(){
- $('#search').css('visibility','visible');
- },600);
- },2000);
- });
-
- fontDecrease.addEventListener('touchstart', function(event) {
- clearInterval(r);
- size = size / 1.2;
- $('#mobile-font-size1').val(size);
- $('textarea').css('font-size',size);
- $('.keymanweb-input').css('font-size',size).focus();
- r = setTimeout(function(){
- $('#mobile-font').hide();
- setTimeout(function(){
- $('#search').css('visibility','visible');
- },600);
- },2000);
- });
-
- });
-
- setTimeout(function(){
- $('.kmw_button_a').click(function(){
- if ($(this).parent().attr('id') == 'kmw_btn_off') {
- //$('body').removeClass('osk-always-visible');
- $('#message').blur();
- } else {
- //$('body').addClass('osk-always-visible');
- }
- });
- },5000);
-
-
- /* Setup the bookmarklet */
- $('#bookmarklet div a').click( function() {
- alert("Don't click this: drag it to your Bookmarks toolbar. Then you can click the '" + $(this).text() + "' bookmark on any web page to access your web keyboard on that page!")
- return false;
- });
-
- // Detect desktop browser height and modify css
- function updateSize(){
- // Set OSK size/position
- p = new Object();
-
- var height = $(window).height();
- var width = $(window).width();
- var appPos = $('#app').position();
- var appLeft = appPos.left;
-
- // We can't proceed any further if KMW hasn't loaded yet.
- // No point handling resizes until that's occurred.
- if(!getKeymanWeb() || !getKeymanWeb().osk) {
- return;
- }
-
- // Adjust the message box height only if a desktop browser
- if(!getKeymanWeb().util.isTouchDevice())
- {
- if (height <= 768) {
- // Hide footer
- $('footer').hide();
- $('#message').css('height', '209px');
- p['height'] = 264;
- }
- if(height > 768) {
- $('#message').css('height', '260px');
- if (height < 820) {
- p['height'] = 246;
- $('footer').hide();
- }else if(height < 860){
- p['height'] = 246;
- $('footer').show();
- }else{
- p['height'] = 264;
- $('footer').show();
- }
- }
- }
- p['top'] = $('#app').offset().top + $('#app').outerHeight() + 8;
- p['left'] = appLeft + 15;
- p['width'] = 710;
-
- // Update keyboard position and size
- getKeymanWeb().osk.setRect(p);
- }
-
- window.onresize = updateSize;
-
- //$('#example').css('height','50px');
-
- /* Check for result for other modules in the query string */
- var result = $.urlParam('result');
- if(result)
- {
- var module = $.urlParam('module');
- var error = $.urlParam('error');
- var data = $.urlParam('data');
- if(result == 'error')
- {
- showError(module, error, redirectHome);
- }
- return;
- }
-
- $('#search').click(function(event){
- event.preventDefault();
- if($('#search').attr('disabled') == 'disabled') return;
- var newURL='https://www.google.com/search?q='+encodeURIComponent($('#message').val());
- window.open(newURL,'_blank');
- //location.href = 'http://www.google.com/search?q='+encodeURIComponent($('#message').val());
- });
-
- var copy = document.getElementById('copy');
- var clipboard = new ClipboardJS(copy);
- var isSupported = ClipboardJS.isSupported('copy');
- if (isSupported === false) {
- $('#copy').attr('class', 'linksOff');
- $('#copy').attr('disabled', 'disabled');
- $('#copy').hide();
- }
-
-
- clipboard.on('success', function(e) {
- //console.log(e);
- $('#copy').children('p').html('Copied');
- e.clearSelection();
- setTimeout(function(){
- $('#copy').children('p').html('Copy');
- },2000);
- });
-
- clipboard.on('error', function(e) {
- //console.log(e);
- $('#copy').children('p').html('Copy Failed');
- e.clearSelection();
- setTimeout(function(){
- $('#copy').children('p').html('Copy');
- },2000);
- });
-
- // Email subscribe form
- $('.subscribe').click(function(){
- $('#mc-embedded-subscribe-form').submit();
- });
-
- //Cannot detect change of content from KMW, so use a timer instead to refresh button state
- //$('#message').bind("keypress keyup keydown change click focus blur", refreshButtons);
- window.setInterval(refreshButtons,200);
-});
-
-var afterInitRun = false;
-function afterInit() {
- if(afterInitRun) {
- return;
- }
- afterInitRun = true;
-
- // Focus on message box
- if (!!$('.messageBox').length) {
- getKeymanWeb().moveToElement('message');
- }
- // On touch devices, this is necessary (but not sufficient) for ClipboardJS compatibility.
- // Must take affect AFTER KMW has initialized.
- $('#message').removeAttr('disabled');
-
- getKeymanWeb().util.attachDOMEvent(window,'orientationchange', function() {
- window.scrollTo(0,1);
- },false);
-
- //getKeymanWeb().addEventListener('keyboardloaded',function(p){changeKeyboard(p['keyboardName']);});
- getKeymanWeb().addEventListener('keyboardchange',function(p){if(!pageLoading) changeKeyboard(p['internalName'],p['languageCode'],p);});
-}
-
-function refreshButtons() {
- var len=getKMWInputLength('message'); // returns -1 for desktop page element
-
- if(len < 0) {
- // refreshButtons runs via setInterval. It is possible for one call to persist through page reload,
- // in which case the #message element is unavailable.
- var message = $('#message').val();
- if(!message) {
- return;
- }
-
- len = message.length;
- }
-
- // if message length is 0, use white SM buttons
- if(len == 0) {
- $('#buttons').children('div').attr('class','linksOff');
- $('#buttons div').attr('disabled', 'disabled');
- } else {
- $('#buttons').children('div').attr('class','links');
- $('#buttons div').removeAttr('disabled');
- }
-}
-
-function getKMWInputElement(id){
- var i,len;
- try
- {
- var eList=document.getElementsByClassName('KMW_input');
- if(eList && eList.length > 0)
- {
- for(i=0; i');
- box.append($('').append(message));
- $('body').append(box);
- return box;
-}
-
-var boxVisible = false;
-
-function showBox(module, message, callback)
-{
- boxVisible = true;
- var box = $('');
- box.append($('').append(message));
- box.append($('