Skip to content
Snippets Groups Projects
Commit a01e2034 authored by Mateusz Charytoniuk's avatar Mateusz Charytoniuk
Browse files

feat: support ML models

parent ac362079
No related branches found
Tags v0.31.0
No related merge requests found
Showing
with 1790 additions and 229 deletions
---
collections:
- documents
layout: dm:document
parent: docs/features/ai/index
title: Machine Learning
description: >
Incorporate Machine Learning models into your application.
---
# Machine Learning
Resonance integrates with [Rubix ML](https://rubixml.com/) to serve Machine Learning models. Rubix ML is a high-level machine learning library that allows you to train and serve machine learning models.
## Training
Put your datasets in a place from which your application can access to them (for example `datasets` directory) and prepare `models` directory for the outputs.
```php
<?php
declare(strict_types=1);
namespace App\Command;
use Distantmagic\Resonance\Attribute\ConsoleCommand;
use Distantmagic\Resonance\Command;
use Psr\Log\LoggerInterface;
use Rubix\ML\Classifiers\KNearestNeighbors;
use Rubix\ML\CrossValidation\Metrics\Accuracy;
use Rubix\ML\Datasets\Labeled;
use Rubix\ML\Extractors\NDJSON;
use Rubix\ML\Persisters\Filesystem;
use Rubix\ML\Serializers\RBX;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @see https://github.com/RubixML/Iris
*/
#[ConsoleCommand(
name: 'train:iris',
description: 'Train Iris Flower Classifier'
)]
final class TrainIris extends Command
{
public function __construct(private LoggerInterface $logger)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logger->info('Loading data into memory');
$training = Labeled::fromIterator(new NDJSON(DM_ROOT.'/datasets/iris.ndjson'));
$testing = $training->randomize()->take(10);
$estimator = new KNearestNeighbors(5);
$this->logger->info('Training');
$estimator->train($training);
$this->logger->info('Making predictions');
$predictions = $estimator->predict($testing);
$metric = new Accuracy();
$score = $metric->score($predictions, $testing->labels());
$this->logger->info("Accuracy is $score");
$this->logger->info('Serializing');
$serializer = new RBX();
$encoding = $serializer->serialize($estimator);
$filesystemPersister = new Filesystem(DM_ROOT.'/models/iris.model');
$filesystemPersister->save($encoding);
return Command::SUCCESS;
}
}
```
## Serving
After the model is trained, you can serve responses from a specific responder in your application.
It must be loaded in the constructor so the model will be loaded during runtime. Resonance will keep it in the memory and serve predictions from it. You can use the same server as for the rest of your application.
```php
<?php
declare(strict_types=1);
namespace App\HttpResponder;
use Distantmagic\Resonance\Attribute\RespondsToHttp;
use Distantmagic\Resonance\Attribute\Singleton;
use Distantmagic\Resonance\HttpInterceptableInterface;
use Distantmagic\Resonance\HttpResponder;
use Distantmagic\Resonance\RequestMethod;
use Distantmagic\Resonance\SingletonCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Rubix\ML\Datasets\Unlabeled;
use Rubix\ML\Estimator;
use Rubix\ML\PersistentModel;
use Rubix\ML\Persisters\Filesystem;
use Symfony\Component\HttpFoundation\JsonResponse;
#[RespondsToHttp(
method: RequestMethod::POST,
pattern: '/predict',
)]
#[Singleton(collection: SingletonCollection::HttpResponder)]
readonly class Predict extends HttpResponder
{
private Estimator $model;
public function __construct()
{
$this->model = PersistentModel::load(new Filesystem(DM_ROOT.'/models/iris.model'));
}
public function respond(
ServerRequestInterface $request,
ResponseInterface $response,
): HttpInterceptableInterface {
$dataset = new Unlabeled($request->getParsedBody());
$predictions = $this->model->predict($dataset);
return new JsonResponse($predictions);
}
}
```
\ No newline at end of file
---
collections:
- documents
draft: true
layout: dm:document
parent: docs/features/index
title: Machine Learning
description: >
Learn how to incorporate Machine Learning features into your application.
---
# Machine Leargning
...@@ -43,69 +43,74 @@ description: > ...@@ -43,69 +43,74 @@ description: >
</hgroup> </hgroup>
</div> </div>
</div> </div>
<div class="homepage-drivers"> <div class="homepage-gallery homepage-gallery--reasons">
<h3>Backend Drivers Support</h3> <h3>Why Resonance?</h3>
<div class="homepage-drivers__items"> <ul class="homepage-gallery__grid">
<a <li class="homepage-gallery__item">
class="homepage-drivers__item homepage-drivers__item--swoole" <h4>
href="https://swoole.com/" <a href="/docs/features/">
rel=”noopener noreferrer” Predictable Performance
target="_blank" </a>
> </h4>
<div class="homepage-drivers__item__background"></div> <p>
<div class="homepage-drivers__item__name"> Resonance is designed with a few priorities: no memory leaks, blocking operations, and garbage collector surprises.<br><br>
Swoole Most of the internals are read-only and stateless. After the application startup, nothing disturbs JIT and opcode (Reflection is only used during the application startup), so there are no surprise slowdowns during runtime.
</div> </p>
<div class="homepage-drivers__item__state"> </li>
Supported since v0.1.0 <li class="homepage-gallery__item">
</div> <h4>
</a> <a href="/docs/features/">
<a Opinionated
class="homepage-drivers__item homepage-drivers__item--openswoole" </a>
href="https://openswoole.com/" </h4>
rel=”noopener noreferrer” <p>
target="_blank" All the libraries under the hood have been thoroughly tested to ensure they work together correctly, complement each other, and work perfectly under async environments.<br><br>
> For example, Resonance implements custom <a href="https://www.doctrine-project.org/">Doctrine</a> drivers, so it uses Swoole's connection pools.
<div class="homepage-drivers__item__background"></div> </p>
<div class="homepage-drivers__item__name"> </li>
OpenSwoole <li class="homepage-gallery__item">
</div> <h4>
<div class="homepage-drivers__item__state"> <a href="/docs/features/">
Supported since v0.1.0 Resolves Input/Output Issues
</div> </a>
</a> </h4>
<a <p>
class="homepage-drivers__item homepage-drivers__item--amphp" Resonance is designed to handle IO-intensive tasks, such as serving Machine Learning models, handling WebSocket connections, and processing long-running HTTP requests.<br><br>
href="https://amphp.org/" It views modern applications as a mix of services that communicate with each other asynchronously, including AI completions and ML inferences, so it provides a set of tools to make this communication as easy as possible.
rel=”noopener noreferrer” </p>
target="_blank" </li>
> <li class="homepage-gallery__item">
<div class="homepage-drivers__item__background"></div> <h4>
<div class="homepage-drivers__item__name"> <a href="/docs/features/">
AMPHP Complete Package
</div> </a>
<div class="homepage-drivers__item__state"> </h4>
In progress <p>
</div> Resonance includes everything you need to build a modern web application, from the HTTP server to the AI capabilities.<br><br>
</a> It provides security features, HTML templating, integration with open-source LLMs, and provides capability to serve ML models.
<a </p>
class="homepage-drivers__item homepage-drivers__item--other" </li>
href="https://github.com/distantmagic/resonance/discussions" </ul>
target="_blank"
>
<div class="homepage-drivers__item__background"></div>
<div class="homepage-drivers__item__name">
Do you have a driver in mind?
</div>
<div class="homepage-drivers__item__state">
Start a discussion
</div>
</a>
</div>
</div> </div>
<div class="homepage-gallery"> <div class="homepage-gallery homepage-gallery--releases">
<h3>New Releases</h3> <h3>New Releases</h3>
<ul class="homepage-gallery__items"> <ul class="homepage-gallery__items">
<li class="homepage-gallery__item">
<h4>
<a href="/docs/features/ai/machine-learning/">
Serve Machine Learning Models
<span class="homepage-gallery__version">v0.31.0</span>
</a>
</h4>
<p>
Resonance integrates with Rubix ML to serve Machine Learning
models in the same codebase as the rest of your application.
</p>
<a
class="homepage-gallery__item__learnmore"
href="/docs/features/ai/machine-learning/"
>Learn More</a>
</li>
<li class="homepage-gallery__item"> <li class="homepage-gallery__item">
<h4> <h4>
<a href="/tutorials/semi-scripted-conversational-applications/"> <a href="/tutorials/semi-scripted-conversational-applications/">
...@@ -346,6 +351,59 @@ readonly class CatAdopt implements PromptSubjectResponderInterface ...@@ -346,6 +351,59 @@ readonly class CatAdopt implements PromptSubjectResponderInterface
$response->write(" ((_((|))_))\n"); $response->write(" ((_((|))_))\n");
$response->end(); $response->end();
} }
}</code></pre>
</li>
<li class="formatted-content homepage__example">
<h2 class="homepage__example__title">
Serve Machine Learning Models
</h2>
<div class="homepage__example__description">
<p>
Resonance integrates with
<a href="https://rubixml.com/" target="_blank">Rubix ML</a>
and allows you to serve Machine Learning models in the
same codebase as the rest of your application.
</p>
<p>
Resonance allows you to serve inferences from your
models through HTTP, WebSocket, and other protocols.
</p>
<a
class="homepage__cta homepage__cta--example"
href="/docs/features/ai/"
>
Learn More
</a>
</div>
<pre class="homepage__example__code fenced-code"><code
class="language-php"
data-controller="hljs"
data-hljs-language-value="php"
>#[RespondsToHttp(
method: RequestMethod::POST,
pattern: '/predict',
)]
#[Singleton(collection: SingletonCollection::HttpResponder)]
readonly class Predict extends HttpResponder
{
private Estimator $model;
public function __construct()
{
$this->model = PersistentModel::load(new Filesystem(DM_ROOT.'/models/iris.model'));
}
public function respond(
ServerRequestInterface $request,
ResponseInterface $response,
): HttpInterceptableInterface
{
$dataset = new Unlabeled($request->getParsedBody());
$predictions = $this->model->predict($dataset);
return new JsonResponse($predictions);
}
}</code></pre> }</code></pre>
</li> </li>
<li class="formatted-content homepage__example"> <li class="formatted-content homepage__example">
......
...@@ -32,7 +32,7 @@ warned. ...@@ -32,7 +32,7 @@ warned.
## The Goal ## The Goal
Our final goal is to be able to send transactional emails from our server and Our final goal is to send transactional emails from our server and
still be able to both receive and send them through 3rd party service provider. still be able to both receive and send them through 3rd party service provider.
Let me explain. Let me explain.
......
/cache /cache
/config.ini /config.ini
/oauth2 /oauth2
/repository
/ssl /ssl
/vendor /vendor
# Resonance Project
To start the project you need to:
1. Install dependencies with `composer install`
2. Create `config.ini` (you can copy `config.ini.example`)
3. Run `php bin/resonance.php serve` in the terminal to start the server
## Using SSL
In order to use SSL you need to [generate SSL certificate for a local development](https://resonance.distantmagic.com/docs/extras/ssl-certificate-for-local-development.html)
and uncomment SSL related settings in `app/Command/Serve.php`.
<?php
declare(strict_types=1);
namespace App\Command;
use Distantmagic\Resonance\Attribute\ConsoleCommand;
use Distantmagic\Resonance\Command;
use Psr\Log\LoggerInterface;
use Rubix\ML\Classifiers\KNearestNeighbors;
use Rubix\ML\CrossValidation\Metrics\Accuracy;
use Rubix\ML\Datasets\Labeled;
use Rubix\ML\Extractors\NDJSON;
use Rubix\ML\Persisters\Filesystem;
use Rubix\ML\Serializers\RBX;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @see https://github.com/RubixML/Iris
*/
#[ConsoleCommand(
name: 'train:iris',
description: 'Train Iris Flower Classifier'
)]
final class TrainIris extends Command
{
public function __construct(private LoggerInterface $logger)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logger->info('Loading data into memory');
$training = Labeled::fromIterator(new NDJSON(DM_ROOT.'/datasets/iris.ndjson'));
$testing = $training->randomize()->take(10);
$estimator = new KNearestNeighbors(5);
$this->logger->info('Training');
$estimator->train($training);
$this->logger->info('Making predictions');
$predictions = $estimator->predict($testing);
$metric = new Accuracy();
$score = $metric->score($predictions, $testing->labels());
$this->logger->info("Accuracy is $score");
$this->logger->info('Serializing');
$serializer = new RBX();
$encoding = $serializer->serialize($estimator);
$filesystemPersister = new Filesystem(DM_ROOT.'/models/iris.model');
$filesystemPersister->save($encoding);
return Command::SUCCESS;
}
}
<?php
declare(strict_types=1);
namespace App\HttpResponder;
use App\HttpRouteSymbol;
use Distantmagic\Resonance\Attribute\RespondsToHttp;
use Distantmagic\Resonance\HttpInterceptableInterface;
use Distantmagic\Resonance\RequestMethod;
use Distantmagic\Resonance\TwigTemplate;
#[RespondsToHttp(
method: RequestMethod::GET,
pattern: '/',
routeSymbol: HttpRouteSymbol::Homepage,
)]
function Homepage(): HttpInterceptableInterface
{
return new TwigTemplate('homepage.twig');
}
<?php
declare(strict_types=1);
namespace App\HttpResponder;
use Distantmagic\Resonance\Attribute\RespondsToHttp;
use Distantmagic\Resonance\Attribute\Singleton;
use Distantmagic\Resonance\HttpInterceptableInterface;
use Distantmagic\Resonance\HttpResponder;
use Distantmagic\Resonance\RequestMethod;
use Distantmagic\Resonance\SingletonCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Rubix\ML\Datasets\Unlabeled;
use Rubix\ML\Estimator;
use Rubix\ML\PersistentModel;
use Rubix\ML\Persisters\Filesystem;
use Symfony\Component\HttpFoundation\JsonResponse;
#[RespondsToHttp(
method: RequestMethod::POST,
pattern: '/predict',
)]
#[Singleton(collection: SingletonCollection::HttpResponder)]
readonly class Predict extends HttpResponder
{
private Estimator $model;
public function __construct()
{
$this->model = PersistentModel::load(new Filesystem(DM_ROOT.'/models/iris.model'));
}
public function respond(
ServerRequestInterface $request,
ResponseInterface $response,
): HttpInterceptableInterface {
$dataset = new Unlabeled($request->getParsedBody());
$predictions = $this->model->predict($dataset);
return new JsonResponse($predictions);
}
}
<?php
declare(strict_types=1);
namespace App;
use Distantmagic\Resonance\CastableEnumTrait;
use Distantmagic\Resonance\HttpRouteSymbolInterface;
use Distantmagic\Resonance\NameableEnumTrait;
enum HttpRouteSymbol implements HttpRouteSymbolInterface
{
use CastableEnumTrait;
use NameableEnumTrait;
case Homepage;
}
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
"repositories": [ "repositories": [
{ {
"type": "path", "type": "path",
"url": "vendor/distantmagic/resonance", "url": "./repository",
"options": { "options": {
"symlink": true "symlink": true
} }
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
"symlink-resonance": [ "symlink-resonance": [
"mkdir -p vendor/distantmagic", "mkdir -p vendor/distantmagic",
"rm -rf vendor/distantmagic/resonance", "rm -rf vendor/distantmagic/resonance",
"ln -s -f ../../ vendor/distantmagic/resonance" "ln -s -f ../../ ./repository"
], ],
"pre-install-cmd": "@symlink-resonance", "pre-install-cmd": "@symlink-resonance",
"pre-update-cmd": "@symlink-resonance" "pre-update-cmd": "@symlink-resonance"
......
This diff is collapsed.
[5.1,3.5,1.4,0.2,"Iris-setosa"]
[4.9,3,1.4,0.2,"Iris-setosa"]
[4.7,3.2,1.3,0.2,"Iris-setosa"]
[4.6,3.1,1.5,0.2,"Iris-setosa"]
[5,3.6,1.4,0.2,"Iris-setosa"]
[5.4,3.9,1.7,0.4,"Iris-setosa"]
[4.6,3.4,1.4,0.3,"Iris-setosa"]
[5,3.4,1.5,0.2,"Iris-setosa"]
[4.4,2.9,1.4,0.2,"Iris-setosa"]
[4.9,3.1,1.5,0.1,"Iris-setosa"]
[5.4,3.7,1.5,0.2,"Iris-setosa"]
[4.8,3.4,1.6,0.2,"Iris-setosa"]
[4.8,3,1.4,0.1,"Iris-setosa"]
[4.3,3,1.1,0.1,"Iris-setosa"]
[5.8,4,1.2,0.2,"Iris-setosa"]
[5.7,4.4,1.5,0.4,"Iris-setosa"]
[5.4,3.9,1.3,0.4,"Iris-setosa"]
[5.1,3.5,1.4,0.3,"Iris-setosa"]
[5.7,3.8,1.7,0.3,"Iris-setosa"]
[5.1,3.8,1.5,0.3,"Iris-setosa"]
[5.4,3.4,1.7,0.2,"Iris-setosa"]
[5.1,3.7,1.5,0.4,"Iris-setosa"]
[4.6,3.6,1,0.2,"Iris-setosa"]
[5.1,3.3,1.7,0.5,"Iris-setosa"]
[4.8,3.4,1.9,0.2,"Iris-setosa"]
[5,3,1.6,0.2,"Iris-setosa"]
[5,3.4,1.6,0.4,"Iris-setosa"]
[5.2,3.5,1.5,0.2,"Iris-setosa"]
[5.2,3.4,1.4,0.2,"Iris-setosa"]
[4.7,3.2,1.6,0.2,"Iris-setosa"]
[4.8,3.1,1.6,0.2,"Iris-setosa"]
[5.4,3.4,1.5,0.4,"Iris-setosa"]
[5.2,4.1,1.5,0.1,"Iris-setosa"]
[5.5,4.2,1.4,0.2,"Iris-setosa"]
[4.9,3.1,1.5,0.1,"Iris-setosa"]
[5,3.2,1.2,0.2,"Iris-setosa"]
[5.5,3.5,1.3,0.2,"Iris-setosa"]
[4.9,3.1,1.5,0.1,"Iris-setosa"]
[4.4,3,1.3,0.2,"Iris-setosa"]
[5.1,3.4,1.5,0.2,"Iris-setosa"]
[5,3.5,1.3,0.3,"Iris-setosa"]
[4.5,2.3,1.3,0.3,"Iris-setosa"]
[4.4,3.2,1.3,0.2,"Iris-setosa"]
[5,3.5,1.6,0.6,"Iris-setosa"]
[5.1,3.8,1.9,0.4,"Iris-setosa"]
[4.8,3,1.4,0.3,"Iris-setosa"]
[5.1,3.8,1.6,0.2,"Iris-setosa"]
[4.6,3.2,1.4,0.2,"Iris-setosa"]
[5.3,3.7,1.5,0.2,"Iris-setosa"]
[5,3.3,1.4,0.2,"Iris-setosa"]
[7,3.2,4.7,1.4,"Iris-versicolor"]
[6.4,3.2,4.5,1.5,"Iris-versicolor"]
[6.9,3.1,4.9,1.5,"Iris-versicolor"]
[5.5,2.3,4,1.3,"Iris-versicolor"]
[6.5,2.8,4.6,1.5,"Iris-versicolor"]
[5.7,2.8,4.5,1.3,"Iris-versicolor"]
[6.3,3.3,4.7,1.6,"Iris-versicolor"]
[4.9,2.4,3.3,1,"Iris-versicolor"]
[6.6,2.9,4.6,1.3,"Iris-versicolor"]
[5.2,2.7,3.9,1.4,"Iris-versicolor"]
[5,2,3.5,1,"Iris-versicolor"]
[5.9,3,4.2,1.5,"Iris-versicolor"]
[6,2.2,4,1,"Iris-versicolor"]
[6.1,2.9,4.7,1.4,"Iris-versicolor"]
[5.6,2.9,3.6,1.3,"Iris-versicolor"]
[6.7,3.1,4.4,1.4,"Iris-versicolor"]
[5.6,3,4.5,1.5,"Iris-versicolor"]
[5.8,2.7,4.1,1,"Iris-versicolor"]
[6.2,2.2,4.5,1.5,"Iris-versicolor"]
[5.6,2.5,3.9,1.1,"Iris-versicolor"]
[5.9,3.2,4.8,1.8,"Iris-versicolor"]
[6.1,2.8,4,1.3,"Iris-versicolor"]
[6.3,2.5,4.9,1.5,"Iris-versicolor"]
[6.1,2.8,4.7,1.2,"Iris-versicolor"]
[6.4,2.9,4.3,1.3,"Iris-versicolor"]
[6.6,3,4.4,1.4,"Iris-versicolor"]
[6.8,2.8,4.8,1.4,"Iris-versicolor"]
[6.7,3,5,1.7,"Iris-versicolor"]
[6,2.9,4.5,1.5,"Iris-versicolor"]
[5.7,2.6,3.5,1,"Iris-versicolor"]
[5.5,2.4,3.8,1.1,"Iris-versicolor"]
[5.5,2.4,3.7,1,"Iris-versicolor"]
[5.8,2.7,3.9,1.2,"Iris-versicolor"]
[6,2.7,5.1,1.6,"Iris-versicolor"]
[5.4,3,4.5,1.5,"Iris-versicolor"]
[6,3.4,4.5,1.6,"Iris-versicolor"]
[6.7,3.1,4.7,1.5,"Iris-versicolor"]
[6.3,2.3,4.4,1.3,"Iris-versicolor"]
[5.6,3,4.1,1.3,"Iris-versicolor"]
[5.5,2.5,4,1.3,"Iris-versicolor"]
[5.5,2.6,4.4,1.2,"Iris-versicolor"]
[6.1,3,4.6,1.4,"Iris-versicolor"]
[5.8,2.6,4,1.2,"Iris-versicolor"]
[5,2.3,3.3,1,"Iris-versicolor"]
[5.6,2.7,4.2,1.3,"Iris-versicolor"]
[5.7,3,4.2,1.2,"Iris-versicolor"]
[5.7,2.9,4.2,1.3,"Iris-versicolor"]
[6.2,2.9,4.3,1.3,"Iris-versicolor"]
[5.1,2.5,3,1.1,"Iris-versicolor"]
[5.7,2.8,4.1,1.3,"Iris-versicolor"]
[6.3,3.3,6,2.5,"Iris-virginica"]
[5.8,2.7,5.1,1.9,"Iris-virginica"]
[7.1,3,5.9,2.1,"Iris-virginica"]
[6.3,2.9,5.6,1.8,"Iris-virginica"]
[6.5,3,5.8,2.2,"Iris-virginica"]
[7.6,3,6.6,2.1,"Iris-virginica"]
[4.9,2.5,4.5,1.7,"Iris-virginica"]
[7.3,2.9,6.3,1.8,"Iris-virginica"]
[6.7,2.5,5.8,1.8,"Iris-virginica"]
[7.2,3.6,6.1,2.5,"Iris-virginica"]
[6.5,3.2,5.1,2,"Iris-virginica"]
[6.4,2.7,5.3,1.9,"Iris-virginica"]
[6.8,3,5.5,2.1,"Iris-virginica"]
[5.7,2.5,5,2,"Iris-virginica"]
[5.8,2.8,5.1,2.4,"Iris-virginica"]
[6.4,3.2,5.3,2.3,"Iris-virginica"]
[6.5,3,5.5,1.8,"Iris-virginica"]
[7.7,3.8,6.7,2.2,"Iris-virginica"]
[7.7,2.6,6.9,2.3,"Iris-virginica"]
[6,2.2,5,1.5,"Iris-virginica"]
[6.9,3.2,5.7,2.3,"Iris-virginica"]
[5.6,2.8,4.9,2,"Iris-virginica"]
[7.7,2.8,6.7,2,"Iris-virginica"]
[6.3,2.7,4.9,1.8,"Iris-virginica"]
[6.7,3.3,5.7,2.1,"Iris-virginica"]
[7.2,3.2,6,1.8,"Iris-virginica"]
[6.2,2.8,4.8,1.8,"Iris-virginica"]
[6.1,3,4.9,1.8,"Iris-virginica"]
[6.4,2.8,5.6,2.1,"Iris-virginica"]
[7.2,3,5.8,1.6,"Iris-virginica"]
[7.4,2.8,6.1,1.9,"Iris-virginica"]
[7.9,3.8,6.4,2,"Iris-virginica"]
[6.4,2.8,5.6,2.2,"Iris-virginica"]
[6.3,2.8,5.1,1.5,"Iris-virginica"]
[6.1,2.6,5.6,1.4,"Iris-virginica"]
[7.7,3,6.1,2.3,"Iris-virginica"]
[6.3,3.4,5.6,2.4,"Iris-virginica"]
[6.4,3.1,5.5,1.8,"Iris-virginica"]
[6,3,4.8,1.8,"Iris-virginica"]
[6.9,3.1,5.4,2.1,"Iris-virginica"]
[6.7,3.1,5.6,2.4,"Iris-virginica"]
[6.9,3.1,5.1,2.3,"Iris-virginica"]
[5.8,2.7,5.1,1.9,"Iris-virginica"]
[6.8,3.2,5.9,2.3,"Iris-virginica"]
[6.7,3.3,5.7,2.5,"Iris-virginica"]
[6.7,3,5.2,2.3,"Iris-virginica"]
[6.3,2.5,5,1.9,"Iris-virginica"]
[6.5,3,5.2,2,"Iris-virginica"]
[6.2,3.4,5.4,2.3,"Iris-virginica"]
[5.9,3,5.1,1.8,"Iris-virginica"]
\ No newline at end of file
*
!.gitignore
\ No newline at end of file
...@@ -18,10 +18,22 @@ ...@@ -18,10 +18,22 @@
background-color: var(--color-body-background); background-color: var(--color-body-background);
} }
.homepage-drivers, .homepage-gallery.homepage-gallery--reasons {
--homepage-gallery-background: var(--color-block-dark-background);
--homepage-gallery-color: var(--color-body-font-dark-background);
}
.homepage-gallery.homepage-gallery--releases {
--homepage-gallery-background: white;
--homepage-gallery-color: var(--color-body-font);
border-bottom: 1px solid var(--color-border);
height: 460px;
}
.homepage-gallery { .homepage-gallery {
background-color: var(--color-block-dark-background); background-color: var(--homepage-gallery-background);
color: var(--color-body-font-dark-background); color: var(--homepage-gallery-color);
max-width: 100vw; max-width: 100vw;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
...@@ -39,72 +51,22 @@ ...@@ -39,72 +51,22 @@
} }
} }
.homepage-drivers { .homepage-gallery {
border-top: 1px solid var(--color-border);
display: flex;
flex-direction: column;
row-gap: 20px;
}
.homepage-drivers__items {
align-items: center;
column-gap: 20px;
display: flex;
flex-direction: row;
list-style-type: none;
}
.homepage-drivers__item {
border: 1px solid var(--color-border);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 40px 20px 20px 20px;
row-gap: 20px; row-gap: 20px;
text-align: center;
text-decoration: none;
} }
.homepage-drivers__item.homepage-drivers__item--amphp { .homepage-gallery__grid {
--drivers-bg-image: url(../images/amphp.webp); display: grid;
gap: 20px;
.homepage-drivers__item__background { @media screen and (max-width: 1023px) {
width: 450px; grid-template-columns: 1fr;
}
@media screen and (min-width: 1024px) {
grid-template-columns: repeat(2, 1fr);
} }
}
.homepage-drivers__item.homepage-drivers__item--openswoole {
--drivers-bg-image: url(../images/openswoole.png);
}
.homepage-drivers__item.homepage-drivers__item--other {
--drivers-bg-image: url(../icons/plug-circle-plus-333333.svg);
}
.homepage-drivers__item.homepage-drivers__item--swoole {
--drivers-bg-image: url(../images/swoole.png);
}
.homepage-drivers__item__background {
background-image: var(--drivers-bg-image);
background-position: center;
background-repeat: no-repeat;
height: 140px;
width: 350px;
}
.homepage-drivers__item__name {
font-weight: bold;
}
.homepage-drivers__item__state {
font-size: var(--font-size-smaller);
}
.homepage-gallery {
display: flex;
flex-direction: column;
height: 460px;
row-gap: 20px;
} }
.homepage-gallery__items { .homepage-gallery__items {
...@@ -265,6 +227,7 @@ h2.homepage__example__title { ...@@ -265,6 +227,7 @@ h2.homepage__example__title {
.homepage__title h2 { .homepage__title h2 {
font-weight: bold; font-weight: bold;
line-height: 1.3;
margin-top: 20px; margin-top: 20px;
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment