Commit 088e23f1 authored by eileen's avatar eileen
Browse files

Add initial support for elFinder uploaded images integration

parent 4b1814e3
<?php
use CRM_Ckeditor5_ExtensionUtil as E;
class CRM_Ckeditor5_Page_ImageManager extends CRM_Core_Page {
/**
* Run upload.
*
* @throws \CRM_Core_Exception
* @throws \Exception
*/
public function run() {
if (!CRM_Core_Permission::check('access uploaded Files')) {
throw new CRM_Core_Exception('Permission denied');
}
// // Optional exec path settings (Default is called with command name only)
// define('ELFINDER_TAR_PATH', '/PATH/TO/tar');
// define('ELFINDER_GZIP_PATH', '/PATH/TO/gzip');
// define('ELFINDER_BZIP2_PATH', '/PATH/TO/bzip2');
// define('ELFINDER_XZ_PATH', '/PATH/TO/xz');
// define('ELFINDER_ZIP_PATH', '/PATH/TO/zip');
// define('ELFINDER_UNZIP_PATH', '/PATH/TO/unzip');
// define('ELFINDER_RAR_PATH', '/PATH/TO/rar');
// define('ELFINDER_UNRAR_PATH', '/PATH/TO/unrar');
// define('ELFINDER_7Z_PATH', '/PATH/TO/7za');
// define('ELFINDER_CONVERT_PATH', '/PATH/TO/convert');
// define('ELFINDER_IDENTIFY_PATH', '/PATH/TO/identify');
// define('ELFINDER_EXIFTRAN_PATH', '/PATH/TO/exiftran');
// define('ELFINDER_JPEGTRAN_PATH', '/PATH/TO/jpegtran');
// define('ELFINDER_FFMPEG_PATH', '/PATH/TO/ffmpeg');
define('ELFINDER_CONNECTOR_URL', CRM_Utils_System::url('civicrm/image/access'));
// see elFinder::getConnectorUrl()
// define('ELFINDER_DEBUG_ERRORLEVEL', -1); // Error reporting level of debug mode
// // To Enable(true) handling of PostScript files by ImageMagick
// // It is disabled by default as a countermeasure
// // of Ghostscript multiple -dSAFER sandbox bypass vulnerabilities
// // see https://www.kb.cert.org/vuls/id/332928
// define('ELFINDER_IMAGEMAGICK_PS', true);
// ===============================================
// // load composer autoload before load elFinder autoload If you need composer
// // You need to run the composer command in the php directory.
is_readable('./vendor/autoload.php') && require './vendor/autoload.php';
// // elFinder autoload
require CRM_Core_Resources::singleton()->getPath('ckeditor5', '/js/elFinder/php/autoload.php');
// ===============================================
// // Enable FTP connector netmount
elFinder::$netDrivers['ftp'] = 'FTP';
// ===============================================
// // Required for Dropbox network mount
// // Installation by composer
// // `composer require kunalvarma05/dropbox-php-sdk` on php directory
// // Enable network mount
// elFinder::$netDrivers['dropbox2'] = 'Dropbox2';
// // Dropbox2 Netmount driver need next two settings. You can get at https://www.dropbox.com/developers/apps
// // AND require register redirect url to "YOUR_CONNECTOR_URL?cmd=netmount&protocol=dropbox2&host=1"
// define('ELFINDER_DROPBOX_APPKEY', '');
// define('ELFINDER_DROPBOX_APPSECRET', '');
// ===============================================
// // Required for Google Drive network mount
// // Installation by composer
// // `composer require google/apiclient:^2.0` on php directory
// // Enable network mount
// elFinder::$netDrivers['googledrive'] = 'GoogleDrive';
// // GoogleDrive Netmount driver need next two settings. You can get at https://console.developers.google.com
// // AND require register redirect url to "YOUR_CONNECTOR_URL?cmd=netmount&protocol=googledrive&host=1"
// define('ELFINDER_GOOGLEDRIVE_CLIENTID', '');
// define('ELFINDER_GOOGLEDRIVE_CLIENTSECRET', '');
// // Required case when Google API is NOT added via composer
// define('ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT', '/path/to/google-api-php-client/vendor/autoload.php');
// ===============================================
// // Required for Google Drive network mount with Flysystem
// // Installation by composer
// // `composer require nao-pon/flysystem-google-drive:~1.1 nao-pon/elfinder-flysystem-driver-ext` on php directory
// // Enable network mount
// elFinder::$netDrivers['googledrive'] = 'FlysystemGoogleDriveNetmount';
// // GoogleDrive Netmount driver need next two settings. You can get at https://console.developers.google.com
// // AND require register redirect url to "YOUR_CONNECTOR_URL?cmd=netmount&protocol=googledrive&host=1"
// define('ELFINDER_GOOGLEDRIVE_CLIENTID', '');
// define('ELFINDER_GOOGLEDRIVE_CLIENTSECRET', '');
// // And "php/.tmp" directory must exist and be writable by PHP.
// ===============================================
// // Required for One Drive network mount
// // * cURL PHP extension required
// // * HTTP server PATH_INFO supports required
// // Enable network mount
// elFinder::$netDrivers['onedrive'] = 'OneDrive';
// // GoogleDrive Netmount driver need next two settings. You can get at https://dev.onedrive.com
// // AND require register redirect url to "YOUR_CONNECTOR_URL/netmount/onedrive/1"
// define('ELFINDER_ONEDRIVE_CLIENTID', '');
// define('ELFINDER_ONEDRIVE_CLIENTSECRET', '');
// ===============================================
// // Required for Box network mount
// // * cURL PHP extension required
// // Enable network mount
// elFinder::$netDrivers['box'] = 'Box';
// // Box Netmount driver need next two settings. You can get at https://developer.box.com
// // AND require register redirect url to "YOUR_CONNECTOR_URL?cmd=netmount&protocol=box&host=1"
// define('ELFINDER_BOX_CLIENTID', '');
// define('ELFINDER_BOX_CLIENTSECRET', '');
// ===============================================
// // Zoho Office Editor APIKey
// // https://www.zoho.com/docs/help/office-apis.html
// define('ELFINDER_ZOHO_OFFICE_APIKEY', '');
// ===============================================
// // Online converter (online-convert.com) APIKey
// // https://apiv2.online-convert.com/docs/getting_started/api_key.html
// define('ELFINDER_ONLINE_CONVERT_APIKEY', '');
// ===============================================
// // Zip Archive editor
// // Installation by composer
// // `composer require nao-pon/elfinder-flysystem-ziparchive-netmount` on php directory
// define('ELFINDER_DISABLE_ZIPEDITOR', false); // set `true` to disable zip editor
// ===============================================
/**
* Simple function to demonstrate how to control file access using "accessControl" callback.
* This method will disable accessing files/folders starting from '.' (dot)
*
* @param string $attr attribute name (read|write|locked|hidden)
* @param string $path absolute file path
* @param string $data value of volume option `accessControlData`
* @param object $volume elFinder volume driver object
* @param bool|null $isDir path is directory (true: directory, false: file, null: unknown)
* @param string $relpath file path relative to volume root directory started with directory separator
*
* @return bool|null
**/
function access($attr, $path, $data, $volume, $isDir, $relpath) {
$basename = basename($path);
return $basename[0] === '.' // if file/folder begins with '.' (dot)
&& strlen($relpath) !== 1 // but with out volume root
? !($attr === 'read' || $attr === 'write') // set read+write to false, other (locked+hidden) set to true
: NULL; // else elFinder decide it itself
}
$requiredDirectories = ['persist/editorimages', 'persist/editorimages/trash', 'persist/editorimages/trash.tmp'];
foreach ($requiredDirectories as $directory) {
$directory = Civi::paths()->getPath('[civicrm.files]/' . $directory);
if (!is_dir($directory) && !mkdir($directory)) {
throw new \RuntimeException(sprintf('Directory "%s" does not exist', $directory));
}
}
// Documentation for connector options:
// https://github.com/Studio-42/elFinder/wiki/Connector-configuration-options
$opts = [
'debug' => Civi::settings()->get('debug_enabled'),
'roots' => [
// Items volume
[
'driver' => 'LocalFileSystem', // driver for accessing file system (REQUIRED)
// path to files (REQUIRED)
'path' => Civi::paths()->getPath('[civicrm.files]/persist'),
// URL to files (REQUIRED)
'URL' => Civi::paths()->getUrl('[civicrm.files]/persist'),
'trashHash' => 't1_Lw', // elFinder's hash of trash folder
'winHashFix' => DIRECTORY_SEPARATOR !== '/', // to make hash same to Linux one on windows too
'uploadDeny' => ['all'], // All Mimetypes not allowed to upload
'uploadAllow' => ['image/x-ms-bmp', 'image/gif', 'image/jpeg', 'image/png', 'image/x-icon', 'text/plain'], // Mimetype `image` and `text/plain` allowed to upload
'uploadOrder' => ['deny', 'allow'], // allowed Mimetype `image` and `text/plain` only
'accessControl' => 'access' // disable and hide dot starting files (OPTIONAL)
],
// Trash volume
[
'id' => '1',
'driver' => 'Trash',
'path' => Civi::paths()->getPath('[civicrm.files]/persist/editorimages/trash/') .'/.trash/',
'tmbURL' => Civi::paths()->getUrl('[civicrm.files]/persist/editorimages/trash/.tmb/'),
'winHashFix' => DIRECTORY_SEPARATOR !== '/', // to make hash same to Linux one on windows too
'uploadDeny' => ['all'], // Recomend the same settings as the original volume that uses the trash
'uploadAllow' => ['image/x-ms-bmp', 'image/gif', 'image/jpeg', 'image/png', 'image/x-icon', 'text/plain'], // Same as above
'uploadOrder' => ['deny', 'allow'], // Same as above
'accessControl' => 'access', // Same as above
],
]
];
// run elFinder
$connector = new elFinderConnector(new elFinder($opts));
$connector->run();
exit;
}
}
......@@ -15,8 +15,14 @@ class CRM_Ckeditor5_Upgrader extends CRM_Ckeditor5_Upgrader_Base {
public function install() {
CRM_Core_BAO_OptionValue::ensureOptionValueExists([
'option_group_id' => 'wysiwyg_editor',
'label' => 'CKEditor5',
'name' => 'CKEditor5',
'label' => 'CKEditor5 with embedded images',
'name' => 'CKEditor5-base64',
]
);
CRM_Core_BAO_OptionValue::ensureOptionValueExists([
'option_group_id' => 'wysiwyg_editor',
'label' => 'CKEditor5 with uploaded images (experimental)',
'name' => 'CKEditor5-elfinder',
]
);
return TRUE;
......
......@@ -2,17 +2,24 @@
![Screenshot](images/screenshot.png)
This adds ckeditor5 as a usable Wysiwig in CiviCRM. ckeditor5 is better than
This adds ckeditor5 as a usable Wysiwig in CiviCRM. Ckeditor5 is being actively developed while CkEditor4 is in LTS stage so it
makes more sense to invest time in ckeditor5 than ckeditor4. However both have gaps.
Ckeditor5 is better than
ckeditor4 when it comes to inserting formatted text from word / google (note
that you no longer see the paste from Word button as you just paste). Also note
ckeditor5 always uses an expandable window so the field will be small if it has
that you no longer see the paste from Word button as you just paste).
Also note ckeditor5 always uses an expandable window so the field will be small if it has
no data.
Currently the packaged version is useful for sites operating behind
a firewall as it embeds any uploaded images into the html
(as a base64 blob), meaning that images embedded in emails will
not be inaccessible to the recipients. For example a user who is sending
an email from CiviCRM from behind a firewall who wants to add a signature
This implementation gets away from the kcfinder file manager which was last updated
in 2014. KcFinder was brittle around paths - especially for sites with non-standard layouts
like symlinks or Drupal8.
Currently the packaged version comes with 2 options - embedded images is useful
for sites operating behind a firewall as it embeds any uploaded images into the html
(as a base64 blob), meaning that images embedded in emails will not be inaccessible to the recipients.
For example a user who is sending an email from CiviCRM from behind a firewall who wants to add a signature
cannot use the traditional ckeditor4 image insert because the image will
be located behind the firewall and inaccessible to the reader. By embedding it
it will be available.
......@@ -21,6 +28,13 @@ Note - there are discrepancies between email clients and some
might suppress or mis-display embedded images, as some might suppress linked ones.
I also hope to support [elfinder](https://github.com/Studio-42/elFinder#downloads) integration as an alternative inn future
The second option uses ElFinder is also an option for using uploaded files. I've labeled it experimental
because it has some gaps. Intriguingly ElFinder also supports google drive, dropbox
or Onedrive as backend file systems. This is not currently enabled and requires some
credentials etc.
The extension is licensed under [AGPL-3.0](LICENSE.txt).
## Requirements
......@@ -61,21 +75,24 @@ Administer->Customize Data and Screens->Display Preferences
## Known Issues
Configuration
- the language is not yet passed through to the editor. It should be added in crm.ckeditor5.js
where [lang](https://github.com/Studio-42/elFinder/wiki/Client-configuration-options-2.1#lang) is set.
- some session errors are visible when using elfinder with debug enabled - need to implement a [session override](https://github.com/Studio-42/elFinder/wiki/Connector-configuration-options-2.1#session-218)
- The file browser for elFinder is in the background when popups are on - but given the
previous item this is OK for now.
- At the moment the elFinder seems to work well for uploading but less so for selecting
previously uploaded files
- a niche feature of ckeditor4 was a UI for configuration. The toolbar IS configured
through a javascript array so it would be possible to find a way to customise that
(but there is no current plan / demand for this)
- [view source is not a feature in ckeditor5](https://github.com/ckeditor/ckeditor5/issues/592)
- The current implementation does not have a file manager. A file manager allows you to select
from previously uploaded images on the server and to provide urls to those images in mails rather than
embedding. My hope is to add a variant - see
technical discussion below.
- An interesting feature of ckeditor5 is that you can do more Gutenberg-like edit in place.
This would make sense possibly on a contribution page. No current plans.
- This uses the es6 script standard which works on browsers from around 2016/17
## A word about builds
ckeditor has a concept of builds constructed with npm. To change some components
CKeditor has a concept of builds constructed with npm. To change some components
(e.g include base64 uploader) it is necessary to used a build. These can be generated
using the [online builder](https://ckeditor.com/ckeditor-5/online-builder/). Alternatively
the classic editor [can be downloaded & installed](https://ckeditor.com/ckeditor-5/download/).
......@@ -92,16 +109,18 @@ Ckeditor5 offers the following file managers
- [Base64 upload](https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/base64-upload-adapter.html)
which is currently packaged & is firewall friendly.
- Integrations. The integration that currently looks most promising is [elFinder](https://github.com/Studio-42/elFinder)
Work on ElFinder integration is currently on the elfinder branch.
Right now I'm stuck on getting jquery to load in the elfinder promise when it is not part of our global scope.
This is currently being maintained (as opposed to kcfinder which is not)
Other issues to deal with
1) Ckeditor works with builds - my expectation is we will wind up with several builds shipping
and a setting to choose which one to use - ie firewall friendly vs elfinder
1) Ckeditor works with builds - I am shipping this with one downloaded build
and one created build. Not sure how this is best managed going forwards.
2) We currently need a core fix tweak our settings pages to permit conditional settings based on metadata - ie
only show ckeditor5-specific-settings if $('#editor_id) = ckeditor5 (this is something)
we also need for invoicing settings).
3) ckeditor builds are generated with npm - it's a bit unclear to me how we deal with that
in extensions (currently committing all the code to the extension). Moving ckeditor4 to a core
extension makes sense but we'd need to figure out this challenge.
Note that I'm currently using the downloaded classic build for elfinder
as opposed to the lightly customised one I used for the base-uploader
- this has a slightly different structure -ie no build folder
......@@ -150,14 +150,34 @@ function ckeditor5_civicrm_themes(&$themes) {
*/
function ckeditor5_civicrm_coreResourceList(&$items, $region) {
if ($region === 'html-header') {
if (Civi::settings()->get('editor_id') === 'CKEditor5') {
if (Civi::settings()->get('editor_id') === 'CKEditor5-elfinder') {
$items[] = [
'config' => [
'wysisygScriptLocation' => CRM_Core_Resources::singleton()->getUrl('ckeditor5', 'js/wysiwyg/crm.ckeditor5.js'),
// Note that I am just using 'classic build' at the moment - not a configured
// build so no build in the path.
'CKEditor5Location' => CRM_Core_Resources::singleton()->getUrl('ckeditor5', 'js/ckeditor5/ckeditor-classic-build/ckeditor.js'),
'ELFinderLocation' => CRM_Core_Resources::singleton()->getUrl('ckeditor5', 'js/elFinder/js/elFinder.min.js'),
'ELFinderConnnector' => CRM_Utils_System::url('civicrm/image/access'),
],
];
CRM_Core_Resources::singleton()->addStyleUrl(CRM_Core_Resources::singleton()->getUrl('ckeditor5', 'js/elFinder/css/elFinder.min.css'));
}
if (Civi::settings()->get('editor_id') === 'CKEditor5-base64') {
$items[] = [
'config' => [
'wysisygScriptLocation' => CRM_Core_Resources::singleton()->getUrl('ckeditor5', 'js/wysiwyg/crm.ckeditor5.js'),
// Note that I am just using 'classic build' at the moment - not a configured
// build so no build in the path.
'CKEditor5Location' => CRM_Core_Resources::singleton()->getUrl('ckeditor5', 'js/ckeditor5/ckeditor-base64-upload-adapter/build/ckeditor.js'),
'ELFinderLocation' => NULL,
'ELFinderConnnector' => NULL,
],
];
CRM_Core_Resources::singleton()->addStyleUrl(CRM_Core_Resources::singleton()->getUrl('ckeditor5', 'js/elFinder/css/elFinder.min.css'));
}
}
}
......
Software License Agreement
==========================
**CKEditor 5 classic editor build** – https://github.com/ckeditor/ckeditor5-build-classic <br>
Copyright (c) 2003-2020, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
Sources of Intellectual Property Included in CKEditor
-----------------------------------------------------
Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission.
The following libraries are included in CKEditor under the [MIT license](https://opensource.org/licenses/MIT):
* Lo-Dash - Copyright (c) JS Foundation and other contributors https://js.foundation/. Based on Underscore.js, copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors http://underscorejs.org/.
Trademarks
----------
**CKEditor** is a trademark of [CKSource](http://cksource.com) Frederico Knabben. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders.
CKEditor 5 classic editor build v17.0.0
=======================================
In order to start using CKEditor 5 Builds, configure or customize them, please visit http://docs.ckeditor.com/ckeditor5/latest/builds/index.html
## License
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
For full details about the license, please check the LICENSE.md file.
This diff is collapsed.
This diff is collapsed.
<!DOCTYPE html>
<!--
Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<link type="text/css" href="sample/css/sample.css" rel="stylesheet" media="screen" />
<title>CKEditor 5 – classic editor build sample</title>
</head>
<body>
<header>
<div class="centered">
<h1><a href="https://ckeditor.com/ckeditor-5"><img src="sample/img/logo.svg" alt="WYSIWYG editor - CKEditor 5" /></a></h1>
<input type="checkbox" id="menu-toggle" />
<label for="menu-toggle"></label>
<nav>
<ul>
<li><a href="https://ckeditor.com/ckeditor-5">Project homepage</a></li>
<li><a href="https://ckeditor.com/docs/">Documentation</a></li>
<li><a href="https://github.com/ckeditor/ckeditor5">GitHub</a></li>
</ul>
</nav>
</div>
</header>
<main>
<div class="message">
<div class="centered">
<h1>Congratulations!</h1>
<p>If you can see CKEditor below, it means that the installation succeeded. You can now try out your new editor version, see its features,
and check some of the most useful <a href="#references">resources recommended below</a>.</p>
</div>
</div>
<div class="centered">
<div id="editor">
<h2>The three greatest things you learn from traveling</h2>
<p>Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve learned over the years of traveling.</p>
<h3>Appreciation of diversity</h3>
<p>Getting used to an entirely different culture can be challenging. While it’s also nice to learn about cultures online or from books, nothing comes close to experiencing <a href="https://en.wikipedia.org/wiki/Cultural_diversity">cultural diversity</a> in person. You learn to appreciate each and every single one of the differences while you become more culturally fluid.</p>
<figure class="image image-style-side"><img src="sample/img/umbrellas.jpg" alt="Three Monks walking on ancient temple.">
<figcaption>Leaving your comfort zone might lead you to such beautiful sceneries like this one.</figcaption>
</figure>
<h3>Confidence</h3>
<p>Going to a new place can be quite terrifying. While change and uncertainty makes us scared, traveling teaches us how ridiculous it is to be afraid of something before it happens. The moment you face your fear and see there was nothing to be afraid of, is the moment you discover bliss.</p>
</div>
<div id="references">
<section>
<h2>Configure the editor</h2>
<p>CKEditor 5 is configurable so you can change many of its aspects (like the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/configuration.html#toolbar-setup">toolbar</a>) to get most of the editor in your project.</p>
<p><a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/configuration.html">Learn how to configure</a></p>
</section>
<section>
<h2>Discover the features</h2>
<p>CKEditor 5 comes with plenty of rich text editing features. Most of them are available out of the box in your build.</p>
<p><a href="https://ckeditor.com/docs/ckeditor5/latest/features/index.html">Discover rich text editor features</a></p> </section>
<section>
<h2>Discover editor builds</h2>
<p>There are other editor builds you can use in your project. They offer a different user interface and features but they all share the same solid core of CKEditor 5.</p>
<p><a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html">Discover the builds</a></p>
</section>
<section>
<h2>Real-time collaboration</h2>
<p>CKEditor 5 Collaboration Features let you customize any CKEditor 5 build to include real-time collaborative editing and commenting features and tailor them to your needs.</p>
<p><a href="https://ckeditor.com/docs/ckeditor5/latest/features/collaboration/collaboration.html">Real-time collaboration overview</a></p>
</section>
<section>
<h2>Create your own rich text editor</h2>
<p>CKEditor 5 is a rich text editing framework that allows you to create your own editor using the building blocks it offers. You can customize existing builds or create a new one from scratch.</p>
<p><a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/custom-builds.html">Create your own build</a></p>
</section>
<section>
<h2>Integration with the frameworks</h2>
<p>CKEditor 5 supports the most popular web frameworks like React, Angular or Vue.js &mdash; get the full benefit of CKEditor 5 in your project using official integrations.</p>
<p><a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/overview.html">Browse the integrations</a></p>
</section>
</div>
</div>
</main>
<footer>
<div>
<p>CKEditor 5 – The text editor for the Internet – <a href="https://ckeditor.com/ckeditor-5">https://ckeditor.com/ckeditor-5</a></p>
<p>Copyright © 2003-2019, <a href="https://cksource.com/">CKSource</a> – Frederico Knabben. All rights reserved.</p>
</div>
</footer>
<script src="ckeditor.js"></script>
<script>
ClassicEditor
.create( document.querySelector( '#editor' ), {
// toolbar: [ 'heading', '|', 'bold', 'italic', 'link' ]
} )
.then( editor => {
window.editor = editor;
} )
.catch( err => {
console.error( err.stack );
} );
</script>
</body>
</html>
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
:root {
--ck-sample-base-spacing: 2em;
--ck-sample-color-white: #fff;
--ck-sample-color-green: #279863;
}
body, html {
padding: 0;
margin: 0;
font-family: sans-serif, Arial, Verdana, "Trebuchet MS", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 16px;
line-height: 22px;
}
body {
height: 100%;
color: #2D3A4A;
background-image: url(../img/bg.png);
background-repeat: no-repeat;
background-position: 50% 100%;
background-size: 100% auto;
}
body * {
box-sizing: border-box;
}
a {
color: #38A5EE;
}
abbr {
border-bottom: 1px dotted #333;
text-decoration: none;
}
.centered {
max-width: 960px;
margin: 0 auto;
padding: 0 var(--ck-sample-base-spacing);
}
/* --------- HEADER ---------------------------------------------------------------------------- */
header .centered {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
min-height: 8em;
}
header h1 {
margin: 0;
font-size: 1em;
display: inline-block;
}
header h1 a {
display: inline-block;
line-height: 0;
}
header h1 img {
height: 3em;
}
header nav ul {
margin: 0;
padding: 0;
list-style-type: none;
}
header nav ul li {
display: inline-block;
}
header nav ul li + li {
margin-left: 1em;
}
header nav ul li a {
font-weight: bold;
text-decoration: none;
color: #2D3A4A;
}
header nav ul li a:hover {
text-decoration: underline;
}